diff --git a/DEPS b/DEPS
index 73949da..d46f960e 100644
--- a/DEPS
+++ b/DEPS
@@ -283,7 +283,7 @@
   'dawn_standalone': False,
 
   # reclient CIPD package version
-  'reclient_version': 're_client_version:0.85.0.91db7be-gomaip',
+  'reclient_version': 're_client_version:0.86.0.25feac0-gomaip',
 
   # Fetch Rust-related packages.
   'use_rust': False,
@@ -306,15 +306,15 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '160bf907c0eea2b489b4ca5260233958fac6f368',
+  'skia_revision': '926c36ffbd516df0878972c68245fc4c4836369e',
   # 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': 'b4d7eaaf926cfbfadb6cd4144081eb68a72da029',
+  'v8_revision': '54cbf2abd316e18a4c943e0cb7a94d5fd9a80a4d',
   # 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': 'a6fae3fec80902dc810efc44e153bd97e507275d',
+  'angle_revision': '7e7a47dffca034c50bfc96225e6c910bacc55c0f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -333,7 +333,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Fuchsia sdk
   # and whatever else without interference from each other.
-  'fuchsia_version': 'version:10.20221116.3.1',
+  'fuchsia_version': 'version:10.20221117.0.1',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling google-toolbox-for-mac
   # and whatever else without interference from each other.
@@ -385,7 +385,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': '7f5186963e90f13f0a7be3d102925a96a71afe6d',
+  'devtools_frontend_revision': 'c73fe21a0ec8ff28fd24c584fcb001571a2ca025',
   # 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.
@@ -421,7 +421,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': '7df98007abd651f72944ed13f5b5d95f4605b22a',
+  'dawn_revision': '4d65fc91bb284269dd723fadb8ecead1eb28696c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -445,7 +445,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling nearby
   # and whatever else without interference from each other.
-  'nearby_revision': '4a0242472abc7913d0ce00d83d12284f0b3a0f6e',
+  'nearby_revision': 'e58dd0a5977fa551c8985f913b389940846130a9',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling securemessage
   # and whatever else without interference from each other.
@@ -816,12 +816,12 @@
 
   'src/clank': {
     'url': 'https://chrome-internal.googlesource.com/clank/internal/apps.git' + '@' +
-    '78e33b77f5ed84569adb89598e86f7748b9b73a2',
+    '54fe34ecc4bf86eae7866ded7ca9b7d9e575b909',
     'condition': 'checkout_android and checkout_src_internal and not checkout_clank_via_src_internal',
   },
 
   'src/docs/website': {
-    'url': Var('chromium_git') + '/website.git' + '@' + '102fcefea116b0e8c2c80307dbf3a0997640d2cf',
+    'url': Var('chromium_git') + '/website.git' + '@' + '9aff7766594d07d48603073fafa5d07b16e1d411',
   },
 
   'src/ios/third_party/earl_grey2/src': {
@@ -1215,7 +1215,7 @@
   # Tools used when building Chrome for Chrome OS. This affects both the Simple
   # Chrome workflow, as well as the chromeos-chrome ebuild.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '066fd4019e94c13e7ae8753a70df62fba2d81091',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'abcc3894eb642d4c87b124a08b7d3eb9c4b51548',
       'condition': 'checkout_chromeos',
   },
 
@@ -1243,13 +1243,13 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'fe46d759b592c87d5a356ac9b6bfdd05526ad3a1',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'aad574d4464f1b26ff0fa8434caa73653f93da44',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
 
   'src/third_party/devtools-frontend-internal': {
-      'url': 'https://chrome-internal.googlesource.com/devtools/devtools-internal.git' + '@' + '5a5aab5420323133d15e69c9f6a4f846f0de8ebc',
+      'url': 'https://chrome-internal.googlesource.com/devtools/devtools-internal.git' + '@' + '4aa2cc16ba72ae73f7a8594af4c38ce7c9f49daf',
     'condition': 'checkout_src_internal',
   },
 
@@ -1664,7 +1664,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '209aac9ddf6a7bf049ac7c4d847cb4926f65ee81',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '3c036ab6b21b9d9ddf91dc6b7fe7f50142f28758',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1846,7 +1846,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'd1b65aa5a88f6efd900604dfcda840154e9f16e2',
 
   'src/third_party/webgpu-cts/src':
-    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '992c9a857757ebbebf8aa50f2e05355cfa7b9efd',
+    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '23c3bebbdb83fbe81892cee4cb82ec9c5f9bff4f',
 
   'src/third_party/webrtc':
     Var('webrtc_git') + '/src.git' + '@' + '408f0be5c23024a4b88f80b7707f570cd53ca6a6',
@@ -1919,7 +1919,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@e9ffd1665c432c9eb5f3fe06ec6dafb0f867a684',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@ff5edc7472abacea705e1b9d086ba9ab2e1c8d4e',
     'condition': 'checkout_src_internal',
   },
 
@@ -1960,7 +1960,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': 'zmiBpjkt4VMEbMwulYceFXxVUBIcN62gEmLqXGvVyXUC',
+        'version': '3mBQBlx79qCC2FMRpnqUo0y2C6UcFKsgLsRncAx8_lYC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/android_webview/browser/aw_browser_context.cc b/android_webview/browser/aw_browser_context.cc
index cdaad137..40969ef2 100644
--- a/android_webview/browser/aw_browser_context.cc
+++ b/android_webview/browser/aw_browser_context.cc
@@ -447,8 +447,7 @@
   return ssl_host_state_delegate_.get();
 }
 
-content::PermissionControllerDelegate*
-AwBrowserContext::GetPermissionControllerDelegate() {
+AwPermissionManager* AwBrowserContext::GetPermissionControllerDelegate() {
   if (!permission_manager_.get())
     permission_manager_ = std::make_unique<AwPermissionManager>();
   return permission_manager_.get();
diff --git a/android_webview/browser/aw_browser_context.h b/android_webview/browser/aw_browser_context.h
index 1cf55d2e..619b371 100644
--- a/android_webview/browser/aw_browser_context.h
+++ b/android_webview/browser/aw_browser_context.h
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "android_webview/browser/aw_contents_origin_matcher.h"
+#include "android_webview/browser/aw_permission_manager.h"
 #include "android_webview/browser/aw_ssl_host_state_delegate.h"
 #include "android_webview/browser/network_service/aw_proxy_config_monitor.h"
 #include "base/compiler_specific.h"
@@ -30,7 +31,6 @@
 
 namespace content {
 class ClientHintsControllerDelegate;
-class PermissionControllerDelegate;
 class ResourceContext;
 class SSLHostStateDelegate;
 class WebContents;
@@ -109,8 +109,7 @@
   content::PushMessagingService* GetPushMessagingService() override;
   content::StorageNotificationService* GetStorageNotificationService() override;
   content::SSLHostStateDelegate* GetSSLHostStateDelegate() override;
-  content::PermissionControllerDelegate* GetPermissionControllerDelegate()
-      override;
+  AwPermissionManager* GetPermissionControllerDelegate() override;
   content::ClientHintsControllerDelegate* GetClientHintsControllerDelegate()
       override;
   content::BackgroundFetchDelegate* GetBackgroundFetchDelegate() override;
@@ -163,7 +162,7 @@
 
   std::unique_ptr<PrefService> user_pref_service_;
   std::unique_ptr<AwSSLHostStateDelegate> ssl_host_state_delegate_;
-  std::unique_ptr<content::PermissionControllerDelegate> permission_manager_;
+  std::unique_ptr<AwPermissionManager> permission_manager_;
   std::unique_ptr<content::ClientHintsControllerDelegate>
       client_hints_controller_delegate_;
   std::unique_ptr<content::OriginTrialsControllerDelegate>
diff --git a/android_webview/browser/aw_permission_manager.cc b/android_webview/browser/aw_permission_manager.cc
index cc49280d..2968380 100644
--- a/android_webview/browser/aw_permission_manager.cc
+++ b/android_webview/browser/aw_permission_manager.cc
@@ -614,6 +614,65 @@
   DCHECK(pending_requests_.IsEmpty());
 }
 
+void AwPermissionManager::SetOriginCanReadEnumerateDevicesAudioLabels(
+    const GURL& origin,
+    bool audio) {
+  if (origin.spec().empty() || origin.SchemeIsFile())
+    return;
+  auto it = enumerate_devices_labels_cache_.find(origin);
+  if (it == enumerate_devices_labels_cache_.end()) {
+    enumerate_devices_labels_cache_[origin] = std::make_pair(audio, false);
+  } else {
+    it->second.first = audio;
+  }
+}
+
+void AwPermissionManager::SetOriginCanReadEnumerateDevicesVideoLabels(
+    const GURL& origin,
+    bool video) {
+  if (origin.spec().empty() || origin.SchemeIsFile())
+    return;
+  auto it = enumerate_devices_labels_cache_.find(origin);
+  if (it == enumerate_devices_labels_cache_.end())
+    enumerate_devices_labels_cache_[origin] = std::make_pair(false, video);
+  else
+    it->second.second = video;
+}
+
+bool AwPermissionManager::ShouldShowEnumerateDevicesAudioLabels(
+    const GURL& origin) {
+  auto it = enumerate_devices_labels_cache_.find(origin);
+  if (it == enumerate_devices_labels_cache_.end())
+    return false;
+  return it->second.first;
+}
+
+bool AwPermissionManager::ShouldShowEnumerateDevicesVideoLabels(
+    const GURL& origin) {
+  auto it = enumerate_devices_labels_cache_.find(origin);
+  if (it == enumerate_devices_labels_cache_.end())
+    return false;
+  return it->second.second;
+}
+
+void AwPermissionManager::ClearEnumerateDevicesCachedPermission(
+    const GURL& origin,
+    bool remove_audio,
+    bool remove_video) {
+  if (origin.spec().empty())
+    return;
+  auto it = enumerate_devices_labels_cache_.find(origin);
+  if (it == enumerate_devices_labels_cache_.end())
+    return;
+  else if (remove_audio && remove_video) {
+    enumerate_devices_labels_cache_.erase(origin);
+  } else if (remove_audio) {
+    it->second.first = false;
+  } else if (remove_video) {
+    it->second.second = false;
+  }
+}
+
 int AwPermissionManager::GetRenderProcessID(
     content::RenderFrameHost* render_frame_host) {
   return render_frame_host->GetProcess()->GetID();
@@ -637,4 +696,4 @@
                                                     render_frame_id);
 }
 
-}  // namespace android_webview
+}  // namespace android_webview
\ No newline at end of file
diff --git a/android_webview/browser/aw_permission_manager.h b/android_webview/browser/aw_permission_manager.h
index ac97596d..2b8a5e6 100644
--- a/android_webview/browser/aw_permission_manager.h
+++ b/android_webview/browser/aw_permission_manager.h
@@ -5,6 +5,7 @@
 #ifndef ANDROID_WEBVIEW_BROWSER_AW_PERMISSION_MANAGER_H_
 #define ANDROID_WEBVIEW_BROWSER_AW_PERMISSION_MANAGER_H_
 
+#include <map>
 #include <memory>
 
 #include "base/callback_forward.h"
@@ -80,6 +81,15 @@
       override;
   void UnsubscribePermissionStatusChange(
       SubscriptionId subscription_id) override;
+  void SetOriginCanReadEnumerateDevicesAudioLabels(const GURL& origin,
+                                                   bool audio);
+  void SetOriginCanReadEnumerateDevicesVideoLabels(const GURL& origin,
+                                                   bool video);
+  bool ShouldShowEnumerateDevicesAudioLabels(const GURL& origin);
+  bool ShouldShowEnumerateDevicesVideoLabels(const GURL& origin);
+  void ClearEnumerateDevicesCachedPermission(const GURL& origin,
+                                             bool remove_audio,
+                                             bool remove_video);
 
  protected:
   void CancelPermissionRequest(int request_id);
@@ -107,6 +117,9 @@
 
   PendingRequestsMap pending_requests_;
   std::unique_ptr<LastRequestResultCache> result_cache_;
+  // Maps origins to whether they can view device labels.
+  // The pair is ordered as (Audio, Video).
+  std::map<GURL, std::pair<bool, bool>> enumerate_devices_labels_cache_;
 
   base::WeakPtrFactory<AwPermissionManager> weak_ptr_factory_{this};
 };
diff --git a/android_webview/browser/aw_web_contents_delegate.cc b/android_webview/browser/aw_web_contents_delegate.cc
index f06495e..0f72dbd3 100644
--- a/android_webview/browser/aw_web_contents_delegate.cc
+++ b/android_webview/browser/aw_web_contents_delegate.cc
@@ -6,13 +6,16 @@
 
 #include <utility>
 
+#include "android_webview/browser/aw_browser_context.h"
 #include "android_webview/browser/aw_contents.h"
 #include "android_webview/browser/aw_contents_io_thread_client.h"
 #include "android_webview/browser/aw_javascript_dialog_manager.h"
+#include "android_webview/browser/aw_permission_manager.h"
 #include "android_webview/browser/find_helper.h"
 #include "android_webview/browser/permission/media_access_permission_request.h"
 #include "android_webview/browser/permission/permission_request_handler.h"
 #include "android_webview/browser_jni_headers/AwWebContentsDelegate_jni.h"
+#include "android_webview/common/aw_features.h"
 #include "base/android/jni_array.h"
 #include "base/android/jni_string.h"
 #include "base/android/scoped_java_ref.h"
@@ -26,6 +29,7 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "components/navigation_interception/intercept_navigation_delegate.h"
 #include "content/public/browser/file_select_listener.h"
+#include "content/public/browser/permission_controller_delegate.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/render_view_host.h"
@@ -33,6 +37,7 @@
 #include "content/public/browser/web_contents.h"
 #include "net/base/filename_util.h"
 #include "third_party/blink/public/common/mediastream/media_stream_request.h"
+#include "third_party/blink/public/common/permissions/permission_utils.h"
 #include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
 
 using base::android::AttachCurrentThread;
@@ -264,8 +269,34 @@
     return;
   }
   aw_contents->GetPermissionRequestHandler()->SendRequest(
-      std::make_unique<MediaAccessPermissionRequest>(request,
-                                                     std::move(callback)));
+      std::make_unique<MediaAccessPermissionRequest>(
+          request, std::move(callback),
+          *AwBrowserContext::FromWebContents(web_contents)
+               ->GetPermissionControllerDelegate()));
+}
+
+bool AwWebContentsDelegate::CheckMediaAccessPermission(
+    content::RenderFrameHost* render_frame_host,
+    const GURL& security_origin,
+    blink::mojom::MediaStreamType type) {
+  if (!base::FeatureList::IsEnabled(features::kWebViewEnumerateDevicesCache)) {
+    return false;
+  }
+  WebContents* web_contents =
+      WebContents::FromRenderFrameHost(render_frame_host);
+  if (!web_contents) {
+    return false;
+  }
+  AwPermissionManager* pm = AwBrowserContext::FromWebContents(web_contents)
+                                ->GetPermissionControllerDelegate();
+  switch (type) {
+    case blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE:
+      return pm->ShouldShowEnumerateDevicesAudioLabels(security_origin);
+    case blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE:
+      return pm->ShouldShowEnumerateDevicesVideoLabels(security_origin);
+    default:
+      return false;
+  }
 }
 
 void AwWebContentsDelegate::EnterFullscreenModeForTab(
diff --git a/android_webview/browser/aw_web_contents_delegate.h b/android_webview/browser/aw_web_contents_delegate.h
index 8aad1de0..91c47105 100644
--- a/android_webview/browser/aw_web_contents_delegate.h
+++ b/android_webview/browser/aw_web_contents_delegate.h
@@ -66,6 +66,9 @@
       content::WebContents* web_contents,
       const content::MediaStreamRequest& request,
       content::MediaResponseCallback callback) override;
+  bool CheckMediaAccessPermission(content::RenderFrameHost* render_frame_host,
+                                  const GURL& security_origin,
+                                  blink::mojom::MediaStreamType type) override;
   void EnterFullscreenModeForTab(
       content::RenderFrameHost* requesting_frame,
       const blink::mojom::FullscreenOptions& options) override;
diff --git a/android_webview/browser/permission/media_access_permission_request.cc b/android_webview/browser/permission/media_access_permission_request.cc
index 0d30cba..29f6cb9 100644
--- a/android_webview/browser/permission/media_access_permission_request.cc
+++ b/android_webview/browser/permission/media_access_permission_request.cc
@@ -7,7 +7,9 @@
 #include <algorithm>
 #include <utility>
 
+#include "android_webview/browser/aw_browser_context.h"
 #include "android_webview/browser/permission/aw_permission_request.h"
+#include "android_webview/common/aw_features.h"
 #include "content/public/browser/media_capture_devices.h"
 #include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
 #include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
@@ -42,14 +44,23 @@
 
 MediaAccessPermissionRequest::MediaAccessPermissionRequest(
     const content::MediaStreamRequest& request,
-    content::MediaResponseCallback callback)
-    : request_(request), callback_(std::move(callback)) {}
+    content::MediaResponseCallback callback,
+    AwPermissionManager& permission_manager)
+    : request_(request),
+      callback_(std::move(callback)),
+      permission_manager_(permission_manager) {}
 
 MediaAccessPermissionRequest::~MediaAccessPermissionRequest() {}
 
 void MediaAccessPermissionRequest::NotifyRequestResult(bool allowed) {
   std::unique_ptr<content::MediaStreamUI> ui;
   if (!allowed) {
+    permission_manager_->ClearEnumerateDevicesCachedPermission(
+        request_.security_origin,
+        /* remove_audio */ request_.audio_type ==
+            blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE,
+        /* remove_video */ request_.video_type ==
+            blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE);
     std::move(callback_).Run(
         blink::mojom::StreamDevicesSet(),
         blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED,
@@ -71,6 +82,10 @@
         audio_devices, request_.requested_audio_device_id);
     if (device)
       devices.audio_device = *device;
+    if (base::FeatureList::IsEnabled(features::kWebViewEnumerateDevicesCache)) {
+      permission_manager_->SetOriginCanReadEnumerateDevicesAudioLabels(
+          request_.security_origin, true);
+    }
   }
 
   if (request_.video_type ==
@@ -83,6 +98,10 @@
         video_devices, request_.requested_video_device_id);
     if (device)
       devices.video_device = *device;
+    if (base::FeatureList::IsEnabled(features::kWebViewEnumerateDevicesCache)) {
+      permission_manager_->SetOriginCanReadEnumerateDevicesVideoLabels(
+          request_.security_origin, true);
+    }
   }
 
   const bool has_no_hardware =
diff --git a/android_webview/browser/permission/media_access_permission_request.h b/android_webview/browser/permission/media_access_permission_request.h
index 456e5c6..cd97316d 100644
--- a/android_webview/browser/permission/media_access_permission_request.h
+++ b/android_webview/browser/permission/media_access_permission_request.h
@@ -13,13 +13,15 @@
 #include "third_party/blink/public/common/mediastream/media_stream_request.h"
 
 namespace android_webview {
+class AwPermissionManager;
 
 // The AwPermissionRequestDelegate implementation for media access permission
 // request.
 class MediaAccessPermissionRequest : public AwPermissionRequestDelegate {
  public:
   MediaAccessPermissionRequest(const content::MediaStreamRequest& request,
-                               content::MediaResponseCallback callback);
+                               content::MediaResponseCallback callback,
+                               AwPermissionManager& permission_manager);
 
   MediaAccessPermissionRequest(const MediaAccessPermissionRequest&) = delete;
   MediaAccessPermissionRequest& operator=(const MediaAccessPermissionRequest&) =
@@ -37,6 +39,7 @@
 
   const content::MediaStreamRequest request_;
   content::MediaResponseCallback callback_;
+  const raw_ref<AwPermissionManager> permission_manager_;
 
   // For test only.
   blink::MediaStreamDevices audio_test_devices_;
diff --git a/android_webview/browser/permission/media_access_permission_request_unittest.cc b/android_webview/browser/permission/media_access_permission_request_unittest.cc
index edd7d3f..62c9884 100644
--- a/android_webview/browser/permission/media_access_permission_request_unittest.cc
+++ b/android_webview/browser/permission/media_access_permission_request_unittest.cc
@@ -6,7 +6,10 @@
 
 #include <memory>
 
+#include "android_webview/browser/aw_permission_manager.h"
+#include "android_webview/common/aw_features.h"
 #include "base/bind.h"
+#include "base/test/scoped_feature_list.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/mediastream/media_stream_request.h"
 #include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
@@ -20,8 +23,11 @@
       const content::MediaStreamRequest& request,
       content::MediaResponseCallback callback,
       const blink::MediaStreamDevices& audio_devices,
-      const blink::MediaStreamDevices& video_devices)
-      : MediaAccessPermissionRequest(request, std::move(callback)) {
+      const blink::MediaStreamDevices& video_devices,
+      AwPermissionManager& aw_permission_manager_)
+      : MediaAccessPermissionRequest(request,
+                                     std::move(callback),
+                                     aw_permission_manager_) {
     audio_test_devices_ = audio_devices;
     video_test_devices_ = video_devices;
   }
@@ -68,7 +74,7 @@
         request,
         base::BindOnce(&MediaAccessPermissionRequestTest::Callback,
                        base::Unretained(this)),
-        audio_devices, video_devices);
+        audio_devices, video_devices, aw_permission_manager_);
     return permission_request;
   }
 
@@ -78,6 +84,8 @@
   std::string first_video_device_id_;
   blink::MediaStreamDevices devices_;
   blink::mojom::MediaStreamRequestResult result_;
+  AwPermissionManager aw_permission_manager_;
+  base::test::ScopedFeatureList feature_list_;
 
  private:
   void Callback(const blink::mojom::StreamDevicesSet& stream_devices_set,
@@ -144,4 +152,36 @@
   EXPECT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED, result_);
 }
 
+TEST_F(MediaAccessPermissionRequestTest,
+       TestGrantedPermissionRequestCachesResult) {
+  GURL origin("https://www.google.com");
+  EXPECT_FALSE(
+      aw_permission_manager_.ShouldShowEnumerateDevicesAudioLabels(origin));
+  EXPECT_FALSE(
+      aw_permission_manager_.ShouldShowEnumerateDevicesVideoLabels(origin));
+  std::unique_ptr<TestMediaAccessPermissionRequest> request =
+      CreateRequest(audio_device_id_, video_device_id_);
+  request->NotifyRequestResult(true);
+  EXPECT_TRUE(
+      aw_permission_manager_.ShouldShowEnumerateDevicesAudioLabels(origin));
+  EXPECT_TRUE(
+      aw_permission_manager_.ShouldShowEnumerateDevicesVideoLabels(origin));
+}
+
+TEST_F(MediaAccessPermissionRequestTest,
+       TestGrantedPermissionRequestWithoutCacheFailsEnumerateDevices) {
+  feature_list_.InitAndDisableFeature(features::kWebViewEnumerateDevicesCache);
+  GURL origin("https://www.google.com");
+  EXPECT_FALSE(
+      aw_permission_manager_.ShouldShowEnumerateDevicesAudioLabels(origin));
+  EXPECT_FALSE(
+      aw_permission_manager_.ShouldShowEnumerateDevicesVideoLabels(origin));
+  std::unique_ptr<TestMediaAccessPermissionRequest> request =
+      CreateRequest(audio_device_id_, video_device_id_);
+  request->NotifyRequestResult(true);
+  EXPECT_FALSE(
+      aw_permission_manager_.ShouldShowEnumerateDevicesAudioLabels(origin));
+  EXPECT_FALSE(
+      aw_permission_manager_.ShouldShowEnumerateDevicesVideoLabels(origin));
+}
 }  // namespace android_webview
diff --git a/android_webview/common/aw_features.cc b/android_webview/common/aw_features.cc
index 9fba7244..d0bfb27c 100644
--- a/android_webview/common/aw_features.cc
+++ b/android_webview/common/aw_features.cc
@@ -27,6 +27,12 @@
              "WebViewConnectionlessSafeBrowsing",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Cache origins which have camera/mic permissions approved to allow subsequent
+// calls to enumerate devices to return device labels.
+BASE_FEATURE(kWebViewEnumerateDevicesCache,
+             "WebViewEnumerateDevicesCache",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 // Enable WebView to automatically darken the page in FORCE_DARK_AUTO mode if
 // the app's theme is dark.
 BASE_FEATURE(kWebViewForceDarkModeMatchTheme,
diff --git a/android_webview/common/aw_features.h b/android_webview/common/aw_features.h
index 1b9a436..7049145 100644
--- a/android_webview/common/aw_features.h
+++ b/android_webview/common/aw_features.h
@@ -18,9 +18,11 @@
 // Alphabetical:
 BASE_DECLARE_FEATURE(kWebViewBrotliSupport);
 BASE_DECLARE_FEATURE(kWebViewCheckReturnResources);
+BASE_DECLARE_FEATURE(kWebViewClientHintsControllerDelegate);
 BASE_DECLARE_FEATURE(kWebViewConnectionlessSafeBrowsing);
 BASE_DECLARE_FEATURE(kWebViewDisplayCutout);
 BASE_DECLARE_FEATURE(kWebViewEmptyComponentLoaderPolicy);
+BASE_DECLARE_FEATURE(kWebViewEnumerateDevicesCache);
 BASE_DECLARE_FEATURE(kWebViewExtraHeadersSameOriginOnly);
 BASE_DECLARE_FEATURE(kWebViewForceDarkModeMatchTheme);
 BASE_DECLARE_FEATURE(kWebViewHitTestInBlinkOnTouchStart);
@@ -36,7 +38,6 @@
 BASE_DECLARE_FEATURE(kWebViewXRequestedWithHeaderControl);
 extern const base::FeatureParam<int> kWebViewXRequestedWithHeaderMode;
 BASE_DECLARE_FEATURE(kWebViewXRequestedWithHeaderManifestAllowList);
-BASE_DECLARE_FEATURE(kWebViewClientHintsControllerDelegate);
 
 }  // namespace features
 }  // namespace android_webview
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwAutofillTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwAutofillTest.java
index 76aa035..4cf41a2 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwAutofillTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwAutofillTest.java
@@ -50,6 +50,7 @@
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.task.PostTask;
 import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.DisableIf;
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
@@ -1111,6 +1112,57 @@
     }
 
     /**
+     * Tests that a frame-transcending form is filled correctly.
+     */
+    @Test
+    @SmallTest
+    @Feature({"AndroidWebView"})
+    @CommandLineFlags.Add({"enable-features=AutofillAcrossIframes"})
+    public void testCrossFrameAutofill() throws Throwable {
+        final String data = "<html><body><form>"
+                + "<input autocomplete=cc-name>"
+                + "<iframe srcdoc='<input autocomplete=cc-number>'></iframe>"
+                + "<iframe srcdoc='<input autocomplete=cc-exp>'></iframe>"
+                + "<iframe srcdoc='<input autocomplete=cc-csc>'></iframe>"
+                + "</form></body></html>";
+        loadUrlSync(mWebServer.setResponse(FILE, data, null));
+        int cnt = 0;
+        executeJavaScriptAndWaitForResult(
+                "window.frames[0].document.body.firstElementChild.select();");
+        dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_A);
+        cnt += waitForCallbackAndVerifyTypes(cnt,
+                new Integer[] {AUTOFILL_CANCEL, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED,
+                        AUTOFILL_VIEW_EXITED, AUTOFILL_VIEW_ENTERED, AUTOFILL_VALUE_CHANGED});
+
+        invokeOnProvideAutoFillVirtualStructure();
+        TestViewStructure viewStructure = mTestValues.testViewStructure;
+        assertNotNull(viewStructure);
+
+        // Autofill form and verify filled values.
+        SparseArray<AutofillValue> values = new SparseArray<AutofillValue>();
+        values.append(viewStructure.getChild(0).getId(), AutofillValue.forText("Barack Obama"));
+        values.append(viewStructure.getChild(1).getId(), AutofillValue.forText("4444333322221111"));
+        values.append(viewStructure.getChild(2).getId(), AutofillValue.forText("12 / 2035"));
+        values.append(viewStructure.getChild(3).getId(), AutofillValue.forText("123"));
+        invokeAutofill(values);
+        waitForCallbackAndVerifyTypes(cnt,
+                new Integer[] {AUTOFILL_VALUE_CHANGED, AUTOFILL_VALUE_CHANGED,
+                        AUTOFILL_VALUE_CHANGED, AUTOFILL_VALUE_CHANGED});
+
+        assertEquals("\"Barack Obama\"",
+                executeJavaScriptAndWaitForResult("document.forms[0].elements[0].value;"));
+        assertEquals("\"4444333322221111\"",
+                executeJavaScriptAndWaitForResult(
+                        "window.frames[0].document.body.firstElementChild.value;"));
+        assertEquals("\"12 / 2035\"",
+                executeJavaScriptAndWaitForResult(
+                        "window.frames[1].document.body.firstElementChild.value;"));
+        assertEquals("\"123\"",
+                executeJavaScriptAndWaitForResult(
+                        "window.frames[2].document.body.firstElementChild.value;"));
+    }
+
+    /**
      * This test is verifying that a user interacting with a form after reloading a webpage
      * triggers a new autofill session rather than continuing a session that was started before the
      * reload. This is necessary to ensure that autofill is properly triggered in this case (see
@@ -1256,6 +1308,76 @@
         assertEquals(SubmissionSource.FORM_SUBMISSION, mSubmissionSource);
     }
 
+    /**
+     * Tests that when a multi-frame form is submitted in a subframe, we register the submission of
+     * the overall form.
+     */
+    @Test
+    @SmallTest
+    @Feature({"AndroidWebView"})
+    @CommandLineFlags.Add({"enable-features=AutofillAcrossIframes"})
+    public void testCrossFrameCommit() throws Throwable {
+        // The only reason we use a <form> inside the iframe is that this makes it easiest to
+        // trigger a form submission in that frame.
+        // TODO(crbug.com/1385768): Need to set the "id" so GetSimilarFieldIndex() doesn't confuse
+        // the fields.
+        final String data = "<html><head></head><body><form>"
+                + "<input id=name>"
+                + "<iframe srcdoc='<form action=arbitrary.html method=GET>"
+                + "                <input id=num></form>'></iframe>"
+                + "<iframe srcdoc='<input id=exp>'></iframe>"
+                + "<iframe srcdoc='<input id=csc>'></iframe>"
+                + "</form></body></html>";
+        loadUrlSync(mWebServer.setResponse(FILE, data, null));
+        int cnt = 0;
+        // Fill name field.
+        executeJavaScriptAndWaitForResult("document.forms[0].elements[0].select();");
+        dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_A);
+        cnt += waitForCallbackAndVerifyTypes(cnt,
+                new Integer[] {AUTOFILL_CANCEL, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED,
+                        AUTOFILL_VALUE_CHANGED});
+        invokeOnProvideAutoFillVirtualStructure();
+        // Fill number field.
+        executeJavaScriptAndWaitForResult(
+                "window.frames[0].document.forms[0].elements[0].select();");
+        cnt += waitForCallbackAndVerifyTypes(cnt,
+                new Integer[] {
+                        AUTOFILL_VIEW_EXITED, AUTOFILL_VIEW_ENTERED, AUTOFILL_VALUE_CHANGED});
+        dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_B);
+        cnt += waitForCallbackAndVerifyTypes(cnt, new Integer[] {AUTOFILL_VALUE_CHANGED});
+        clearChangedValues();
+        // Fill expiration date field.
+        executeJavaScriptAndWaitForResult(
+                "window.frames[1].document.body.firstElementChild.select();");
+        dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_C);
+        cnt += waitForCallbackAndVerifyTypes(cnt,
+                new Integer[] {
+                        AUTOFILL_VIEW_EXITED, AUTOFILL_VIEW_ENTERED, AUTOFILL_VALUE_CHANGED});
+        cnt += waitForCallbackAndVerifyTypes(cnt, new Integer[] {AUTOFILL_VALUE_CHANGED});
+        clearChangedValues();
+        // Fill CVC field.
+        executeJavaScriptAndWaitForResult(
+                "window.frames[2].document.body.firstElementChild.select();");
+        dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_D);
+        cnt += waitForCallbackAndVerifyTypes(cnt,
+                new Integer[] {
+                        AUTOFILL_VIEW_EXITED, AUTOFILL_VIEW_ENTERED, AUTOFILL_VALUE_CHANGED});
+        cnt += waitForCallbackAndVerifyTypes(cnt, new Integer[] {AUTOFILL_VALUE_CHANGED});
+        clearChangedValues();
+        // Submit a form in the subframe.
+        executeJavaScriptAndWaitForResult("window.frames[0].document.forms[0].submit();");
+        waitForCallbackAndVerifyTypes(cnt,
+                new Integer[] {AUTOFILL_VALUE_CHANGED, AUTOFILL_VALUE_CHANGED,
+                        AUTOFILL_VALUE_CHANGED, AUTOFILL_VALUE_CHANGED, AUTOFILL_COMMIT});
+        ArrayList<Pair<Integer, AutofillValue>> values = getChangedValues();
+        assertEquals(4, values.size());
+        assertEquals("a", values.get(0).second.getTextValue());
+        assertEquals("b", values.get(1).second.getTextValue());
+        assertEquals("c", values.get(2).second.getTextValue());
+        assertEquals("d", values.get(3).second.getTextValue());
+        assertEquals(SubmissionSource.FORM_SUBMISSION, mSubmissionSource);
+    }
+
     @Test
     @SmallTest
     @Feature({"AndroidWebView"})
@@ -2307,6 +2429,90 @@
         assertNull(binder);
     }
 
+    /**
+     * Tests that server predictions are mapped to the fields of a cross-frame form.
+     */
+    @Test
+    @SmallTest
+    @Feature({"AndroidWebView"})
+    @CommandLineFlags.Add({"enable-features=AutofillAcrossIframes"})
+    public void testCrossFrameServerPredictionArrivesBeforeAutofillStart() throws Throwable {
+        final String data = "<html><head></head><body><form>"
+                + "<input id=name>"
+                + "<iframe srcdoc='<form action=arbitrary.html method=GET>"
+                + "                <input id=num autocomplete=cc-number></form>'"
+                + "        sandbox></iframe>"
+                + "<iframe srcdoc='<input id=exp>'></iframe>"
+                + "<iframe srcdoc='<input id=csc>'></iframe>"
+                + "</form></body></html>";
+        final String url = mWebServer.setResponse(FILE, data, null);
+        loadUrlSync(url);
+        TestThreadUtils.runOnUiThreadBlocking(
+                ()
+                        -> AutofillProviderTestHelper
+                                   .simulateMainFramePredictionsAutofillServerResponseForTesting(
+                                           mAwContents.getWebContents(),
+                                           new String[] {"name", "num", "exp", "csc"},
+                                           new int[][] {{/*CREDIT_CARD_NAME_FULL*/ 51},
+                                                   {/*CREDIT_CARD_NUMBER*/ 52},
+                                                   {/*CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR*/ 56,
+                                                           /*CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR*/
+                                                           57},
+                                                   {/*CREDIT_CARD_VERIFICATION_CODE*/ 59}}));
+
+        int cnt = 0;
+        executeJavaScriptAndWaitForResult("document.forms[0].elements[0].select();");
+        dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_A);
+
+        cnt += waitForCallbackAndVerifyTypes(cnt,
+                new Integer[] {AUTOFILL_CANCEL, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED,
+                        AUTOFILL_VALUE_CHANGED});
+
+        invokeOnProvideAutoFillVirtualStructure();
+        TestViewStructure viewStructure = mTestValues.testViewStructure;
+        assertNotNull(viewStructure);
+        assertEquals(4, viewStructure.getChildCount());
+        // Name field.
+        assertEquals("CREDIT_CARD_NAME_FULL",
+                viewStructure.getChild(0).getHtmlInfo().getAttribute(
+                        "crowdsourcing-autofill-hints"));
+        assertEquals("CREDIT_CARD_NAME_FULL",
+                viewStructure.getChild(0).getHtmlInfo().getAttribute("computed-autofill-hints"));
+        assertEquals("CREDIT_CARD_NAME_FULL",
+                viewStructure.getChild(0).getHtmlInfo().getAttribute(
+                        "crowdsourcing-predictions-autofill-hints"));
+        // Number field.
+        assertEquals("CREDIT_CARD_NUMBER",
+                viewStructure.getChild(1).getHtmlInfo().getAttribute(
+                        "crowdsourcing-autofill-hints"));
+        assertEquals("HTML_TYPE_CREDIT_CARD_NUMBER",
+                viewStructure.getChild(1).getHtmlInfo().getAttribute("computed-autofill-hints"));
+        assertEquals("CREDIT_CARD_NUMBER",
+                viewStructure.getChild(1).getHtmlInfo().getAttribute(
+                        "crowdsourcing-predictions-autofill-hints"));
+        // Expiration date field.
+        assertEquals("CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR",
+                viewStructure.getChild(2).getHtmlInfo().getAttribute(
+                        "crowdsourcing-autofill-hints"));
+        assertEquals("CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR",
+                viewStructure.getChild(2).getHtmlInfo().getAttribute("computed-autofill-hints"));
+        assertEquals("CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR,CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR",
+                viewStructure.getChild(2).getHtmlInfo().getAttribute(
+                        "crowdsourcing-predictions-autofill-hints"));
+        // CVC field.
+        assertEquals("CREDIT_CARD_VERIFICATION_CODE",
+                viewStructure.getChild(3).getHtmlInfo().getAttribute(
+                        "crowdsourcing-autofill-hints"));
+        assertEquals("CREDIT_CARD_VERIFICATION_CODE",
+                viewStructure.getChild(3).getHtmlInfo().getAttribute("computed-autofill-hints"));
+        assertEquals("CREDIT_CARD_VERIFICATION_CODE",
+                viewStructure.getChild(3).getHtmlInfo().getAttribute(
+                        "crowdsourcing-predictions-autofill-hints"));
+        // Binder is not set if the prediction has already arrived.
+        IBinder binder = viewStructure.getExtras().getBinder("AUTOFILL_HINTS_SERVICE");
+        assertNull(binder);
+    }
+
     @Test
     @SmallTest
     @Feature({"AndroidWebView"})
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwPermissionManagerTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwPermissionManagerTest.java
index 43ca08f..5d6443d75 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwPermissionManagerTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwPermissionManagerTest.java
@@ -7,8 +7,11 @@
 import android.os.Handler;
 import android.os.Looper;
 
+import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
 
+import org.json.JSONArray;
+import org.json.JSONException;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
@@ -19,7 +22,11 @@
 import org.chromium.android_webview.AwContents;
 import org.chromium.android_webview.permission.AwPermissionRequest;
 import org.chromium.android_webview.test.util.CommonResources;
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
+import org.chromium.content_public.browser.test.util.DomAutomationController;
+import org.chromium.content_public.browser.test.util.JavaScriptUtils;
+import org.chromium.content_public.common.ContentSwitches;
 import org.chromium.net.test.util.TestWebServer;
 
 /**
@@ -39,8 +46,24 @@
             + "</script><body>"
             + "</body></html>";
 
+    private static final String EMPTY_PAGE = "<html><script>"
+            + "</script><body>"
+            + "</body></html>";
+
+    private static final String GUM_JS =
+            "navigator.mediaDevices.getUserMedia({video: true, audio: true})"
+            + ".then((_) => domAutomationController.send('success'))"
+            + ".catch((error) => domAutomationController.send('failure'));";
+
+    private static final String ENUMERATE_DEVICES_JS =
+            "navigator.mediaDevices.enumerateDevices().then("
+            + "(devices) => domAutomationController.send(devices.map("
+            + "  (d) => `${d['label']}`)));";
+
+    private final DomAutomationController mDomAutomationController = new DomAutomationController();
     private TestWebServer mTestWebServer;
     private String mPage;
+    private TestAwContentsClient mContentsClient;
 
     @Before
     public void setUp() throws Exception {
@@ -60,7 +83,7 @@
         mPage = mTestWebServer.setResponse("/permissions", REQUEST_DUPLICATE,
                 CommonResources.getTextHtmlHeaders(true));
 
-        TestAwContentsClient contentsClient = new TestAwContentsClient() {
+        mContentsClient = new TestAwContentsClient() {
             private boolean mCalled;
 
             @Override
@@ -73,21 +96,145 @@
 
                 // Emulate a delayed response to the request by running four seconds in the future.
                 Handler handler = new Handler(Looper.myLooper());
-                handler.postDelayed(() -> awPermissionRequest.grant(), 4000);
+                handler.postDelayed(awPermissionRequest::grant, 4000);
             }
         };
 
         final AwTestContainerView testContainerView =
-                mActivityTestRule.createAwTestContainerViewOnMainSync(contentsClient);
+                mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
         final AwContents awContents = testContainerView.getAwContents();
         AwActivityTestRule.enableJavaScriptOnUiThread(awContents);
         mActivityTestRule.loadUrlAsync(awContents, mPage, null);
         pollTitleAs("second-granted", awContents);
     }
 
+    @Test
+    @Feature({"AndroidWebView"})
+    @SmallTest
+    @CommandLineFlags.Add({ContentSwitches.USE_FAKE_DEVICE_FOR_MEDIA_STREAM})
+    public void testRequestMediaPermissions() throws Exception {
+        AwContents awContents = setUpEnumerateDevicesTest(null);
+
+        mActivityTestRule.loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(), mPage);
+        JavaScriptUtils.runJavascriptWithUserGestureAndAsyncResult(
+                awContents.getWebContents(), GUM_JS);
+        String devices = JavaScriptUtils.runJavascriptWithUserGestureAndAsyncResult(
+                awContents.getWebContents(), ENUMERATE_DEVICES_JS);
+
+        assertDeviceLabels(devices, false);
+    }
+
+    @Test
+    @Feature({"AndroidWebView"})
+    @SmallTest
+    @CommandLineFlags.Add({ContentSwitches.USE_FAKE_DEVICE_FOR_MEDIA_STREAM})
+    public void testNavigationRevokesEnumerateDevicesLabelsPermissions() throws Exception {
+        AwContents awContents = setUpEnumerateDevicesTest(null);
+
+        TestWebServer secondServer = TestWebServer.startAdditional();
+        String secondPage = secondServer.setResponse(
+                "/new-page", EMPTY_PAGE, CommonResources.getTextHtmlHeaders(true));
+
+        mActivityTestRule.loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(), mPage);
+        JavaScriptUtils.runJavascriptWithUserGestureAndAsyncResult(
+                awContents.getWebContents(), GUM_JS);
+        JavaScriptUtils.runJavascriptWithUserGestureAndAsyncResult(
+                awContents.getWebContents(), ENUMERATE_DEVICES_JS);
+
+        // Navigate to a page with a different origin.
+        mActivityTestRule.loadUrlSync(
+                awContents, mContentsClient.getOnPageFinishedHelper(), secondPage);
+        String devices = JavaScriptUtils.runJavascriptWithUserGestureAndAsyncResult(
+                awContents.getWebContents(), ENUMERATE_DEVICES_JS);
+
+        assertDeviceLabels(devices, true);
+    }
+
+    @Test
+    @Feature({"AndroidWebView"})
+    @SmallTest
+    @CommandLineFlags.Add({ContentSwitches.USE_FAKE_DEVICE_FOR_MEDIA_STREAM})
+    public void testEnumerateDevicesDoesNotShowLabelsBeforeGetUserMedia() throws Exception {
+        AwContents awContents = setUpEnumerateDevicesTest(null);
+
+        mActivityTestRule.loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(), mPage);
+        String devices = JavaScriptUtils.runJavascriptWithUserGestureAndAsyncResult(
+                awContents.getWebContents(), ENUMERATE_DEVICES_JS);
+
+        assertDeviceLabels(devices, true);
+    }
+
+    @Test
+    @Feature({"AndroidWebView"})
+    @SmallTest
+    @CommandLineFlags.Add({ContentSwitches.USE_FAKE_DEVICE_FOR_MEDIA_STREAM})
+    public void testRevokeEnumerateDevicesPermission() throws Exception {
+        AwContents awContents = setUpEnumerateDevicesTest(new TestAwContentsClient() {
+            private boolean mHasBeenGranted;
+
+            @Override
+            public void onPermissionRequest(AwPermissionRequest awPermissionRequest) {
+                if (mHasBeenGranted) {
+                    awPermissionRequest.deny();
+                    return;
+                }
+                mHasBeenGranted = true;
+                awPermissionRequest.grant();
+            }
+        });
+
+        mActivityTestRule.loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(), mPage);
+        JavaScriptUtils.runJavascriptWithUserGestureAndAsyncResult(
+                awContents.getWebContents(), GUM_JS);
+        JavaScriptUtils.runJavascriptWithUserGestureAndAsyncResult(
+                awContents.getWebContents(), ENUMERATE_DEVICES_JS);
+        JavaScriptUtils.runJavascriptWithUserGestureAndAsyncResult(
+                awContents.getWebContents(), GUM_JS);
+        String devices = JavaScriptUtils.runJavascriptWithUserGestureAndAsyncResult(
+                awContents.getWebContents(), ENUMERATE_DEVICES_JS);
+        assertDeviceLabels(devices, true);
+    }
+
     private void pollTitleAs(final String title, final AwContents awContents) {
         AwActivityTestRule.pollInstrumentationThread(
                 () -> title.equals(mActivityTestRule.getTitleOnUiThread(awContents)));
     }
+
+    private AwContents setUpEnumerateDevicesTest(@Nullable TestAwContentsClient contentsClient)
+            throws Exception {
+        mPage = mTestWebServer.setResponse(
+                "/media", EMPTY_PAGE, CommonResources.getTextHtmlHeaders(true));
+
+        mContentsClient = contentsClient != null ? contentsClient : new TestAwContentsClient() {
+            @Override
+            public void onPermissionRequest(final AwPermissionRequest awPermissionRequest) {
+                awPermissionRequest.grant();
+            }
+        };
+
+        final AwTestContainerView testContainerView =
+                mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
+        AwContents awContents = testContainerView.getAwContents();
+        mDomAutomationController.inject(awContents.getWebContents());
+        AwActivityTestRule.enableJavaScriptOnUiThread(awContents);
+
+        return awContents;
+    }
+
+    private void assertDeviceLabels(String devices, boolean shouldBeEmpty) throws JSONException {
+        JSONArray devicesJson = new JSONArray(devices);
+        boolean isEmpty = true;
+        for (int i = 0; i < devicesJson.length(); i++) {
+            if (!devicesJson.getString(i).isEmpty()) {
+                isEmpty = false;
+                break;
+            }
+        }
+        if (shouldBeEmpty) {
+            Assert.assertTrue(isEmpty);
+        } else {
+            Assert.assertFalse(isEmpty);
+        }
+    }
 }
 
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 0a71006..ef88a167 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -1542,6 +1542,10 @@
     "system/palette/tools/metalayer_mode.h",
     "system/pcie_peripheral/pcie_peripheral_notification_controller.cc",
     "system/pcie_peripheral/pcie_peripheral_notification_controller.h",
+    "system/phonehub/app_stream_launcher_item.cc",
+    "system/phonehub/app_stream_launcher_item.h",
+    "system/phonehub/app_stream_launcher_view.cc",
+    "system/phonehub/app_stream_launcher_view.h",
     "system/phonehub/bluetooth_disabled_view.cc",
     "system/phonehub/bluetooth_disabled_view.h",
     "system/phonehub/camera_roll_menu_model.cc",
@@ -3016,6 +3020,7 @@
     "system/palette/tools/create_note_unittest.cc",
     "system/palette/tools/metalayer_unittest.cc",
     "system/pcie_peripheral/pcie_peripheral_notification_controller_unittest.cc",
+    "system/phonehub/app_stream_launcher_view_unittest.cc",
     "system/phonehub/camera_roll_thumbnail_unittest.cc",
     "system/phonehub/camera_roll_view_unittest.cc",
     "system/phonehub/phone_hub_notification_controller_unittest.cc",
diff --git a/ash/app_list/model/search/test_search_result.cc b/ash/app_list/model/search/test_search_result.cc
index 869a4f6..c62d9ec8 100644
--- a/ash/app_list/model/search/test_search_result.cc
+++ b/ash/app_list/model/search/test_search_result.cc
@@ -40,4 +40,9 @@
   SetDetailsTextVector(StringToTextVector(details));
 }
 
+void TestSearchResult::SetCategory(
+    const ash::AppListSearchResultCategory category) {
+  SearchResult::set_category(category);
+}
+
 }  // namespace ash
diff --git a/ash/app_list/model/search/test_search_result.h b/ash/app_list/model/search/test_search_result.h
index ce26bfb..947816a4 100644
--- a/ash/app_list/model/search/test_search_result.h
+++ b/ash/app_list/model/search/test_search_result.h
@@ -25,6 +25,7 @@
   void set_result_id(const std::string& id);
   void SetTitle(const std::u16string& title);
   void SetDetails(const std::u16string& details);
+  void SetCategory(const ash::AppListSearchResultCategory category);
 };
 
 }  // namespace ash
diff --git a/ash/app_list/views/search_box_view_unittest.cc b/ash/app_list/views/search_box_view_unittest.cc
index 264d14fd..d89769e 100644
--- a/ash/app_list/views/search_box_view_unittest.cc
+++ b/ash/app_list/views/search_box_view_unittest.cc
@@ -196,9 +196,10 @@
   void CreateSearchResult(ash::SearchResultDisplayType display_type,
                           double display_score,
                           const std::u16string& title,
-                          const std::u16string& details) {
+                          const std::u16string& details,
+                          const ash::AppListSearchResultCategory& category) {
     CreateSearchResultAt(results()->item_count(), display_type, display_score,
-                         title, details);
+                         title, details, category);
   }
 
   // Creates a SearchResult with the given parameters at the given index in
@@ -207,13 +208,15 @@
                             ash::SearchResultDisplayType display_type,
                             double display_score,
                             const std::u16string& title,
-                            const std::u16string& details) {
+                            const std::u16string& details,
+                            const ash::AppListSearchResultCategory& category) {
     auto search_result = std::make_unique<TestSearchResult>();
     search_result->set_result_id(base::NumberToString(++last_result_id_));
     search_result->set_display_type(display_type);
     search_result->set_display_score(display_score);
     search_result->SetTitle(title);
     search_result->SetDetails(details);
+    search_result->SetCategory(category);
     search_result->set_best_match(true);
     results()->AddAt(index, std::move(search_result));
   }
@@ -356,9 +359,9 @@
 TEST_F(SearchBoxViewTest, ChangeSelectionWhileResultsAreChanging) {
   SimulateQuery(u"test");
   CreateSearchResult(ash::SearchResultDisplayType::kList, 0.7, u"tester",
-                     std::u16string());
+                     std::u16string(), ash::AppListSearchResultCategory::kWeb);
   CreateSearchResult(ash::SearchResultDisplayType::kList, 0.5, u"testing",
-                     std::u16string());
+                     std::u16string(), ash::AppListSearchResultCategory::kWeb);
   base::RunLoop().RunUntilIdle();
 
   const SearchResultBaseView* selection =
@@ -372,7 +375,8 @@
   // Add a new result - the selection controller is updated asynchronously, so
   // the result is expected to remain the same until the loop is run.
   CreateSearchResultAt(0, ash::SearchResultDisplayType::kList, 1., u"test",
-                       std::u16string());
+                       std::u16string(),
+                       ash::AppListSearchResultCategory::kWeb);
   EXPECT_EQ(selection, GetResultSelectionController()->selected_result());
   EXPECT_EQ(u"tester", selection->result()->title());
 
@@ -403,9 +407,9 @@
   SimulateQuery(u"test");
 
   CreateSearchResult(ash::SearchResultDisplayType::kList, 0.7, u"tester",
-                     std::u16string());
+                     std::u16string(), ash::AppListSearchResultCategory::kWeb);
   CreateSearchResult(ash::SearchResultDisplayType::kList, 0.5, u"testing",
-                     std::u16string());
+                     std::u16string(), ash::AppListSearchResultCategory::kWeb);
   base::RunLoop().RunUntilIdle();
 
   const SearchResultBaseView* selection =
@@ -420,7 +424,7 @@
   // the loop is run.
   results()->RemoveAll();
   CreateSearchResult(ash::SearchResultDisplayType::kList, 1., u"test",
-                     std::u16string());
+                     std::u16string(), ash::AppListSearchResultCategory::kWeb);
   EXPECT_EQ(selection, GetResultSelectionController()->selected_result());
   EXPECT_FALSE(selection->result());
 
@@ -444,9 +448,9 @@
 TEST_F(SearchBoxViewTest, UserSelectionNotOverridenByNewResults) {
   SimulateQuery(u"test");
   CreateSearchResult(ash::SearchResultDisplayType::kList, 0.7, u"tester",
-                     std::u16string());
+                     std::u16string(), ash::AppListSearchResultCategory::kWeb);
   CreateSearchResult(ash::SearchResultDisplayType::kList, 0.5, u"testing",
-                     std::u16string());
+                     std::u16string(), ash::AppListSearchResultCategory::kWeb);
   base::RunLoop().RunUntilIdle();
 
   const SearchResultBaseView* selection =
@@ -464,7 +468,8 @@
 
   // Add a new result - verify the selected result remains the same.
   CreateSearchResultAt(0, ash::SearchResultDisplayType::kList, 0.9, u"test1",
-                       std::u16string());
+                       std::u16string(),
+                       ash::AppListSearchResultCategory::kWeb);
   // Finish results update.
   base::RunLoop().RunUntilIdle();
 
@@ -473,7 +478,8 @@
 
   // Add a new result at the end, and verify the selection stays the same.
   CreateSearchResult(ash::SearchResultDisplayType::kList, 0.2,
-                     u"testing almost", std::u16string());
+                     u"testing almost", std::u16string(),
+                     ash::AppListSearchResultCategory::kWeb);
   base::RunLoop().RunUntilIdle();
 
   selection = GetResultSelectionController()->selected_result();
@@ -501,7 +507,8 @@
 
   // New result can override the default selection.
   CreateSearchResultAt(0, ash::SearchResultDisplayType::kList, 1.0, u"test",
-                       std::u16string());
+                       std::u16string(),
+                       ash::AppListSearchResultCategory::kWeb);
   base::RunLoop().RunUntilIdle();
 
   selection = GetResultSelectionController()->selected_result();
@@ -512,9 +519,9 @@
        UserSelectionInNonDefaultContainerNotOverridenByNewResults) {
   SimulateQuery(u"test");
   CreateSearchResult(ash::SearchResultDisplayType::kList, 0.7, u"tester",
-                     std::u16string());
+                     std::u16string(), ash::AppListSearchResultCategory::kWeb);
   CreateSearchResult(ash::SearchResultDisplayType::kList, 0.5, u"testing",
-                     std::u16string());
+                     std::u16string(), ash::AppListSearchResultCategory::kWeb);
   base::RunLoop().RunUntilIdle();
 
   const SearchResultBaseView* selection =
@@ -532,7 +539,8 @@
 
   // Add a new result at the end, and verify the selection stays the same.
   CreateSearchResult(ash::SearchResultDisplayType::kList, 0.2,
-                     u"testing almost", std::u16string());
+                     u"testing almost", std::u16string(),
+                     ash::AppListSearchResultCategory::kWeb);
   base::RunLoop().RunUntilIdle();
 
   selection = GetResultSelectionController()->selected_result();
@@ -557,7 +565,8 @@
 
     // New result can override the default selection.
     CreateSearchResultAt(0, ash::SearchResultDisplayType::kList, 1.0, u"test",
-                         std::u16string());
+                         std::u16string(),
+                         ash::AppListSearchResultCategory::kWeb);
     base::RunLoop().RunUntilIdle();
 
     selection = GetResultSelectionController()->selected_result();
@@ -569,9 +578,9 @@
 TEST_F(SearchBoxViewTest, ResetSelectionAfterResettingSearchBox) {
   SimulateQuery(u"test");
   CreateSearchResult(ash::SearchResultDisplayType::kList, 0.7, u"test1",
-                     std::u16string());
+                     std::u16string(), ash::AppListSearchResultCategory::kWeb);
   CreateSearchResult(ash::SearchResultDisplayType::kList, 0.5, u"test2",
-                     std::u16string());
+                     std::u16string(), ash::AppListSearchResultCategory::kWeb);
   base::RunLoop().RunUntilIdle();
 
   auto* result_selection_controller = GetResultSelectionController();
@@ -655,9 +664,14 @@
   EXPECT_TRUE(view()->assistant_button()->GetVisible());
 }
 
-class SearchBoxViewAutocompleteTest : public SearchBoxViewTest {
+class SearchBoxViewAutocompleteTest : public SearchBoxViewTest,
+                                      public testing::WithParamInterface<bool> {
  public:
-  SearchBoxViewAutocompleteTest() = default;
+  SearchBoxViewAutocompleteTest() {
+    scoped_features_.InitWithFeatureState(
+        features::kAutocompleteExtendedSuggestions,
+        IsExtendedAutocompleteEnabled());
+  }
   SearchBoxViewAutocompleteTest(const SearchBoxViewAutocompleteTest&) = delete;
   SearchBoxViewAutocompleteTest& operator=(
       const SearchBoxViewAutocompleteTest&) = delete;
@@ -667,6 +681,8 @@
     view()->ProcessAutocomplete(GetFirstResultView());
   }
 
+  bool IsExtendedAutocompleteEnabled() { return GetParam(); }
+
   // Sets up the test by creating a SearchResult and displaying an autocomplete
   // suggestion.
   void SetupAutocompleteBehaviorTest() {
@@ -675,73 +691,100 @@
     KeyPress(ui::VKEY_E);
     // Add a search result with a non-empty title field.
     CreateSearchResult(ash::SearchResultDisplayType::kList, 1.0,
-                       u"hello world!", std::u16string());
+                       u"hello world!", std::u16string(),
+                       ash::AppListSearchResultCategory::kWeb);
     base::RunLoop().RunUntilIdle();
     ProcessAutocomplete();
   }
+
+ private:
+  base::test::ScopedFeatureList scoped_features_;
 };
 
+// Instantiate the values in the parameterized tests. The boolean
+// determines whether to run the test in tablet mode.
+INSTANTIATE_TEST_SUITE_P(ExtendedAutocomplete,
+                         SearchBoxViewAutocompleteTest,
+                         testing::Bool());
+
 // Tests that autocomplete suggestions are consistent with top SearchResult list
 // titles.
-TEST_F(SearchBoxViewAutocompleteTest,
+TEST_P(SearchBoxViewAutocompleteTest,
        SearchBoxAutocompletesTopListResultTitle) {
   SimulateQuery(u"he");
 
   // Add two SearchResults. The higher ranked result should be selected by
   // default and it's title should be autocompleted into the search box.
   CreateSearchResult(ash::SearchResultDisplayType::kList, 2.0, u"hello list",
-                     std::u16string());
+                     std::u16string(), ash::AppListSearchResultCategory::kWeb);
   CreateSearchResult(ash::SearchResultDisplayType::kList, 1.0, u"hello list2",
-                     std::u16string());
+                     std::u16string(), ash::AppListSearchResultCategory::kApps);
   base::RunLoop().RunUntilIdle();
 
   ProcessAutocomplete();
   EXPECT_EQ(view()->search_box()->GetText(), u"hello list");
   EXPECT_EQ(view()->search_box()->GetSelectedText(), u"llo list");
-  base::RunLoop().RunUntilIdle();
+
+  if (IsExtendedAutocompleteEnabled()) {
+    EXPECT_EQ("Websites", view()->GetSearchBoxGhostTextForTest());
+    KeyPress(ui::VKEY_DOWN);
+    EXPECT_EQ("Apps", view()->GetSearchBoxGhostTextForTest());
+  }
 }
 
 // Tests that autocomplete suggestions are consistent with top SearchResult list
 // details.
-TEST_F(SearchBoxViewAutocompleteTest,
+TEST_P(SearchBoxViewAutocompleteTest,
        SearchBoxAutocompletesTopListResultDetails) {
   SimulateQuery(u"he");
 
   // Add two SearchResults. The higher ranked result should be selected by
   // default and it's title should be autocompleted into the search box.
   CreateSearchResult(ash::SearchResultDisplayType::kList, 2.0, std::u16string(),
-                     u"hello list");
+                     u"hello list", ash::AppListSearchResultCategory::kWeb);
   CreateSearchResult(ash::SearchResultDisplayType::kList, 1.0, std::u16string(),
-                     u"hello list2");
+                     u"hello list2", ash::AppListSearchResultCategory::kApps);
   base::RunLoop().RunUntilIdle();
 
   ProcessAutocomplete();
   EXPECT_EQ(view()->search_box()->GetText(), u"hello list");
   EXPECT_EQ(view()->search_box()->GetSelectedText(), u"llo list");
+
+  if (IsExtendedAutocompleteEnabled()) {
+    EXPECT_EQ("Websites", view()->GetSearchBoxGhostTextForTest());
+    KeyPress(ui::VKEY_DOWN);
+    EXPECT_EQ("Apps", view()->GetSearchBoxGhostTextForTest());
+  }
 }
 
 // Tests that SearchBoxView's textfield text does not autocomplete if the top
 // result title or details do not have a matching prefix.
-TEST_F(SearchBoxViewAutocompleteTest,
+TEST_P(SearchBoxViewAutocompleteTest,
        SearchBoxDoesNotAutocompleteWrongCharacter) {
-  // Send Z to the SearchBoxView textfield, then trigger an autocomplete.
-  KeyPress(ui::VKEY_Z);
+  // Send ABC to the SearchBoxView textfield, then trigger an autocomplete.
+  KeyPress(ui::VKEY_A);
+  KeyPress(ui::VKEY_B);
+  KeyPress(ui::VKEY_C);
   // Add a search result with non-empty details and title fields.
   CreateSearchResult(ash::SearchResultDisplayType::kList, 1.0, u"title",
-                     u"details");
+                     u"details", ash::AppListSearchResultCategory::kWeb);
   base::RunLoop().RunUntilIdle();
   ProcessAutocomplete();
   // The text should not be autocompleted.
-  EXPECT_EQ(view()->search_box()->GetText(), u"z");
+  EXPECT_EQ(view()->search_box()->GetText(), u"abc");
+
+  if (IsExtendedAutocompleteEnabled()) {
+    EXPECT_EQ("title - Websites", view()->GetSearchBoxGhostTextForTest());
+  }
 }
 
 // Tests that autocomplete suggestion will remain if next key in the suggestion
 // is typed.
-TEST_F(SearchBoxViewAutocompleteTest, SearchBoxAutocompletesAcceptsNextChar) {
+TEST_P(SearchBoxViewAutocompleteTest, SearchBoxAutocompletesAcceptsNextChar) {
   SimulateQuery(u"he");
   // Add a search result with a non-empty title field.
   CreateSearchResult(ash::SearchResultDisplayType::kList, 1.0, u"hello world!",
-                     std::u16string());
+                     std::u16string(), ash::AppListSearchResultCategory::kWeb);
   base::RunLoop().RunUntilIdle();
   ProcessAutocomplete();
 
@@ -756,11 +799,14 @@
   selected_text = view()->search_box()->GetSelectedText();
   EXPECT_EQ(view()->search_box()->GetText(), u"hello world!");
   EXPECT_EQ(u"lo world!", selected_text);
+
+  if (IsExtendedAutocompleteEnabled())
+    EXPECT_EQ("Websites", view()->GetSearchBoxGhostTextForTest());
 }
 
 // Tests that autocomplete suggestion is accepted and displayed in SearchModel
 // after clicking or tapping on the search box.
-TEST_F(SearchBoxViewAutocompleteTest, SearchBoxAcceptsAutocompleteForClick) {
+TEST_P(SearchBoxViewAutocompleteTest, SearchBoxAcceptsAutocompleteForClick) {
   SetupAutocompleteBehaviorTest();
 
   ui::MouseEvent mouse_event(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
@@ -776,9 +822,12 @@
   // trigger another query, thus it is not reflected in Search Model.
   EXPECT_EQ(u"hello world!", view()->search_box()->GetText());
   EXPECT_EQ(u"he", view()->current_query());
+
+  if (IsExtendedAutocompleteEnabled())
+    EXPECT_EQ("Websites", view()->GetSearchBoxGhostTextForTest());
 }
 
-TEST_F(SearchBoxViewAutocompleteTest, SearchBoxAcceptsAutocompleteForTap) {
+TEST_P(SearchBoxViewAutocompleteTest, SearchBoxAcceptsAutocompleteForTap) {
   SetupAutocompleteBehaviorTest();
 
   ui::GestureEvent gesture_event(0, 0, 0, ui::EventTimeForNow(),
@@ -795,17 +844,19 @@
   // trigger another query, thus it is not reflected in Search Model.
   EXPECT_EQ(u"hello world!", view()->search_box()->GetText());
   EXPECT_EQ(u"he", view()->current_query());
+  if (IsExtendedAutocompleteEnabled())
+    EXPECT_EQ("Websites", view()->GetSearchBoxGhostTextForTest());
 }
 
 // Tests that autocomplete is not handled if IME is using composition text.
-TEST_F(SearchBoxViewAutocompleteTest, SearchBoxAutocompletesNotHandledForIME) {
+TEST_P(SearchBoxViewAutocompleteTest, SearchBoxAutocompletesNotHandledForIME) {
   // Simulate uncomposited text. The autocomplete should be handled.
   KeyPress(ui::VKEY_H);
   KeyPress(ui::VKEY_E);
   view()->set_highlight_range_for_test(gfx::Range(2, 2));
   // Add a search result with a non-empty title field.
   CreateSearchResult(ash::SearchResultDisplayType::kList, 1.0, u"hello world!",
-                     std::u16string());
+                     std::u16string(), ash::AppListSearchResultCategory::kWeb);
   base::RunLoop().RunUntilIdle();
 
   ProcessAutocomplete();
@@ -825,6 +876,9 @@
   selected_text = view()->search_box()->GetSelectedText();
   EXPECT_EQ(view()->search_box()->GetText(), u"he");
   EXPECT_EQ(u"", selected_text);
+
+  if (IsExtendedAutocompleteEnabled())
+    EXPECT_EQ("", view()->GetSearchBoxGhostTextForTest());
 }
 
 // TODO(crbug.com/1216082): Refactor the above tests to use AshTestBase, then
diff --git a/ash/components/phonehub/BUILD.gn b/ash/components/phonehub/BUILD.gn
index a5c1ac9..e68e108 100644
--- a/ash/components/phonehub/BUILD.gn
+++ b/ash/components/phonehub/BUILD.gn
@@ -9,6 +9,8 @@
 
 static_library("phonehub") {
   sources = [
+    "app_stream_launcher_data_model.cc",
+    "app_stream_launcher_data_model.h",
     "app_stream_manager.cc",
     "app_stream_manager.h",
     "browser_tabs_metadata_fetcher.h",
diff --git a/ash/components/phonehub/app_stream_launcher_data_model.cc b/ash/components/phonehub/app_stream_launcher_data_model.cc
new file mode 100644
index 0000000..9987bc37
--- /dev/null
+++ b/ash/components/phonehub/app_stream_launcher_data_model.cc
@@ -0,0 +1,36 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/components/phonehub/app_stream_launcher_data_model.h"
+
+namespace ash::phonehub {
+
+AppStreamLauncherDataModel::AppStreamLauncherDataModel() = default;
+
+AppStreamLauncherDataModel::~AppStreamLauncherDataModel() = default;
+
+void AppStreamLauncherDataModel::AddObserver(Observer* observer) {
+  observer_list_.AddObserver(observer);
+}
+
+void AppStreamLauncherDataModel::RemoveObserver(Observer* observer) {
+  observer_list_.RemoveObserver(observer);
+}
+
+void AppStreamLauncherDataModel::SetShouldShowMiniLauncher(
+    bool should_show_mini_launcher) {
+  should_show_app_stream_launcher_ = should_show_mini_launcher;
+  for (auto& observer : observer_list_)
+    observer.OnShouldShowMiniLauncherChanged();
+}
+
+bool AppStreamLauncherDataModel::GetShouldShowMiniLauncher() {
+  return should_show_app_stream_launcher_;
+}
+
+void AppStreamLauncherDataModel::ResetState() {
+  should_show_app_stream_launcher_ = false;
+}
+
+}  // namespace ash::phonehub
diff --git a/ash/components/phonehub/app_stream_launcher_data_model.h b/ash/components/phonehub/app_stream_launcher_data_model.h
new file mode 100644
index 0000000..39563a8
--- /dev/null
+++ b/ash/components/phonehub/app_stream_launcher_data_model.h
@@ -0,0 +1,55 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_COMPONENTS_PHONEHUB_APP_STREAM_LAUNCHER_DATA_MODEL_H_
+#define ASH_COMPONENTS_PHONEHUB_APP_STREAM_LAUNCHER_DATA_MODEL_H_
+
+#include "base/observer_list.h"
+#include "base/observer_list_types.h"
+
+namespace ash::phonehub {
+
+// Keeps that data that is associated with the App Stream Mini Launcher.
+class AppStreamLauncherDataModel {
+ public:
+  class Observer : public base::CheckedObserver {
+   public:
+    ~Observer() override = default;
+
+    virtual void OnShouldShowMiniLauncherChanged() = 0;
+  };
+
+  AppStreamLauncherDataModel(const AppStreamLauncherDataModel&) = delete;
+  AppStreamLauncherDataModel& operator=(const AppStreamLauncherDataModel&) =
+      delete;
+  ~AppStreamLauncherDataModel();
+
+  // Whether the App Stream Mini Launcher should be shown.
+  // It only affects the UI when the state is "phone connected" and Eche
+  // is turned on. As soon as the ui state becomes any other states other than
+  // `kMiniLauncher` this will reset. So the next time the UI is shown
+  // it goes back to the main phone connected view.
+  void SetShouldShowMiniLauncher(bool should_show_mini_launcher);
+  bool GetShouldShowMiniLauncher();
+
+  // Resets the internal state w/o updating the UI.
+  void ResetState();
+
+  void AddObserver(Observer* observer);
+  void RemoveObserver(Observer* observer);
+
+ public:
+  AppStreamLauncherDataModel();
+
+ private:
+  // Indicates if the Mini Launcher should be shown when the status is
+  // "phone connected" or not.
+  bool should_show_app_stream_launcher_ = false;
+
+  base::ObserverList<Observer> observer_list_;
+};
+
+}  // namespace ash::phonehub
+
+#endif  // ASH_COMPONENTS_PHONEHUB_APP_STREAM_LAUNCHER_DATA_MODEL_H_
diff --git a/ash/components/phonehub/fake_phone_hub_manager.cc b/ash/components/phonehub/fake_phone_hub_manager.cc
index dbf749c..9e9b776 100644
--- a/ash/components/phonehub/fake_phone_hub_manager.cc
+++ b/ash/components/phonehub/fake_phone_hub_manager.cc
@@ -4,6 +4,7 @@
 
 #include "ash/components/phonehub/fake_phone_hub_manager.h"
 
+#include "ash/components/phonehub/app_stream_launcher_data_model.h"
 #include "ash/constants/ash_features.h"
 
 namespace ash {
@@ -53,6 +54,11 @@
   return &fake_onboarding_ui_tracker_;
 }
 
+AppStreamLauncherDataModel*
+FakePhoneHubManager::GetAppStreamLauncherDataModel() {
+  return &app_stream_launcher_data_model_;
+}
+
 PhoneModel* FakePhoneHubManager::GetPhoneModel() {
   return &mutable_phone_model_;
 }
diff --git a/ash/components/phonehub/fake_phone_hub_manager.h b/ash/components/phonehub/fake_phone_hub_manager.h
index 9ce2b31..11c71a6 100644
--- a/ash/components/phonehub/fake_phone_hub_manager.h
+++ b/ash/components/phonehub/fake_phone_hub_manager.h
@@ -5,6 +5,7 @@
 #ifndef ASH_COMPONENTS_PHONEHUB_FAKE_PHONE_HUB_MANAGER_H_
 #define ASH_COMPONENTS_PHONEHUB_FAKE_PHONE_HUB_MANAGER_H_
 
+#include "ash/components/phonehub/app_stream_launcher_data_model.h"
 #include "ash/components/phonehub/app_stream_manager.h"
 #include "ash/components/phonehub/fake_browser_tabs_model_provider.h"
 #include "ash/components/phonehub/fake_camera_roll_manager.h"
@@ -63,6 +64,10 @@
     return &fake_onboarding_ui_tracker_;
   }
 
+  AppStreamLauncherDataModel* fake_app_stream_launcher_data_model() {
+    return &app_stream_launcher_data_model_;
+  }
+
   FakeRecentAppsInteractionHandler* fake_recent_apps_interaction_handler() {
     return &fake_recent_apps_interaction_handler_;
   }
@@ -109,6 +114,7 @@
   NotificationInteractionHandler* GetNotificationInteractionHandler() override;
   NotificationManager* GetNotificationManager() override;
   OnboardingUiTracker* GetOnboardingUiTracker() override;
+  AppStreamLauncherDataModel* GetAppStreamLauncherDataModel() override;
   PhoneModel* GetPhoneModel() override;
   RecentAppsInteractionHandler* GetRecentAppsInteractionHandler() override;
   ScreenLockManager* GetScreenLockManager() override;
@@ -127,6 +133,7 @@
   FakeNotificationInteractionHandler fake_notification_interaction_handler_;
   FakeNotificationManager fake_notification_manager_;
   FakeOnboardingUiTracker fake_onboarding_ui_tracker_;
+  AppStreamLauncherDataModel app_stream_launcher_data_model_;
   MutablePhoneModel mutable_phone_model_;
   FakeRecentAppsInteractionHandler fake_recent_apps_interaction_handler_;
   FakeScreenLockManager fake_screen_lock_manager_;
diff --git a/ash/components/phonehub/fake_user_action_recorder.cc b/ash/components/phonehub/fake_user_action_recorder.cc
index e4c9d1be..eee25b7 100644
--- a/ash/components/phonehub/fake_user_action_recorder.cc
+++ b/ash/components/phonehub/fake_user_action_recorder.cc
@@ -43,5 +43,9 @@
   ++num_camera_roll_downloads_;
 }
 
+void FakeUserActionRecorder::RecordAppStreamLauncherOpened() {
+  ++app_stream_launcher_opened_;
+}
+
 }  // namespace phonehub
 }  // namespace ash
diff --git a/ash/components/phonehub/fake_user_action_recorder.h b/ash/components/phonehub/fake_user_action_recorder.h
index ccbe80c..264645a6 100644
--- a/ash/components/phonehub/fake_user_action_recorder.h
+++ b/ash/components/phonehub/fake_user_action_recorder.h
@@ -32,6 +32,10 @@
     return num_camera_roll_downloads_;
   }
 
+  size_t app_stream_launcher_opened() const {
+    return app_stream_launcher_opened_;
+  }
+
  private:
   // UserActionRecorder:
   void RecordUiOpened() override;
@@ -42,6 +46,7 @@
   void RecordNotificationDismissAttempt() override;
   void RecordNotificationReplyAttempt() override;
   void RecordCameraRollDownloadAttempt() override;
+  void RecordAppStreamLauncherOpened() override;
 
   size_t num_ui_opened_events_ = 0u;
   size_t num_tether_attempts_ = 0u;
@@ -51,6 +56,7 @@
   size_t num_notification_dismissals_ = 0u;
   size_t num_notification_replies_ = 0u;
   size_t num_camera_roll_downloads_ = 0u;
+  size_t app_stream_launcher_opened_ = 0u;
 };
 
 }  // namespace phonehub
diff --git a/ash/components/phonehub/phone_hub_manager.h b/ash/components/phonehub/phone_hub_manager.h
index 549b233..ddee5461 100644
--- a/ash/components/phonehub/phone_hub_manager.h
+++ b/ash/components/phonehub/phone_hub_manager.h
@@ -5,6 +5,7 @@
 #ifndef ASH_COMPONENTS_PHONEHUB_PHONE_HUB_MANAGER_H_
 #define ASH_COMPONENTS_PHONEHUB_PHONE_HUB_MANAGER_H_
 
+#include "ash/components/phonehub/app_stream_launcher_data_model.h"
 #include "ash/components/phonehub/app_stream_manager.h"
 #include "base/callback.h"
 #include "base/time/time.h"
@@ -53,6 +54,7 @@
   GetNotificationInteractionHandler() = 0;
   virtual NotificationManager* GetNotificationManager() = 0;
   virtual OnboardingUiTracker* GetOnboardingUiTracker() = 0;
+  virtual AppStreamLauncherDataModel* GetAppStreamLauncherDataModel() = 0;
   virtual PhoneModel* GetPhoneModel() = 0;
   virtual RecentAppsInteractionHandler* GetRecentAppsInteractionHandler() = 0;
   virtual ScreenLockManager* GetScreenLockManager() = 0;
diff --git a/ash/components/phonehub/phone_hub_manager_impl.cc b/ash/components/phonehub/phone_hub_manager_impl.cc
index 5664512..73c752d 100644
--- a/ash/components/phonehub/phone_hub_manager_impl.cc
+++ b/ash/components/phonehub/phone_hub_manager_impl.cc
@@ -3,7 +3,9 @@
 // found in the LICENSE file.
 
 #include "ash/components/phonehub/phone_hub_manager_impl.h"
+#include <memory>
 
+#include "ash/components/phonehub/app_stream_launcher_data_model.h"
 #include "ash/components/phonehub/app_stream_manager.h"
 #include "ash/components/phonehub/browser_tabs_metadata_fetcher.h"
 #include "ash/components/phonehub/browser_tabs_model_controller.h"
@@ -112,6 +114,8 @@
           feature_status_provider_.get(),
           multidevice_setup_client,
           show_multidevice_setup_dialog_callback)),
+      app_stream_launcher_data_model_(
+          std::make_unique<AppStreamLauncherDataModel>()),
       notification_processor_(
           std::make_unique<NotificationProcessor>(notification_manager_.get())),
       recent_apps_interaction_handler_(
@@ -214,6 +218,11 @@
   return onboarding_ui_tracker_.get();
 }
 
+AppStreamLauncherDataModel*
+PhoneHubManagerImpl::GetAppStreamLauncherDataModel() {
+  return app_stream_launcher_data_model_.get();
+}
+
 PhoneModel* PhoneHubManagerImpl::GetPhoneModel() {
   return phone_model_.get();
 }
diff --git a/ash/components/phonehub/phone_hub_manager_impl.h b/ash/components/phonehub/phone_hub_manager_impl.h
index 1534dbf6..7d55a78 100644
--- a/ash/components/phonehub/phone_hub_manager_impl.h
+++ b/ash/components/phonehub/phone_hub_manager_impl.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 
+#include "ash/components/phonehub/app_stream_launcher_data_model.h"
 #include "ash/components/phonehub/app_stream_manager.h"
 #include "ash/components/phonehub/feature_setup_response_processor.h"
 #include "ash/components/phonehub/icon_decoder.h"
@@ -72,6 +73,7 @@
   NotificationInteractionHandler* GetNotificationInteractionHandler() override;
   NotificationManager* GetNotificationManager() override;
   OnboardingUiTracker* GetOnboardingUiTracker() override;
+  AppStreamLauncherDataModel* GetAppStreamLauncherDataModel() override;
   PhoneModel* GetPhoneModel() override;
   RecentAppsInteractionHandler* GetRecentAppsInteractionHandler() override;
   ScreenLockManager* GetScreenLockManager() override;
@@ -105,6 +107,7 @@
       notification_interaction_handler_;
   std::unique_ptr<NotificationManager> notification_manager_;
   std::unique_ptr<OnboardingUiTracker> onboarding_ui_tracker_;
+  std::unique_ptr<AppStreamLauncherDataModel> app_stream_launcher_data_model_;
   std::unique_ptr<NotificationProcessor> notification_processor_;
   std::unique_ptr<RecentAppsInteractionHandler>
       recent_apps_interaction_handler_;
diff --git a/ash/components/phonehub/user_action_recorder.h b/ash/components/phonehub/user_action_recorder.h
index 84e8936..e37a23a 100644
--- a/ash/components/phonehub/user_action_recorder.h
+++ b/ash/components/phonehub/user_action_recorder.h
@@ -44,6 +44,9 @@
   // been attempted.
   virtual void RecordCameraRollDownloadAttempt() = 0;
 
+  // Records opening of app stream launher.
+  virtual void RecordAppStreamLauncherOpened() = 0;
+
  protected:
   UserActionRecorder() = default;
 };
diff --git a/ash/components/phonehub/user_action_recorder_impl.cc b/ash/components/phonehub/user_action_recorder_impl.cc
index 1768f07..9accfe0a 100644
--- a/ash/components/phonehub/user_action_recorder_impl.cc
+++ b/ash/components/phonehub/user_action_recorder_impl.cc
@@ -49,6 +49,10 @@
   HandleUserAction(UserAction::kCameraRollDownload);
 }
 
+void UserActionRecorderImpl::RecordAppStreamLauncherOpened() {
+  HandleUserAction(UserAction::kAppStreamLauncherOpened);
+}
+
 void UserActionRecorderImpl::HandleUserAction(UserAction action) {
   base::UmaHistogramEnumeration("PhoneHub.CompletedUserAction", action);
 }
diff --git a/ash/components/phonehub/user_action_recorder_impl.h b/ash/components/phonehub/user_action_recorder_impl.h
index d91ae6b..fa9320de 100644
--- a/ash/components/phonehub/user_action_recorder_impl.h
+++ b/ash/components/phonehub/user_action_recorder_impl.h
@@ -36,7 +36,8 @@
     kNotificationDismissal = 5,
     kNotificationReply = 6,
     kCameraRollDownload = 7,
-    kMaxValue = kCameraRollDownload,
+    kAppStreamLauncherOpened = 8,
+    kMaxValue = kAppStreamLauncherOpened,
   };
 
   // UserActionRecorder:
@@ -48,6 +49,7 @@
   void RecordNotificationDismissAttempt() override;
   void RecordNotificationReplyAttempt() override;
   void RecordCameraRollDownloadAttempt() override;
+  void RecordAppStreamLauncherOpened() override;
 
   void HandleUserAction(UserAction action);
 
diff --git a/ash/resources/vector_icons/phone_hub_full_apps_list.icon b/ash/resources/vector_icons/phone_hub_full_apps_list.icon
index 17f0206b..2c61b34f 100644
--- a/ash/resources/vector_icons/phone_hub_full_apps_list.icon
+++ b/ash/resources/vector_icons/phone_hub_full_apps_list.icon
@@ -2,67 +2,75 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-CANVAS_DIMENSIONS, 40,
+CANVAS_DIMENSIONS, 57,
 MOVE_TO, 23.23f, 13,
-R_CUBIC_TO, 1.1f, 0, 2, -0.9f, 2, -2,
-R_CUBIC_TO, 0, -1.1f, -0.9f, -2, -2, -2,
-R_CUBIC_TO, -1.1f, 0, -2, 0.9f, -2, 2,
-R_CUBIC_TO, 0, 1.1f, 0.9f, 2, 2, 2,
+CUBIC_TO, 24.33f, 13, 25.23f, 12.1f, 25.23f, 11,
+CUBIC_TO, 25.23f, 9.9f, 24.33f, 9, 23.23f, 9,
+CUBIC_TO, 22.13f, 9, 21.23f, 9.9f, 21.23f, 11,
+CUBIC_TO, 21.23f, 12.1f, 22.13f, 13, 23.23f, 13,
 CLOSE,
-R_MOVE_TO, -10, 10,
-R_CUBIC_TO, 1.1f, 0, 2, -0.9f, 2, -2,
-R_CUBIC_TO, 0, -1.1f, -0.9f, -2, -2, -2,
-R_CUBIC_TO, -1.1f, 0, -2, 0.9f, -2, 2,
-R_CUBIC_TO, 0, 1.1f, 0.9f, 2, 2, 2,
+NEW_PATH,
+MOVE_TO, 13.23f, 23,
+CUBIC_TO, 14.33f, 23, 15.23f, 22.1f, 15.23f, 21,
+CUBIC_TO, 15.23f, 19.9f, 14.33f, 19, 13.23f, 19,
+CUBIC_TO, 12.13f, 19, 11.23f, 19.9f, 11.23f, 21,
+CUBIC_TO, 11.23f, 22.1f, 12.13f, 23, 13.23f, 23,
 CLOSE,
-R_MOVE_TO, 10, 0,
-R_CUBIC_TO, 1.1f, 0, 2, -0.9f, 2, -2,
-R_CUBIC_TO, 0, -1.1f, -0.9f, -2, -2, -2,
-R_CUBIC_TO, -1.1f, 0, -2, 0.9f, -2, 2,
-R_CUBIC_TO, 0, 1.1f, 0.9f, 2, 2, 2,
+NEW_PATH,
+MOVE_TO, 23.23f, 23,
+CUBIC_TO, 24.33f, 23, 25.23f, 22.1f, 25.23f, 21,
+CUBIC_TO, 25.23f, 19.9f, 24.33f, 19, 23.23f, 19,
+CUBIC_TO, 22.13f, 19, 21.23f, 19.9f, 21.23f, 21,
+CUBIC_TO, 21.23f, 22.1f, 22.13f, 23, 23.23f, 23,
 CLOSE,
-R_MOVE_TO, -3, -2,
-R_CUBIC_TO, 0, 1.1f, -0.9f, 2, -2, 2,
-R_CUBIC_TO, -1.1f, 0, -2, -0.9f, -2, -2,
-R_CUBIC_TO, 0, -1.1f, 0.9f, -2, 2, -2,
-R_CUBIC_TO, 1.1f, 0, 2, 0.9f, 2, 2,
+NEW_PATH,
+MOVE_TO, 20.23f, 21,
+CUBIC_TO, 20.23f, 22.1f, 19.33f, 23, 18.23f, 23,
+CUBIC_TO, 17.13f, 23, 16.23f, 22.1f, 16.23f, 21,
+CUBIC_TO, 16.23f, 19.9f, 17.13f, 19, 18.23f, 19,
+CUBIC_TO, 19.33f, 19, 20.23f, 19.9f, 20.23f, 21,
 CLOSE,
-R_MOVE_TO, -5, -10,
-R_CUBIC_TO, 0, 1.1f, -0.9f, 2, -2, 2,
-R_CUBIC_TO, -1.1f, 0, -2, -0.9f, -2, -2,
-R_CUBIC_TO, 0, -1.1f, 0.9f, -2, 2, -2,
-R_CUBIC_TO, 1.1f, 0, 2, 0.9f, 2, 2,
+NEW_PATH,
+MOVE_TO, 15.23f, 11,
+CUBIC_TO, 15.23f, 12.1f, 14.33f, 13, 13.23f, 13,
+CUBIC_TO, 12.13f, 13, 11.23f, 12.1f, 11.23f, 11,
+CUBIC_TO, 11.23f, 9.9f, 12.13f, 9, 13.23f, 9,
+CUBIC_TO, 14.33f, 9, 15.23f, 9.9f, 15.23f, 11,
 CLOSE,
-R_MOVE_TO, 3, 2,
-R_CUBIC_TO, 1.1f, 0, 2, -0.9f, 2, -2,
-R_CUBIC_TO, 0, -1.1f, -0.9f, -2, -2, -2,
-R_CUBIC_TO, -1.1f, 0, -2, 0.9f, -2, 2,
-R_CUBIC_TO, 0, 1.1f, 0.9f, 2, 2, 2,
+NEW_PATH,
+MOVE_TO, 18.23f, 13,
+CUBIC_TO, 19.33f, 13, 20.23f, 12.1f, 20.23f, 11,
+CUBIC_TO, 20.23f, 9.9f, 19.33f, 9, 18.23f, 9,
+CUBIC_TO, 17.13f, 9, 16.23f, 9.9f, 16.23f, 11,
+CUBIC_TO, 16.23f, 12.1f, 17.13f, 13, 18.23f, 13,
 CLOSE,
-R_MOVE_TO, 7, 3,
-R_CUBIC_TO, 0, 1.1f, -0.9f, 2, -2, 2,
-R_CUBIC_TO, -1.1f, 0, -2, -0.9f, -2, -2,
-R_CUBIC_TO, 0, -1.1f, 0.9f, -2, 2, -2,
-R_CUBIC_TO, 1.1f, 0, 2, 0.9f, 2, 2,
+NEW_PATH,
+MOVE_TO, 25.23f, 16,
+CUBIC_TO, 25.23f, 17.1f, 24.33f, 18, 23.23f, 18,
+CUBIC_TO, 22.13f, 18, 21.23f, 17.1f, 21.23f, 16,
+CUBIC_TO, 21.23f, 14.9f, 22.13f, 14, 23.23f, 14,
+CUBIC_TO, 24.33f, 14, 25.23f, 14.9f, 25.23f, 16,
 CLOSE,
-R_MOVE_TO, -12, 2,
-R_CUBIC_TO, 1.1f, 0, 2, -0.9f, 2, -2,
-R_CUBIC_TO, 0, -1.1f, -0.9f, -2, -2, -2,
-R_CUBIC_TO, -1.1f, 0, -2, 0.9f, -2, 2,
-R_CUBIC_TO, 0, 1.1f, 0.9f, 2, 2, 2,
+NEW_PATH,
+MOVE_TO, 13.23f, 18,
+CUBIC_TO, 14.33f, 18, 15.23f, 17.1f, 15.23f, 16,
+CUBIC_TO, 15.23f, 14.9f, 14.33f, 14, 13.23f, 14,
+CUBIC_TO, 12.13f, 14, 11.23f, 14.9f, 11.23f, 16,
+CUBIC_TO, 11.23f, 17.1f, 12.13f, 18, 13.23f, 18,
 CLOSE,
-R_MOVE_TO, 7, -2,
-R_CUBIC_TO, 0, 1.1f, -0.9f, 2, -2, 2,
-R_CUBIC_TO, -1.1f, 0, -2, -0.9f, -2, -2,
-R_CUBIC_TO, 0, -1.1f, 0.9f, -2, 2, -2,
-R_CUBIC_TO, 1.1f, 0, 2, 0.9f, 2, 2,
+NEW_PATH,
+MOVE_TO, 20.23f, 16,
+CUBIC_TO, 20.23f, 17.1f, 19.33f, 18, 18.23f, 18,
+CUBIC_TO, 17.13f, 18, 16.23f, 17.1f, 16.23f, 16,
+CUBIC_TO, 16.23f, 14.9f, 17.13f, 14, 18.23f, 14,
+CUBIC_TO, 19.33f, 14, 20.23f, 14.9f, 20.23f, 16,
 CLOSE,
 NEW_PATH,
 MOVE_TO, 34.9f, 19.83f,
 LINE_TO, 39.02f, 16,
-R_LINE_TO, -4.12f, -3.82f,
+LINE_TO, 34.9f, 12.18f,
 LINE_TO, 36.17f, 11,
-R_LINE_TO, 5.4f, 5,
-R_LINE_TO, -5.4f, 5,
-R_LINE_TO, -1.27f, -1.17f,
+LINE_TO, 41.56f, 16,
+LINE_TO, 36.17f, 21,
+LINE_TO, 34.9f, 19.83f,
 CLOSE
diff --git a/ash/style/ash_color_id.h b/ash/style/ash_color_id.h
index c4599a2..94c043ce 100644
--- a/ash/style/ash_color_id.h
+++ b/ash/style/ash_color_id.h
@@ -119,7 +119,9 @@
   E_CPONLY(kColorAshButtonIconDisabledColor) \
   E_CPONLY(kColorAshIconSecondaryDisabledColor) \
   E_CPONLY(kColorAshIconPrimaryDisabledColor) \
-  E_CPONLY(KColorAshTextDisabledColor)
+  E_CPONLY(KColorAshTextDisabledColor) \
+  /* Color for icon of the blocked bluetooth device */ \
+  E_CPONLY(kColorAshIconColorBlocked)
 
 #include "ui/color/color_id_macros.inc"
 
diff --git a/ash/style/ash_color_mixer.cc b/ash/style/ash_color_mixer.cc
index 8be8b4f..3db80d4 100644
--- a/ash/style/ash_color_mixer.cc
+++ b/ash/style/ash_color_mixer.cc
@@ -478,6 +478,8 @@
       ui::SetAlpha(cros_tokens::kCrosSysPrimary, kDisabledColorOpacity);
   mixer[KColorAshTextDisabledColor] =
       ui::SetAlpha(cros_tokens::kCrosSysOnSurface, kDisabledColorOpacity);
+
+  mixer[kColorAshIconColorBlocked] = {gfx::kGoogleGrey100};
 }
 
 }  // namespace ash
diff --git a/ash/system/accessibility/autoclick_scroll_view.cc b/ash/system/accessibility/autoclick_scroll_view.cc
index d326008b..cae65d8 100644
--- a/ash/system/accessibility/autoclick_scroll_view.cc
+++ b/ash/system/accessibility/autoclick_scroll_view.cc
@@ -9,7 +9,6 @@
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/style/ash_color_id.h"
-#include "ash/style/ash_color_provider.h"
 #include "ash/system/accessibility/autoclick_menu_bubble_controller.h"
 #include "ash/system/accessibility/floating_menu_button.h"
 #include "ash/system/unified/custom_shape_button.h"
@@ -19,10 +18,10 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/base/models/image_model.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/scoped_layer_animation_settings.h"
 #include "ui/gfx/canvas.h"
-#include "ui/gfx/paint_vector_icon.h"
 #include "ui/gfx/vector_icon_types.h"
 #include "ui/views/controls/highlight_path_generator.h"
 #include "ui/views/masked_targeter_delegate.h"
@@ -30,8 +29,6 @@
 
 namespace ash {
 
-using ContentLayerType = AshColorProvider::ContentLayerType;
-
 namespace {
 
 // Constants for size and position.
@@ -144,6 +141,10 @@
     }
     SetPreferredSize(size_);
 
+    SetImageModel(
+        views::Button::STATE_NORMAL,
+        ui::ImageModel::FromVectorIcon(icon_, kColorAshIconColorPrimary));
+
     SetClipPath(CreateCustomShapePath(gfx::Rect(GetPreferredSize())));
     SetEventTargeter(std::make_unique<views::ViewTargeter>(this));
 
@@ -193,15 +194,6 @@
     return ComputePath(true /* all_edges */);
   }
 
-  void OnThemeChanged() override {
-    CustomShapeButton::OnThemeChanged();
-    SetImage(views::Button::STATE_NORMAL,
-             gfx::CreateVectorIcon(
-                 icon_, AshColorProvider::Get()->GetContentLayerColor(
-                            ContentLayerType::kIconColorPrimary)));
-    SchedulePaint();
-  }
-
   // Computes the path which is the outline of this button. If |all_edges|,
   // returns a path which fully encloses the shape, otherwise just returns a
   // path that can be used for drawing the edges but avoids overlap with
@@ -260,8 +252,7 @@
 
     flags.setStyle(cc::PaintFlags::kStroke_Style);
     flags.setStrokeWidth(kScrollpadStrokeWidthDips);
-    flags.setColor(AshColorProvider::Get()->GetContentLayerColor(
-        ContentLayerType::kSeparatorColor));
+    flags.setColor(GetColorProvider()->GetColor(kColorAshSeparatorColor));
     canvas->DrawPath(ComputePath(false /* only drawn edges */), flags);
 
     gfx::ImageSkia img = GetImageToPaint();
diff --git a/ash/system/accessibility/dictation_bubble_controller.cc b/ash/system/accessibility/dictation_bubble_controller.cc
index f6fc6ca..a4ff568 100644
--- a/ash/system/accessibility/dictation_bubble_controller.cc
+++ b/ash/system/accessibility/dictation_bubble_controller.cc
@@ -7,7 +7,6 @@
 #include "ash/display/window_tree_host_manager.h"
 #include "ash/public/cpp/accessibility_controller_enums.h"
 #include "ash/shell.h"
-#include "ash/style/ash_color_provider.h"
 #include "ash/system/accessibility/dictation_bubble_view.h"
 #include "ash/wm/collision_detection/collision_detection_utils.h"
 #include "ui/base/ime/text_input_client.h"
@@ -21,9 +20,6 @@
       Shell::Get()->window_tree_host_manager()->input_method();
   if (!input_method_observer_.IsObservingSource(input_method))
     input_method_observer_.Observe(input_method);
-  if (!color_mode_observer_.IsObservingSource(
-          DarkLightModeControllerImpl::Get()))
-    color_mode_observer_.Observe(DarkLightModeControllerImpl::Get());
 }
 
 DictationBubbleController::~DictationBubbleController() {
@@ -57,13 +53,6 @@
   dictation_bubble_view_->SetAnchorRect(new_caret_bounds);
 }
 
-void DictationBubbleController::OnColorModeChanged(bool dark_mode_enabled) {
-  if (!dictation_bubble_view_)
-    return;
-
-  dictation_bubble_view_->OnColorModeChanged(dark_mode_enabled);
-}
-
 void DictationBubbleController::OnViewIsDeleting(views::View* observed_view) {
   if (observed_view != dictation_bubble_view_)
     return;
diff --git a/ash/system/accessibility/dictation_bubble_controller.h b/ash/system/accessibility/dictation_bubble_controller.h
index bf2c178..cdbf65f 100644
--- a/ash/system/accessibility/dictation_bubble_controller.h
+++ b/ash/system/accessibility/dictation_bubble_controller.h
@@ -34,7 +34,6 @@
 
 // Manages the Dictation bubble view.
 class ASH_EXPORT DictationBubbleController : public ui::InputMethodObserver,
-                                             public ColorModeObserver,
                                              public views::ViewObserver {
  public:
   DictationBubbleController();
@@ -57,9 +56,6 @@
   void OnTextInputStateChanged(const ui::TextInputClient* client) override {}
   void OnInputMethodDestroyed(const ui::InputMethod* input_method) override {}
 
-  // ColorModeObserver:
-  void OnColorModeChanged(bool dark_mode_enabled) override;
-
   // views::ViewObserver:
   void OnViewIsDeleting(views::View* observed_view) override;
 
@@ -82,8 +78,6 @@
 
   base::ScopedObservation<ui::InputMethod, ui::InputMethodObserver>
       input_method_observer_{this};
-  base::ScopedObservation<DarkLightModeControllerImpl, ColorModeObserver>
-      color_mode_observer_{this};
 };
 
 }  // namespace ash
diff --git a/ash/system/accessibility/dictation_bubble_controller_unittest.cc b/ash/system/accessibility/dictation_bubble_controller_unittest.cc
index a6a0551..1f05a7d1 100644
--- a/ash/system/accessibility/dictation_bubble_controller_unittest.cc
+++ b/ash/system/accessibility/dictation_bubble_controller_unittest.cc
@@ -9,7 +9,7 @@
 #include "ash/constants/ash_pref_names.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
-#include "ash/style/ash_color_provider.h"
+#include "ash/style/ash_color_id.h"
 #include "ash/style/dark_light_mode_controller_impl.h"
 #include "ash/system/accessibility/dictation_bubble_view.h"
 #include "ash/test/ash_test_base.h"
@@ -30,8 +30,7 @@
   void SetUp() override {
     scoped_feature_list_.InitWithFeatures(
         /*enabled_features=*/{},
-        /*disabled_features=*/{chromeos::features::kDarkLightMode,
-                               features::kNotificationsRefresh});
+        /*disabled_features=*/{features::kNotificationsRefresh});
 
     AshTestBase::SetUp();
     Shell::Get()->accessibility_controller()->dictation().SetEnabled(true);
@@ -163,21 +162,6 @@
   HideAndCheckExpectations();
 }
 
-// Verifies text and icon colors when the dark light mode feature is disabled.
-TEST_F(DictationBubbleControllerTest, NoDarkMode) {
-  ASSERT_FALSE(chromeos::features::IsDarkLightModeEnabled());
-
-  // Show bubble UI.
-  EXPECT_FALSE(GetView());
-  Show(DictationBubbleIconType::kHidden,
-       absl::optional<std::u16string>(u"Testing"),
-       absl::optional<std::vector<DictationBubbleHintType>>());
-  EXPECT_TRUE(GetView());
-  EXPECT_TRUE(IsBubbleVisible());
-  EXPECT_EQ(u"Testing", GetBubbleText());
-  EXPECT_EQ(SK_ColorBLACK, GetLabelTextColor());
-}
-
 // Verifies that the bubble UI respects the dark mode setting. For convenience
 // purposes, we perform checks on the label's text and background color.
 TEST_F(DictationBubbleControllerTest, DarkMode) {
@@ -185,7 +169,6 @@
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeature(chromeos::features::kDarkLightMode);
   ASSERT_TRUE(chromeos::features::IsDarkLightModeEnabled());
-  AshColorProvider* color_provider = AshColorProvider::Get();
   auto* dark_light_mode_controller = DarkLightModeControllerImpl::Get();
   dark_light_mode_controller->OnActiveUserPrefServiceChanged(
       Shell::Get()->session_controller()->GetPrimaryUserPrefService());
@@ -202,28 +185,30 @@
   EXPECT_EQ(u"Testing", GetBubbleText());
   const SkColor initial_text_color = GetLabelTextColor();
   const SkColor initial_background_color = GetLabelBackgroundColor();
+  auto* color_provider = GetView()->GetColorProvider();
   EXPECT_EQ(initial_text_color,
-            color_provider->GetContentLayerColor(
-                AshColorProvider::ContentLayerType::kTextColorPrimary));
-  EXPECT_EQ(initial_background_color, GetView()->GetColorProvider()->GetColor(
-                                          ui::kColorDialogBackground));
+            color_provider->GetColor(kColorAshTextColorPrimary));
+  EXPECT_EQ(initial_background_color,
+            color_provider->GetColor(ui::kColorDialogBackground));
 
   // Switch the color mode.
   dark_light_mode_controller->ToggleColorMode();
   const bool dark_mode_status = dark_light_mode_controller->IsDarkModeEnabled();
   ASSERT_NE(initial_dark_mode_status, dark_mode_status);
 
+  // Since the color mode has been updated, we need to get the refreshed color
+  // provider.
+  color_provider = GetView()->GetColorProvider();
+
   // Verify that the text and background colors changed and still have the
   // right colors according to the color modes.
   const SkColor text_color = GetLabelTextColor();
   const SkColor background_color = GetLabelBackgroundColor();
   EXPECT_NE(text_color, initial_text_color);
-  EXPECT_EQ(text_color,
-            color_provider->GetContentLayerColor(
-                AshColorProvider::ContentLayerType::kTextColorPrimary));
+  EXPECT_EQ(text_color, color_provider->GetColor(kColorAshTextColorPrimary));
   EXPECT_NE(background_color, initial_background_color);
-  EXPECT_EQ(background_color, GetView()->GetColorProvider()->GetColor(
-                                  ui::kColorDialogBackground));
+  EXPECT_EQ(background_color,
+            color_provider->GetColor(ui::kColorDialogBackground));
 
   HideAndCheckExpectations();
 }
diff --git a/ash/system/accessibility/dictation_bubble_view.cc b/ash/system/accessibility/dictation_bubble_view.cc
index 8fe2548..589eaa6 100644
--- a/ash/system/accessibility/dictation_bubble_view.cc
+++ b/ash/system/accessibility/dictation_bubble_view.cc
@@ -14,6 +14,7 @@
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/style/ash_color_id.h"
 #include "ash/style/ash_color_provider.h"
 #include "cc/paint/skottie_wrapper.h"
 #include "third_party/skia/include/core/SkColor.h"
@@ -21,7 +22,9 @@
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/base/models/image_model.h"
 #include "ui/base/resource/resource_bundle.h"
+#include "ui/color/color_id.h"
 #include "ui/gfx/geometry/size.h"
 #include "ui/gfx/paint_vector_icon.h"
 #include "ui/lottie/animation.h"
@@ -38,54 +41,24 @@
 constexpr int kSpaceBetweenHintLabelsDip = 4;
 constexpr int kSpaceBetweenIconAndTextDip = 4;
 constexpr int kMaxNumHints = 5;
-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) {
   return views::Builder<views::ImageView>()
       .CopyAddressTo(destination_view)
-      .SetImage(gfx::CreateVectorIcon(icon, kIconSizeDip, icon_color_primary()))
+      .SetImage(ui::ImageModel::FromVectorIcon(icon, kColorAshTextColorPrimary,
+                                               kIconSizeDip))
       .Build();
 }
 
-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,
                                               const std::u16string& text,
-                                              SkColor color) {
+                                              ui::ColorId enabled_color_id) {
   return views::Builder<views::Label>()
       .CopyAddressTo(destination_view)
       .SetText(text)
-      .SetEnabledColor(color)
+      .SetEnabledColorId(enabled_color_id)
       .SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT)
       .SetMultiLine(false)
       .Build();
@@ -130,7 +103,7 @@
     AddChildView(
         CreateImageView(&macro_failed_image_, kDictationBubbleMacroFailedIcon));
     AddChildView(
-        CreateLabelView(&label_, std::u16string(), text_color_primary()));
+        CreateLabelView(&label_, std::u16string(), kColorAshTextColorPrimary));
   }
 
   TopRowView(const TopRowView&) = delete;
@@ -161,16 +134,6 @@
     SizeToPreferredSize();
   }
 
-  // Updates this view so that it respects the global dark mode setting.
-  void OnColorModeChanged(bool dark_mode_enabled) {
-    if (!use_standby_animation_)
-      SetImageHelper(standby_image_, kDictationBubbleIcon);
-
-    SetImageHelper(macro_succeeded_image_, kDictationBubbleMacroSucceededIcon);
-    SetImageHelper(macro_failed_image_, kDictationBubbleMacroFailedIcon);
-    label_->SetEnabledColor(text_color_primary());
-  }
-
   // views::View:
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override {
     node_data->role = ax::mojom::Role::kGenericContainer;
@@ -229,13 +192,12 @@
     layout->set_between_child_spacing(kSpaceBetweenHintLabelsDip);
     SetLayoutManager(std::move(layout));
 
-    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));
+      ui::ColorId color_id =
+          i == 0 ? kColorAshTextColorSecondary : kColorAshTextColorPrimary;
+      AddChildView(CreateLabelView(&labels_[i], std::u16string(), color_id));
     }
   }
 
@@ -276,17 +238,6 @@
     SizeToPreferredSize();
   }
 
-  // Updates this view so that it respects the global dark mode setting.
-  void OnColorModeChanged(bool dark_mode_enabled) {
-    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);
-    }
-  }
-
   // views::View:
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override {
     node_data->role = ax::mojom::Role::kGenericContainer;
@@ -326,11 +277,6 @@
   SizeToContents();
 }
 
-void DictationBubbleView::OnColorModeChanged(bool dark_mode_enabled) {
-  top_row_view_->OnColorModeChanged(dark_mode_enabled);
-  hint_view_->OnColorModeChanged(dark_mode_enabled);
-}
-
 void DictationBubbleView::Init() {
   std::unique_ptr<views::BoxLayout> layout = std::make_unique<views::BoxLayout>(
       views::BoxLayout::Orientation::kVertical);
diff --git a/ash/system/accessibility/dictation_bubble_view.h b/ash/system/accessibility/dictation_bubble_view.h
index aea8ec3..2747db1 100644
--- a/ash/system/accessibility/dictation_bubble_view.h
+++ b/ash/system/accessibility/dictation_bubble_view.h
@@ -45,8 +45,6 @@
       const absl::optional<std::u16string>& text,
       const absl::optional<std::vector<DictationBubbleHintType>>& hints);
 
-  void OnColorModeChanged(bool dark_mode_enabled);
-
   // views::BubbleDialogDelegateView:
   void Init() override;
   void OnBeforeBubbleWidgetInit(views::Widget::InitParams* params,
diff --git a/ash/system/accessibility/dictation_button_tray.cc b/ash/system/accessibility/dictation_button_tray.cc
index 349740fe..af14e04e 100644
--- a/ash/system/accessibility/dictation_button_tray.cc
+++ b/ash/system/accessibility/dictation_button_tray.cc
@@ -14,6 +14,7 @@
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/style/ash_color_id.h"
 #include "ash/system/progress_indicator/progress_indicator.h"
 #include "ash/system/tray/tray_constants.h"
 #include "ash/system/tray/tray_container.h"
@@ -23,6 +24,8 @@
 #include "ui/base/ime/text_input_client.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/base/models/image_model.h"
+#include "ui/color/color_id.h"
 #include "ui/compositor/layer.h"
 #include "ui/gfx/paint_vector_icon.h"
 #include "ui/views/border.h"
@@ -36,14 +39,12 @@
 // changes to an "on" icon from "off" when Dictation is listening.
 // |enabled| indicates whether the tray button is enabled, i.e. clickable.
 // A secondary color is used to indicate the icon is not enabled.
-gfx::ImageSkia GetIconImage(bool active, bool enabled) {
-  const SkColor color =
-      enabled
-          ? TrayIconColor(Shell::Get()->session_controller()->GetSessionState())
-          : AshColorProvider::Get()->GetContentLayerColor(
-                AshColorProvider::ContentLayerType::kIconColorSecondary);
-  return active ? gfx::CreateVectorIcon(kDictationOnNewuiIcon, color)
-                : gfx::CreateVectorIcon(kDictationOffNewuiIcon, color);
+ui::ImageModel GetIconImage(bool active, bool enabled) {
+  const ui::ColorId color_id =
+      enabled ? kColorAshIconColorPrimary : kColorAshIconColorSecondary;
+  return active
+             ? ui::ImageModel::FromVectorIcon(kDictationOnNewuiIcon, color_id)
+             : ui::ImageModel::FromVectorIcon(kDictationOffNewuiIcon, color_id);
 }
 
 }  // namespace
@@ -60,11 +61,13 @@
       shell->window_tree_host_manager()->input_method()->GetTextInputClient();
   in_text_input_ =
       (client && client->GetTextInputType() != ui::TEXT_INPUT_TYPE_NONE);
-  const gfx::ImageSkia icon_image =
+  const ui::ImageModel icon_image =
       GetIconImage(/*active=*/false, /*enabled=*/in_text_input_);
-  const int vertical_padding = (kTrayItemSize - icon_image.height()) / 2;
-  const int horizontal_padding = (kTrayItemSize - icon_image.width()) / 2;
+  const int vertical_padding = (kTrayItemSize - icon_image.Size().height()) / 2;
+  const int horizontal_padding =
+      (kTrayItemSize - icon_image.Size().height()) / 2;
   auto icon = std::make_unique<views::ImageView>();
+  icon->SetImage(icon_image);
   icon->SetBorder(views::CreateEmptyBorder(
       gfx::Insets::VH(vertical_padding, horizontal_padding)));
   icon->SetTooltipText(
@@ -126,9 +129,6 @@
 
 void DictationButtonTray::OnThemeChanged() {
   TrayBackgroundView::OnThemeChanged();
-  icon_->SetImage(
-      GetIconImage(Shell::Get()->accessibility_controller()->dictation_active(),
-                   GetEnabled()));
   if (progress_indicator_)
     progress_indicator_->InvalidateLayer();
 }
diff --git a/ash/system/accessibility/dictation_button_tray_unittest.cc b/ash/system/accessibility/dictation_button_tray_unittest.cc
index 6059f3cad..012a7158 100644
--- a/ash/system/accessibility/dictation_button_tray_unittest.cc
+++ b/ash/system/accessibility/dictation_button_tray_unittest.cc
@@ -16,7 +16,7 @@
 #include "ash/rotator/screen_rotation_animator.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
-#include "ash/style/ash_color_provider.h"
+#include "ash/style/ash_color_id.h"
 #include "ash/system/progress_indicator/progress_indicator.h"
 #include "ash/system/status_area_widget.h"
 #include "ash/system/status_area_widget_test_helper.h"
@@ -206,17 +206,17 @@
 }
 
 TEST_F(DictationButtonTrayTest, ImageIcons) {
-  SkColor color = AshColorProvider::Get()->GetContentLayerColor(
-      AshColorProvider::ContentLayerType::kIconColorPrimary);
-  gfx::ImageSkia off_icon =
-      gfx::CreateVectorIcon(kDictationOffNewuiIcon, color);
-  gfx::ImageSkia on_icon = gfx::CreateVectorIcon(kDictationOnNewuiIcon, color);
-
   AccessibilityControllerImpl* controller =
       Shell::Get()->accessibility_controller();
   TestAccessibilityControllerClient client;
   controller->dictation().SetEnabled(true);
 
+  SkColor color =
+      GetTray()->GetColorProvider()->GetColor(kColorAshIconColorPrimary);
+  gfx::ImageSkia off_icon =
+      gfx::CreateVectorIcon(kDictationOffNewuiIcon, color);
+  gfx::ImageSkia on_icon = gfx::CreateVectorIcon(kDictationOnNewuiIcon, color);
+
   views::ImageView* view = GetImageView(GetTray());
   EXPECT_TRUE(gfx::test::AreBitmapsEqual(*view->GetImage().bitmap(),
                                          *off_icon.bitmap()));
diff --git a/ash/system/accessibility/floating_accessibility_detailed_controller.cc b/ash/system/accessibility/floating_accessibility_detailed_controller.cc
index c708cb92..9f7a572 100644
--- a/ash/system/accessibility/floating_accessibility_detailed_controller.cc
+++ b/ash/system/accessibility/floating_accessibility_detailed_controller.cc
@@ -9,7 +9,7 @@
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
-#include "ash/style/ash_color_provider.h"
+#include "ash/style/ash_color_id.h"
 #include "ash/system/accessibility/accessibility_detailed_view.h"
 #include "ash/system/tray/tray_background_view.h"
 #include "ash/system/tray/tray_constants.h"
@@ -141,9 +141,7 @@
   views::ImageButton* button = static_cast<views::ImageButton*>(
       DetailedViewDelegate::CreateBackButton(std::move(callback)));
   ui::ImageModel image = ui::ImageModel::FromVectorIcon(
-      kAutoclickCloseIcon,
-      AshColorProvider::Get()->GetContentLayerColor(
-          AshColorProvider::ContentLayerType::kIconColorPrimary));
+      kAutoclickCloseIcon, kColorAshIconColorPrimary);
   button->SetImageModel(views::Button::STATE_NORMAL, image);
   button->SetTooltipText(l10n_util::GetStringUTF16(
       IDS_ASH_FLOATING_ACCESSIBILITY_DETAILED_MENU_CLOSE));
diff --git a/ash/system/accessibility/floating_menu_button.cc b/ash/system/accessibility/floating_menu_button.cc
index d4732e8..050626c6 100644
--- a/ash/system/accessibility/floating_menu_button.cc
+++ b/ash/system/accessibility/floating_menu_button.cc
@@ -4,12 +4,13 @@
 
 #include "ash/system/accessibility/floating_menu_button.h"
 
-#include "ash/style/ash_color_provider.h"
+#include "ash/style/ash_color_id.h"
 #include "ash/style/color_util.h"
 #include "ash/style/style_util.h"
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/base/models/image_model.h"
 #include "ui/color/color_id.h"
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/paint_vector_icon.h"
@@ -59,6 +60,7 @@
       is_a11y_togglable_(is_a11y_togglable) {
   SetImageHorizontalAlignment(ALIGN_CENTER);
   SetImageVerticalAlignment(ALIGN_MIDDLE);
+  UpdateImage();
   SetFlipCanvasOnPaintForRTLUI(flip_for_rtl);
   SetPreferredSize(gfx::Size(size_, size_));
   StyleUtil::SetUpInkDropForButton(this);
@@ -115,11 +117,9 @@
     gfx::Rect rect(GetContentsBounds());
     cc::PaintFlags flags;
     flags.setAntiAlias(true);
-    flags.setColor(AshColorProvider::Get()->GetControlsLayerColor(
-        toggled_
-            ? AshColorProvider::ControlsLayerType::kControlBackgroundColorActive
-            : AshColorProvider::ControlsLayerType::
-                  kControlBackgroundColorInactive));
+    flags.setColor(GetColorProvider()->GetColor(
+        toggled_ ? kColorAshControlBackgroundColorActive
+                 : kColorAshControlBackgroundColorInactive));
     flags.setStyle(cc::PaintFlags::kFill_Style);
     canvas->DrawCircle(gfx::PointF(rect.CenterPoint()), size_ / 2, flags);
   }
@@ -142,25 +142,15 @@
                                       : ax::mojom::CheckedState::kFalse);
 }
 
-void FloatingMenuButton::OnThemeChanged() {
-  ImageButton::OnThemeChanged();
-  UpdateImage();
-  SchedulePaint();
-}
-
 void FloatingMenuButton::UpdateImage() {
   DCHECK(icon_);
-  auto* color_provider = AshColorProvider::Get();
-  const SkColor normal_color = color_provider->GetContentLayerColor(
-      AshColorProvider::ContentLayerType::kButtonIconColor);
-  const SkColor toggled_icon_color = color_provider->GetContentLayerColor(
-      AshColorProvider::ContentLayerType::kButtonIconColorPrimary);
-  const SkColor icon_color = toggled_ ? toggled_icon_color : normal_color;
-  SetImage(views::Button::STATE_NORMAL,
-           gfx::CreateVectorIcon(*icon_, icon_color));
-  SetImage(
+  const ui::ColorId icon_color_id =
+      toggled_ ? kColorAshButtonIconColorPrimary : kColorAshButtonIconColor;
+  SetImageModel(views::Button::STATE_NORMAL,
+                ui::ImageModel::FromVectorIcon(*icon_, icon_color_id));
+  SetImageModel(
       views::Button::STATE_DISABLED,
-      gfx::CreateVectorIcon(*icon_, ColorUtil::GetDisabledColor(normal_color)));
+      ui::ImageModel::FromVectorIcon(*icon_, kColorAshButtonIconDisabledColor));
 }
 
 BEGIN_METADATA(FloatingMenuButton, views::ImageButton)
diff --git a/ash/system/accessibility/floating_menu_button.h b/ash/system/accessibility/floating_menu_button.h
index 45032f7..64392bd 100644
--- a/ash/system/accessibility/floating_menu_button.h
+++ b/ash/system/accessibility/floating_menu_button.h
@@ -59,7 +59,6 @@
   void PaintButtonContents(gfx::Canvas* canvas) override;
   gfx::Size CalculatePreferredSize() const override;
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
-  void OnThemeChanged() override;
 
  private:
   void UpdateImage();
diff --git a/ash/system/accessibility/select_to_speak/select_to_speak_tray.cc b/ash/system/accessibility/select_to_speak/select_to_speak_tray.cc
index 057048f..148fb5c 100644
--- a/ash/system/accessibility/select_to_speak/select_to_speak_tray.cc
+++ b/ash/system/accessibility/select_to_speak/select_to_speak_tray.cc
@@ -10,12 +10,15 @@
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/style/ash_color_id.h"
 #include "ash/system/tray/tray_constants.h"
 #include "ash/system/tray/tray_container.h"
 #include "ash/system/tray/tray_utils.h"
 #include "ui/accessibility/accessibility_features.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/base/models/image_model.h"
+#include "ui/color/color_id.h"
 #include "ui/gfx/paint_vector_icon.h"
 #include "ui/views/border.h"
 #include "ui/views/controls/image_view.h"
@@ -28,20 +31,18 @@
 
 namespace {
 
-gfx::ImageSkia GetImageOnCurrentSelectToSpeakStatus(
+ui::ImageModel GetImageOnCurrentSelectToSpeakStatus(
     const SelectToSpeakState& select_to_speak_state) {
-  auto* shell = Shell::Get();
-  const SkColor color =
-      TrayIconColor(shell->session_controller()->GetSessionState());
-
   switch (select_to_speak_state) {
     case SelectToSpeakState::kSelectToSpeakStateInactive:
-      return gfx::CreateVectorIcon(kSystemTraySelectToSpeakNewuiIcon, color);
+      return ui::ImageModel::FromVectorIcon(kSystemTraySelectToSpeakNewuiIcon,
+                                            kColorAshIconColorPrimary);
     case SelectToSpeakState::kSelectToSpeakStateSelecting:
-      return gfx::CreateVectorIcon(kSystemTraySelectToSpeakActiveNewuiIcon,
-                                   color);
+      return ui::ImageModel::FromVectorIcon(
+          kSystemTraySelectToSpeakActiveNewuiIcon, kColorAshIconColorPrimary);
     case SelectToSpeakState::kSelectToSpeakStateSpeaking:
-      return gfx::CreateVectorIcon(kSystemTrayStopNewuiIcon, color);
+      return ui::ImageModel::FromVectorIcon(kSystemTrayStopNewuiIcon,
+                                            kColorAshIconColorPrimary);
   }
 }
 
@@ -74,13 +75,14 @@
     Shell::Get()->accessibility_controller()->RequestSelectToSpeakStateChange();
   }));
 
-  const gfx::ImageSkia inactive_image = gfx::CreateVectorIcon(
-      kSystemTraySelectToSpeakNewuiIcon,
-      TrayIconColor(Shell::Get()->session_controller()->GetSessionState()));
+  const ui::ImageModel inactive_image = ui::ImageModel::FromVectorIcon(
+      kSystemTraySelectToSpeakNewuiIcon, kColorAshIconColorPrimary);
   auto icon = std::make_unique<views::ImageView>();
   icon->SetImage(inactive_image);
-  const int vertical_padding = (kTrayItemSize - inactive_image.height()) / 2;
-  const int horizontal_padding = (kTrayItemSize - inactive_image.width()) / 2;
+  const int vertical_padding =
+      (kTrayItemSize - inactive_image.Size().height()) / 2;
+  const int horizontal_padding =
+      (kTrayItemSize - inactive_image.Size().width()) / 2;
   icon->SetBorder(views::CreateEmptyBorder(
       gfx::Insets::VH(vertical_padding, horizontal_padding)));
   icon->SetTooltipText(l10n_util::GetStringUTF16(
diff --git a/ash/system/accessibility/select_to_speak/select_to_speak_tray_unittest.cc b/ash/system/accessibility/select_to_speak/select_to_speak_tray_unittest.cc
index 22f551b..c170c694 100644
--- a/ash/system/accessibility/select_to_speak/select_to_speak_tray_unittest.cc
+++ b/ash/system/accessibility/select_to_speak/select_to_speak_tray_unittest.cc
@@ -9,7 +9,7 @@
 #include "ash/accessibility/test_accessibility_controller_client.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/shell.h"
-#include "ash/style/ash_color_provider.h"
+#include "ash/style/ash_color_id.h"
 #include "ash/system/status_area_widget.h"
 #include "ash/system/status_area_widget_test_helper.h"
 #include "ash/test/ash_test_base.h"
@@ -70,9 +70,8 @@
   views::ImageView* GetImageView() { return GetTray()->icon_; }
 
   // Gets the corresponding image given the |select_to_speak_state|.
-  gfx::ImageSkia GetIconImage(SelectToSpeakState select_to_speak_state) {
-    SkColor color = AshColorProvider::Get()->GetContentLayerColor(
-        AshColorProvider::ContentLayerType::kIconColorPrimary);
+  gfx::ImageSkia GetIconImage(SelectToSpeakState select_to_speak_state,
+                              SkColor color) {
     switch (select_to_speak_state) {
       case SelectToSpeakState::kSelectToSpeakStateInactive:
         return gfx::CreateVectorIcon(kSystemTraySelectToSpeakNewuiIcon, color);
@@ -121,16 +120,19 @@
   controller->SetSelectToSpeakState(
       SelectToSpeakState::kSelectToSpeakStateSelecting);
   EXPECT_TRUE(IsTrayBackgroundActive());
-  gfx::ImageSkia expected_icon_image =
-      GetIconImage(SelectToSpeakState::kSelectToSpeakStateSelecting);
+  const auto icon_color =
+      GetTray()->GetColorProvider()->GetColor(kColorAshIconColorPrimary);
+  gfx::ImageSkia expected_icon_image = GetIconImage(
+      SelectToSpeakState::kSelectToSpeakStateSelecting, icon_color);
   gfx::ImageSkia actual_icon_image = GetImageView()->GetImage();
   EXPECT_TRUE(gfx::test::AreBitmapsEqual(*expected_icon_image.bitmap(),
                                          *actual_icon_image.bitmap()));
   controller->SetSelectToSpeakState(
       SelectToSpeakState::kSelectToSpeakStateSpeaking);
   EXPECT_TRUE(IsTrayBackgroundActive());
+
   expected_icon_image =
-      GetIconImage(SelectToSpeakState::kSelectToSpeakStateSpeaking);
+      GetIconImage(SelectToSpeakState::kSelectToSpeakStateSpeaking, icon_color);
   actual_icon_image = GetImageView()->GetImage();
   EXPECT_TRUE(gfx::test::AreBitmapsEqual(*expected_icon_image.bitmap(),
                                          *actual_icon_image.bitmap()));
@@ -139,7 +141,7 @@
       SelectToSpeakState::kSelectToSpeakStateInactive);
   EXPECT_FALSE(IsTrayBackgroundActive());
   expected_icon_image =
-      GetIconImage(SelectToSpeakState::kSelectToSpeakStateInactive);
+      GetIconImage(SelectToSpeakState::kSelectToSpeakStateInactive, icon_color);
   actual_icon_image = GetImageView()->GetImage();
   EXPECT_TRUE(gfx::test::AreBitmapsEqual(*expected_icon_image.bitmap(),
                                          *actual_icon_image.bitmap()));
diff --git a/ash/system/accessibility/switch_access/switch_access_back_button_view.cc b/ash/system/accessibility/switch_access/switch_access_back_button_view.cc
index 8f12a1a..d706686f 100644
--- a/ash/system/accessibility/switch_access/switch_access_back_button_view.cc
+++ b/ash/system/accessibility/switch_access/switch_access_back_button_view.cc
@@ -6,7 +6,7 @@
 
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/strings/grit/ash_strings.h"
-#include "ash/style/ash_color_provider.h"
+#include "ash/style/ash_color_id.h"
 #include "ash/system/accessibility/floating_menu_button.h"
 #include "ash/system/tray/tray_constants.h"
 #include "base/bind.h"
@@ -84,27 +84,26 @@
 }
 
 void SwitchAccessBackButtonView::OnPaint(gfx::Canvas* canvas) {
-  auto* color_provider = AshColorProvider::Get();
+  auto* color_provider = GetColorProvider();
   gfx::Rect rect(GetContentsBounds());
   cc::PaintFlags flags;
   flags.setAntiAlias(true);
-  flags.setColor(color_provider->GetBaseLayerColor(
-      AshColorProvider::BaseLayerType::kTransparent80));
+  flags.setColor(color_provider->GetColor(kColorAshShieldAndBase80));
   flags.setStyle(cc::PaintFlags::kFill_Style);
   canvas->DrawCircle(gfx::PointF(rect.CenterPoint()), kRadiusDp, flags);
 
   if (!show_focus_ring_)
     return;
 
-  flags.setColor(color_provider->GetContentLayerColor(
-      AshColorProvider::ContentLayerType::kSwitchAccessInnerStrokeColor));
+  flags.setColor(
+      color_provider->GetColor(kColorAshSwitchAccessInnerStrokeColor));
   flags.setStyle(cc::PaintFlags::kStroke_Style);
   flags.setStrokeWidth(kFocusRingSingleColorWidthDp);
   canvas->DrawCircle(gfx::PointF(rect.CenterPoint()),
                      kRadiusDp + kFocusRingSingleColorWidthDp, flags);
 
-  flags.setColor(color_provider->GetContentLayerColor(
-      AshColorProvider::ContentLayerType::kSwitchAccessOuterStrokeColor));
+  flags.setColor(
+      color_provider->GetColor(kColorAshSwitchAccessOuterStrokeColor));
   canvas->DrawCircle(gfx::PointF(rect.CenterPoint()),
                      kRadiusDp + (2 * kFocusRingSingleColorWidthDp), flags);
 }
diff --git a/ash/system/accessibility/switch_access/switch_access_menu_button.cc b/ash/system/accessibility/switch_access/switch_access_menu_button.cc
index 14b08f8b..e2920399 100644
--- a/ash/system/accessibility/switch_access/switch_access_menu_button.cc
+++ b/ash/system/accessibility/switch_access/switch_access_menu_button.cc
@@ -4,12 +4,13 @@
 
 #include "ash/system/accessibility/switch_access/switch_access_menu_button.h"
 
-#include "ash/style/ash_color_provider.h"
+#include "ash/style/ash_color_id.h"
 #include "base/bind.h"
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/accessibility/mojom/ax_node_data.mojom-shared.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/base/models/image_model.h"
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/paint_vector_icon.h"
 #include "ui/gfx/vector_icon_types.h"
@@ -41,26 +42,21 @@
           base::BindRepeating(&SwitchAccessMenuButton::OnButtonPressed,
                               base::Unretained(this))),
       action_name_(action_name) {
-  SkColor icon_color = AshColorProvider::Get()->GetContentLayerColor(
-      AshColorProvider::ContentLayerType::kIconColorPrimary);
-  SkColor label_color = AshColorProvider::Get()->GetContentLayerColor(
-      AshColorProvider::ContentLayerType::kTextColorPrimary);
-
   std::u16string label_text = l10n_util::GetStringUTF16(label_text_id);
   views::Builder<SwitchAccessMenuButton>(this)
       .SetFocusBehavior(views::View::FocusBehavior::ACCESSIBLE_ONLY)
-      .AddChildren(
-          views::Builder<views::ImageView>()
-              .CopyAddressTo(&image_view_)
-              .SetImage(gfx::CreateVectorIcon(icon, kIconSizeDip, icon_color)),
-          views::Builder<views::Label>()
-              .CopyAddressTo(&label_)
-              .SetText(label_text)
-              .SetTextContext(views::style::CONTEXT_BUTTON)
-              .SetAutoColorReadabilityEnabled(false)
-              .SetEnabledColor(label_color)
-              .SetMultiLine(true)
-              .SetMaximumWidth(kLabelMaxWidthDip))
+      .AddChildren(views::Builder<views::ImageView>()
+                       .CopyAddressTo(&image_view_)
+                       .SetImage(ui::ImageModel::FromVectorIcon(
+                           icon, kColorAshIconColorPrimary, kIconSizeDip)),
+                   views::Builder<views::Label>()
+                       .CopyAddressTo(&label_)
+                       .SetText(label_text)
+                       .SetTextContext(views::style::CONTEXT_BUTTON)
+                       .SetAutoColorReadabilityEnabled(false)
+                       .SetEnabledColorId(kColorAshTextColorPrimary)
+                       .SetMultiLine(true)
+                       .SetMaximumWidth(kLabelMaxWidthDip))
       .BuildChildren();
 
   std::unique_ptr<views::BoxLayout> layout = std::make_unique<views::BoxLayout>(
diff --git a/ash/system/audio/audio_detailed_view.cc b/ash/system/audio/audio_detailed_view.cc
index f71a8f23..a00794c 100644
--- a/ash/system/audio/audio_detailed_view.cc
+++ b/ash/system/audio/audio_detailed_view.cc
@@ -10,7 +10,7 @@
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
-#include "ash/style/ash_color_provider.h"
+#include "ash/style/ash_color_id.h"
 #include "ash/system/audio/mic_gain_slider_controller.h"
 #include "ash/system/audio/mic_gain_slider_view.h"
 #include "ash/system/tray/hover_highlight_view.h"
@@ -282,9 +282,7 @@
       std::make_unique<views::Label>(l10n_util::GetStringUTF16(
           IDS_ASH_STATUS_TRAY_AUDIO_INPUT_NOISE_CANCELLATION));
 
-  const SkColor text_color = AshColorProvider::Get()->GetContentLayerColor(
-      AshColorProvider::ContentLayerType::kTextColorPrimary);
-  noise_cancellation_label->SetEnabledColor(text_color);
+  noise_cancellation_label->SetEnabledColorId(kColorAshTextColorPrimary);
   noise_cancellation_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
   noise_cancellation_label->SetFontList(
       gfx::FontList().DeriveWithSizeDelta(kLabelFontSizeDelta));
diff --git a/ash/system/bluetooth/bluetooth_device_list_item_battery_view.cc b/ash/system/bluetooth/bluetooth_device_list_item_battery_view.cc
index 01128f1..6ca99a4 100644
--- a/ash/system/bluetooth/bluetooth_device_list_item_battery_view.cc
+++ b/ash/system/bluetooth/bluetooth_device_list_item_battery_view.cc
@@ -6,7 +6,6 @@
 
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/style/ash_color_id.h"
-#include "ash/style/ash_color_provider.h"
 #include "ash/system/tray/tray_constants.h"
 #include "ash/system/tray/tray_popup_utils.h"
 #include "ash/system/tray/unfocusable_label.h"
@@ -70,16 +69,15 @@
         0, kSpacingBetweenIconAndLabel, 0, kSpacingBetweenIconAndLabel)));
   }
 
-  const AshColorProvider::ContentLayerType content_layer_type =
+  const ui::ColorId color_id =
       new_battery_percentage >= kPositiveBatteryPercentageCutoff
-          ? AshColorProvider::ContentLayerType::kTextColorSecondary
-          : AshColorProvider::ContentLayerType::kTextColorAlert;
+          ? kColorAshTextColorSecondary
+          : kColorAshTextColorAlert;
 
   label_->SetText(l10n_util::GetStringFUTF16(
       message_id, base::NumberToString16(new_battery_percentage)));
   label_->SetAutoColorReadabilityEnabled(false);
-  label_->SetEnabledColor(
-      AshColorProvider::Get()->GetContentLayerColor(content_layer_type));
+  label_->SetEnabledColorId(color_id);
 
   if (last_shown_battery_percentage_ &&
       ApproximatelyEqual(last_shown_battery_percentage_.value(),
@@ -92,10 +90,11 @@
   PowerStatus::BatteryImageInfo battery_image_info;
   battery_image_info.charge_percent = new_battery_percentage;
 
+  auto* color_provider = GetColorProvider();
   icon_->SetImage(PowerStatus::GetBatteryImage(
       battery_image_info, kUnifiedTraySubIconSize,
-      GetColorProvider()->GetColor(kColorAshShieldAndBaseOpaque),
-      AshColorProvider::Get()->GetContentLayerColor(content_layer_type)));
+      color_provider->GetColor(kColorAshShieldAndBaseOpaque),
+      color_provider->GetColor(color_id)));
 }
 
 bool BluetoothDeviceListItemBatteryView::ApproximatelyEqual(
diff --git a/ash/system/bluetooth/bluetooth_device_list_item_view.cc b/ash/system/bluetooth/bluetooth_device_list_item_view.cc
index 8152da9..4739d2f 100644
--- a/ash/system/bluetooth/bluetooth_device_list_item_view.cc
+++ b/ash/system/bluetooth/bluetooth_device_list_item_view.cc
@@ -8,9 +8,10 @@
 
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/strings/grit/ash_strings.h"
-#include "ash/style/ash_color_provider.h"
+#include "ash/style/ash_color_id.h"
 #include "ash/system/bluetooth/bluetooth_device_list_item_battery_view.h"
 #include "ash/system/bluetooth/bluetooth_device_list_item_multiple_battery_view.h"
+#include "ash/system/tray/hover_highlight_view.h"
 #include "ash/system/tray/tray_utils.h"
 #include "base/check.h"
 #include "base/notreached.h"
@@ -20,6 +21,7 @@
 #include "chromeos/ui/vector_icons/vector_icons.h"
 #include "mojo/public/cpp/bindings/clone_traits.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/image_model.h"
 #include "ui/gfx/color_palette.h"
 #include "ui/gfx/image/image_skia.h"
 #include "ui/gfx/paint_vector_icon.h"
@@ -184,12 +186,9 @@
   const DeviceType& device_type =
       device_properties_->device_properties->device_type;
 
-  AddIconAndLabel(
-      gfx::CreateVectorIcon(
-          GetDeviceIcon(device_type),
-          AshColorProvider::Get()->GetContentLayerColor(
-              AshColorProvider::ContentLayerType::kIconColorPrimary)),
-      GetPairedDeviceName(device_properties_));
+  AddIconAndLabel(ui::ImageModel::FromVectorIcon(GetDeviceIcon(device_type),
+                                                 kColorAshIconColorPrimary),
+                  GetPairedDeviceName(device_properties_));
 
   UpdateAccessibleName(device_index, total_device_count);
 
diff --git a/ash/system/bluetooth/bluetooth_device_list_item_view_unittest.cc b/ash/system/bluetooth/bluetooth_device_list_item_view_unittest.cc
index c7e7b4f..1146b6f 100644
--- a/ash/system/bluetooth/bluetooth_device_list_item_view_unittest.cc
+++ b/ash/system/bluetooth/bluetooth_device_list_item_view_unittest.cc
@@ -10,7 +10,7 @@
 
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/strings/grit/ash_strings.h"
-#include "ash/style/ash_color_provider.h"
+#include "ash/style/ash_color_id.h"
 #include "ash/system/bluetooth/bluetooth_device_list_item_battery_view.h"
 #include "ash/system/bluetooth/bluetooth_device_list_item_multiple_battery_view.h"
 #include "ash/system/bluetooth/fake_bluetooth_detailed_view.h"
@@ -350,9 +350,10 @@
           {DeviceType::kTablet, &ash::kSystemMenuTabletIcon},
           {DeviceType::kUnknown, &ash::kSystemMenuBluetoothIcon},
       }};
-  const SkColor icon_color = AshColorProvider::Get()->GetContentLayerColor(
-      AshColorProvider::ContentLayerType::kIconColorPrimary);
 
+  const SkColor icon_color =
+      bluetooth_device_list_item()->GetColorProvider()->GetColor(
+          kColorAshIconColorPrimary);
   for (const auto& it : device_type_to_icon_map) {
     PairedBluetoothDevicePropertiesPtr paired_device_properties =
         CreatePairedDeviceProperties();
diff --git a/ash/system/bluetooth/bluetooth_disabled_detailed_view.cc b/ash/system/bluetooth/bluetooth_disabled_detailed_view.cc
index b0f72267..d7b9b86 100644
--- a/ash/system/bluetooth/bluetooth_disabled_detailed_view.cc
+++ b/ash/system/bluetooth/bluetooth_disabled_detailed_view.cc
@@ -8,7 +8,7 @@
 
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/strings/grit/ash_strings.h"
-#include "ash/style/ash_color_provider.h"
+#include "ash/style/ash_color_id.h"
 #include "ash/style/color_util.h"
 #include "ash/system/tray/tray_popup_utils.h"
 #include "base/check.h"
@@ -39,17 +39,9 @@
   box_layout->set_main_axis_alignment(BoxLayout::MainAxisAlignment::kCenter);
   SetLayoutManager(std::move(box_layout));
 
-  AshColorProvider* color_provider = AshColorProvider::Get();
-  const SkColor icon_color =
-      ColorUtil::GetDisabledColor(color_provider->GetContentLayerColor(
-          AshColorProvider::ContentLayerType::kIconColorPrimary));
-  const SkColor text_color =
-      ColorUtil::GetDisabledColor(color_provider->GetContentLayerColor(
-          AshColorProvider::ContentLayerType::kTextColorPrimary));
-
-  ImageView* image_view = AddChildView(std::make_unique<ImageView>(
-      ui::ImageModel::FromImageSkia(gfx::CreateVectorIcon(
-          kSystemMenuBluetoothDisabledIcon, icon_color))));
+  ImageView* image_view =
+      AddChildView(std::make_unique<ImageView>(ui::ImageModel::FromVectorIcon(
+          kSystemMenuBluetoothDisabledIcon, kColorAshButtonIconDisabledColor)));
   image_view->SetVerticalAlignment(ImageView::Alignment::kTrailing);
 
   Label* label = AddChildView(std::make_unique<Label>(
@@ -58,7 +50,7 @@
       label, TrayPopupUtils::FontStyle::kDetailedViewLabel);
   label->SetBorder(views::CreateEmptyBorder(gfx::Insets::TLBR(
       kDesiredLabelBaselineY - label->GetBaseline(), 0, 0, 0)));
-  label->SetEnabledColor(text_color);
+  label->SetEnabledColorId(kColorAshTextColorPrimary);
 
   // Make top padding of the icon equal to the height of the label so that the
   // icon is vertically aligned to center of the container.
diff --git a/ash/system/brightness/unified_brightness_view.cc b/ash/system/brightness/unified_brightness_view.cc
index 1a2f7d7e..9727199 100644
--- a/ash/system/brightness/unified_brightness_view.cc
+++ b/ash/system/brightness/unified_brightness_view.cc
@@ -7,9 +7,10 @@
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
-#include "ash/style/ash_color_provider.h"
+#include "ash/style/ash_color_id.h"
 #include "ash/system/brightness/unified_brightness_slider_controller.h"
 #include "base/memory/scoped_refptr.h"
+#include "ui/base/models/image_model.h"
 #include "ui/gfx/paint_vector_icon.h"
 
 namespace ash {
@@ -23,6 +24,12 @@
                         IDS_ASH_STATUS_TRAY_BRIGHTNESS),
       model_(model) {
   button()->SetEnabled(false);
+  // The button is set to disabled but wants to keep the color for an enabled
+  // icon.
+  button()->SetImageModel(
+      views::Button::STATE_DISABLED,
+      ui::ImageModel::FromVectorIcon(kUnifiedMenuBrightnessIcon,
+                                     kColorAshButtonIconColor));
 
   model_->AddObserver(this);
   OnDisplayBrightnessChanged(false /* by_user */);
@@ -40,17 +47,4 @@
   return "UnifiedBrightnessView";
 }
 
-void UnifiedBrightnessView::OnThemeChanged() {
-  UnifiedSliderView::OnThemeChanged();
-
-  // Override the color for the icon. Since the button is set to disabled but
-  // wants to keep the color for an enabled icon.
-  button()->SetImage(
-      views::Button::STATE_DISABLED,
-      gfx::CreateVectorIcon(
-          kUnifiedMenuBrightnessIcon,
-          AshColorProvider::Get()->GetContentLayerColor(
-              AshColorProvider::ContentLayerType::kButtonIconColor)));
-}
-
 }  // namespace ash
diff --git a/ash/system/brightness/unified_brightness_view.h b/ash/system/brightness/unified_brightness_view.h
index aae0986..9b23a3f6 100644
--- a/ash/system/brightness/unified_brightness_view.h
+++ b/ash/system/brightness/unified_brightness_view.h
@@ -31,7 +31,6 @@
 
   // views::View:
   const char* GetClassName() const override;
-  void OnThemeChanged() override;
 
  private:
   scoped_refptr<UnifiedSystemTrayModel> model_;
diff --git a/ash/system/human_presence/snooping_protection_view.cc b/ash/system/human_presence/snooping_protection_view.cc
index 04d6f533..61f284fd 100644
--- a/ash/system/human_presence/snooping_protection_view.cc
+++ b/ash/system/human_presence/snooping_protection_view.cc
@@ -9,6 +9,7 @@
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/style/ash_color_id.h"
 #include "ash/style/ash_color_provider.h"
 #include "ash/system/tray/tray_constants.h"
 #include "ash/system/tray/tray_utils.h"
@@ -16,6 +17,7 @@
 #include "components/session_manager/session_manager_types.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/image_model.h"
 #include "ui/gfx/image/image_skia.h"
 #include "ui/gfx/paint_vector_icon.h"
 #include "ui/gfx/vector_icon_types.h"
@@ -27,16 +29,14 @@
     : TrayItemView(shelf) {
   CreateImageView();
 
-  SessionControllerImpl* session_controller =
-      Shell::Get()->session_controller();
-  session_observation_.Observe(session_controller);
-
   SnoopingProtectionController* controller =
       Shell::Get()->snooping_protection_controller();
   controller_observation_.Observe(controller);
 
   SetVisible(controller->SnooperPresent());
-  UpdateIconColor(session_controller->GetSessionState());
+  image_view()->SetImage(ui::ImageModel::FromVectorIcon(
+      kSystemTraySnoopingProtectionIcon, kColorAshIconColorPrimary,
+      kUnifiedTrayIconSize));
   image_view()->SetTooltipText(l10n_util::GetStringUTF16(
       IDS_ASH_SMART_PRIVACY_SNOOPING_NOTIFICATION_SYSTEM_TRAY_TOOLTIP_TEXT));
 }
@@ -45,16 +45,6 @@
 
 void SnoopingProtectionView::HandleLocaleChange() {}
 
-void SnoopingProtectionView::OnSessionStateChanged(
-    session_manager::SessionState session_state) {
-  UpdateIconColor(session_state);
-}
-
-void SnoopingProtectionView::OnThemeChanged() {
-  TrayItemView::OnThemeChanged();
-  UpdateIconColor(Shell::Get()->session_controller()->GetSessionState());
-}
-
 const char* SnoopingProtectionView::GetClassName() const {
   return "SnoopingProtectionView";
 }
@@ -67,12 +57,4 @@
   controller_observation_.Reset();
 }
 
-void SnoopingProtectionView::UpdateIconColor(
-    session_manager::SessionState session_state) {
-  const SkColor new_color = TrayIconColor(session_state);
-  const gfx::ImageSkia new_icon = gfx::CreateVectorIcon(gfx::IconDescription(
-      kSystemTraySnoopingProtectionIcon, kUnifiedTrayIconSize, new_color));
-  image_view()->SetImage(new_icon);
-}
-
 }  // namespace ash
diff --git a/ash/system/human_presence/snooping_protection_view.h b/ash/system/human_presence/snooping_protection_view.h
index b541c5fb..08140ac 100644
--- a/ash/system/human_presence/snooping_protection_view.h
+++ b/ash/system/human_presence/snooping_protection_view.h
@@ -6,13 +6,9 @@
 #define ASH_SYSTEM_HUMAN_PRESENCE_SNOOPING_PROTECTION_VIEW_H_
 
 #include "ash/ash_export.h"
-#include "ash/public/cpp/session/session_controller.h"
-#include "ash/public/cpp/session/session_observer.h"
 #include "ash/system/human_presence/snooping_protection_controller.h"
 #include "ash/system/tray/tray_item_view.h"
 #include "base/scoped_observation.h"
-#include "components/session_manager/session_manager_types.h"
-#include "third_party/skia/include/core/SkColor.h"
 
 namespace ash {
 
@@ -20,7 +16,6 @@
 // detected looking over their shoulder.
 class ASH_EXPORT SnoopingProtectionView
     : public TrayItemView,
-      public SessionObserver,
       public SnoopingProtectionController::Observer {
  public:
   explicit SnoopingProtectionView(Shelf* shelf);
@@ -31,23 +26,12 @@
   // views::TrayItemView:
   const char* GetClassName() const override;
   void HandleLocaleChange() override;
-  void OnThemeChanged() override;
-
-  // SessionObserver:
-  void OnSessionStateChanged(session_manager::SessionState state) override;
 
   // SnoopingProtectionController::Observer:
   void OnSnoopingStatusChanged(bool snooper) override;
   void OnSnoopingProtectionControllerDestroyed() override;
 
  private:
-  // Updates the system tray icon to use the color corresponding to the current
-  // session state (e.g. darker during OOBE).
-  void UpdateIconColor(session_manager::SessionState session_state);
-
-  base::ScopedObservation<SessionController, SessionObserver>
-      session_observation_{this};
-
   base::ScopedObservation<SnoopingProtectionController,
                           SnoopingProtectionController::Observer>
       controller_observation_{this};
diff --git a/ash/system/media/media_tray.cc b/ash/system/media/media_tray.cc
index e55e74ab..f624e1a 100644
--- a/ash/system/media/media_tray.cc
+++ b/ash/system/media/media_tray.cc
@@ -13,6 +13,7 @@
 #include "ash/shelf/shelf.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/style/ash_color_id.h"
 #include "ash/style/ash_color_provider.h"
 #include "ash/style/icon_button.h"
 #include "ash/system/media/media_notification_provider.h"
@@ -30,6 +31,7 @@
 #include "components/prefs/pref_service.h"
 #include "media/base/media_switches.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/image_model.h"
 #include "ui/compositor/layer.h"
 #include "ui/display/manager/display_manager.h"
 #include "ui/display/manager/managed_display_info.h"
@@ -213,10 +215,12 @@
 
   Shell::Get()->session_controller()->AddObserver(this);
 
+  tray_container()->SetMargin(kMediaTrayPadding, 0);
   auto icon = std::make_unique<views::ImageView>();
   icon->SetTooltipText(l10n_util::GetStringUTF16(
       IDS_ASH_GLOBAL_MEDIA_CONTROLS_BUTTON_TOOLTIP_TEXT));
-  tray_container()->SetMargin(kMediaTrayPadding, 0);
+  icon->SetImage(ui::ImageModel::FromVectorIcon(kGlobalMediaControlsIcon,
+                                                kColorAshIconColorPrimary));
   icon_ = tray_container()->AddChildView(std::move(icon));
 }
 
@@ -422,11 +426,4 @@
       shelf()->GetStatusAreaWidget()->GetMediaTrayAnchorRect());
 }
 
-void MediaTray::OnThemeChanged() {
-  TrayBackgroundView::OnThemeChanged();
-  icon_->SetImage(gfx::CreateVectorIcon(
-      kGlobalMediaControlsIcon,
-      TrayIconColor(Shell::Get()->session_controller()->GetSessionState())));
-}
-
 }  // namespace ash
diff --git a/ash/system/media/media_tray.h b/ash/system/media/media_tray.h
index 5b10af9..2f3421610 100644
--- a/ash/system/media/media_tray.h
+++ b/ash/system/media/media_tray.h
@@ -72,7 +72,6 @@
   void HideBubbleWithView(const TrayBubbleView* bubble_view) override;
   void ClickedOutsideBubble() override;
   void AnchorUpdated() override;
-  void OnThemeChanged() override;
 
   // SessionObserver implementation.
   void OnLockStateChanged(bool locked) override;
diff --git a/ash/system/notification_center/notification_center_bubble.cc b/ash/system/notification_center/notification_center_bubble.cc
index 9fab9df..1346e56 100644
--- a/ash/system/notification_center/notification_center_bubble.cc
+++ b/ash/system/notification_center/notification_center_bubble.cc
@@ -36,7 +36,7 @@
   init_params.translucent = true;
 
   // Create and customize bubble view.
-  TrayBubbleView* bubble_view = new TrayBubbleView(init_params);
+  auto bubble_view = std::make_unique<TrayBubbleView>(init_params);
   bubble_view->SetMaxHeight(CalculateMaxTrayBubbleHeight());
 
   notification_center_view_ =
@@ -44,8 +44,9 @@
   notification_center_view_->Init();
 
   // Show the bubble.
-  bubble_wrapper_ = std::make_unique<TrayBubbleWrapper>(
-      notification_center_tray_, bubble_view);
+  bubble_wrapper_ =
+      std::make_unique<TrayBubbleWrapper>(notification_center_tray_);
+  bubble_wrapper_->ShowBubble(std::move(bubble_view));
 }
 
 NotificationCenterBubble::~NotificationCenterBubble() {
diff --git a/ash/system/phonehub/app_stream_launcher_item.cc b/ash/system/phonehub/app_stream_launcher_item.cc
new file mode 100644
index 0000000..66ef08c1
--- /dev/null
+++ b/ash/system/phonehub/app_stream_launcher_item.cc
@@ -0,0 +1,71 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/system/phonehub/app_stream_launcher_item.h"
+
+#include "ui/gfx/geometry/insets.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/box_layout.h"
+
+namespace ash {
+
+namespace {
+
+constexpr gfx::Size kEcheAppItemSize(64, 55);
+constexpr int kEcheAppItemSpacing = 6;
+constexpr int kEcheAppNameLabelLineHeight = 14;
+constexpr int kEcheAppNameLabelFontSize = 12;
+
+void ConfigureLabel(views::Label* label, int line_height, int font_size) {
+  label->SetAutoColorReadabilityEnabled(false);
+  label->SetSubpixelRenderingEnabled(false);
+  label->SetCanProcessEventsWithinSubtree(false);
+
+  label->SetLineHeight(line_height);
+  label->SetTruncateLength(36);
+
+  gfx::Font default_font;
+  gfx::Font label_font =
+      default_font.Derive(font_size - default_font.GetFontSize(),
+                          gfx::Font::NORMAL, gfx::Font::Weight::NORMAL);
+  gfx::FontList font_list(label_font);
+  label->SetFontList(font_list);
+}
+
+}  // namespace
+
+AppStreamLauncherItem::AppStreamLauncherItem(
+    views::ImageButton::PressedCallback callback,
+    const phonehub::Notification::AppMetadata& app_metadata) {
+  SetPreferredSize(kEcheAppItemSize);
+  auto* layout = SetLayoutManager(std::make_unique<views::BoxLayout>(
+      views::BoxLayout::Orientation::kVertical, gfx::Insets(),
+      kEcheAppItemSpacing));
+  layout->set_cross_axis_alignment(
+      views::BoxLayout::CrossAxisAlignment::kCenter);
+
+  recent_app_button_ = AddChildView(std::make_unique<PhoneHubRecentAppButton>(
+      app_metadata.icon, app_metadata.visible_app_name, callback));
+
+  label_ = AddChildView(
+      std::make_unique<views::Label>(app_metadata.visible_app_name));
+  ConfigureLabel(label_, kEcheAppNameLabelLineHeight,
+                 kEcheAppNameLabelFontSize);
+}
+
+AppStreamLauncherItem::~AppStreamLauncherItem() = default;
+
+bool AppStreamLauncherItem::HasFocus() const {
+  return recent_app_button_->HasFocus() || label_->HasFocus();
+}
+
+void AppStreamLauncherItem::RequestFocus() {
+  recent_app_button_->RequestFocus();
+}
+
+const char* AppStreamLauncherItem::GetClassName() const {
+  return "AppStreamLauncherItem";
+}
+
+}  // namespace ash
diff --git a/ash/system/phonehub/app_stream_launcher_item.h b/ash/system/phonehub/app_stream_launcher_item.h
new file mode 100644
index 0000000..e279f91
--- /dev/null
+++ b/ash/system/phonehub/app_stream_launcher_item.h
@@ -0,0 +1,44 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_SYSTEM_PHONEHUB_APP_STREAM_LAUNCHER_ITEM_H_
+#define ASH_SYSTEM_PHONEHUB_APP_STREAM_LAUNCHER_ITEM_H_
+
+#include "ash/ash_export.h"
+#include "ash/components/phonehub/notification.h"
+#include "ash/system/phonehub/phone_hub_recent_app_button.h"
+#include "ui/views/controls/button/image_button.h"
+
+namespace views {
+class Label;
+}
+
+namespace ash {
+
+// A view contains a PhoneHubRecentAppButton and a label with app name.
+class ASH_EXPORT AppStreamLauncherItem : public views::View {
+ public:
+  AppStreamLauncherItem(
+      views::ImageButton::PressedCallback callback,
+      const phonehub::Notification::AppMetadata& app_metadata);
+
+  ~AppStreamLauncherItem() override;
+  AppStreamLauncherItem(AppStreamLauncherItem&) = delete;
+  AppStreamLauncherItem operator=(AppStreamLauncherItem&) = delete;
+
+  // views::View:
+  bool HasFocus() const override;
+  void RequestFocus() override;
+  const char* GetClassName() const override;
+
+ private:
+  // Owned by views hierarchy.
+  // TODO(b/259426750) refactor PhoneHubRecentAppButton to a more generic name.
+  PhoneHubRecentAppButton* recent_app_button_ = nullptr;
+  views::Label* label_ = nullptr;
+};
+
+}  // namespace ash
+
+#endif  // ASH_SYSTEM_PHONEHUB_APP_STREAM_LAUNCHER_ITEM_H_
diff --git a/ash/system/phonehub/app_stream_launcher_view.cc b/ash/system/phonehub/app_stream_launcher_view.cc
new file mode 100644
index 0000000..4481993f
--- /dev/null
+++ b/ash/system/phonehub/app_stream_launcher_view.cc
@@ -0,0 +1,263 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/system/phonehub/app_stream_launcher_view.h"
+#include "ash/controls/rounded_scroll_bar.h"
+#include "ash/resources/vector_icons/vector_icons.h"
+
+#include <cmath>
+#include <memory>
+
+#include "ash/components/phonehub/multidevice_feature_access_manager.h"
+#include "ash/components/phonehub/phone_hub_manager.h"
+#include "ash/components/phonehub/user_action_recorder.h"
+#include "ash/constants/ash_features.h"
+#include "ash/style/ash_color_provider.h"
+#include "ash/system/phonehub/camera_roll_view.h"
+#include "ash/system/phonehub/multidevice_feature_opt_in_view.h"
+#include "ash/system/phonehub/phone_hub_recent_apps_view.h"
+#include "ash/system/phonehub/phone_hub_view_ids.h"
+#include "ash/system/phonehub/phone_status_view.h"
+#include "ash/system/phonehub/quick_actions_view.h"
+#include "ash/system/phonehub/task_continuation_view.h"
+#include "ash/system/phonehub/ui_constants.h"
+#include "ash/system/tray/tray_constants.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/compositor/layer.h"
+#include "ui/gfx/geometry/insets.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/paint_vector_icon.h"
+#include "ui/gfx/text_constants.h"
+#include "ui/strings/grit/ui_strings.h"
+#include "ui/views/controls/button/button.h"
+#include "ui/views/controls/button/image_button.h"
+#include "ui/views/controls/button/image_button_factory.h"
+#include "ui/views/controls/button/label_button.h"
+#include "ui/views/controls/highlight_path_generator.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/controls/scroll_view.h"
+#include "ui/views/controls/separator.h"
+#include "ui/views/controls/textfield/textfield.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/layout/flex_layout.h"
+#include "ui/views/layout/layout_types.h"
+#include "ui/views/layout/table_layout.h"
+#include "ui/views/view.h"
+
+namespace ash {
+
+namespace {
+
+// Insets for the vertical scroll bar.
+constexpr auto kVerticalScrollInsets = gfx::Insets::TLBR(1, 0, 1, 1);
+
+constexpr auto kBubblePadding = gfx::Insets::VH(8, 8);
+constexpr int kHeaderHeight = 40;
+constexpr auto kHeaderDefaultSpacing = gfx::Insets::VH(0, 6);
+
+// The horizontal interior margin for the apps page container - i.e. the margin
+// between the apps page bounds and the page content.
+constexpr int kHorizontalInteriorMargin = 16;
+
+// Number of columns of apps in the grid
+constexpr int kColumns = 5;
+
+constexpr int kRowHeight = 60;
+
+// The padding between different sections within the apps page. Also used for
+// interior apps page container margin.
+constexpr int kVerticalPaddingBetweenSections = 16;
+
+}  // namespace
+
+AppStreamLauncherView::AppStreamLauncherView(
+    phonehub::PhoneHubManager* phone_hub_manager)
+    : phone_hub_manager_(phone_hub_manager) {
+  SetID(PhoneHubViewID::kAppStreamLauncherView);
+
+  auto* layout_manager =
+      SetLayoutManager(std::make_unique<views::FlexLayout>());
+  layout_manager->SetInteriorMargin(gfx::Insets::VH(0, 0))
+      .SetOrientation(views::LayoutOrientation::kVertical)
+      .SetCollapseMargins(false)
+      .SetDefault(views::kMarginsKey, kHeaderDefaultSpacing)
+      .SetInteriorMargin(kBubblePadding)
+      .SetCrossAxisAlignment(views::LayoutAlignment::kStretch);
+
+  AddChildView(CreateHeaderView());
+
+  auto* app_list_view = AddChildView(CreateAppListView());
+  app_list_view->SetPreferredSize(gfx::Size(400, 400));
+  app_list_view->SetProperty(
+      views::kFlexBehaviorKey,
+      views::FlexSpecification(views::MinimumFlexSizeRule::kPreferred,
+                               views::MaximumFlexSizeRule::kPreferred,
+                               /*adjust_height_for_width =*/false)
+          .WithWeight(1));
+
+  phone_hub_manager->GetUserActionRecorder()->RecordUiOpened();
+}
+
+AppStreamLauncherView::~AppStreamLauncherView() = default;
+
+// The behavior is inspired from ash/app_list/views/app_list_bubble_apps_page.cc
+std::unique_ptr<views::View> AppStreamLauncherView::CreateAppListView() {
+  // The entire page scrolls.
+  auto scroll_view = std::make_unique<views::ScrollView>(
+      views::ScrollView::ScrollWithLayers::kEnabled);
+  scroll_view->ClipHeightTo(0, std::numeric_limits<int>::max());
+  scroll_view->SetDrawOverflowIndicator(false);
+  // Don't paint a background. The bubble already has one.
+  scroll_view->SetBackgroundColor(absl::nullopt);
+  // Arrow keys are used to select app icons.
+  scroll_view->SetAllowKeyboardScrolling(false);
+
+  // Scroll view will have a gradient mask layer.
+  scroll_view->SetPaintToLayer(ui::LAYER_NOT_DRAWN);
+
+  // Set up scroll bars.
+  scroll_view->SetHorizontalScrollBarMode(
+      views::ScrollView::ScrollBarMode::kDisabled);
+  auto vertical_scroll =
+      std::make_unique<RoundedScrollBar>(/*horizontal=*/false);
+  vertical_scroll->SetInsets(kVerticalScrollInsets);
+  vertical_scroll->SetSnapBackOnDragOutside(false);
+  scroll_view->SetVerticalScrollBar(std::move(vertical_scroll));
+
+  auto scroll_contents = std::make_unique<views::View>();
+  scroll_contents->SetProperty(
+      views::kFlexBehaviorKey,
+      views::FlexSpecification(views::MinimumFlexSizeRule::kPreferred,
+                               views::MaximumFlexSizeRule::kUnbounded,
+                               /*adjust_height_for_width =*/false)
+          .WithWeight(1));
+
+  auto* layout =
+      scroll_contents->SetLayoutManager(std::make_unique<views::FlexLayout>());
+  layout->SetOrientation(views::LayoutOrientation::kVertical)
+      .SetCrossAxisAlignment(views::LayoutAlignment::kStretch)
+      .SetInteriorMargin(gfx::Insets::VH(kVerticalPaddingBetweenSections,
+                                         kHorizontalInteriorMargin));
+  //.SetDefault(views::kMarginsKey, kVerticalPaddingBetweenSections);
+
+  // All apps section.
+  items_container_ =
+      scroll_contents->AddChildView(std::make_unique<views::View>());
+  items_container_->SetPaintToLayer();
+  items_container_->layer()->SetFillsBoundsOpaquely(false);
+  auto* table_layout = items_container_->SetLayoutManager(
+      std::make_unique<views::TableLayout>());
+  for (int i = 0; i < kColumns; i++) {
+    table_layout->AddColumn(
+        views::LayoutAlignment::kStretch, views::LayoutAlignment::kStretch, 1.0,
+        views::TableLayout::ColumnSize::kUsePreferred, 0, 0);
+  }
+  int n_apps = 107;
+  table_layout->AddRows(ceil((double)n_apps / kColumns),
+                        views::TableLayout::kFixedSize, kRowHeight);
+
+  for (int i = 0; i < n_apps; i++) {
+    std::unique_ptr<View> view = CreateViewForItemAtIndex(i);
+    view->SetPreferredSize(gfx::Size(50, 50));
+    items_container_->AddChildView(std::move(view));
+  }
+  scroll_view->SetContents(std::move(scroll_contents));
+
+  return scroll_view;
+}
+
+std::unique_ptr<views::View> AppStreamLauncherView::CreateViewForItemAtIndex(
+    size_t index) {
+  // TODO(nayebi): Replace this plceholder with the real implementation of the
+  //  icon+text.
+  auto view = std::make_unique<views::LabelButton>(
+      base::BindRepeating(&AppStreamLauncherView::AppIconActivated,
+                          base::Unretained(this)),
+      u"ICON");
+  view->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_CENTER);
+  return view;
+}
+
+void AppStreamLauncherView::AppIconActivated() {}
+
+std::unique_ptr<views::View> AppStreamLauncherView::CreateHeaderView() {
+  auto header = std::make_unique<views::View>();
+  header->SetLayoutManager(std::make_unique<views::FlexLayout>())
+      ->SetInteriorMargin(gfx::Insets::VH(0, 0))
+      .SetCollapseMargins(false)
+      .SetMinimumCrossAxisSize(kHeaderHeight)
+      .SetDefault(views::kMarginsKey, kHeaderDefaultSpacing)
+      .SetCrossAxisAlignment(views::LayoutAlignment::kCenter);
+
+  // Add arrowback button
+  arrow_back_button_ = header->AddChildView(CreateButton(
+      base::BindRepeating(&AppStreamLauncherView::OnArrowBackActivated,
+                          weak_factory_.GetWeakPtr()),
+      kEcheArrowBackIcon, IDS_APP_ACCNAME_BACK));
+
+  views::Label* title = header->AddChildView(std::make_unique<views::Label>(
+      std::u16string(), views::style::CONTEXT_DIALOG_TITLE,
+      views::style::STYLE_PRIMARY,
+      gfx::DirectionalityMode::DIRECTIONALITY_AS_URL));
+  title->SetMultiLine(true);
+  title->SetAllowCharacterBreak(true);
+  title->SetProperty(
+      views::kFlexBehaviorKey,
+      views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
+                               views::MaximumFlexSizeRule::kUnbounded,
+                               /*adjust_height_for_width =*/true)
+          .WithWeight(1));
+  title->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+  // TODO(b/259299246) Finalize the text and read from the resource
+  title->SetText(u"Apps on your phone");
+
+  return header;
+}
+
+// Creates a button with the given callback, icon, and tooltip text.
+// `message_id` is the resource id of the tooltip text of the icon.
+std::unique_ptr<views::Button> AppStreamLauncherView::CreateButton(
+    views::Button::PressedCallback callback,
+    const gfx::VectorIcon& icon,
+    int message_id) {
+  SkColor color = AshColorProvider::Get()->GetContentLayerColor(
+      AshColorProvider::ContentLayerType::kIconColorPrimary);
+  SkColor disabled_color = SkColorSetA(color, gfx::kDisabledControlAlpha);
+  auto button = views::CreateVectorImageButton(std::move(callback));
+  views::SetImageFromVectorIconWithColor(button.get(), icon, color,
+                                         disabled_color);
+  button->SetTooltipText(l10n_util::GetStringUTF16(message_id));
+  button->SizeToPreferredSize();
+
+  views::InstallCircleHighlightPathGenerator(button.get());
+
+  return button;
+}
+
+void AppStreamLauncherView::OnArrowBackActivated() {
+  phone_hub_manager_->GetAppStreamLauncherDataModel()
+      ->SetShouldShowMiniLauncher(false);
+}
+
+void AppStreamLauncherView::ChildPreferredSizeChanged(View* child) {
+  // Resize the bubble when the child change its size.
+  PreferredSizeChanged();
+}
+
+void AppStreamLauncherView::ChildVisibilityChanged(View* child) {
+  // Resize the bubble when the child change its visibility.
+  PreferredSizeChanged();
+}
+
+const char* AppStreamLauncherView::GetClassName() const {
+  return "AppStreamLauncherView";
+}
+
+phone_hub_metrics::Screen AppStreamLauncherView::GetScreenForMetrics() const {
+  return phone_hub_metrics::Screen::kMiniLauncher;
+}
+
+}  // namespace ash
diff --git a/ash/system/phonehub/app_stream_launcher_view.h b/ash/system/phonehub/app_stream_launcher_view.h
new file mode 100644
index 0000000..c472285a
--- /dev/null
+++ b/ash/system/phonehub/app_stream_launcher_view.h
@@ -0,0 +1,66 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_SYSTEM_PHONEHUB_APP_STREAM_LAUNCHER_VIEW_H_
+#define ASH_SYSTEM_PHONEHUB_APP_STREAM_LAUNCHER_VIEW_H_
+
+#include <memory>
+#include "ash/ash_export.h"
+#include "ash/system/phonehub/phone_hub_content_view.h"
+#include "ui/gfx/vector_icon_types.h"
+#include "ui/views/controls/button/button.h"
+#include "ui/views/view.h"
+
+namespace views {
+class Button;
+}
+namespace ash {
+
+namespace phonehub {
+class PhoneHubManager;
+}
+
+// A view of the Phone Hub panel, displaying the apps that user can launch for
+// app streaming.
+class ASH_EXPORT AppStreamLauncherView : public PhoneHubContentView {
+ public:
+  explicit AppStreamLauncherView(phonehub::PhoneHubManager* phone_hub_manager);
+  ~AppStreamLauncherView() override;
+
+  // views::View:
+  void ChildPreferredSizeChanged(View* child) override;
+  void ChildVisibilityChanged(View* child) override;
+  const char* GetClassName() const override;
+
+  // PhoneHubContentView:
+  phone_hub_metrics::Screen GetScreenForMetrics() const override;
+
+ private:
+  friend class AppStreamLauncherViewTest;
+  FRIEND_TEST_ALL_PREFIXES(AppStreamLauncherViewTest, OpenView);
+
+  std::unique_ptr<views::View> CreateAppListView();
+  std::unique_ptr<views::View> CreateHeaderView();
+  std::unique_ptr<views::Button> CreateButton(
+      views::Button::PressedCallback callback,
+      const gfx::VectorIcon& icon,
+      int message_id);
+  std::unique_ptr<views::View> CreateViewForItemAtIndex(size_t index);
+  void AppIconActivated();
+
+  // Handles the click on the "back" arrow in the header.
+  void OnArrowBackActivated();
+
+  views::Button* arrow_back_button_ = nullptr;
+  phonehub::PhoneHubManager* phone_hub_manager_;
+
+  // Contains all the apps
+  views::View* items_container_;
+
+  base::WeakPtrFactory<AppStreamLauncherView> weak_factory_{this};
+};
+
+}  // namespace ash
+
+#endif  // ASH_SYSTEM_PHONEHUB_APP_STREAM_LAUNCHER_VIEW_H_
diff --git a/ash/system/phonehub/app_stream_launcher_view_unittest.cc b/ash/system/phonehub/app_stream_launcher_view_unittest.cc
new file mode 100644
index 0000000..7fc36e0
--- /dev/null
+++ b/ash/system/phonehub/app_stream_launcher_view_unittest.cc
@@ -0,0 +1,58 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/system/phonehub/app_stream_launcher_view.h"
+#include "ash/components/phonehub/fake_phone_hub_manager.h"
+#include "ash/components/phonehub/phone_hub_manager.h"
+#include "ash/constants/ash_features.h"
+#include "ash/system/phonehub/phone_hub_metrics.h"
+#include "ash/test/ash_test_base.h"
+#include "base/test/scoped_feature_list.h"
+#include "ui/views/test/views_test_utils.h"
+#include "ui/views/view.h"
+
+namespace ash {
+
+class AppStreamLauncherViewTest : public AshTestBase {
+ public:
+  AppStreamLauncherViewTest() = default;
+  ~AppStreamLauncherViewTest() override = default;
+
+  // AshTestBase:
+  void SetUp() override {
+    AshTestBase::SetUp();
+
+    feature_list_.InitWithFeatures(
+        /*enabled_features=*/{features::kEcheLauncher, features::kEcheSWA},
+        /*disabled_features=*/{});
+
+    app_stream_launcher_view_ =
+        std::make_unique<AppStreamLauncherView>(&fake_phone_hub_manager_);
+  }
+
+  // AshTestBase:
+  void TearDown() override {
+    app_stream_launcher_view_.reset();
+    AshTestBase::TearDown();
+  }
+
+ protected:
+  AppStreamLauncherView* app_stream_launcher_view() {
+    return app_stream_launcher_view_.get();
+  }
+  phonehub::FakePhoneHubManager* fake_phone_hub_manager() {
+    return &fake_phone_hub_manager_;
+  }
+
+ private:
+  std::unique_ptr<AppStreamLauncherView> app_stream_launcher_view_;
+  phonehub::FakePhoneHubManager fake_phone_hub_manager_;
+  base::test::ScopedFeatureList feature_list_;
+};
+
+TEST_F(AppStreamLauncherViewTest, OpenView) {
+  EXPECT_TRUE(app_stream_launcher_view()->GetVisible());
+}
+
+}  // namespace ash
diff --git a/ash/system/phonehub/phone_hub_metrics.h b/ash/system/phonehub/phone_hub_metrics.h
index 992fd631..3568b56 100644
--- a/ash/system/phonehub/phone_hub_metrics.h
+++ b/ash/system/phonehub/phone_hub_metrics.h
@@ -31,7 +31,8 @@
   kInvalid = 8,
   kPhoneConnecting = 9,
   kTetherConnectionPending = 10,
-  kMaxValue = kTetherConnectionPending
+  kMiniLauncher = 11,
+  kMaxValue = kMiniLauncher
 };
 
 // Keep in sync with corresponding enum in tools/metrics/histograms/enums.xml.
diff --git a/ash/system/phonehub/phone_hub_recent_apps_view.cc b/ash/system/phonehub/phone_hub_recent_apps_view.cc
index c043d580c..95779c63 100644
--- a/ash/system/phonehub/phone_hub_recent_apps_view.cc
+++ b/ash/system/phonehub/phone_hub_recent_apps_view.cc
@@ -12,8 +12,8 @@
 #include "ash/constants/ash_features.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/style/ash_color_id.h"
 #include "ash/style/ash_color_provider.h"
-#include "ash/style/pill_button.h"
 #include "ash/system/phonehub/phone_hub_recent_app_button.h"
 #include "ash/system/phonehub/phone_hub_view_ids.h"
 #include "ash/system/phonehub/ui_constants.h"
@@ -22,6 +22,10 @@
 #include "base/ranges/algorithm.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/gfx/geometry/insets.h"
+#include "ui/gfx/image/image_skia_operations.h"
+#include "ui/gfx/paint_vector_icon.h"
+#include "ui/views/background.h"
+#include "ui/views/controls/button/image_button.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/layout/box_layout.h"
 
@@ -51,6 +55,10 @@
 // Max number of apps can be shown with more apps button
 constexpr int kMaxAppsWithMoreAppsButton = 5;
 
+// Sizing of more apps button.
+constexpr gfx::Rect kMoreAppsButtonArea = gfx::Rect(57, 32);
+constexpr int kMoreAppsButtonRadius = 16;
+
 class HeaderView : public views::Label {
  public:
   HeaderView() {
@@ -215,18 +223,9 @@
       for (const auto& recent_app : recent_apps_list) {
         if (features::IsEcheLauncherEnabled() &&
             recent_app_button_list_.size() == kMaxAppsWithMoreAppsButton) {
-          auto moreAppsButton = std::make_unique<PillButton>(
-              base::BindRepeating(&PhoneHubRecentAppsView::SwitchToFullAppsList,
-                                  base::Unretained(this)),
-              std::u16string(), PillButton::Type::kDefaultWithIconLeading,
-              &kPhoneHubFullAppsListIcon);
-          moreAppsButton->SetTooltipText(l10n_util::GetStringUTF16(
-              IDS_ASH_PHONE_HUB_FULL_APPS_LIST_BUTTON_TITLE));
-          moreAppsButton->SetAccessibleName(l10n_util::GetStringUTF16(
-              IDS_ASH_PHONE_HUB_FULL_APPS_LIST_BUTTON_TITLE));
           recent_app_button_list_.push_back(
               recent_app_buttons_view_->AddRecentAppButton(
-                  std::move(moreAppsButton)));
+                  GenerateMoreAppsButton()));
           break;
         }
 
@@ -251,4 +250,27 @@
 // TODO(b/259160267): Add function when full apps list view is ready.
 void PhoneHubRecentAppsView::SwitchToFullAppsList() {}
 
+std::unique_ptr<views::ImageButton>
+PhoneHubRecentAppsView::GenerateMoreAppsButton() {
+  auto more_apps_button = std::make_unique<views::ImageButton>(
+      base::BindRepeating(&PhoneHubRecentAppsView::SwitchToFullAppsList,
+                          base::Unretained(this)));
+
+  gfx::ImageSkia image = gfx::CreateVectorIcon(
+      kPhoneHubFullAppsListIcon,
+      AshColorProvider::Get()->GetContentLayerColor(
+          AshColorProvider::ContentLayerType::kButtonIconColor));
+  more_apps_button->SetImage(
+      views::Button::STATE_NORMAL,
+      gfx::ImageSkiaOperations::ExtractSubset(image, kMoreAppsButtonArea));
+  more_apps_button->SetBackground(views::CreateRoundedRectBackground(
+      AshColorProvider::Get()->GetControlsLayerColor(
+          AshColorProvider::ControlsLayerType::kControlBackgroundColorInactive),
+      kMoreAppsButtonRadius));
+  more_apps_button->SetTooltipText(
+      l10n_util::GetStringUTF16(IDS_ASH_PHONE_HUB_FULL_APPS_LIST_BUTTON_TITLE));
+
+  return more_apps_button;
+}
+
 }  // namespace ash
diff --git a/ash/system/phonehub/phone_hub_recent_apps_view.h b/ash/system/phonehub/phone_hub_recent_apps_view.h
index 795ac4d..9aed488 100644
--- a/ash/system/phonehub/phone_hub_recent_apps_view.h
+++ b/ash/system/phonehub/phone_hub_recent_apps_view.h
@@ -5,9 +5,11 @@
 #ifndef ASH_SYSTEM_PHONEHUB_PHONE_HUB_RECENT_APPS_VIEW_H_
 #define ASH_SYSTEM_PHONEHUB_PHONE_HUB_RECENT_APPS_VIEW_H_
 
+#include <memory>
 #include "ash/ash_export.h"
 #include "ash/components/phonehub/recent_apps_interaction_handler.h"
 #include "base/gtest_prod_util.h"
+#include "ui/views/controls/button/image_button.h"
 #include "ui/views/view.h"
 #include "ui/views/view_model.h"
 
@@ -65,6 +67,9 @@
   // Switch to full apps list view.
   void SwitchToFullAppsList();
 
+  // Generate more apps button.
+  std::unique_ptr<views::ImageButton> GenerateMoreAppsButton();
+
   RecentAppButtonsView* recent_app_buttons_view_ = nullptr;
   std::vector<views::View*> recent_app_button_list_;
   phonehub::RecentAppsInteractionHandler* recent_apps_interaction_handler_ =
diff --git a/ash/system/phonehub/phone_hub_recent_apps_view_unittest.cc b/ash/system/phonehub/phone_hub_recent_apps_view_unittest.cc
index d1d7fe13..09e5768 100644
--- a/ash/system/phonehub/phone_hub_recent_apps_view_unittest.cc
+++ b/ash/system/phonehub/phone_hub_recent_apps_view_unittest.cc
@@ -7,13 +7,13 @@
 #include "ash/components/phonehub/fake_recent_apps_interaction_handler.h"
 #include "ash/components/phonehub/notification.h"
 #include "ash/constants/ash_features.h"
-#include "ash/style/pill_button.h"
 #include "ash/system/phonehub/phone_hub_recent_app_button.h"
 #include "ash/test/ash_test_base.h"
 #include "base/test/scoped_feature_list.h"
 #include "chromeos/ash/services/multidevice_setup/public/mojom/multidevice_setup.mojom.h"
 #include "ui/events/test/test_event.h"
 #include "ui/gfx/image/image.h"
+#include "ui/views/controls/button/image_button.h"
 #include "ui/views/test/button_test_api.h"
 
 namespace ash {
@@ -154,7 +154,8 @@
        i++) {
     auto* child = recent_apps_view()->recent_app_buttons_view_->children()[i];
     if (i == 5) {
-      PillButton* more_apps_button = static_cast<PillButton*>(child);
+      views::ImageButton* more_apps_button =
+          static_cast<views::ImageButton*>(child);
       views::test::ButtonTestApi(more_apps_button)
           .NotifyClick(ui::test::TestEvent());
       break;
diff --git a/ash/system/phonehub/phone_hub_tray.cc b/ash/system/phonehub/phone_hub_tray.cc
index 7137e72a..6c2d4b97 100644
--- a/ash/system/phonehub/phone_hub_tray.cc
+++ b/ash/system/phonehub/phone_hub_tray.cc
@@ -18,6 +18,7 @@
 #include "ash/shelf/shelf.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/style/ash_color_id.h"
 #include "ash/style/ash_color_provider.h"
 #include "ash/system/eche/eche_icon_loading_indicator_view.h"
 #include "ash/system/eche/eche_tray.h"
@@ -38,6 +39,7 @@
 #include "base/callback_helpers.h"
 #include "base/task/sequenced_task_runner.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/image_model.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/events/event.h"
 #include "ui/gfx/geometry/insets.h"
@@ -94,6 +96,9 @@
       views::ImageButton::VerticalAlignment::ALIGN_MIDDLE);
   icon->SetImageHorizontalAlignment(
       views::ImageButton::HorizontalAlignment::ALIGN_CENTER);
+  icon->SetImageModel(views::ImageButton::STATE_NORMAL,
+                      ui::ImageModel::FromVectorIcon(
+                          kPhoneHubPhoneIcon, kColorAshIconColorPrimary));
   icon_ = tray_container()->AddChildView(std::move(icon));
 }
 
@@ -149,6 +154,7 @@
 
 void PhoneHubTray::OnPhoneHubUiStateChanged() {
   UpdateVisibility();
+  UpdateHeaderVisibility();
 
   if (!bubble_)
     return;
@@ -180,9 +186,6 @@
 }
 
 void PhoneHubTray::OnSessionStateChanged(session_manager::SessionState state) {
-  icon_->SetImage(views::ImageButton::STATE_NORMAL,
-                  CreateVectorIcon(kPhoneHubPhoneIcon, TrayIconColor(state)));
-
   TemporarilyDisableAnimation();
 }
 
@@ -234,6 +237,7 @@
   phone_status_view_ = phone_status.get();
   DCHECK(phone_status_view_);
   bubble_view->AddChildView(std::move(phone_status));
+  UpdateHeaderVisibility();
 
   // Other contents, i.e. the connected view and the interstitial views,
   // will be positioned underneath the phone status view and updated based
@@ -275,16 +279,6 @@
   return "PhoneHubTray";
 }
 
-void PhoneHubTray::OnThemeChanged() {
-  TrayBackgroundView::OnThemeChanged();
-  icon_->SetImage(
-      views::ImageButton::STATE_NORMAL,
-      CreateVectorIcon(
-          kPhoneHubPhoneIcon,
-          TrayIconColor(
-              Shell::Get()->session_controller()->GetSessionState())));
-}
-
 bool PhoneHubTray::CanOpenConnectedDeviceSettings() {
   return TrayPopupUtils::CanOpenWebUISettings();
 }
@@ -377,6 +371,18 @@
   SetVisiblePreferred(ui_state != PhoneHubUiController::UiState::kHidden);
 }
 
+void PhoneHubTray::UpdateHeaderVisibility() {
+  if (!features::IsEcheSWAEnabled())
+    return;
+  if (!phone_status_view_)
+    return;
+
+  DCHECK(ui_controller_.get());
+  auto ui_state = ui_controller_->ui_state();
+  phone_status_view_->SetVisible(ui_state !=
+                                 PhoneHubUiController::UiState::kMiniLauncher);
+}
+
 void PhoneHubTray::TemporarilyDisableAnimation() {
   base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
       FROM_HERE, DisableShowAnimation().Release(), base::Seconds(5));
diff --git a/ash/system/phonehub/phone_hub_tray.h b/ash/system/phonehub/phone_hub_tray.h
index cdcd1a5..9378aa1a 100644
--- a/ash/system/phonehub/phone_hub_tray.h
+++ b/ash/system/phonehub/phone_hub_tray.h
@@ -73,7 +73,6 @@
   TrayBubbleView* GetBubbleView() override;
   views::Widget* GetBubbleWidget() const override;
   const char* GetClassName() const override;
-  void OnThemeChanged() override;
 
   // PhoneStatusView::Delegate:
   bool CanOpenConnectedDeviceSettings() override;
@@ -130,6 +129,7 @@
   // Updates the visibility of the tray in the shelf based on the feature is
   // enabled.
   void UpdateVisibility();
+  void UpdateHeaderVisibility();
 
   // Disables the animation and enables it back after a 5s delay. This tray's
   // visibility can be updated when the connection is complete. After a session
diff --git a/ash/system/phonehub/phone_hub_ui_controller.cc b/ash/system/phonehub/phone_hub_ui_controller.cc
index aa0f0a7..2bbcb41 100644
--- a/ash/system/phonehub/phone_hub_ui_controller.cc
+++ b/ash/system/phonehub/phone_hub_ui_controller.cc
@@ -16,6 +16,7 @@
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/system/eche/eche_tray.h"
+#include "ash/system/phonehub/app_stream_launcher_view.h"
 #include "ash/system/phonehub/bluetooth_disabled_view.h"
 #include "ash/system/phonehub/onboarding_view.h"
 #include "ash/system/phonehub/phone_connected_view.h"
@@ -62,6 +63,9 @@
     case PhoneHubUiController::UiState::kTetherConnectionPending:
       return phone_hub_metrics::Screen::kTetherConnectionPending;
 
+    case PhoneHubUiController::UiState::kMiniLauncher:
+      return phone_hub_metrics::Screen::kMiniLauncher;
+
     case PhoneHubUiController::UiState::kHidden:
       return phone_hub_metrics::Screen::kInvalid;
   }
@@ -90,6 +94,9 @@
     case PhoneHubUiController::UiState::kTetherConnectionPending:
       return "[kTetherConnectionPending]";
 
+    case PhoneHubUiController::UiState::kMiniLauncher:
+      return "[kMiniLauncher]";
+
     case PhoneHubUiController::UiState::kHidden:
       return "[kHidden]";
   }
@@ -144,6 +151,8 @@
   if (phone_hub_manager_) {
     phone_hub_manager_->GetFeatureStatusProvider()->AddObserver(this);
     phone_hub_manager_->GetOnboardingUiTracker()->AddObserver(this);
+    if (features::IsEcheSWAEnabled())
+      phone_hub_manager_->GetAppStreamLauncherDataModel()->AddObserver(this);
     phone_hub_manager_->GetPhoneModel()->AddObserver(this);
   }
 
@@ -187,6 +196,8 @@
           phone_hub_manager_->GetConnectionScheduler());
     case UiState::kPhoneConnected:
       return std::make_unique<PhoneConnectedView>(phone_hub_manager_);
+    case UiState::kMiniLauncher:
+      return std::make_unique<AppStreamLauncherView>(phone_hub_manager_);
   }
 }
 
@@ -242,6 +253,7 @@
     case UiState::kTetherConnectionPending:
       return;
 
+    case UiState::kMiniLauncher:
     case UiState::kPhoneConnected:
       base::UmaHistogramEnumeration("PhoneHub.BubbleOpened.Connectable.Page",
                                     phone_hub_metrics::Screen::kPhoneConnected);
@@ -290,6 +302,12 @@
   UpdateUiState(GetUiStateFromPhoneHubManager());
 }
 
+void PhoneHubUiController::OnShouldShowMiniLauncherChanged() {
+  if (!features::IsEcheSWAEnabled())
+    return;
+  UpdateUiState(GetUiStateFromPhoneHubManager());
+}
+
 void PhoneHubUiController::OnModelChanged() {
   UpdateUiState(GetUiStateFromPhoneHubManager());
 }
@@ -314,6 +332,21 @@
 
 PhoneHubUiController::UiState
 PhoneHubUiController::GetUiStateFromPhoneHubManager() {
+  PhoneHubUiController::UiState ui_state =
+      GetUiStateFromPhoneHubManagerInternal();
+  if (features::IsEcheSWAEnabled() &&
+      (ui_state != PhoneHubUiController::UiState::kMiniLauncher) &&
+      phone_hub_manager_ &&
+      phone_hub_manager_->GetAppStreamLauncherDataModel()) {
+    // Make sure the next time we go back to the "Phone Connected" state
+    // we do not show the Mini Launcher.
+    phone_hub_manager_->GetAppStreamLauncherDataModel()->ResetState();
+  }
+  return ui_state;
+}
+
+PhoneHubUiController::UiState
+PhoneHubUiController::GetUiStateFromPhoneHubManagerInternal() {
   if (!Shell::Get()->session_controller()->IsUserPrimary() ||
       !phone_hub_manager_)
     return UiState::kHidden;
@@ -369,8 +402,15 @@
         connecting_view_grace_period_timer_.Reset();
 
       // Delay displaying the connected view until the phone model is ready.
-      if (phone_model->phone_status_model().has_value())
-        return UiState::kPhoneConnected;
+      if (phone_model->phone_status_model().has_value()) {
+        // Decide to show the Mini Launcher or the main connected phone view.
+        return phone_hub_manager_->GetAppStreamLauncherDataModel()
+                           ->GetShouldShowMiniLauncher() &&
+                       features::IsEcheSWAEnabled() &&
+                       features::IsEcheLauncherEnabled()
+                   ? UiState::kMiniLauncher
+                   : UiState::kPhoneConnected;
+      }
 
       // If the the |ui_state_| was UiState::kTetherConnectionPending, continue
       // returning the UiState::kTetherConnectionPending state.
@@ -405,6 +445,8 @@
 
   phone_hub_manager_->GetFeatureStatusProvider()->RemoveObserver(this);
   phone_hub_manager_->GetOnboardingUiTracker()->RemoveObserver(this);
+  if (features::IsEcheSWAEnabled())
+    phone_hub_manager_->GetAppStreamLauncherDataModel()->RemoveObserver(this);
   phone_hub_manager_->GetPhoneModel()->RemoveObserver(this);
 }
 
diff --git a/ash/system/phonehub/phone_hub_ui_controller.h b/ash/system/phonehub/phone_hub_ui_controller.h
index 91ed2033d..7e7c346 100644
--- a/ash/system/phonehub/phone_hub_ui_controller.h
+++ b/ash/system/phonehub/phone_hub_ui_controller.h
@@ -6,6 +6,7 @@
 #define ASH_SYSTEM_PHONEHUB_PHONE_HUB_UI_CONTROLLER_H_
 
 #include "ash/ash_export.h"
+#include "ash/components/phonehub/app_stream_launcher_data_model.h"
 #include "ash/components/phonehub/feature_status_provider.h"
 #include "ash/components/phonehub/onboarding_ui_tracker.h"
 #include "ash/components/phonehub/phone_model.h"
@@ -33,6 +34,7 @@
     : public phonehub::FeatureStatusProvider::Observer,
       public phonehub::OnboardingUiTracker::Observer,
       public phonehub::PhoneModel::Observer,
+      public phonehub::AppStreamLauncherDataModel::Observer,
       public SessionObserver {
  public:
   class Observer : public base::CheckedObserver {
@@ -53,7 +55,8 @@
     kPhoneDisconnected,
     kPhoneConnected,
     kTetherConnectionPending,
-    kMaxValue = kTetherConnectionPending
+    kMiniLauncher,
+    kMaxValue = kMiniLauncher
   };
 
   PhoneHubUiController();
@@ -90,6 +93,9 @@
   // phonehub::OnboardingUiTracker::Observer:
   void OnShouldShowOnboardingUiChanged() override;
 
+  // phonehub::AppStreamLauncherDataModel::Observer:
+  void OnShouldShowMiniLauncherChanged() override;
+
   // phonehub::PhoneModel::Observer:
   void OnModelChanged() override;
 
@@ -102,6 +108,9 @@
   // Returns the UiState from the PhoneHubManager.
   UiState GetUiStateFromPhoneHubManager();
 
+  // Returns the UiState from the PhoneHubManager.
+  UiState GetUiStateFromPhoneHubManagerInternal();
+
   // Cleans up |phone_hub_manager_| by removing all observers.
   void CleanUpPhoneHubManager();
 
diff --git a/ash/system/phonehub/phone_hub_view_ids.h b/ash/system/phonehub/phone_hub_view_ids.h
index 214172a6..fcd89dc 100644
--- a/ash/system/phonehub/phone_hub_view_ids.h
+++ b/ash/system/phonehub/phone_hub_view_ids.h
@@ -49,6 +49,7 @@
 
   // Camera roll view and its components.
   kCameraRollView,
+  kAppStreamLauncherView,
 };
 
 }  // namespace ash
diff --git a/ash/system/power/tray_power.cc b/ash/system/power/tray_power.cc
index db9ecae..d6ae6a2 100644
--- a/ash/system/power/tray_power.cc
+++ b/ash/system/power/tray_power.cc
@@ -9,7 +9,6 @@
 #include "ash/accessibility/accessibility_delegate.h"
 #include "ash/public/cpp/shelf_config.h"
 #include "ash/resources/vector_icons/vector_icons.h"
-#include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/style/ash_color_id.h"
@@ -92,16 +91,6 @@
   UpdateStatus();
 }
 
-void PowerTrayView::OnSessionStateChanged(session_manager::SessionState state) {
-  // Icon color changes only happens when switching session states between OOBE
-  // and other state.
-  const bool update_image =
-      session_state_ == session_manager::SessionState::OOBE ||
-      state == session_manager::SessionState::OOBE;
-  session_state_ = state;
-  UpdateImage(update_image);
-}
-
 void PowerTrayView::UpdateStatus() {
   UpdateImage(/*icon_color_changed=*/false);
   SetVisible(PowerStatus::Get()->IsBatteryPresent());
@@ -122,7 +111,8 @@
   info_ = info;
 
   // Note: The icon color (both fg and bg) changes when the UI in in OOBE mode.
-  const SkColor icon_fg_color = TrayIconColor(session_state_);
+  const SkColor icon_fg_color =
+      GetColorProvider()->GetColor(kColorAshIconColorPrimary);
   const SkColor icon_bg_color = color_utils::GetResultingPaintColor(
       ShelfConfig::Get()->GetShelfControlButtonColor(GetWidget()),
       GetColorProvider()->GetColor(kColorAshShieldAndBaseOpaque));
diff --git a/ash/system/power/tray_power.h b/ash/system/power/tray_power.h
index ef910e5f..3028183 100644
--- a/ash/system/power/tray_power.h
+++ b/ash/system/power/tray_power.h
@@ -7,15 +7,12 @@
 
 #include <memory>
 
-#include "ash/public/cpp/session/session_observer.h"
 #include "ash/system/power/power_status.h"
 #include "ash/system/tray/tray_item_view.h"
 
 namespace ash {
 
-class PowerTrayView : public TrayItemView,
-                      public PowerStatus::Observer,
-                      public SessionObserver {
+class PowerTrayView : public TrayItemView, public PowerStatus::Observer {
  public:
   explicit PowerTrayView(Shelf* shelf);
 
@@ -38,9 +35,6 @@
   // PowerStatus::Observer:
   void OnPowerStatusChanged() override;
 
-  // SessionObserver:
-  void OnSessionStateChanged(session_manager::SessionState state) override;
-
  private:
   void UpdateStatus();
   void UpdateImage(bool icon_color_changed);
@@ -48,9 +42,6 @@
   std::u16string accessible_name_;
   std::u16string tooltip_;
   absl::optional<PowerStatus::BatteryImageInfo> info_;
-  session_manager::SessionState session_state_ =
-      session_manager::SessionState::UNKNOWN;
-  ScopedSessionObserver session_observer_{this};
 };
 
 }  // namespace ash
diff --git a/ash/system/time/time_tray_item_view.cc b/ash/system/time/time_tray_item_view.cc
index 09ff3c00..fe84631 100644
--- a/ash/system/time/time_tray_item_view.cc
+++ b/ash/system/time/time_tray_item_view.cc
@@ -6,24 +6,23 @@
 
 #include <memory>
 
-#include "ash/session/session_controller_impl.h"
 #include "ash/shelf/shelf.h"
 #include "ash/shell.h"
+#include "ash/style/ash_color_id.h"
 #include "ash/system/model/clock_model.h"
 #include "ash/system/model/system_tray_model.h"
 #include "ash/system/time/time_view.h"
-#include "ash/system/tray/tray_utils.h"
-#include "ui/views/border.h"
 
 namespace ash {
 
 TimeTrayItemView::TimeTrayItemView(Shelf* shelf, TimeView::Type type)
-    : TrayItemView(shelf), session_observer_(this) {
+    : TrayItemView(shelf) {
   TimeView::ClockLayout clock_layout =
       shelf->IsHorizontalAlignment() ? TimeView::ClockLayout::HORIZONTAL_CLOCK
                                      : TimeView::ClockLayout::VERTICAL_CLOCK;
   time_view_ = AddChildView(std::make_unique<TimeView>(
       clock_layout, Shell::Get()->system_tray_model()->clock(), type));
+  time_view_->SetTextColor(kColorAshIconColorPrimary);
 }
 
 TimeTrayItemView::~TimeTrayItemView() = default;
@@ -39,19 +38,8 @@
   time_view_->Refresh();
 }
 
-void TimeTrayItemView::OnSessionStateChanged(
-    session_manager::SessionState state) {
-  time_view_->SetTextColor(TrayIconColor(state));
-}
-
 const char* TimeTrayItemView::GetClassName() const {
   return "TimeTrayItemView";
 }
 
-void TimeTrayItemView::OnThemeChanged() {
-  TrayItemView::OnThemeChanged();
-  time_view_->SetTextColor(
-      TrayIconColor(Shell::Get()->session_controller()->GetSessionState()));
-}
-
 }  // namespace ash
diff --git a/ash/system/time/time_tray_item_view.h b/ash/system/time/time_tray_item_view.h
index ab491c9..d48cbcfb 100644
--- a/ash/system/time/time_tray_item_view.h
+++ b/ash/system/time/time_tray_item_view.h
@@ -6,15 +6,13 @@
 #define ASH_SYSTEM_TIME_TIME_TRAY_ITEM_VIEW_H_
 
 #include "ash/ash_export.h"
-#include "ash/public/cpp/session/session_observer.h"
 #include "ash/system/tray/tray_item_view.h"
 #include "time_view.h"
 
 namespace ash {
 class Shelf;
 
-class ASH_EXPORT TimeTrayItemView : public TrayItemView,
-                                    public SessionObserver {
+class ASH_EXPORT TimeTrayItemView : public TrayItemView {
  public:
   TimeTrayItemView(Shelf* shelf, TimeView::Type type);
 
@@ -29,18 +27,13 @@
   // TrayItemView:
   void HandleLocaleChange() override;
 
-  // SessionObserver:
-  void OnSessionStateChanged(session_manager::SessionState state) override;
-
   // views::View:
   const char* GetClassName() const override;
-  void OnThemeChanged() override;
 
  private:
   friend class TimeTrayItemViewTest;
 
   TimeView* time_view_ = nullptr;
-  ScopedSessionObserver session_observer_;
 };
 
 }  // namespace ash
diff --git a/ash/system/time/time_view.cc b/ash/system/time/time_view.cc
index 39bcbfc4..259e47e 100644
--- a/ash/system/time/time_view.cc
+++ b/ash/system/time/time_view.cc
@@ -33,6 +33,7 @@
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/color/color_id.h"
 #include "ui/events/event.h"
 #include "ui/events/types/event_type.h"
 #include "ui/gfx/geometry/insets.h"
@@ -194,10 +195,10 @@
   Layout();
 }
 
-void TimeView::SetTextColor(SkColor color,
+void TimeView::SetTextColor(ui::ColorId color_id,
                             bool auto_color_readability_enabled) {
   auto set_color = [&](views::Label* label) {
-    label->SetEnabledColor(color);
+    label->SetEnabledColorId(color_id);
     label->SetAutoColorReadabilityEnabled(auto_color_readability_enabled);
   };
 
diff --git a/ash/system/time/time_view.h b/ash/system/time/time_view.h
index 9bd0a26..d8feeba 100644
--- a/ash/system/time/time_view.h
+++ b/ash/system/time/time_view.h
@@ -75,7 +75,8 @@
   void UpdateClockLayout(ClockLayout clock_layout);
 
   // Updates the time text color.
-  void SetTextColor(SkColor color, bool auto_color_readability_enabled = false);
+  void SetTextColor(ui::ColorId color_id,
+                    bool auto_color_readability_enabled = false);
 
   // Updates the time text fontlist.
   void SetTextFont(const gfx::FontList& font_list);
diff --git a/ash/system/tray/hover_highlight_view.cc b/ash/system/tray/hover_highlight_view.cc
index 83501c39..869323f 100644
--- a/ash/system/tray/hover_highlight_view.cc
+++ b/ash/system/tray/hover_highlight_view.cc
@@ -8,7 +8,7 @@
 
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/strings/grit/ash_strings.h"
-#include "ash/style/ash_color_provider.h"
+#include "ash/style/ash_color_id.h"
 #include "ash/system/tray/tray_constants.h"
 #include "ash/system/tray/tray_popup_utils.h"
 #include "ash/system/tray/tri_view.h"
@@ -84,9 +84,7 @@
         sub_row_->AddChildView(TrayPopupUtils::CreateUnfocusableLabel());
   }
 
-  sub_text_label_->SetEnabledColor(
-      AshColorProvider::Get()->GetContentLayerColor(
-          AshColorProvider::ContentLayerType::kTextColorSecondary));
+  sub_text_label_->SetEnabledColorId(kColorAshTextColorSecondary);
   sub_text_label_->SetAutoColorReadabilityEnabled(false);
   sub_text_label_->SetText(sub_text);
 }
@@ -102,6 +100,17 @@
   AddViewAndLabel(std::move(icon), text);
 }
 
+void HoverHighlightView::AddIconAndLabel(const ui::ImageModel& image,
+                                         const std::u16string& text) {
+  DCHECK(!is_populated_);
+
+  std::unique_ptr<views::ImageView> icon(TrayPopupUtils::CreateMainImageView());
+  icon->SetImage(image);
+  icon->SetEnabled(GetEnabled());
+
+  AddViewAndLabel(std::move(icon), text);
+}
+
 void HoverHighlightView::AddViewAndLabel(std::unique_ptr<views::View> view,
                                          const std::u16string& text) {
   DCHECK(!is_populated_);
@@ -118,8 +127,7 @@
   text_label_ = TrayPopupUtils::CreateUnfocusableLabel();
   text_label_->SetText(text);
   text_label_->SetEnabled(GetEnabled());
-  text_label_->SetEnabledColor(AshColorProvider::Get()->GetContentLayerColor(
-      AshColorProvider::ContentLayerType::kTextColorPrimary));
+  text_label_->SetEnabledColorId(kColorAshTextColorPrimary);
   TrayPopupUtils::SetLabelFontList(
       text_label_, TrayPopupUtils::FontStyle::kDetailedViewLabel);
   tri_view_->AddView(TriView::Container::CENTER, text_label_);
@@ -145,8 +153,7 @@
 
   text_label_ = TrayPopupUtils::CreateUnfocusableLabel();
   text_label_->SetText(text);
-  text_label_->SetEnabledColor(AshColorProvider::Get()->GetContentLayerColor(
-      AshColorProvider::ContentLayerType::kTextColorPrimary));
+  text_label_->SetEnabledColorId(kColorAshTextColorPrimary);
   TrayPopupUtils::SetLabelFontList(
       text_label_, TrayPopupUtils::FontStyle::kDetailedViewLabel);
   tri_view_->AddView(TriView::Container::CENTER, text_label_);
diff --git a/ash/system/tray/hover_highlight_view.h b/ash/system/tray/hover_highlight_view.h
index 2889042..d7fa781 100644
--- a/ash/system/tray/hover_highlight_view.h
+++ b/ash/system/tray/hover_highlight_view.h
@@ -9,13 +9,14 @@
 
 #include "ash/system/tray/actionable_view.h"
 #include "base/bind.h"
+#include "ui/base/models/image_model.h"
 #include "ui/gfx/font.h"
 #include "ui/gfx/text_constants.h"
 
 namespace views {
 class Border;
 class Label;
-}
+}  // namespace views
 
 namespace ash {
 class TriView;
@@ -46,8 +47,13 @@
   // Convenience function for populating the view with an icon and a label. This
   // also sets the accessible name. Primarily used for scrollable rows in
   // detailed views.
+  // New callers should use the function below which takes an ImageModel.
+  // TODO(b/259490845): Change callers to pass an ImageModel and eliminate this.
   void AddIconAndLabel(const gfx::ImageSkia& image, const std::u16string& text);
 
+  // The same as the above function with `ImageModel` parameter instead.
+  void AddIconAndLabel(const ui::ImageModel& image, const std::u16string& text);
+
   // Convenience function for populating the view with an arbitrary view and a
   // label. This also sets the accessible name.
   void AddViewAndLabel(std::unique_ptr<views::View> view,
diff --git a/ash/system/tray/tray_background_view_unittest.cc b/ash/system/tray/tray_background_view_unittest.cc
index d94ff39..2b5b41d 100644
--- a/ash/system/tray/tray_background_view_unittest.cc
+++ b/ash/system/tray/tray_background_view_unittest.cc
@@ -11,16 +11,15 @@
 #include "ash/shelf/shelf_layout_manager.h"
 #include "ash/shell.h"
 #include "ash/system/accessibility/dictation_button_tray.h"
-#include "ash/system/status_area_widget_delegate.h"
 #include "ash/system/status_area_widget_test_helper.h"
 #include "ash/system/tray/tray_bubble_wrapper.h"
 #include "ash/test/ash_test_base.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/task_environment.h"
-#include "components/user_manager/user_manager.h"
 #include "ui/base/models/simple_menu_model.h"
+#include "ui/compositor/layer_animator.h"
 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
-#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/compositor/test/layer_animation_stopped_waiter.h"
 
 namespace ash {
 
@@ -79,7 +78,8 @@
   bool show_bubble_called_ = false;
 };
 
-class TrayBackgroundViewTest : public AshTestBase {
+class TrayBackgroundViewTest : public AshTestBase,
+                               public ui::LayerAnimationObserver {
  public:
   TrayBackgroundViewTest()
       : AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
@@ -110,10 +110,20 @@
     controller->dictation().SetEnabled(true);
   }
 
+  // ui::LayerAnimationObserver:
+  void OnLayerAnimationScheduled(
+      ui::LayerAnimationSequence* sequence) override {
+    num_animations_scheduled_++;
+  }
+  void OnLayerAnimationEnded(ui::LayerAnimationSequence* sequence) override {}
+  void OnLayerAnimationAborted(ui::LayerAnimationSequence* sequence) override {}
+
   TestTrayBackgroundView* test_tray_background_view() const {
     return test_tray_background_view_;
   }
 
+  int num_animations_scheduled() const { return num_animations_scheduled_; }
+
  protected:
   // Here we use dictation tray for testing secondary screen.
   DictationButtonTray* GetPrimaryDictationTray() {
@@ -136,6 +146,7 @@
 
  private:
   TestTrayBackgroundView* test_tray_background_view_ = nullptr;
+  int num_animations_scheduled_ = 0;
 };
 
 TEST_F(TrayBackgroundViewTest, ShowingAnimationAbortedByHideAnimation) {
@@ -240,45 +251,55 @@
   EXPECT_TRUE(test_tray_background_view()->GetVisible());
 }
 
-// TODO(crbug.com/1314693): Flaky.
-TEST_F(TrayBackgroundViewTest, DISABLED_SecondaryDisplay) {
+TEST_F(TrayBackgroundViewTest, SecondaryDisplay) {
   ui::ScopedAnimationDurationScaleMode test_duration_mode(
-      ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
+      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
 
   // Add secondary screen.
   UpdateDisplay("800x600,800x600");
+  GetPrimaryDictationTray()->layer()->GetAnimator()->AddObserver(this);
+  GetSecondaryDictationTray()->layer()->GetAnimator()->AddObserver(this);
 
-  // Switch the primary and secondary screen.
+  // Switch the primary and secondary screen. This should not cause additional
+  // TrayBackgroundView animations to occur.
   SwapPrimaryDisplay();
-  task_environment()->FastForwardBy(base::Milliseconds(20));
-  EXPECT_FALSE(
-      GetPrimaryDictationTray()->layer()->GetAnimator()->is_animating());
+  task_environment()->RunUntilIdle();
   EXPECT_TRUE(GetPrimaryDictationTray()->GetVisible());
-  EXPECT_FALSE(
-      GetSecondaryDictationTray()->layer()->GetAnimator()->is_animating());
   EXPECT_TRUE(GetSecondaryDictationTray()->GetVisible());
+  EXPECT_EQ(num_animations_scheduled(), 0);
 
   // Enable the animation after showing up on the secondary screen.
-  task_environment()->FastForwardBy(base::Milliseconds(20));
+  task_environment()->RunUntilIdle();
+  ui::LayerAnimationStoppedWaiter animation_waiter;
   GetPrimaryDictationTray()->SetVisiblePreferred(false);
-  GetPrimaryDictationTray()->SetVisiblePreferred(true);
-  GetSecondaryDictationTray()->SetVisiblePreferred(false);
-  GetSecondaryDictationTray()->SetVisiblePreferred(true);
-  task_environment()->FastForwardBy(base::Milliseconds(20));
   EXPECT_TRUE(
       GetPrimaryDictationTray()->layer()->GetAnimator()->is_animating());
-  EXPECT_TRUE(GetPrimaryDictationTray()->GetVisible());
+  animation_waiter.Wait(GetPrimaryDictationTray()->layer());
+
+  GetPrimaryDictationTray()->SetVisiblePreferred(true);
+  EXPECT_TRUE(
+      GetPrimaryDictationTray()->layer()->GetAnimator()->is_animating());
+  animation_waiter.Wait(GetPrimaryDictationTray()->layer());
+
+  GetSecondaryDictationTray()->SetVisiblePreferred(false);
   EXPECT_TRUE(
       GetSecondaryDictationTray()->layer()->GetAnimator()->is_animating());
+  animation_waiter.Wait(GetSecondaryDictationTray()->layer());
+
+  GetSecondaryDictationTray()->SetVisiblePreferred(true);
+  EXPECT_TRUE(
+      GetSecondaryDictationTray()->layer()->GetAnimator()->is_animating());
+  animation_waiter.Wait(GetSecondaryDictationTray()->layer());
+
+  EXPECT_TRUE(GetPrimaryDictationTray()->GetVisible());
   EXPECT_TRUE(GetSecondaryDictationTray()->GetVisible());
-  task_environment()->FastForwardBy(base::Seconds(3));
 
-  // Remove the secondary screen.
+  // Remove the secondary screen. This should not cause additional
+  // TrayBackgroundView animations to occur.
+  int num_animations_scheduled_before = num_animations_scheduled();
   UpdateDisplay("800x600");
-
-  task_environment()->FastForwardBy(base::Milliseconds(20));
-  EXPECT_FALSE(
-      GetPrimaryDictationTray()->layer()->GetAnimator()->is_animating());
+  task_environment()->RunUntilIdle();
+  EXPECT_EQ(num_animations_scheduled(), num_animations_scheduled_before);
   EXPECT_TRUE(GetPrimaryDictationTray()->GetVisible());
 }
 
diff --git a/ash/system/tray/tray_bubble_wrapper.cc b/ash/system/tray/tray_bubble_wrapper.cc
index e2f71bf..e3602ef7 100644
--- a/ash/system/tray/tray_bubble_wrapper.cc
+++ b/ash/system/tray/tray_bubble_wrapper.cc
@@ -9,7 +9,9 @@
 #include "ash/system/tray/tray_background_view.h"
 #include "ash/system/tray/tray_bubble_view.h"
 #include "ash/system/tray/tray_event_filter.h"
+#include "base/memory/ptr_util.h"
 #include "ui/aura/window.h"
+#include "ui/views/bubble/bubble_dialog_delegate_view.h"
 #include "ui/views/widget/widget.h"
 #include "ui/wm/core/transient_window_manager.h"
 #include "ui/wm/core/window_util.h"
@@ -18,22 +20,16 @@
 namespace ash {
 
 TrayBubbleWrapper::TrayBubbleWrapper(TrayBackgroundView* tray,
+                                     bool event_handling)
+    : tray_(tray), event_handling_(event_handling) {}
+
+// TODO(b/257129394): Remove this constructor once we migrate to using
+// unique_ptrs for all `TrayBubbleView`s.
+TrayBubbleWrapper::TrayBubbleWrapper(TrayBackgroundView* tray,
                                      TrayBubbleView* bubble_view,
                                      bool event_handling)
     : tray_(tray), bubble_view_(bubble_view), event_handling_(event_handling) {
-  bubble_widget_ = views::BubbleDialogDelegateView::CreateBubble(bubble_view_);
-  bubble_widget_->AddObserver(this);
-
-  TrayBackgroundView::InitializeBubbleAnimations(bubble_widget_);
-  bubble_view_->InitializeAndShowBubble();
-
-  if (!Shell::Get()->tablet_mode_controller()->InTabletMode())
-    Shell::Get()->app_list_controller()->DismissAppList();
-
-  if (event_handling_) {
-    tray->tray_event_filter()->AddBubble(this);
-    Shell::Get()->activation_client()->AddObserver(this);
-  }
+  ShowBubble(base::WrapUnique(bubble_view));
 }
 
 TrayBubbleWrapper::~TrayBubbleWrapper() {
@@ -54,6 +50,29 @@
   CHECK(!IsInObserverList());
 }
 
+void TrayBubbleWrapper::ShowBubble(
+    std::unique_ptr<TrayBubbleView> bubble_view) {
+  // We must ensure `ShowBubble` is only called when there is no existing
+  // `bubble_widget_`.
+  DCHECK(!bubble_widget_);
+
+  bubble_view_ = bubble_view.get();
+  bubble_widget_ =
+      views::BubbleDialogDelegateView::CreateBubble(std::move(bubble_view));
+  bubble_widget_->AddObserver(this);
+
+  TrayBackgroundView::InitializeBubbleAnimations(bubble_widget_);
+  bubble_view_->InitializeAndShowBubble();
+
+  if (!Shell::Get()->tablet_mode_controller()->InTabletMode())
+    Shell::Get()->app_list_controller()->DismissAppList();
+
+  if (event_handling_) {
+    tray_->tray_event_filter()->AddBubble(this);
+    Shell::Get()->activation_client()->AddObserver(this);
+  }
+}
+
 TrayBackgroundView* TrayBubbleWrapper::GetTray() const {
   return tray_;
 }
@@ -69,7 +88,7 @@
 void TrayBubbleWrapper::OnWidgetDestroying(views::Widget* widget) {
   CHECK_EQ(bubble_widget_, widget);
   bubble_widget_->RemoveObserver(this);
-  bubble_widget_ = NULL;
+  bubble_widget_ = nullptr;
 
   // Although the bubble is already closed, the next mouse release event
   // will invoke PerformAction which reopens the bubble again. To prevent the
diff --git a/ash/system/tray/tray_bubble_wrapper.h b/ash/system/tray/tray_bubble_wrapper.h
index 570dadf1..9b4c119 100644
--- a/ash/system/tray/tray_bubble_wrapper.h
+++ b/ash/system/tray/tray_bubble_wrapper.h
@@ -16,7 +16,6 @@
 class TrayBubbleView;
 
 // Creates and manages the Widget and EventFilter components of a bubble.
-// TODO(tetsui): Remove this and use TrayBubbleBase for all bubbles.
 class ASH_EXPORT TrayBubbleWrapper : public TrayBubbleBase,
                                      public ::wm::ActivationChangeObserver {
  public:
@@ -25,6 +24,11 @@
   // case in which we do not want the keyboard events (both inside and outside
   // of the bubble) be filtered and also we do not want activaion of other
   // windows closes the bubble.
+  explicit TrayBubbleWrapper(TrayBackgroundView* tray,
+                             bool event_handling = true);
+
+  // TODO(b/257129394): Remove this constructor once we migrate to using
+  // unique_ptrs for all `TrayBubbleView`s.
   TrayBubbleWrapper(TrayBackgroundView* tray,
                     TrayBubbleView* bubble_view,
                     bool event_handling = true);
@@ -34,6 +38,8 @@
 
   ~TrayBubbleWrapper() override;
 
+  void ShowBubble(std::unique_ptr<TrayBubbleView> bubble_view);
+
   // TrayBubbleBase overrides:
   TrayBackgroundView* GetTray() const override;
   TrayBubbleView* GetBubbleView() const override;
@@ -55,8 +61,10 @@
 
  private:
   TrayBackgroundView* tray_;
-  TrayBubbleView* bubble_view_;  // unowned
-  views::Widget* bubble_widget_;
+  views::Widget* bubble_widget_ = nullptr;
+
+  // Owned by `bubble_widget_`
+  TrayBubbleView* bubble_view_ = nullptr;
 
   // When set to false disables the tray's event filtering
   // and also ignores the activation events. Eche window is an example of a use
diff --git a/ash/system/tray/tray_utils.cc b/ash/system/tray/tray_utils.cc
index 072cce9..17a6677b 100644
--- a/ash/system/tray/tray_utils.cc
+++ b/ash/system/tray/tray_utils.cc
@@ -7,13 +7,11 @@
 #include <string>
 
 #include "ash/bubble/bubble_constants.h"
-#include "ash/constants/ash_features.h"
-#include "ash/public/cpp/shelf_config.h"
 #include "ash/public/cpp/shelf_types.h"
 #include "ash/shelf/shelf.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
-#include "ash/style/ash_color_provider.h"
+#include "ash/style/ash_color_id.h"
 #include "ash/system/tray/hover_highlight_view.h"
 #include "ash/system/tray/tray_constants.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
@@ -55,9 +53,7 @@
   }
 
   view->sub_text_label()->SetAutoColorReadabilityEnabled(false);
-  view->sub_text_label()->SetEnabledColor(
-      AshColorProvider::Get()->GetContentLayerColor(
-          AshColorProvider::ContentLayerType::kTextColorPositive));
+  view->sub_text_label()->SetEnabledColorId(kColorAshTextColorPositive);
 }
 
 void SetupConnectingScrollListItem(HoverHighlightView* view) {
@@ -72,19 +68,7 @@
 
   view->SetSubText(subtext);
   view->sub_text_label()->SetAutoColorReadabilityEnabled(false);
-  view->sub_text_label()->SetEnabledColor(
-      AshColorProvider::Get()->GetContentLayerColor(
-          AshColorProvider::ContentLayerType::kTextColorWarning));
-}
-
-SkColor TrayIconColor(session_manager::SessionState session_state) {
-  if (!features::IsDarkLightModeEnabled() &&
-      session_state == session_manager::SessionState::OOBE) {
-    return kIconColorInOobe;
-  }
-
-  return AshColorProvider::Get()->GetContentLayerColor(
-      AshColorProvider::ContentLayerType::kIconColorPrimary);
+  view->sub_text_label()->SetEnabledColorId(kColorAshTextColorWarning);
 }
 
 gfx::Insets GetTrayBubbleInsets() {
diff --git a/ash/system/tray/tray_utils.h b/ash/system/tray/tray_utils.h
index 3871aae..dfed5ff4 100644
--- a/ash/system/tray/tray_utils.h
+++ b/ash/system/tray/tray_utils.h
@@ -40,9 +40,6 @@
 // Add `subtext` with warning color to `view`.
 void SetWarningSubText(HoverHighlightView* view, std::u16string subtext);
 
-// Gets the current tray icon color for the given session state.
-SkColor TrayIconColor(session_manager::SessionState session_state);
-
 // Returns the insets above the shelf for positioning the quick settings bubble.
 gfx::Insets GetTrayBubbleInsets();
 
diff --git a/ash/system/unified/current_locale_view.cc b/ash/system/unified/current_locale_view.cc
index 67a007b4..b898740 100644
--- a/ash/system/unified/current_locale_view.cc
+++ b/ash/system/unified/current_locale_view.cc
@@ -7,6 +7,7 @@
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/style/ash_color_id.h"
 #include "ash/system/model/system_tray_model.h"
 #include "ash/system/tray/tray_constants.h"
 #include "ash/system/tray/tray_utils.h"
@@ -37,8 +38,7 @@
   SetVisible(locale_model->ShouldShowCurrentLocaleInStatusArea());
   label()->SetText(base::i18n::ToUpper(base::UTF8ToUTF16(
       l10n_util::GetLanguage(locale_model->current_locale_iso_code()))));
-  label()->SetEnabledColor(
-      TrayIconColor(Shell::Get()->session_controller()->GetSessionState()));
+  label()->SetEnabledColorId(kColorAshIconColorPrimary);
 
   const std::vector<LocaleInfo>& locales = locale_model->locale_list();
   for (auto& entry : locales) {
diff --git a/ash/system/unified/ime_mode_view.cc b/ash/system/unified/ime_mode_view.cc
index f4939b0..6e53bde 100644
--- a/ash/system/unified/ime_mode_view.cc
+++ b/ash/system/unified/ime_mode_view.cc
@@ -8,6 +8,7 @@
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/style/ash_color_id.h"
 #include "ash/system/model/system_tray_model.h"
 #include "ash/system/tray/system_tray_notifier.h"
 #include "ash/system/tray/tray_constants.h"
@@ -60,10 +61,6 @@
   Update();
 }
 
-void ImeModeView::OnSessionStateChanged(session_manager::SessionState state) {
-  Update();
-}
-
 const char* ImeModeView::GetClassName() const {
   return "ImeModeView";
 }
@@ -72,12 +69,6 @@
   Update();
 }
 
-void ImeModeView::OnThemeChanged() {
-  TrayItemView::OnThemeChanged();
-  label()->SetEnabledColor(
-      TrayIconColor(Shell::Get()->session_controller()->GetSessionState()));
-}
-
 void ImeModeView::Update() {
   // Hide the IME mode icon when the locale is shown, because showing locale and
   // IME together is confusing.
@@ -108,8 +99,7 @@
              (ime_count > 1 || ime_controller->managed_by_policy()));
 
   label()->SetText(ime_controller->current_ime().short_name);
-  label()->SetEnabledColor(
-      TrayIconColor(Shell::Get()->session_controller()->GetSessionState()));
+  label()->SetEnabledColorId(kColorAshIconColorPrimary);
   std::u16string description =
       l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_INDICATOR_IME_TOOLTIP,
                                  ime_controller->current_ime().name);
diff --git a/ash/system/unified/ime_mode_view.h b/ash/system/unified/ime_mode_view.h
index 1f2ad50..28fa11c 100644
--- a/ash/system/unified/ime_mode_view.h
+++ b/ash/system/unified/ime_mode_view.h
@@ -5,7 +5,6 @@
 #ifndef ASH_SYSTEM_UNIFIED_IME_MODE_VIEW_H_
 #define ASH_SYSTEM_UNIFIED_IME_MODE_VIEW_H_
 
-#include "ash/public/cpp/session/session_observer.h"
 #include "ash/public/cpp/tablet_mode_observer.h"
 #include "ash/system/ime/ime_observer.h"
 #include "ash/system/model/locale_model.h"
@@ -17,8 +16,7 @@
 class ImeModeView : public TrayItemView,
                     public IMEObserver,
                     public LocaleModel::Observer,
-                    public TabletModeObserver,
-                    public SessionObserver {
+                    public TabletModeObserver {
  public:
   explicit ImeModeView(Shelf* shelf);
 
@@ -38,22 +36,14 @@
   void OnTabletModeStarted() override;
   void OnTabletModeEnded() override;
 
-  // SessionObserver:
-  void OnSessionStateChanged(session_manager::SessionState state) override;
-
   // views::TrayItemView:
   const char* GetClassName() const override;
   void HandleLocaleChange() override;
 
-  // views::View:
-  void OnThemeChanged() override;
-
  private:
   void Update();
 
   bool ime_menu_on_shelf_activated_ = false;
-
-  ScopedSessionObserver session_observer_{this};
 };
 
 }  // namespace ash
diff --git a/ash/system/unified/managed_device_tray_item_view.cc b/ash/system/unified/managed_device_tray_item_view.cc
index 2fed069..1d14af58 100644
--- a/ash/system/unified/managed_device_tray_item_view.cc
+++ b/ash/system/unified/managed_device_tray_item_view.cc
@@ -8,6 +8,7 @@
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/style/ash_color_id.h"
 #include "ash/system/enterprise/enterprise_domain_observer.h"
 #include "ash/system/model/enterprise_domain_model.h"
 #include "ash/system/model/system_tray_model.h"
@@ -16,6 +17,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "chromeos/strings/grit/chromeos_strings.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/image_model.h"
 #include "ui/chromeos/devicetype_utils.h"
 #include "ui/gfx/paint_vector_icon.h"
 #include "ui/views/controls/image_view.h"
@@ -79,8 +81,8 @@
     icon = &kSystemTraySupervisedUserIcon;
 
   if (icon) {
-    image_view()->SetImage(gfx::CreateVectorIcon(
-        *icon, TrayIconColor(session->GetSessionState())));
+    image_view()->SetImage(
+        ui::ImageModel::FromVectorIcon(*icon, kColorAshIconColorPrimary));
   }
 }
 
diff --git a/ash/system/unified/notification_counter_view.cc b/ash/system/unified/notification_counter_view.cc
index cbbe096..d1e0f856 100644
--- a/ash/system/unified/notification_counter_view.cc
+++ b/ash/system/unified/notification_counter_view.cc
@@ -11,6 +11,7 @@
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/style/ash_color_id.h"
 #include "ash/style/ash_color_provider.h"
 #include "ash/system/message_center/ash_message_center_lock_screen_controller.h"
 #include "ash/system/message_center/message_center_utils.h"
@@ -19,6 +20,7 @@
 #include "ash/system/unified/notification_icons_controller.h"
 #include "base/i18n/number_formatting.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/image_model.h"
 #include "ui/color/color_id.h"
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/font_list.h"
@@ -74,9 +76,12 @@
 
 class NumberIconImageSource : public gfx::CanvasImageSource {
  public:
-  explicit NumberIconImageSource(size_t count)
+  explicit NumberIconImageSource(
+      NotificationCounterView* NotificationCounterView,
+      size_t count)
       : CanvasImageSource(
             gfx::Size(kUnifiedTrayIconSize, kUnifiedTrayIconSize)),
+        notification_counter_view_(NotificationCounterView),
         count_(count) {
     DCHECK_LE(count_, kTrayNotificationMaxCount + 1);
   }
@@ -86,7 +91,8 @@
 
   void Draw(gfx::Canvas* canvas) override {
     SkColor tray_icon_color =
-        TrayIconColor(Shell::Get()->session_controller()->GetSessionState());
+        notification_counter_view_->GetColorProvider()->GetColor(
+            kColorAshIconColorPrimary);
     // Paint the contents inside the circle background. The color doesn't matter
     // as it will be hollowed out by the XOR operation.
     if (count_ > kTrayNotificationMaxCount) {
@@ -109,6 +115,7 @@
   }
 
  private:
+  NotificationCounterView* notification_counter_view_;
   size_t count_;
 };
 
@@ -155,7 +162,8 @@
   int icon_id = std::min(notification_count, kTrayNotificationMaxCount + 1);
   if (icon_id != count_for_display_) {
     image_view()->SetImage(
-        gfx::CanvasImageSource::MakeImageSkia<NumberIconImageSource>(icon_id));
+        gfx::CanvasImageSource::MakeImageSkia<NumberIconImageSource>(this,
+                                                                     icon_id));
     count_for_display_ = icon_id;
   }
   SetVisible(true);
@@ -173,7 +181,7 @@
   TrayItemView::OnThemeChanged();
   image_view()->SetImage(
       gfx::CanvasImageSource::MakeImageSkia<NumberIconImageSource>(
-          count_for_display_));
+          this, count_for_display_));
 }
 
 const char* NotificationCounterView::GetClassName() const {
@@ -195,9 +203,8 @@
   // DCHECK_EQ(kTrayIconSize,
   //     gfx::GetDefaultSizeOfVectorIcon(kSystemTrayDoNotDisturbIcon));
   if (message_center::MessageCenter::Get()->IsQuietMode()) {
-    image_view()->SetImage(gfx::CreateVectorIcon(
-        kSystemTrayDoNotDisturbIcon,
-        TrayIconColor(Shell::Get()->session_controller()->GetSessionState())));
+    image_view()->SetImage(ui::ImageModel::FromVectorIcon(
+        kSystemTrayDoNotDisturbIcon, kColorAshIconColorPrimary));
     SetVisible(true);
   } else {
     SetVisible(false);
diff --git a/ash/system/unified/notification_icons_controller.cc b/ash/system/unified/notification_icons_controller.cc
index f3567ea..e783eb5 100644
--- a/ash/system/unified/notification_icons_controller.cc
+++ b/ash/system/unified/notification_icons_controller.cc
@@ -11,6 +11,7 @@
 #include "ash/shelf/shelf.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/style/ash_color_id.h"
 #include "ash/system/message_center/ash_message_center_lock_screen_controller.h"
 #include "ash/system/message_center/message_center_utils.h"
 #include "ash/system/notification_center/notification_center_tray.h"
@@ -21,6 +22,7 @@
 #include "ash/system/unified/unified_system_tray.h"
 #include "ash/system/unified/unified_system_tray_model.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/image_model.h"
 #include "ui/color/color_id.h"
 #include "ui/color/color_provider.h"
 #include "ui/gfx/paint_vector_icon.h"
@@ -99,16 +101,15 @@
 
   const auto* color_provider = GetColorProvider();
   gfx::Image masked_small_icon = notification->GenerateMaskedSmallIcon(
-      kUnifiedTrayIconSize,
-      TrayIconColor(Shell::Get()->session_controller()->GetSessionState()),
+      kUnifiedTrayIconSize, color_provider->GetColor(kColorAshIconColorPrimary),
       color_provider->GetColor(ui::kColorNotificationIconBackground),
       color_provider->GetColor(ui::kColorNotificationIconForeground));
   if (!masked_small_icon.IsEmpty()) {
     image_view()->SetImage(masked_small_icon.AsImageSkia());
   } else {
-    image_view()->SetImage(gfx::CreateVectorIcon(
-        message_center::kProductIcon, kUnifiedTrayIconSize,
-        TrayIconColor(Shell::Get()->session_controller()->GetSessionState())));
+    image_view()->SetImage(ui::ImageModel::FromVectorIcon(
+        message_center::kProductIcon, kColorAshIconColorPrimary,
+        kUnifiedTrayIconSize));
   }
 
   image_view()->SetTooltipText(notification->title());
diff --git a/ash/system/video_conference/video_conference_bubble.cc b/ash/system/video_conference/video_conference_bubble.cc
index 6f64c83a..76cec4410 100644
--- a/ash/system/video_conference/video_conference_bubble.cc
+++ b/ash/system/video_conference/video_conference_bubble.cc
@@ -5,8 +5,9 @@
 #include "ash/system/video_conference/video_conference_bubble.h"
 
 #include "ash/resources/vector_icons/vector_icons.h"
-#include "ash/style/ash_color_provider.h"
+#include "ash/style/ash_color_id.h"
 #include "ash/system/tray/tray_bubble_view.h"
+#include "ui/base/models/image_model.h"
 #include "ui/views/controls/image_view.h"
 
 namespace ash {
@@ -17,10 +18,8 @@
   // TODO(b/253088232): Added an icon so that the bubble can show. Will remove
   // this with the newly created class VideoConferenceBubbleView.
   auto icon = std::make_unique<views::ImageView>();
-  icon->SetImage(gfx::CreateVectorIcon(
-      kPrivacyIndicatorsMicrophoneIcon,
-      AshColorProvider::Get()->GetContentLayerColor(
-          AshColorProvider::ContentLayerType::kIconColorPrimary)));
+  icon->SetImage(ui::ImageModel::FromVectorIcon(
+      kPrivacyIndicatorsMicrophoneIcon, kColorAshIconColorPrimary));
   AddChildView(std::move(icon));
 }
 
diff --git a/ash/system/video_conference/video_conference_tray.cc b/ash/system/video_conference/video_conference_tray.cc
index e489681..2d824e7 100644
--- a/ash/system/video_conference/video_conference_tray.cc
+++ b/ash/system/video_conference/video_conference_tray.cc
@@ -13,6 +13,7 @@
 #include "ash/shelf/shelf.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/style/ash_color_id.h"
 #include "ash/style/icon_button.h"
 #include "ash/system/tray/tray_background_view.h"
 #include "ash/system/tray/tray_bubble_wrapper.h"
@@ -23,6 +24,7 @@
 #include "base/functional/bind.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/base/models/image_model.h"
 #include "ui/gfx/image/image_skia_operations.h"
 #include "ui/gfx/skbitmap_operations.h"
 #include "ui/views/controls/image_view.h"
@@ -149,7 +151,7 @@
 void VideoConferenceTray::UpdateExpandIndicator() {
   auto image = gfx::CreateVectorIcon(
       kUnifiedMenuExpandIcon,
-      TrayIconColor(Shell::Get()->session_controller()->GetSessionState()));
+      GetColorProvider()->GetColor(kColorAshIconColorPrimary));
 
   SkBitmapOperations::RotationAmount rotation;
   switch (shelf()->alignment()) {
diff --git a/ash/system/video_conference/video_conference_tray_unittest.cc b/ash/system/video_conference/video_conference_tray_unittest.cc
index d732fe38..c3cf7c16 100644
--- a/ash/system/video_conference/video_conference_tray_unittest.cc
+++ b/ash/system/video_conference/video_conference_tray_unittest.cc
@@ -7,7 +7,7 @@
 #include "ash/constants/ash_features.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/shelf/shelf.h"
-#include "ash/style/ash_color_provider.h"
+#include "ash/style/ash_color_id.h"
 #include "ash/system/status_area_widget.h"
 #include "ash/system/status_area_widget_test_helper.h"
 #include "ash/system/unified/unified_system_tray.h"
@@ -82,8 +82,8 @@
   GetPrimaryShelf()->SetAlignment(ShelfAlignment::kBottom);
   auto expected_image = gfx::CreateVectorIcon(
       kUnifiedMenuExpandIcon,
-      AshColorProvider::Get()->GetContentLayerColor(
-          AshColorProvider::ContentLayerType::kIconColorPrimary));
+      video_conference_tray()->GetColorProvider()->GetColor(
+          kColorAshIconColorPrimary));
 
   // When the bubble is not open in horizontal shelf, the indicator should point
   // up (not rotated).
diff --git a/ash/system/virtual_keyboard/virtual_keyboard_tray.cc b/ash/system/virtual_keyboard/virtual_keyboard_tray.cc
index 9787d931..fff7161 100644
--- a/ash/system/virtual_keyboard/virtual_keyboard_tray.cc
+++ b/ash/system/virtual_keyboard/virtual_keyboard_tray.cc
@@ -11,45 +11,35 @@
 #include "ash/keyboard/ui/keyboard_ui_controller.h"
 #include "ash/metrics/user_metrics_recorder.h"
 #include "ash/resources/vector_icons/vector_icons.h"
-#include "ash/session/session_controller_impl.h"
 #include "ash/shelf/shelf.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/style/ash_color_id.h"
 #include "ash/system/tray/tray_constants.h"
 #include "ash/system/tray/tray_container.h"
-#include "ash/system/tray/tray_utils.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/image_model.h"
 #include "ui/display/display.h"
 #include "ui/display/screen.h"
 #include "ui/events/event.h"
-#include "ui/gfx/image/image_skia.h"
-#include "ui/gfx/paint_vector_icon.h"
 #include "ui/views/border.h"
 #include "ui/views/controls/image_view.h"
 
 namespace ash {
 
-namespace {
-
-gfx::ImageSkia GetIconImage() {
-  return gfx::CreateVectorIcon(
-      kShelfKeyboardNewuiIcon,
-      TrayIconColor(Shell::Get()->session_controller()->GetSessionState()));
-}
-
-}  // namespace
-
 VirtualKeyboardTray::VirtualKeyboardTray(
     Shelf* shelf,
     TrayBackgroundViewCatalogName catalog_name)
     : TrayBackgroundView(shelf, catalog_name),
       icon_(new views::ImageView),
       shelf_(shelf) {
-  const gfx::ImageSkia image = GetIconImage();
+  const ui::ImageModel image = ui::ImageModel::FromVectorIcon(
+      kShelfKeyboardNewuiIcon, kColorAshIconColorPrimary);
+  icon_->SetImage(image);
   icon_->SetTooltipText(l10n_util::GetStringUTF16(
       IDS_ASH_STATUS_TRAY_ACCESSIBILITY_VIRTUAL_KEYBOARD));
-  const int vertical_padding = (kTrayItemSize - image.height()) / 2;
-  const int horizontal_padding = (kTrayItemSize - image.width()) / 2;
+  const int vertical_padding = (kTrayItemSize - image.Size().height()) / 2;
+  const int horizontal_padding = (kTrayItemSize - image.Size().width()) / 2;
   icon_->SetBorder(views::CreateEmptyBorder(
       gfx::Insets::VH(vertical_padding, horizontal_padding)));
   tray_container()->AddChildView(icon_);
@@ -120,11 +110,6 @@
   return true;
 }
 
-void VirtualKeyboardTray::OnThemeChanged() {
-  TrayBackgroundView::OnThemeChanged();
-  icon_->SetImage(GetIconImage());
-}
-
 void VirtualKeyboardTray::OnAccessibilityStatusChanged() {
   bool new_enabled =
       Shell::Get()->accessibility_controller()->virtual_keyboard().enabled();
@@ -135,11 +120,6 @@
   SetIsActive(is_visible);
 }
 
-void VirtualKeyboardTray::OnSessionStateChanged(
-    session_manager::SessionState state) {
-  icon_->SetImage(GetIconImage());
-}
-
 const char* VirtualKeyboardTray::GetClassName() const {
   return "VirtualKeyboardTray";
 }
diff --git a/ash/system/virtual_keyboard/virtual_keyboard_tray.h b/ash/system/virtual_keyboard/virtual_keyboard_tray.h
index 78e55db..35d74f7 100644
--- a/ash/system/virtual_keyboard/virtual_keyboard_tray.h
+++ b/ash/system/virtual_keyboard/virtual_keyboard_tray.h
@@ -22,8 +22,7 @@
 class VirtualKeyboardTray : public TrayBackgroundView,
                             public AccessibilityObserver,
                             public KeyboardControllerObserver,
-                            public ShellObserver,
-                            public SessionObserver {
+                            public ShellObserver {
  public:
   VirtualKeyboardTray(Shelf* shelf, TrayBackgroundViewCatalogName catalog_name);
 
@@ -39,7 +38,6 @@
   void HideBubbleWithView(const TrayBubbleView* bubble_view) override;
   void ClickedOutsideBubble() override;
   bool PerformAction(const ui::Event& event) override;
-  void OnThemeChanged() override;
 
   // AccessibilityObserver:
   void OnAccessibilityStatusChanged() override;
@@ -47,9 +45,6 @@
   // KeyboardControllerObserver:
   void OnKeyboardVisibilityChanged(bool is_visible) override;
 
-  // SessionObserver:
-  void OnSessionStateChanged(session_manager::SessionState state) override;
-
   // views::View:
   const char* GetClassName() const override;
 
@@ -58,8 +53,6 @@
   views::ImageView* icon_;
 
   Shelf* shelf_;
-
-  ScopedSessionObserver session_observer_{this};
 };
 
 }  // namespace ash
diff --git a/ash/webui/diagnostics_ui/resources/keyboard_tester.html b/ash/webui/diagnostics_ui/resources/keyboard_tester.html
index 16744d1..6b8b36d 100644
--- a/ash/webui/diagnostics_ui/resources/keyboard_tester.html
+++ b/ash/webui/diagnostics_ui/resources/keyboard_tester.html
@@ -57,7 +57,9 @@
     </cr-toast>
   </div>
   <div slot="button-container">
-    <div id="help" inner-h-t-m-l="[[i18nAdvanced('keyboardTesterHelpLink')]]"></div>
+    <div id="help" inner-h-t-m-l="[[i18nAdvanced('keyboardTesterHelpLink')]]"
+        hidden$="[[!isLoggedIn]]">
+    </div>
     <cr-button class="action-button" on-click="close">
       [[i18n('inputTesterDone')]]
     </cr-button>
diff --git a/ash/webui/diagnostics_ui/resources/keyboard_tester.ts b/ash/webui/diagnostics_ui/resources/keyboard_tester.ts
index e9f79814..5efa8636 100644
--- a/ash/webui/diagnostics_ui/resources/keyboard_tester.ts
+++ b/ash/webui/diagnostics_ui/resources/keyboard_tester.ts
@@ -5,6 +5,7 @@
 import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
 import 'chrome://resources/cr_elements/cr_toast/cr_toast.js';
 import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
+import './strings.m.js';
 
 import {KeyboardDiagramElement, MechanicalLayout as DiagramMechanicalLayout, PhysicalLayout as DiagramPhysicalLayout, TopRightKey as DiagramTopRightKey, TopRowKey as DiagramTopRowKey} from 'chrome://resources/ash/common/keyboard_diagram.js';
 import {KeyboardKeyState} from 'chrome://resources/ash/common/keyboard_key.js';
@@ -14,6 +15,7 @@
 import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {EventTracker} from 'chrome://resources/js/event_tracker.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {InputDataProviderInterface, KeyboardInfo, KeyboardObserverReceiver, KeyEvent, KeyEventType, MechanicalLayout, NumberPadPresence, PhysicalLayout, TopRightKey, TopRowKey} from './input_data_provider.mojom-webui.js';
@@ -163,12 +165,18 @@
         type: Array,
         computed: 'computeTopRowKeys_(keyboard)',
       },
+
+      isLoggedIn: {
+        type: Boolean,
+        value: loadTimeData.getBoolean('isLoggedIn'),
+      },
     };
   }
 
   keyboard: KeyboardInfo;
   // TODO(crbug.com/1257138): use the proper type annotation instead of
   // string.
+  protected isLoggedIn: boolean;
   protected diagramTopRightKey_: string;
   private layoutIsKnown_: boolean;
   // TODO(crbug.com/1257138): use the proper type annotation instead of
@@ -363,9 +371,6 @@
           diagram.topRightKey !== topRightKeyByCode.get(keyEvent.keyCode)) {
         const newValue =
             topRightKeyByCode.get(keyEvent.keyCode) as DiagramTopRightKey;
-        console.warn(
-            'Corrected diagram top right key from ' +
-            `${this.diagramTopRightKey_} to ${newValue}`);
         diagram.topRightKey = newValue;
       }
 
@@ -378,9 +383,6 @@
       // There may be Chromebooks where hasNumberPad is incorrect, so if we see
       // any number pad key codes we need to adapt on-the-fly.
       if (!diagram.showNumberPad && this.isNumberPadKey_(keyEvent.keyCode)) {
-        console.warn(
-            'Corrected number pad presence due to key code ' +
-            keyEvent.keyCode);
         diagram.showNumberPad = true;
       }
 
@@ -392,7 +394,6 @@
    * Implements KeyboardObserver.OnKeyEventsPaused.
    */
   onKeyEventsPaused(): void {
-    console.log('Key events paused');
     const diagram: KeyboardDiagramElement|null =
         this.shadowRoot!.querySelector('#diagram');
     assert(diagram);
@@ -404,7 +405,6 @@
    * Implements KeyboardObserver.OnKeyEventsResumed.
    */
   onKeyEventsResumed(): void {
-    console.log('Key events resumed');
     if (this.isOpen()) {
       this.$.dialog.focus();
     }
diff --git a/ash/webui/eche_app_ui/eche_notification_click_handler.cc b/ash/webui/eche_app_ui/eche_notification_click_handler.cc
index b35f44f..4599849 100644
--- a/ash/webui/eche_app_ui/eche_notification_click_handler.cc
+++ b/ash/webui/eche_app_ui/eche_notification_click_handler.cc
@@ -8,6 +8,7 @@
 #include "ash/root_window_controller.h"
 #include "ash/shell.h"
 #include "ash/webui/eche_app_ui/launch_app_helper.h"
+#include "base/metrics/histogram_functions.h"
 #include "chromeos/ash/components/multidevice/logging/logging.h"
 
 namespace ash {
@@ -44,6 +45,9 @@
           feature_status_provider_->GetStatus());
   switch (prohibited_reason) {
     case LaunchAppHelper::AppLaunchProhibitedReason::kNotProhibited:
+      base::UmaHistogramEnumeration(
+          "Eche.AppStream.LaunchAttempt",
+          mojom::AppStreamLaunchEntryPoint::NOTIFICATION);
       launch_app_helper_->LaunchEcheApp(
           notification_id, app_metadata.package_name,
           app_metadata.visible_app_name, app_metadata.user_id,
diff --git a/ash/webui/eche_app_ui/eche_notification_click_handler.h b/ash/webui/eche_app_ui/eche_notification_click_handler.h
index a06bf01d2..3b8c18a 100644
--- a/ash/webui/eche_app_ui/eche_notification_click_handler.h
+++ b/ash/webui/eche_app_ui/eche_notification_click_handler.h
@@ -11,6 +11,7 @@
 // TODO(https://crbug.com/1164001): move to forward declaration.
 #include "ash/components/phonehub/phone_hub_manager.h"
 #include "ash/webui/eche_app_ui/feature_status_provider.h"
+#include "ash/webui/eche_app_ui/mojom/eche_app.mojom.h"
 #include "base/callback.h"
 
 namespace ash {
diff --git a/ash/webui/eche_app_ui/eche_notification_click_handler_unittest.cc b/ash/webui/eche_app_ui/eche_notification_click_handler_unittest.cc
index 4b5218f7..385f015 100644
--- a/ash/webui/eche_app_ui/eche_notification_click_handler_unittest.cc
+++ b/ash/webui/eche_app_ui/eche_notification_click_handler_unittest.cc
@@ -12,6 +12,7 @@
 #include "ash/webui/eche_app_ui/fake_launch_app_helper.h"
 #include "ash/webui/eche_app_ui/launch_app_helper.h"
 #include "base/bind.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -149,6 +150,7 @@
   const char16_t app_name[] = u"Test App";
   const char package_name[] = "com.google.testapp";
   const int64_t user_id = 0;
+  base::HistogramTester histogram_tester;
   phonehub::Notification::AppMetadata app_meta_data =
       phonehub::Notification::AppMetadata(app_name, package_name,
                                           /*icon=*/gfx::Image(),
@@ -164,6 +166,9 @@
   HandleNotificationClick(notification_id, app_meta_data);
   EXPECT_EQ(num_app_launch(), 0u);
   EXPECT_EQ(num_notifications_shown(), 1u);
+  histogram_tester.ExpectUniqueSample(
+      "Eche.AppStream.LaunchAttempt",
+      mojom::AppStreamLaunchEntryPoint::NOTIFICATION, 1);
 }
 
 }  // namespace eche_app
diff --git a/ash/webui/eche_app_ui/eche_recent_app_click_handler.cc b/ash/webui/eche_app_ui/eche_recent_app_click_handler.cc
index a063d02..e5b8a91 100644
--- a/ash/webui/eche_app_ui/eche_recent_app_click_handler.cc
+++ b/ash/webui/eche_app_ui/eche_recent_app_click_handler.cc
@@ -8,8 +8,10 @@
 #include "ash/root_window_controller.h"
 #include "ash/shell.h"
 #include "ash/system/eche/eche_tray.h"
+#include "ash/system/phonehub/phone_hub_ui_controller.h"
 #include "ash/webui/eche_app_ui/launch_app_helper.h"
 #include "ash/webui/eche_app_ui/mojom/eche_app.mojom.h"
+#include "base/metrics/histogram_functions.h"
 
 namespace ash {
 namespace eche_app {
@@ -19,7 +21,8 @@
     FeatureStatusProvider* feature_status_provider,
     LaunchAppHelper* launch_app_helper,
     EcheStreamStatusChangeHandler* stream_status_change_handler)
-    : feature_status_provider_(feature_status_provider),
+    : phone_hub_manager_(phone_hub_manager),
+      feature_status_provider_(feature_status_provider),
       launch_app_helper_(launch_app_helper),
       stream_status_change_handler_(stream_status_change_handler) {
   notification_handler_ =
@@ -48,6 +51,7 @@
 void EcheRecentAppClickHandler::HandleNotificationClick(
     int64_t notification_id,
     const phonehub::Notification::AppMetadata& app_metadata) {
+  DCHECK(phone_hub_manager_);
   // Add the notification’s `app_metadata` that the user clicks to recents list
   // if the stream is already started. If the stream hasn't started yet, we keep
   // this notification’s `app_metadata` until this notification is streaming
@@ -77,6 +81,9 @@
           feature_status_provider_->GetStatus());
   switch (prohibited_reason) {
     case LaunchAppHelper::AppLaunchProhibitedReason::kNotProhibited:
+      base::UmaHistogramEnumeration(
+          "Eche.AppStream.LaunchAttempt",
+          mojom::AppStreamLaunchEntryPoint::RECENT_APPS);
       to_stream_apps_.emplace_back(app_metadata);
       launch_app_helper_->LaunchEcheApp(
           /*notification_id=*/absl::nullopt, app_metadata.package_name,
diff --git a/ash/webui/eche_app_ui/eche_recent_app_click_handler.h b/ash/webui/eche_app_ui/eche_recent_app_click_handler.h
index 309bf68..b37e512 100644
--- a/ash/webui/eche_app_ui/eche_recent_app_click_handler.h
+++ b/ash/webui/eche_app_ui/eche_recent_app_click_handler.h
@@ -58,6 +58,7 @@
  private:
   bool IsClickable(FeatureStatus status);
 
+  phonehub::PhoneHubManager* phone_hub_manager_;
   phonehub::NotificationInteractionHandler* notification_handler_;
   phonehub::RecentAppsInteractionHandler* recent_apps_handler_;
   FeatureStatusProvider* feature_status_provider_;
diff --git a/ash/webui/eche_app_ui/eche_recent_app_click_handler_unittest.cc b/ash/webui/eche_app_ui/eche_recent_app_click_handler_unittest.cc
index 16301e8f..88ec8fe8 100644
--- a/ash/webui/eche_app_ui/eche_recent_app_click_handler_unittest.cc
+++ b/ash/webui/eche_app_ui/eche_recent_app_click_handler_unittest.cc
@@ -13,6 +13,7 @@
 #include "ash/webui/eche_app_ui/fake_launch_app_helper.h"
 #include "ash/webui/eche_app_ui/launch_app_helper.h"
 #include "base/bind.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -171,6 +172,7 @@
   const int64_t user_id = 1;
   const char16_t app_visible_name[] = u"Fake App";
   const char package_name[] = "com.fakeapp";
+  base::HistogramTester histogram_tester;
   auto fake_app_metadata = phonehub::Notification::AppMetadata(
       app_visible_name, package_name, gfx::Image(),
       /*icon_color=*/absl::nullopt, /*icon_is_monochrome=*/true, user_id);
@@ -181,6 +183,9 @@
   EXPECT_EQ(app_metadata.size(), 0u);
 
   RecentAppClicked(fake_app_metadata);
+  histogram_tester.ExpectUniqueSample(
+      "Eche.AppStream.LaunchAttempt",
+      mojom::AppStreamLaunchEntryPoint::RECENT_APPS, 1);
   // Call one more time to make sure deduplication works.
   RecentAppClicked(fake_app_metadata);
 
@@ -196,6 +201,9 @@
             app_metadata[0].visible_app_name);
   EXPECT_EQ(fake_app_metadata.package_name, app_metadata[0].package_name);
   EXPECT_EQ(fake_app_metadata.user_id, app_metadata[0].user_id);
+  histogram_tester.ExpectUniqueSample(
+      "Eche.AppStream.LaunchAttempt",
+      mojom::AppStreamLaunchEntryPoint::RECENT_APPS, 2);
 }
 
 TEST_F(EcheRecentAppClickHandlerTest, HandleNotificationClick) {
@@ -252,6 +260,7 @@
   const int64_t user_id = 1;
   const char16_t app_visible_name[] = u"Fake App";
   const char package_name[] = "com.fakeapp";
+  base::HistogramTester histogram_tester;
   auto fake_app_metadata = phonehub::Notification::AppMetadata(
       app_visible_name, package_name, gfx::Image(),
       /*icon_color=*/absl::nullopt, /*icon_is_monochrome=*/true, user_id);
@@ -260,6 +269,9 @@
       LaunchAppHelper::AppLaunchProhibitedReason::kDisabledByScreenLock);
   RecentAppClicked(fake_app_metadata);
   EXPECT_EQ(num_notifications_shown(), 1u);
+  histogram_tester.ExpectUniqueSample(
+      "Eche.AppStream.LaunchAttempt",
+      mojom::AppStreamLaunchEntryPoint::RECENT_APPS, 0);
 }
 
 }  // namespace eche_app
diff --git a/ash/webui/eche_app_ui/mojom/eche_app.mojom b/ash/webui/eche_app_ui/mojom/eche_app.mojom
index 5ca253c..cdbe2528 100644
--- a/ash/webui/eche_app_ui/mojom/eche_app.mojom
+++ b/ash/webui/eche_app_ui/mojom/eche_app.mojom
@@ -132,3 +132,12 @@
   // SWA
   OnStreamAction(StreamAction action);
 };
+
+// Enum representing what EntryPoint was used to launch the eche app stream.
+// Keep in sync with AppStreamLaunchEntryPoint in
+// tools/metrics/histograms/enums.xml
+enum AppStreamLaunchEntryPoint {
+  APPS_LIST = 0,    // App stream was launched from the full apps list.
+  NOTIFICATION = 1, // App stream was launched from a notification.
+  RECENT_APPS = 2,  // App stream was launched from the recent apps.
+};
diff --git a/ash/webui/shortcut_customization_ui/backend/BUILD.gn b/ash/webui/shortcut_customization_ui/backend/BUILD.gn
index 125761fa..e6227c6d 100644
--- a/ash/webui/shortcut_customization_ui/backend/BUILD.gn
+++ b/ash/webui/shortcut_customization_ui/backend/BUILD.gn
@@ -18,6 +18,7 @@
     "//ash/public/mojom",
     "//ash/webui/shortcut_customization_ui/mojom",
     "//components/prefs:prefs",
+    "//ui/base/ime/ash",
     "//ui/events/devices",
   ]
 }
@@ -38,6 +39,8 @@
     "//chromeos/ash/components:test_support",
     "//content/test:test_support",
     "//testing/gtest",
+    "//ui/base:test_support",
+    "//ui/base/ime/ash",
     "//ui/events/devices",
     "//ui/events/devices:test_support",
   ]
diff --git a/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.cc b/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.cc
index 2fca209d..fbf51cb 100644
--- a/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.cc
+++ b/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.cc
@@ -21,6 +21,7 @@
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote_set.h"
+#include "ui/base/ime/ash/input_method_manager.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/events/devices/device_data_manager.h"
 #include "ui/events/devices/input_device.h"
@@ -137,6 +138,9 @@
   // Observe connected keyboard events.
   ui::DeviceDataManager::GetInstance()->AddObserver(this);
 
+  // Observe keyboard input method changes.
+  input_method::InputMethodManager::Get()->AddObserver(this);
+
   ash_accelerator_configuration_->AddAcceleratorsUpdatedCallback(
       base::BindRepeating(
           &AcceleratorConfigurationProvider::OnAcceleratorsUpdated,
@@ -152,7 +156,11 @@
 }
 
 AcceleratorConfigurationProvider::~AcceleratorConfigurationProvider() {
+  DCHECK(ui::DeviceDataManager::GetInstance());
+  DCHECK(input_method::InputMethodManager::Get());
+
   ui::DeviceDataManager::GetInstance()->RemoveObserver(this);
+  input_method::InputMethodManager::Get()->RemoveObserver(this);
 }
 
 void AcceleratorConfigurationProvider::IsMutable(
@@ -188,6 +196,15 @@
   }
 }
 
+void AcceleratorConfigurationProvider::InputMethodChanged(
+    input_method::InputMethodManager* manager,
+    Profile* profile,
+    bool show_message) {
+  // Accelerators are updated to match the current input method, e.g. positional
+  // shortcuts.
+  NotifyAcceleratorsUpdated();
+}
+
 void AcceleratorConfigurationProvider::GetAcceleratorLayoutInfos(
     GetAcceleratorLayoutInfosCallback callback) {
   std::move(callback).Run(mojo::Clone(layout_infos_));
diff --git a/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.h b/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.h
index dd48c24c..c5d9927 100644
--- a/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.h
+++ b/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.h
@@ -13,6 +13,7 @@
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote_set.h"
+#include "ui/base/ime/ash/input_method_manager.h"
 #include "ui/events/devices/input_device.h"
 #include "ui/events/devices/input_device_event_observer.h"
 
@@ -21,7 +22,8 @@
 
 class AcceleratorConfigurationProvider
     : public shortcut_customization::mojom::AcceleratorConfigurationProvider,
-      public ui::InputDeviceEventObserver {
+      public ui::InputDeviceEventObserver,
+      public input_method::InputMethodManager::Observer {
  public:
   using AcceleratorConfigurationMap =
       base::flat_map<mojom::AcceleratorSource,
@@ -51,6 +53,11 @@
   // ui::InputDeviceEventObserver:
   void OnInputDeviceConfigurationChanged(uint8_t input_device_types) override;
 
+  // InputMethodManager::Observer:
+  void InputMethodChanged(input_method::InputMethodManager* manager,
+                          Profile* profile,
+                          bool show_message) override;
+
   void BindInterface(
       mojo::PendingReceiver<
           shortcut_customization::mojom::AcceleratorConfigurationProvider>
diff --git a/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider_unittest.cc b/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider_unittest.cc
index a892881..7e203f0 100644
--- a/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider_unittest.cc
+++ b/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider_unittest.cc
@@ -20,6 +20,7 @@
 #include "ash/webui/shortcut_customization_ui/mojom/shortcut_customization.mojom.h"
 #include "base/bind.h"
 #include "base/callback_forward.h"
+#include "base/memory/raw_ptr.h"
 #include "base/run_loop.h"
 #include "base/test/bind.h"
 #include "base/test/scoped_feature_list.h"
@@ -28,6 +29,8 @@
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/ime/ash/input_method_manager.h"
+#include "ui/base/ime/ash/mock_input_method_manager.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/events/devices/device_data_manager_test_api.h"
 #include "ui/events/devices/input_device.h"
@@ -54,6 +57,8 @@
 
   int num_times_notified() { return num_times_notified_; }
 
+  void clear_num_times_notified() { num_times_notified_ = 0; }
+
   shortcut_ui::AcceleratorConfigurationProvider::AcceleratorConfigurationMap
   config() {
     return mojo::Clone(config_);
@@ -164,11 +169,35 @@
   AcceleratorConfigurationProviderTest() = default;
   ~AcceleratorConfigurationProviderTest() override = default;
 
+  class TestInputMethodManager : public input_method::MockInputMethodManager {
+   public:
+    void AddObserver(
+        input_method::InputMethodManager::Observer* observer) override {
+      observers_.AddObserver(observer);
+    }
+
+    void RemoveObserver(
+        input_method::InputMethodManager::Observer* observer) override {
+      observers_.RemoveObserver(observer);
+    }
+
+    // Calls all observers with Observer::InputMethodChanged
+    void NotifyInputMethodChanged() {
+      for (auto& observer : observers_) {
+        observer.InputMethodChanged(
+            /*manager=*/this, /*profile=*/nullptr, /*show_message=*/false);
+      }
+    }
+
+    base::ObserverList<InputMethodManager::Observer>::Unchecked observers_;
+  };
+
   // AshTestBase:
   void SetUp() override {
     scoped_feature_list_.InitWithFeatures(
         {::features::kImprovedKeyboardShortcuts}, {});
-    // ui::ScopedKeyboardLayout keyboard_layout(ui::KEYBOARD_LAYOUT_ENGLISH_US);
+    input_method_manager_ = new TestInputMethodManager();
+    input_method::InputMethodManager::Initialize(input_method_manager_);
 
     ui::ResourceBundle::CleanupSharedInstance();
     AshTestSuite::LoadTestResources();
@@ -178,7 +207,13 @@
     base::RunLoop().RunUntilIdle();
   }
 
-  void TearDown() override { AshTestBase::TearDown(); }
+  void TearDown() override {
+    AshTestBase::TearDown();
+    // `provider_` has a dependency on `input_method_manager_`.
+    provider_.reset();
+    input_method::InputMethodManager::Shutdown();
+    input_method_manager_ = nullptr;
+  }
 
  protected:
   const std::vector<ui::InputDevice>& GetConnectedKeyboards() {
@@ -192,6 +227,8 @@
 
   std::unique_ptr<AcceleratorConfigurationProvider> provider_;
   base::test::ScopedFeatureList scoped_feature_list_;
+  // Test global singleton. Delete is handled by InputMethodManager::Shutdown().
+  base::raw_ptr<TestInputMethodManager> input_method_manager_;
 };
 
 TEST_F(AcceleratorConfigurationProviderTest, ResetReceiverOnBindInterface) {
@@ -227,6 +264,19 @@
   base::RunLoop().RunUntilIdle();
 }
 
+TEST_F(AcceleratorConfigurationProviderTest, InitialAccelInitCalls) {
+  FakeAcceleratorsUpdatedObserver observer;
+  SetUpObserver(&observer);
+  EXPECT_EQ(0, observer.num_times_notified());
+
+  Shell::Get()->ash_accelerator_configuration()->Initialize();
+  base::RunLoop().RunUntilIdle();
+
+  // Observer is initially notified twice, one for ash accelerators and the
+  // other for deprecated accelerators.
+  EXPECT_EQ(2, observer.num_times_notified());
+}
+
 TEST_F(AcceleratorConfigurationProviderTest, AshAcceleratorsUpdated) {
   FakeAcceleratorsUpdatedObserver observer;
   SetUpObserver(&observer);
@@ -489,6 +539,22 @@
                                observer.config());
 }
 
+TEST_F(AcceleratorConfigurationProviderTest, InputMethodChanged) {
+  FakeAcceleratorsUpdatedObserver observer;
+  SetUpObserver(&observer);
+  EXPECT_EQ(0, observer.num_times_notified());
+  Shell::Get()->ash_accelerator_configuration()->Initialize();
+  base::RunLoop().RunUntilIdle();
+  // Clear extraneous observer calls.
+  observer.clear_num_times_notified();
+  EXPECT_EQ(0, observer.num_times_notified());
+
+  // Change input method, expect observer to be called.
+  input_method_manager_->NotifyInputMethodChanged();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(1, observer.num_times_notified());
+}
+
 }  // namespace shortcut_ui
 
 }  // namespace ash
diff --git a/ash/wm/desks/templates/restore_data_collector.cc b/ash/wm/desks/templates/restore_data_collector.cc
index 390d90c2..84a1aa8 100644
--- a/ash/wm/desks/templates/restore_data_collector.cc
+++ b/ash/wm/desks/templates/restore_data_collector.cc
@@ -145,8 +145,13 @@
   DCHECK(call_it != calls_.end());
   Call& call = call_it->second;
 
+  base::GUID desk_template_guid =
+      call.template_type == DeskTemplateType::kFloatingWorkspace
+          ? base::GUID::ParseLowercase(kFloatingWorkspaceTemplateUuid)
+          : base::GUID::GenerateRandomV4();
+
   auto desk_template = std::make_unique<DeskTemplate>(
-      base::GUID::GenerateRandomV4(), DeskTemplateSource::kUser,
+      std::move(desk_template_guid), DeskTemplateSource::kUser,
       call.template_name, base::Time::Now(), call.template_type);
   desk_template->set_desk_restore_data(std::move(call.data));
 
diff --git a/ash/wm/desks/templates/restore_data_collector.h b/ash/wm/desks/templates/restore_data_collector.h
index 24ce2e3..9b157c8 100644
--- a/ash/wm/desks/templates/restore_data_collector.h
+++ b/ash/wm/desks/templates/restore_data_collector.h
@@ -26,6 +26,9 @@
 
 namespace ash {
 
+inline constexpr char kFloatingWorkspaceTemplateUuid[] =
+    "c098bdcf-5803-484b-9bfd-d3a9a4b497ab";
+
 class DeskTemplate;
 enum class DeskTemplateType;
 
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 440798b9..e5af792 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -36,7 +36,6 @@
 import("//build/config/sysroot.gni")
 import("//build/config/ui.gni")
 import("//build/nocompile.gni")
-import("//build/rust/mixed_component.gni")
 import("//build/timestamp.gni")
 import("//build_overrides/build.gni")
 import("//testing/libfuzzer/fuzzer_test.gni")
@@ -97,23 +96,19 @@
   import("//third_party/fuchsia-sdk/sdk/build/fidl_library.gni")
 }
 
-# TODO(crbug.com/1280708, crbug.com/1304707): Drop toolchain_has_rust after we
-# have support for all our toolchains: NaCl, Linux x86, etc.
-build_rust_json_parser = enable_rust && toolchain_has_rust
+# TODO(crbug.com/1280708): Drop toolchain_has_rust and move the C++ parser into
+# components/nacl to just run in-process there. Don't compile base::JSONReader
+# on NaCL at all.
+#
+# TODO(crbug.com/1304707): Drop toolchain_has_rust after we have support for all
+# our toolchains: Linux x86 is missing in order to build for Android.
+#
+# The Rust implementation of base::JSONReader.
+build_rust_json_reader = toolchain_has_rust && enable_rust_json
 
 buildflag_header("parsing_buildflags") {
   header = "parsing_buildflags.h"
-  flags = [ "BUILD_RUST_JSON_PARSER=$build_rust_json_parser" ]
-}
-
-# TODO(crbug.com/1298039): The BUILDFLAG should be used instead. So
-# buildflag_header() should generate a Rust config (and perhaps a Rust crate
-# which depends on said config, so its usage can be expressed via deps).
-config("todo_buildflag_build_rust_json_parser") {
-  rustflags = [
-    "--cfg",
-    "buildflag__build_rust_json_parser",
-  ]
+  flags = [ "BUILD_RUST_JSON_READER=$build_rust_json_reader" ]
 }
 
 if (is_win) {
@@ -186,10 +181,7 @@
 # base compilation units to be linked in where they wouldn't have otherwise.
 # This does not include test code (test support and anything in the test
 # directory) which should use source_set as is recommended for GN targets).
-#
-# mixed_component is a regular C++ "component", but may have some Rust files
-# if Rust is enabled.
-mixed_component("base") {
+component("base") {
   sources = [
     "allocator/allocator_check.cc",
     "allocator/allocator_check.h",
@@ -281,6 +273,7 @@
     "containers/lru_cache.h",
     "containers/small_map.h",
     "containers/span.h",
+    "containers/span_rust.h",
     "containers/stack.h",
     "containers/stack_container.h",
     "containers/unique_ptr_adapters.h",
@@ -677,6 +670,7 @@
     "strings/string_piece.cc",
     "strings/string_piece.h",
     "strings/string_piece_forward.h",
+    "strings/string_piece_rust.h",
     "strings/string_split.cc",
     "strings/string_split.h",
     "strings/string_split_internal.h",
@@ -1505,41 +1499,14 @@
     "//third_party/modp_b64",
   ]
 
+  if (build_rust_json_reader) {
+    deps += [ "//third_party/rust/serde_json_lenient/v0_1/wrapper" ]
+  }
+
   # native_unwinder_android is intended for use solely via a dynamic feature
   # module, to avoid increasing Chrome's executable size.
   assert_no_deps = [ ":native_unwinder_android" ]
 
-  rs_crate_root = "base.rs"
-  rs_sources = [ "base.rs" ]
-
-  if (build_rust_json_parser) {
-    # TODO(crbug.com/1298039): The BUILDFLAG should be used instead.
-    rs_configs = [ ":todo_buildflag_build_rust_json_parser" ]
-    rs_deps = [
-      "//third_party/rust/serde/v1:lib",
-      "//third_party/rust/serde_json_lenient/v0_1:lib",
-    ]
-    rs_sources += [
-      "json/json_parser.rs",
-      "json/mod.rs",
-      "rs_glue/mod.rs",
-      "values.rs",
-      "values_deserialization.rs",
-    ]
-    rs_cxx_bindings = [
-      "rs_glue/mod.rs",
-      "json/json_parser.rs",
-    ]
-    sources += [
-      "rs_glue/values_glue.cc",
-      "rs_glue/values_glue.h",
-    ]
-    sources -= [
-      "json/json_parser.cc",
-      "json/json_parser.h",
-    ]
-  }
-
   public_deps = [
     ":anchor_functions_buildflags",
     ":base_static",
@@ -1561,9 +1528,16 @@
     "//base/numerics:base_numerics",
     "//build:chromecast_buildflags",
     "//build:chromeos_buildflags",
+    "//build/rust:rust_buildflags",
     "//third_party/abseil-cpp:absl",
   ]
 
+  if (toolchain_has_rust) {
+    # Base provides conversions between CXX types and base types (e.g.
+    # StringPiece).
+    public_deps += [ "//build/rust:cxx_cppdeps" ]
+  }
+
   if (use_custom_libcxx && enable_safe_libcxx && !is_debug) {
     public_deps += [ ":nodebug_assertion" ]
   }
@@ -1634,19 +1608,6 @@
     }
   }
 
-  if (enable_rust && toolchain_has_rust) {
-    # TODO(adetaylor): we include conversions between base and cxx's Rust types.
-    # We assume that any //base client who has flipped 'enable_rust' likely wants
-    # these, but it's conceivable that some clients might want to enable Rust
-    # without adding these cxx conversions. If so we would want to add an
-    # additional gn conditional e.g. enable_rust_binding_in_base
-    sources += [
-      "containers/span_rust.h",
-      "strings/string_piece_rust.h",
-    ]
-    deps += [ "//build/rust:cxx_cppdeps" ]
-  }
-
   if (use_clang_profiling) {
     # Call-sites use this conditional on the CLANG_PROFILING macro, for clarity.
     sources += [
@@ -3153,6 +3114,7 @@
     "containers/linked_list_unittest.cc",
     "containers/lru_cache_unittest.cc",
     "containers/small_map_unittest.cc",
+    "containers/span_rust_unittest.cc",
     "containers/span_unittest.cc",
     "containers/stack_container_unittest.cc",
     "containers/unique_ptr_adapters_unittest.cc",
@@ -3321,6 +3283,7 @@
     "strings/safe_sprintf_unittest.cc",
     "strings/strcat_unittest.cc",
     "strings/string_number_conversions_unittest.cc",
+    "strings/string_piece_rust_unittest.cc",
     "strings/string_piece_unittest.cc",
     "strings/string_split_unittest.cc",
     "strings/string_tokenizer_unittest.cc",
@@ -3474,23 +3437,6 @@
     sources += [ "libcpp_hardening_test.cc" ]
   }
 
-  # TODO(crbug.com/1304253): iOS test() targets don't support mixing Rust code
-  # yet.
-  if (!is_ios) {
-    rs_sources = []
-
-    if (build_rust_json_parser) {
-      # TODO(crbug.com/1298039): The BUILDFLAG should be used instead.
-      rs_configs = [ ":todo_buildflag_build_rust_json_parser" ]
-      rs_sources += [
-        "json/json_parser_unittest.rs",
-        "values_unittest.rs",
-      ]
-
-      sources -= [ "json/json_parser_unittest.cc" ]
-    }
-  }
-
   defines = []
 
   deps = [
@@ -3514,12 +3460,6 @@
     "//third_party/modp_b64",
   ]
 
-  # TODO(crbug.com/1304253): iOS test() targets don't support mixing Rust code
-  # yet.
-  if (!is_ios) {
-    rs_deps = [ ":base" ]
-  }
-
   data_deps = [
     "//base/test:immediate_crash_test_helper",
     "//base/test:test_child_process",
@@ -3527,6 +3467,10 @@
     "//testing/buildbot/filters:base_unittests_filters",
   ]
 
+  if (toolchain_has_rust) {
+    deps += [ "//build/rust:cxx_cppdeps" ]
+  }
+
   if (is_apple) {
     public_deps = [ ":base_unittests_bundle_data" ]
 
@@ -4041,14 +3985,6 @@
       "//third_party/perfetto/protos/perfetto/trace/track_event:lite",
     ]
   }
-
-  if (enable_rust && toolchain_has_rust) {
-    sources += [
-      "containers/span_rust_unittest.cc",
-      "strings/string_piece_rust_unittest.cc",
-    ]
-    deps += [ "//build/rust:cxx_cppdeps" ]
-  }
 }
 
 action("build_date") {
diff --git a/base/DEPS b/base/DEPS
index 33d260c7..e9e595b 100644
--- a/base/DEPS
+++ b/base/DEPS
@@ -15,6 +15,8 @@
   # //build/rust:cxx_cppdeps.
   "+third_party/rust/cxx",
   "+third_party/test_fonts",
+  # JSON Deserialization.
+  "+third_party/rust/serde_json_lenient/v0_1/wrapper",
 
   # These are implicitly brought in from the root, and we don't want them.
   "-ipc",
diff --git a/base/allocator/partition_allocator/address_pool_manager.cc b/base/allocator/partition_allocator/address_pool_manager.cc
index 01f98c9f..0467a2504 100644
--- a/base/allocator/partition_allocator/address_pool_manager.cc
+++ b/base/allocator/partition_allocator/address_pool_manager.cc
@@ -51,7 +51,7 @@
 void AddressPoolManager::Add(pool_handle handle, uintptr_t ptr, size_t length) {
   PA_DCHECK(!(ptr & kSuperPageOffsetMask));
   PA_DCHECK(!((ptr + length) & kSuperPageOffsetMask));
-  PA_CHECK(handle > 0 && handle <= std::size(pools_));
+  PA_CHECK(handle > 0 && handle <= std::size(aligned_pools_.pools_));
 
   Pool* pool = GetPool(handle);
   PA_CHECK(!pool->IsInitialized());
@@ -77,8 +77,8 @@
 }
 
 void AddressPoolManager::ResetForTesting() {
-  for (pool_handle i = 0; i < std::size(pools_); ++i)
-    pools_[i].Reset();
+  for (pool_handle i = 0; i < std::size(aligned_pools_.pools_); ++i)
+    aligned_pools_.pools_[i].Reset();
 }
 
 void AddressPoolManager::Remove(pool_handle handle) {
diff --git a/base/allocator/partition_allocator/address_pool_manager.h b/base/allocator/partition_allocator/address_pool_manager.h
index 42da662..7b4d4ba 100644
--- a/base/allocator/partition_allocator/address_pool_manager.h
+++ b/base/allocator/partition_allocator/address_pool_manager.h
@@ -152,7 +152,7 @@
 
   PA_ALWAYS_INLINE Pool* GetPool(pool_handle handle) {
     PA_DCHECK(0 < handle && handle <= kNumPools);
-    return &pools_[handle - 1];
+    return &aligned_pools_.pools_[handle - 1];
   }
 
   // Gets the stats for the pool identified by `handle`, if
@@ -162,11 +162,11 @@
   // If pkey support is enabled, we need to pkey-tag the pkey pool (which needs
   // to be last). For this, we need to add padding in front of the pools so that
   // pkey one starts on a page boundary.
-  PA_PKEY_ALIGN struct {
+  struct {
     char pad_[PA_PKEY_ARRAY_PAD_SZ(Pool, kNumPools)] = {};
     Pool pools_[kNumPools];
     char pad_after_[PA_PKEY_FILL_PAGE_SZ(sizeof(Pool))] = {};
-  };
+  } aligned_pools_ PA_PKEY_ALIGN;
 
 #endif  // defined(PA_HAS_64_BITS_POINTERS)
 
diff --git a/base/allocator/partition_allocator/partition_alloc_base/cpu.cc b/base/allocator/partition_allocator/partition_alloc_base/cpu.cc
index 6a1d6c66..97bf240 100644
--- a/base/allocator/partition_allocator/partition_alloc_base/cpu.cc
+++ b/base/allocator/partition_allocator/partition_alloc_base/cpu.cc
@@ -148,7 +148,7 @@
     has_fma3_ = (cpu_info[2] & 0x00001000) != 0;
     has_avx2_ = has_avx_ && (cpu_info7[1] & 0x00000020) != 0;
 
-    has_pku_ = (cpu_info7[2] & 0x00000008) != 0;
+    has_pku_ = (cpu_info7[2] & 0x00000010) != 0;
   }
 
   // Get the brand string of the cpu.
diff --git a/base/base.rs b/base/base.rs
deleted file mode 100644
index 1373f3e..0000000
--- a/base/base.rs
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2021 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// TODO(crbug.com/1298039): A buildflag macro should be used instead.
-#[cfg(buildflag__build_rust_json_parser)]
-mod json;
-// TODO(crbug.com/1298039): A buildflag macro should be used instead.
-#[cfg(buildflag__build_rust_json_parser)]
-mod rs_glue;
-// TODO(crbug.com/1298039): A buildflag macro should be used instead.
-#[cfg(buildflag__build_rust_json_parser)]
-mod values;
-// TODO(crbug.com/1298039): A buildflag macro should be used instead.
-#[cfg(buildflag__build_rust_json_parser)]
-mod values_deserialization;
-
-// TODO(crbug.com/1298039): A buildflag macro should be used instead.
-#[cfg(buildflag__build_rust_json_parser)]
-pub use json::json_parser::{decode_json, JsonOptions};
-// TODO(crbug.com/1298039): A buildflag macro should be used instead.
-#[cfg(buildflag__build_rust_json_parser)]
-pub use values::ValueSlotRef;
-
-// TODO(crbug.com/1298039): A buildflag macro should be used instead.
-#[cfg(buildflag__build_rust_json_parser)]
-pub use rs_glue::ffi::NewValueSlotForTesting;
diff --git a/base/base_unittests.rs b/base/base_unittests.rs
deleted file mode 100644
index 235bb2f..0000000
--- a/base/base_unittests.rs
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// TODO(crbug.com/1298039): A buildflag macro should be used instead.
-#[cfg(buildflag__build_rust_json_parser)]
-#[path = "values_unittest.rs"]
-mod values_unittest;
-
-// TODO(crbug.com/1298039): A buildflag macro should be used instead.
-#[cfg(buildflag__build_rust_json_parser)]
-#[path = "json/json_parser_unittest.rs"]
-mod json_parser_unittest;
diff --git a/base/check_op.h b/base/check_op.h
index 2119365..9bc38d9 100644
--- a/base/check_op.h
+++ b/base/check_op.h
@@ -188,7 +188,7 @@
   constexpr ::logging::CheckOpResult Check##name##Impl(                     \
       bool is_dcheck, const char* file, int line, const T& v1, const U& v2, \
       const char* expr_str) {                                               \
-    if (ANALYZER_ASSUME_TRUE(v1 op v2))                                     \
+    if (LIKELY(ANALYZER_ASSUME_TRUE(v1 op v2)))                             \
       return ::logging::CheckOpResult();                                    \
     return CheckOpResult(CheckOpResult::CreateLogMessage(                   \
         is_dcheck, file, line, expr_str, CheckOpValueStr(v1),               \
@@ -201,7 +201,7 @@
   constexpr ::logging::CheckOpResult Check##name##Impl(                     \
       bool is_dcheck, const char* file, int line, T v1, U v2,               \
       const char* expr_str) {                                               \
-    if (ANALYZER_ASSUME_TRUE(v1 op v2))                                     \
+    if (LIKELY(ANALYZER_ASSUME_TRUE(v1 op v2)))                             \
       return ::logging::CheckOpResult();                                    \
     return CheckOpResult(CheckOpResult::CreateLogMessage(                   \
         is_dcheck, file, line, expr_str, CheckOpValueStr(v1),               \
diff --git a/base/containers/span_rust.h b/base/containers/span_rust.h
index aa6d11c..2d065d3c 100644
--- a/base/containers/span_rust.h
+++ b/base/containers/span_rust.h
@@ -5,6 +5,10 @@
 #ifndef BASE_CONTAINERS_SPAN_RUST_H_
 #define BASE_CONTAINERS_SPAN_RUST_H_
 
+#include "build/rust/rust_buildflags.h"
+
+#if BUILDFLAG(TOOLCHAIN_HAS_RUST)
+
 #include <stdint.h>
 
 #include "base/containers/span.h"
@@ -19,4 +23,6 @@
 
 }  // namespace base
 
+#endif  // BUILDFLAG(TOOLCHAIN_HAS_RUST)
+
 #endif  // BASE_CONTAINERS_SPAN_RUST_H_
diff --git a/base/containers/span_rust_unittest.cc b/base/containers/span_rust_unittest.cc
index 94ac591..aaa06c5 100644
--- a/base/containers/span_rust_unittest.cc
+++ b/base/containers/span_rust_unittest.cc
@@ -3,9 +3,12 @@
 // found in the LICENSE file.
 
 #include "base/containers/span_rust.h"
+#include "build/rust/rust_buildflags.h"
 
 #include "testing/gtest/include/gtest/gtest.h"
 
+#if BUILDFLAG(TOOLCHAIN_HAS_RUST)
+
 namespace base {
 namespace {
 
@@ -19,3 +22,5 @@
 
 }  // namespace
 }  // namespace base
+
+#endif  // BUILDFLAG(TOOLCHAIN_HAS_RUST)
diff --git a/base/cpu.cc b/base/cpu.cc
index 7c3c2f3..ac7655cd 100644
--- a/base/cpu.cc
+++ b/base/cpu.cc
@@ -289,7 +289,7 @@
     has_fma3_ = (cpu_info[2] & 0x00001000) != 0;
     has_avx2_ = has_avx_ && (cpu_info7[1] & 0x00000020) != 0;
 
-    has_pku_ = (cpu_info7[2] & 0x00000008) != 0;
+    has_pku_ = (cpu_info7[2] & 0x00000010) != 0;
   }
 
   // Get the brand string of the cpu.
diff --git a/base/json/json_parser.rs b/base/json/json_parser.rs
deleted file mode 100644
index 58c8861..0000000
--- a/base/json/json_parser.rs
+++ /dev/null
@@ -1,176 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// This file contains bindings to convert from JSON to C++ base::Value objects.
-// The code related to `base::Value` can be found in 'values.rs'
-// and 'values_deserialization.rs'.
-
-use crate::values::ValueSlotRef;
-use crate::values_deserialization::ValueVisitor;
-use cxx::CxxString;
-use serde::de::Deserializer;
-use serde_json_lenient::de::SliceRead;
-use std::pin::Pin;
-
-// UTF8 byte order mark.
-const UTF8_BOM: [u8; 3] = [0xef, 0xbb, 0xbf];
-
-#[cxx::bridge(namespace=base::rs_glue)]
-mod ffi {
-    /// Options for parsing JSON inputs. Largely a mirror of the C++ `base::JSONParserOptions`
-    /// bitflags, represented as a friendlier struct-of-bools instead.
-    #[namespace=base::ffi::json::json_parser]
-    struct JsonOptions {
-        /// Allows commas to exist after the last element in structures.
-        allow_trailing_commas: bool,
-        /// If set the parser replaces invalid code points (i.e. lone surrogates) with the Unicode
-        /// replacement character (U+FFFD). If not set, invalid code points trigger a hard error and
-        /// parsing fails.
-        replace_invalid_characters: bool,
-        /// Allows both C (/* */) and C++ (//) style comments.
-        allow_comments: bool,
-        /// Permits unescaped ASCII control characters (such as unescaped \r and \n) in the range
-        /// [0x00,0x1F].
-        allow_control_chars: bool,
-        /// Permits \\v vertical tab escapes.
-        allow_vert_tab: bool,
-        /// Permits \\xNN escapes as described above.
-        allow_x_escapes: bool,
-        /// The maximum recursion depth to walk while parsing nested JSON objects. JSON beyond the
-        /// specified depth will be ignored.
-        max_depth: usize,
-    }
-
-    unsafe extern "C++" {
-        include!("base/rs_glue/values_glue.h");
-        type ValueSlot = crate::rs_glue::ffi::ValueSlot;
-    }
-
-    extern "Rust" {
-        #[namespace=base::ffi::json::json_parser]
-        pub fn decode_json_from_cpp(
-            json: &[u8],
-            options: JsonOptions,
-            value_slot: Pin<&mut ValueSlot>,
-            error_line: &mut i32,
-            error_column: &mut i32,
-            error_message: Pin<&mut CxxString>,
-        ) -> bool;
-    }
-}
-
-pub type JsonOptions = ffi::JsonOptions;
-impl JsonOptions {
-    /// Construct a JsonOptions with common Chromium extensions.
-    ///
-    /// Per base::JSONParserOptions::JSON_PARSE_CHROMIUM_EXTENSIONS:
-    ///
-    /// This parser historically accepted, without configuration flags,
-    /// non-standard JSON extensions. This enables that traditional parsing
-    /// behavior.
-    pub fn with_chromium_extensions(max_depth: usize) -> JsonOptions {
-        JsonOptions {
-            allow_trailing_commas: false,
-            replace_invalid_characters: false,
-            allow_comments: true,
-            allow_control_chars: true,
-            allow_vert_tab: true,
-            allow_x_escapes: true,
-            max_depth,
-        }
-    }
-}
-
-/// Decode some JSON into C++ base::Value object tree.
-///
-/// This function takes and returns normal Rust types. For an equivalent which
-/// can be called from C++, see `decode_json_from_cpp`.
-///
-/// # Args:
-///
-/// * `json`: the JSON. Note that this is a slice of bytes rather than a string,
-///           which in Rust terms means it hasn't yet been validated to be
-///           legitimate UTF8. The JSON decoding will do that.
-/// * `options`: configuration options for non-standard JSON extensions
-/// * `value_slot`: a space into which to construct a base::Value
-///
-/// It always strips a UTF8 BOM from the start of the string, if one is found.
-///
-/// Return: a serde_json_lenient::Error or Ok.
-///
-/// It is be desirable in future to expose this API to other Rust code inside
-/// and outside //base. TODO(crbug/1287209): work out API norms and then add
-/// 'pub' to do this.
-pub fn decode_json(
-    json: &[u8],
-    options: JsonOptions,
-    value_slot: ValueSlotRef,
-) -> Result<(), serde_json_lenient::Error> {
-    let mut to_parse = json;
-    if to_parse.len() >= 3 && to_parse[0..3] == UTF8_BOM {
-        to_parse = &to_parse[3..];
-    }
-    let mut deserializer = serde_json_lenient::Deserializer::new(SliceRead::new(
-        &to_parse,
-        options.replace_invalid_characters,
-        options.allow_control_chars,
-        options.allow_vert_tab,
-        options.allow_x_escapes,
-    ));
-    // By default serde_json[rc] has a recursion limit of 128.
-    // As we want different recursion limits in different circumstances,
-    // we disable its own recursion tracking and use our own.
-    deserializer.disable_recursion_limit();
-    deserializer.set_ignore_trailing_commas(options.allow_trailing_commas);
-    deserializer.set_allow_comments(options.allow_comments);
-    // The C++ parser starts counting nesting levels from the first item
-    // inside the outermost dict. We start counting from the
-    // absl::optional<base::Value> and also count the outermost dict,
-    // therefore we start with -2 to match C++ behavior.
-    let result =
-        deserializer.deserialize_any(ValueVisitor::new(value_slot, options.max_depth - 2))?;
-    deserializer.end()?;
-    Ok(result)
-}
-
-/// Decode some JSON into a `base::Value`; for calling by C++.
-///
-/// See `decode_json` for an equivalent which takes and returns idiomatic Rust
-/// types, and a little bit more information about the implementation.
-///
-/// # Args
-///
-/// * `json`: a slice of input JSON unsigned characters.
-/// * `options`: configuration options for non-standard JSON extensions
-/// * `value_slot`:  a space into which to construct a base::Value
-/// * `error_line`/`error_column`/`error_message`: populated with details of
-///                                                any decode error.
-///
-/// # Returns
-///
-/// A Boolean indicating whether the decode succeeded.
-fn decode_json_from_cpp(
-    json: &[u8],
-    options: ffi::JsonOptions,
-    value_slot: Pin<&mut ffi::ValueSlot>,
-    error_line: &mut i32,
-    error_column: &mut i32,
-    mut error_message: Pin<&mut CxxString>,
-) -> bool {
-    let value_slot = ValueSlotRef::from(value_slot);
-    match decode_json(json, options, value_slot) {
-        Err(err) => {
-            *error_line = err.line().try_into().unwrap_or(-1);
-            *error_column = err.column().try_into().unwrap_or(-1);
-            error_message.as_mut().clear();
-            // The following line pulls in a lot of binary bloat, due to all the formatter
-            // implementations required to stringify error messages. This error message is used in
-            // only a couple of places outside unit tests so we could consider trying
-            // to eliminate.
-            error_message.as_mut().push_str(&err.to_string());
-            false
-        }
-        Ok(_) => true,
-    }
-}
diff --git a/base/json/json_parser_unittest.rs b/base/json/json_parser_unittest.rs
deleted file mode 100644
index 6bb9b733..0000000
--- a/base/json/json_parser_unittest.rs
+++ /dev/null
@@ -1,33 +0,0 @@
-use base::{JsonOptions, NewValueSlotForTesting, ValueSlotRef};
-use rust_gtest_interop::prelude::*;
-
-#[gtest(RustJsonParserTest, ChromiumExtensions)]
-fn test_chromium_extensions() {
-    let opts = JsonOptions::with_chromium_extensions(101);
-    expect_eq!(opts.allow_trailing_commas, false);
-    expect_eq!(opts.replace_invalid_characters, false);
-    expect_eq!(opts.allow_comments, true);
-    expect_eq!(opts.allow_control_chars, true);
-    expect_eq!(opts.allow_vert_tab, true);
-    expect_eq!(opts.allow_x_escapes, true);
-    expect_eq!(opts.max_depth, 101);
-}
-
-#[gtest(RustJsonParserTest, DecodeJson)]
-fn test_decode_json() {
-    // Exhaustively tested by existing C++ JSON tests.
-    // This test is almost pointless but it seems wise to have a single
-    // Rust-side test for the basics.
-    let options = JsonOptions {
-        max_depth: 128,
-        allow_trailing_commas: false,
-        replace_invalid_characters: false,
-        allow_comments: false,
-        allow_control_chars: false,
-        allow_vert_tab: false,
-        allow_x_escapes: false,
-    };
-    let mut value_slot = NewValueSlotForTesting();
-    base::decode_json(b"{ \"a\": 4 }", options, ValueSlotRef::from(&mut value_slot)).unwrap();
-    expect_eq!(format!("{:?}", ValueSlotRef::from(&mut value_slot)), "{\n   \"a\": 4\n}\n");
-}
diff --git a/base/json/json_reader.cc b/base/json/json_reader.cc
index 065bb899..5b77bc2 100644
--- a/base/json/json_reader.cc
+++ b/base/json/json_reader.cc
@@ -10,72 +10,141 @@
 #include "base/parsing_buildflags.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
-#if BUILDFLAG(BUILD_RUST_JSON_PARSER)
-#include "base/json/json_parser.rs.h"
+#if BUILDFLAG(BUILD_RUST_JSON_READER)
 #include "base/strings/string_piece_rust.h"
+#include "third_party/rust/serde_json_lenient/v0_1/wrapper/functions.h"
+#include "third_party/rust/serde_json_lenient/v0_1/wrapper/lib.rs.h"
 #else
 #include "base/json/json_parser.h"
 #endif
 
 namespace base {
 
-#if BUILDFLAG(BUILD_RUST_JSON_PARSER)
+#if BUILDFLAG(BUILD_RUST_JSON_READER)
 
 namespace {
+using serde_json_lenient::ContextPointer;
 
-base::expected<Value, JSONReader::Error>
-DecodeJSONInRust(const base::StringPiece& json, int options, size_t max_depth) {
-  int32_t error_line;
-  int32_t error_column;
-  base::ffi::json::json_parser::JsonOptions rust_options;
-  rust_options.allow_trailing_commas =
-      options & base::JSON_ALLOW_TRAILING_COMMAS;
-  rust_options.replace_invalid_characters =
-      options & base::JSON_REPLACE_INVALID_CHARACTERS;
-  rust_options.allow_comments = options & base::JSON_ALLOW_COMMENTS;
-  rust_options.allow_vert_tab = options & base::JSON_ALLOW_VERT_TAB;
-  rust_options.allow_control_chars = options & base::JSON_ALLOW_CONTROL_CHARS;
-  rust_options.allow_x_escapes = options & base::JSON_ALLOW_X_ESCAPES;
-  rust_options.max_depth = max_depth;
-  base::JSONReader::Error error;
-  absl::optional<base::Value> value;
-  bool ok = base::ffi::json::json_parser::decode_json_from_cpp(
-      base::StringPieceToRustSlice(json), rust_options, value, error_line,
-      error_column, error.message);
+ContextPointer& ListAppendList(ContextPointer& ctx, size_t reserve) {
+  auto& value = reinterpret_cast<base::Value&>(ctx);
+  value.GetList().reserve(reserve);
+  value.GetList().Append(base::Value::List());
+  return reinterpret_cast<ContextPointer&>(value.GetList().back());
+}
+
+ContextPointer& ListAppendDict(ContextPointer& ctx) {
+  auto& value = reinterpret_cast<base::Value&>(ctx);
+  value.GetList().Append(base::Value::Dict());
+  return reinterpret_cast<ContextPointer&>(value.GetList().back());
+}
+
+void ListAppendNone(ContextPointer& ctx) {
+  auto& value = reinterpret_cast<base::Value&>(ctx);
+  value.GetList().Append(base::Value());
+}
+
+template <class T, class As = T>
+void ListAppendValue(ContextPointer& ctx, T v) {
+  auto& value = reinterpret_cast<base::Value&>(ctx);
+  value.GetList().Append(As{v});
+}
+
+ContextPointer& DictSetList(ContextPointer& ctx,
+                            rust::Str key,
+                            size_t reserve) {
+  auto& value = reinterpret_cast<base::Value&>(ctx);
+  base::Value::List list;
+  list.reserve(reserve);
+  value.SetKey(base::RustStrToStringPiece(key), base::Value(std::move(list)));
+  return reinterpret_cast<ContextPointer&>(
+      *value.GetDict().Find(base::RustStrToStringPiece(key)));
+}
+
+ContextPointer& DictSetDict(ContextPointer& ctx, rust::Str key) {
+  auto& value = reinterpret_cast<base::Value&>(ctx);
+  value.SetKey(base::RustStrToStringPiece(key),
+               base::Value(base::Value::Dict()));
+  return reinterpret_cast<ContextPointer&>(
+      *value.GetDict().Find(base::RustStrToStringPiece(key)));
+}
+
+void DictSetNone(ContextPointer& ctx, rust::Str key) {
+  auto& value = reinterpret_cast<base::Value&>(ctx);
+  value.SetKey(base::RustStrToStringPiece(key), base::Value());
+}
+
+template <class T, class As = T>
+void DictSetValue(ContextPointer& ctx, rust::Str key, T v) {
+  auto& value = reinterpret_cast<base::Value&>(ctx);
+  value.SetKey(base::RustStrToStringPiece(key), base::Value(As{v}));
+}
+
+JSONReader::Result DecodeJSONInRust(const base::StringPiece& json,
+                                    int options,
+                                    size_t max_depth) {
+  const serde_json_lenient::JsonOptions rust_options = {
+      .allow_trailing_commas =
+          (options & base::JSON_ALLOW_TRAILING_COMMAS) != 0,
+      .replace_invalid_characters =
+          (options & base::JSON_REPLACE_INVALID_CHARACTERS) != 0,
+      .allow_comments = (options & base::JSON_ALLOW_COMMENTS) != 0,
+      .allow_control_chars = (options & base::JSON_ALLOW_CONTROL_CHARS) != 0,
+      .allow_vert_tab = (options & base::JSON_ALLOW_VERT_TAB) != 0,
+      .allow_x_escapes = (options & base::JSON_ALLOW_X_ESCAPES) != 0,
+      .max_depth = max_depth,
+  };
+  const serde_json_lenient::Functions functions = {
+      .list_append_none_fn = ListAppendNone,
+      .list_append_bool_fn = ListAppendValue<bool>,
+      .list_append_i32_fn = ListAppendValue<int32_t>,
+      .list_append_f64_fn = ListAppendValue<double>,
+      .list_append_str_fn = ListAppendValue<rust::Str, std::string>,
+      .list_append_list_fn = ListAppendList,
+      .list_append_dict_fn = ListAppendDict,
+      .dict_set_none_fn = DictSetNone,
+      .dict_set_bool_fn = DictSetValue<bool>,
+      .dict_set_i32_fn = DictSetValue<int32_t>,
+      .dict_set_f64_fn = DictSetValue<double>,
+      .dict_set_str_fn = DictSetValue<rust::Str, std::string>,
+      .dict_set_list_fn = DictSetList,
+      .dict_set_dict_fn = DictSetDict,
+  };
+
+  base::Value value(base::Value::Type::LIST);
+  auto& ctx = reinterpret_cast<ContextPointer&>(value);
+  serde_json_lenient::DecodeError error;
+  bool ok = serde_json_lenient::decode_json(
+      base::StringPieceToRustSlice(json), rust_options, functions, ctx, error);
+
   if (!ok) {
-    error.line = error_line;
-    error.column = error_column;
-    return base::unexpected(std::move(error));
+    return base::unexpected(base::JSONReader::Error{
+        .message = std::string(error.message),
+        .line = error.line,
+        .column = error.column,
+    });
   }
-  return std::move(*value);
+
+  return std::move(std::move(value.GetList()).back());
 }
 
 }  // anonymous namespace
 
-#endif  // BUILDFLAG(BUILD_RUST_JSON_PARSER)
-
-JSONReader::Error::Error() = default;
-
-JSONReader::Error::Error(Error&& other) = default;
-
-JSONReader::Error::~Error() = default;
-
-JSONReader::Error& JSONReader::Error::operator=(Error&& other) = default;
+#endif  // BUILDFLAG(BUILD_RUST_JSON_READER)
 
 // static
 absl::optional<Value> JSONReader::Read(StringPiece json,
                                        int options,
                                        size_t max_depth) {
-#if BUILDFLAG(BUILD_RUST_JSON_PARSER)
-  auto result = DecodeJSONInRust(json, options, max_depth);
+#if BUILDFLAG(BUILD_RUST_JSON_READER)
+  JSONReader::Result result = DecodeJSONInRust(json, options, max_depth);
   if (!result.has_value()) {
     return absl::nullopt;
   }
   return std::move(*result);
-#else   // BUILDFLAG(BUILD_RUST_JSON_PARSER)
+#else   // BUILDFLAG(BUILD_RUST_JSON_READER)
   internal::JSONParser parser(options, max_depth);
   return parser.Parse(json);
-#endif  // BUILDFLAG(BUILD_RUST_JSON_PARSER)
+#endif  // BUILDFLAG(BUILD_RUST_JSON_READER)
 }
 
 // static
@@ -89,9 +158,9 @@
 // static
 JSONReader::Result JSONReader::ReadAndReturnValueWithError(StringPiece json,
                                                            int options) {
-#if BUILDFLAG(BUILD_RUST_JSON_PARSER)
+#if BUILDFLAG(BUILD_RUST_JSON_READER)
   return DecodeJSONInRust(json, options, internal::kAbsoluteMaxDepth);
-#else   // BUILDFLAG(BUILD_RUST_JSON_PARSER)
+#else   // BUILDFLAG(BUILD_RUST_JSON_READER)
   internal::JSONParser parser(options);
   auto value = parser.Parse(json);
   if (!value) {
@@ -103,7 +172,7 @@
   }
 
   return std::move(*value);
-#endif  // BUILDFLAG(BUILD_RUST_JSON_PARSER)
+#endif  // BUILDFLAG(BUILD_RUST_JSON_READER)
 }
 
 }  // namespace base
diff --git a/base/json/json_reader.h b/base/json/json_reader.h
index cb3b7d5..466bff3c 100644
--- a/base/json/json_reader.h
+++ b/base/json/json_reader.h
@@ -88,15 +88,6 @@
 class BASE_EXPORT JSONReader {
  public:
   struct BASE_EXPORT Error {
-    Error();
-    Error(Error&& other);
-    Error& operator=(Error&& other);
-
-    Error(const Error&) = delete;
-    Error& operator=(const Error&) = delete;
-
-    ~Error();
-
     std::string message;
     int line = 0;
     int column = 0;
diff --git a/base/json/mod.rs b/base/json/mod.rs
deleted file mode 100644
index 3bc09b6f..0000000
--- a/base/json/mod.rs
+++ /dev/null
@@ -1,5 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-pub mod json_parser;
diff --git a/base/numerics/checked_math.h b/base/numerics/checked_math.h
index a46f5ca..c84a4f9 100644
--- a/base/numerics/checked_math.h
+++ b/base/numerics/checked_math.h
@@ -41,11 +41,10 @@
 
   // This is not an explicit constructor because we implicitly upgrade regular
   // numerics to CheckedNumerics to make them easier to use.
-  template <typename Src>
+  template <typename Src,
+            typename = std::enable_if_t<std::is_arithmetic<Src>::value>>
   // NOLINTNEXTLINE(google-explicit-constructor)
-  constexpr CheckedNumeric(Src value) : state_(value) {
-    static_assert(UnderlyingType<Src>::is_numeric, "Argument must be numeric.");
-  }
+  constexpr CheckedNumeric(Src value) : state_(value) {}
 
   // This is not an explicit constructor because we want a seamless conversion
   // from StrictNumeric types.
diff --git a/base/power_monitor/battery_level_provider.h b/base/power_monitor/battery_level_provider.h
index 5f4b4dd8..655d06f9 100644
--- a/base/power_monitor/battery_level_provider.h
+++ b/base/power_monitor/battery_level_provider.h
@@ -48,10 +48,6 @@
     // Fully charged battery capacity. nullopt if `battery_count` != 1.
     absl::optional<uint64_t> full_charged_capacity;
 
-    // The voltage of the battery. Only available on MacOS. nullopt if
-    // `battery_count` != 1.
-    absl::optional<uint64_t> voltage_mv;
-
     // The unit of the battery's charge. Usually kMWh (milliwatt-hour) but can
     // be relative on Windows. nullopt if `battery_count` != 1.
     absl::optional<BatteryLevelUnit> charge_unit;
@@ -103,9 +99,6 @@
     // The battery's fully charged capacity.
     uint64_t full_charged_capacity;
 
-    // The voltage of the battery. Only available on MacOS.
-    absl::optional<uint64_t> voltage_mv;
-
     // The battery's unit of charge.
     BatteryLevelUnit charge_unit;
 
diff --git a/base/power_monitor/battery_level_provider_mac.mm b/base/power_monitor/battery_level_provider_mac.mm
index e326543..62c99ed 100644
--- a/base/power_monitor/battery_level_provider_mac.mm
+++ b/base/power_monitor/battery_level_provider_mac.mm
@@ -133,12 +133,16 @@
   DCHECK_GE(*max_capacity, 0);
   DCHECK_GE(*voltage_mv, 0);
 
-  return MakeBatteryState({BatteryDetails{
-      .is_external_power_connected = external_connected.value(),
-      .current_capacity = static_cast<uint64_t>(current_capacity.value()),
-      .full_charged_capacity = static_cast<uint64_t>(max_capacity.value()),
-      .voltage_mv = static_cast<uint64_t>(voltage_mv.value()),
-      .charge_unit = BatteryLevelUnit::kMAh}});
+  uint64_t current_capacity_mwh = static_cast<uint64_t>(
+      current_capacity.value() * voltage_mv.value() / 1000);
+  uint64_t max_capacity_mwh =
+      static_cast<uint64_t>(max_capacity.value() * voltage_mv.value() / 1000);
+
+  return MakeBatteryState(
+      {BatteryDetails{.is_external_power_connected = external_connected.value(),
+                      .current_capacity = current_capacity_mwh,
+                      .full_charged_capacity = max_capacity_mwh,
+                      .charge_unit = BatteryLevelUnit::kMWh}});
 }
 
 }  // namespace base
diff --git a/base/rs_glue/mod.rs b/base/rs_glue/mod.rs
deleted file mode 100644
index 9585a096..0000000
--- a/base/rs_glue/mod.rs
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright 2021 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/// C++ bindings for base.
-///
-/// This mod contains all the FFI bindings for C++ types in the base
-/// namespace. It should declare shared types, and the 'extern "C++"'
-/// section indicating C++ functions that are callable from Rust.
-///
-/// Other [cxx::bridge] mods throughout the //base codebase may exist
-/// for exporting Rust functions to C++.
-///
-/// C++ functions which exist only for the benefit of calls from
-/// Rust->C++ should live within the base::rs_glue C++ namespace.
-#[cxx::bridge(namespace=base::rs_glue)]
-pub(crate) mod ffi {
-    unsafe extern "C++" {
-        include!("base/rs_glue/values_glue.h");
-        /// C++ `base::Value`
-        #[namespace=base]
-        type Value;
-
-        // Bindings to existing base::Value methods which happen to
-        // line up with our needs precisely.
-        #[cxx_name = "Append"]
-        fn ValueAppendBool(self: Pin<&mut Value>, val: bool);
-        #[cxx_name = "Append"]
-        fn ValueAppendInteger(self: Pin<&mut Value>, val: i32);
-        #[cxx_name = "Append"]
-        fn ValueAppendDouble(self: Pin<&mut Value>, val: f64);
-
-        // Free functions in C++ for cases where existing base::Value
-        // APIs don't quite match our needs.
-
-        // Set a key on a base::Value of type DICTIONARY to the given
-        // value.
-        fn ValueSetNoneKey(v: Pin<&mut Value>, key: &str);
-        fn ValueSetBoolKey(v: Pin<&mut Value>, key: &str, val: bool);
-        fn ValueSetIntegerKey(v: Pin<&mut Value>, key: &str, val: i32);
-        fn ValueSetDoubleKey(v: Pin<&mut Value>, key: &str, val: f64);
-        fn ValueSetStringKey(v: Pin<&mut Value>, key: &str, value: &str);
-        // Returns a new child base::Value, of type DICTIONARY.
-        fn ValueSetDictKey<'a>(v: Pin<&'a mut Value>, key: &str) -> Pin<&'a mut Value>;
-        // Returns a new child base::Value, of type LIST.
-        fn ValueSetListKey<'a>(v: Pin<&'a mut Value>, key: &str) -> Pin<&'a mut Value>;
-
-        // Appends to a base::Value of type LIST.
-        fn ValueAppendNone(v: Pin<&mut Value>);
-        fn ValueAppendString(v: Pin<&mut Value>, value: &str);
-        // Returns a new child base::Value, of type DICTIONARY.
-        fn ValueAppendDict(v: Pin<&mut Value>) -> Pin<&mut Value>;
-        // Returns a new child base::Value, of type LIST.
-        fn ValueAppendList(v: Pin<&mut Value>) -> Pin<&mut Value>;
-        fn ValueReserveSize(v: Pin<&mut Value>, len: usize);
-
-        /// Represents a slot (on stack or heap) into which a new
-        /// `base::Value` can be constructed. Only C++ code can construct
-        /// such a slot.
-        ///
-        /// This type exists because we want to expose many functions
-        /// which *construct* a `base::Value` which needs to live somewhere.
-        /// For that reason, we can't simply extract a reference
-        /// to the underlying base::Value because it doesn't yet exist.
-        /// Future interop tools should support constructing a base::Value
-        /// in Rust and moving it (by value).
-        /// TODO(crbug.com/1272780): Use bindgen-like tools to generate
-        /// direct bindings to C++ constructors.
-        type ValueSlot;
-
-        // Construct a base::Value of the given type, with the given value.
-        fn ConstructNoneValue(v: Pin<&mut ValueSlot>);
-        fn ConstructBoolValue(v: Pin<&mut ValueSlot>, val: bool);
-        fn ConstructIntegerValue(v: Pin<&mut ValueSlot>, val: i32);
-        fn ConstructDoubleValue(v: Pin<&mut ValueSlot>, val: f64);
-        fn ConstructStringValue(v: Pin<&mut ValueSlot>, value: &str);
-        // Returns a reference to the base::Value within the
-        // `absl::optional<base::Value>` (which is of type DICTIONARY)
-        // so that it can be populated.
-        fn ConstructDictValue(v: Pin<&mut ValueSlot>) -> Pin<&mut Value>;
-        // Returns a reference to the base::Value within the
-        // `absl::optional<base::Value>` (which is of type LIST)
-        // so that it can be populated.
-        fn ConstructListValue(v: Pin<&mut ValueSlot>) -> Pin<&mut Value>;
-
-        fn DumpValueSlot(v: &ValueSlot) -> String;
-
-        // Defined for Rust tests to crate a ValueSlot, because it requires C++ to do the creation
-        // at this time.
-        fn NewValueSlotForTesting() -> UniquePtr<ValueSlot>;
-    }
-}
diff --git a/base/rs_glue/values_glue.cc b/base/rs_glue/values_glue.cc
deleted file mode 100644
index ca3d57cf..0000000
--- a/base/rs_glue/values_glue.cc
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright 2021 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "base/rs_glue/values_glue.h"
-#include <stddef.h>
-#include <sstream>
-
-namespace base {
-namespace rs_glue {
-
-// This file has functions which are called from Rust code to populate
-// bits of a base::Value. The functions exist because Rust C++ FFI
-// is not yet quite good enough to operate on a base::Value directly
-// without these intermediate layer. With future inprovements in interop,
-// they may disappear.
-
-std::unique_ptr<ValueSlot> NewValueSlotForTesting() {
-  return std::make_unique<ValueSlot>();
-}
-
-void ValueSetNoneKey(base::Value& v, rust::Str key) {
-  v.GetDict().Set(base::RustStrToStringPiece(key), base::Value());
-}
-
-void ValueSetBoolKey(base::Value& v, rust::Str key, bool value) {
-  v.GetDict().Set(base::RustStrToStringPiece(key), value);
-}
-
-void ValueSetIntegerKey(base::Value& v, rust::Str key, int value) {
-  v.GetDict().Set(base::RustStrToStringPiece(key), value);
-}
-
-void ValueSetDoubleKey(base::Value& v, rust::Str key, double value) {
-  v.GetDict().Set(base::RustStrToStringPiece(key), value);
-}
-
-void ValueSetStringKey(base::Value& v, rust::Str key, rust::Str value) {
-  v.GetDict().Set(base::RustStrToStringPiece(key),
-                  base::RustStrToStringPiece(value));
-}
-
-base::Value& ValueSetDictKey(base::Value& v, rust::Str key) {
-  return *v.GetDict().Set(base::RustStrToStringPiece(key), base::Value::Dict());
-}
-
-base::Value& ValueSetListKey(base::Value& v, rust::Str key) {
-  return *v.GetDict().Set(base::RustStrToStringPiece(key), base::Value::List());
-}
-
-void ValueAppendNone(base::Value& v) {
-  v.GetList().Append(base::Value());
-}
-
-void ValueAppendString(base::Value& v, rust::Str value) {
-  v.GetList().Append(base::RustStrToStringPiece(value));
-}
-
-base::Value& ValueAppendDict(base::Value& v) {
-  v.GetList().Append(base::Value::Dict());
-  return v.GetList().back();
-}
-
-base::Value& ValueAppendList(base::Value& v) {
-  v.GetList().Append(base::Value::List());
-  return v.GetList().back();
-}
-
-void ValueReserveSize(base::Value& v, size_t len) {
-  v.GetList().reserve(len);
-}
-
-rust::String DumpValueSlot(const ValueSlot& v) {
-  std::ostringstream os;
-  if (v.has_value()) {
-    os << *v;
-  } else {
-    os << "(empty)";
-  }
-  return rust::String(os.str());
-}
-
-void ConstructNoneValue(ValueSlot& v) {
-  v.emplace(base::Value::Type::NONE);
-}
-
-void ConstructBoolValue(ValueSlot& v, bool value) {
-  v.emplace(value);
-}
-
-void ConstructIntegerValue(ValueSlot& v, int value) {
-  v.emplace(value);
-}
-
-void ConstructDoubleValue(ValueSlot& v, double value) {
-  v.emplace(value);
-}
-
-void ConstructStringValue(ValueSlot& v, rust::Str value) {
-  v.emplace(base::RustStrToStringPiece(value));
-}
-
-base::Value& ConstructDictValue(ValueSlot& v) {
-  return v.emplace(base::Value::Type::DICTIONARY);
-}
-
-base::Value& ConstructListValue(ValueSlot& v) {
-  return v.emplace(base::Value::Type::LIST);
-}
-
-}  // namespace rs_glue
-}  // namespace base
diff --git a/base/rs_glue/values_glue.h b/base/rs_glue/values_glue.h
deleted file mode 100644
index 88d4ee1..0000000
--- a/base/rs_glue/values_glue.h
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2021 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef BASE_RS_GLUE_VALUES_GLUE_H_
-#define BASE_RS_GLUE_VALUES_GLUE_H_
-
-#include <stddef.h>
-
-#include "base/base_export.h"
-#include "base/strings/string_piece_rust.h"
-#include "base/values.h"
-#include "third_party/rust/cxx/v1/crate/include/cxx.h"
-
-namespace base {
-namespace rs_glue {
-
-// This file has functions which are called from Rust code to populate
-// bits of a base::Value. The functions exist because Rust C++ FFI
-// is not yet always good enough to operate on a base::Value directly
-// without these intermediate layer. With future inprovements in interop,
-// they may disappear.
-
-// Storage space into which a `base::Value` can be constructed.
-using ValueSlot = absl::optional<base::Value>;
-
-// Function purposes explained in mod.rs in the same directory.
-BASE_EXPORT void ValueSetNoneKey(base::Value& v, rust::Str key);
-BASE_EXPORT void ValueSetBoolKey(base::Value& v, rust::Str key, bool value);
-BASE_EXPORT void ValueSetIntegerKey(base::Value& v, rust::Str key, int value);
-BASE_EXPORT void ValueSetDoubleKey(base::Value& v, rust::Str key, double value);
-BASE_EXPORT void ValueSetStringKey(base::Value& v,
-                                   rust::Str key,
-                                   rust::Str value);
-BASE_EXPORT base::Value& ValueSetDictKey(base::Value& v, rust::Str key);
-BASE_EXPORT base::Value& ValueSetListKey(base::Value& v, rust::Str key);
-BASE_EXPORT void ValueAppendNone(base::Value& v);
-BASE_EXPORT void ValueAppendString(base::Value& v, rust::Str value);
-BASE_EXPORT base::Value& ValueAppendDict(base::Value& v);
-BASE_EXPORT base::Value& ValueAppendList(base::Value& v);
-BASE_EXPORT void ValueReserveSize(base::Value& v, size_t len);
-BASE_EXPORT std::unique_ptr<ValueSlot> NewValueSlotForTesting();
-BASE_EXPORT rust::String DumpValueSlot(const ValueSlot& v);
-BASE_EXPORT void ConstructNoneValue(ValueSlot& v);
-BASE_EXPORT void ConstructBoolValue(ValueSlot& v, bool value);
-BASE_EXPORT void ConstructIntegerValue(ValueSlot& v, int value);
-BASE_EXPORT void ConstructDoubleValue(ValueSlot& v, double value);
-BASE_EXPORT void ConstructStringValue(ValueSlot& v, rust::Str value);
-BASE_EXPORT base::Value& ConstructDictValue(ValueSlot& v);
-BASE_EXPORT base::Value& ConstructListValue(ValueSlot& v);
-
-}  // namespace rs_glue
-}  // namespace base
-
-#endif  // BASE_RS_GLUE_VALUES_GLUE_H_
diff --git a/base/strings/string_piece.h b/base/strings/string_piece.h
index 30dd149..699ee2f 100644
--- a/base/strings/string_piece.h
+++ b/base/strings/string_piece.h
@@ -34,6 +34,7 @@
 #include "base/check_op.h"
 #include "base/compiler_specific.h"
 #include "base/cxx20_is_constant_evaluated.h"
+#include "base/numerics/safe_math.h"
 #include "base/strings/string_piece_forward.h"  // IWYU pragma: export
 #include "build/build_config.h"
 
@@ -117,8 +118,9 @@
   constexpr BasicStringPiece(const BasicStringPiece& other) noexcept = default;
   constexpr BasicStringPiece& operator=(const BasicStringPiece& view) noexcept =
       default;
-  constexpr BasicStringPiece(const CharT* s, size_type count)
-      : ptr_(s), length_(count) {}
+  constexpr BasicStringPiece(const CharT* s, CheckedNumeric<size_t> count)
+      : ptr_(s), length_(count.ValueOrDie()) {}
+  // NOLINTNEXTLINE(google-explicit-constructor)
   constexpr BasicStringPiece(const CharT* s)
       : ptr_(s), length_(s ? traits_type::length(s) : 0) {
     // Intentional STL deviation: Null-check instead of UB.
diff --git a/base/strings/string_piece_rust.h b/base/strings/string_piece_rust.h
index cc160c69..266ac28d 100644
--- a/base/strings/string_piece_rust.h
+++ b/base/strings/string_piece_rust.h
@@ -5,6 +5,10 @@
 #ifndef BASE_STRINGS_STRING_PIECE_RUST_H_
 #define BASE_STRINGS_STRING_PIECE_RUST_H_
 
+#include "build/rust/rust_buildflags.h"
+
+#if BUILDFLAG(TOOLCHAIN_HAS_RUST)
+
 #include <stdint.h>
 
 #include "base/strings/string_piece.h"
@@ -35,4 +39,6 @@
 
 }  // namespace base
 
+#endif  // BUILDFLAG(TOOLCHAIN_HAS_RUST)
+
 #endif  // BASE_STRINGS_STRING_PIECE_RUST_H_
diff --git a/base/strings/string_piece_rust_unittest.cc b/base/strings/string_piece_rust_unittest.cc
index 718a7db..b9c674c0 100644
--- a/base/strings/string_piece_rust_unittest.cc
+++ b/base/strings/string_piece_rust_unittest.cc
@@ -3,9 +3,12 @@
 // found in the LICENSE file.
 
 #include "base/strings/string_piece_rust.h"
+#include "build/rust/rust_buildflags.h"
 
 #include "testing/gtest/include/gtest/gtest.h"
 
+#if BUILDFLAG(TOOLCHAIN_HAS_RUST)
+
 namespace base {
 namespace {
 
@@ -28,3 +31,5 @@
 
 }  // namespace
 }  // namespace base
+
+#endif  // BUILDFLAG(TOOLCHAIN_HAS_RUST)
diff --git a/base/strings/string_piece_unittest.cc b/base/strings/string_piece_unittest.cc
index babad6392..cebb3196 100644
--- a/base/strings/string_piece_unittest.cc
+++ b/base/strings/string_piece_unittest.cc
@@ -720,6 +720,11 @@
   }
 }
 
+TEST(StringPieceTest, InvalidLengthDeath) {
+  int length = -1;
+  ASSERT_DEATH_IF_SUPPORTED({ StringPiece piece("hello", length); }, "");
+}
+
 TEST(StringPieceTest, ConstexprData) {
   {
     constexpr StringPiece piece;
diff --git a/base/values.rs b/base/values.rs
deleted file mode 100644
index ab1ecdd6..0000000
--- a/base/values.rs
+++ /dev/null
@@ -1,206 +0,0 @@
-// Copyright 2021 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-use std::pin::Pin;
-
-use crate::rs_glue;
-
-/// A reference to a C++ `base::Value` of type `base::Value::Type::DICTIONARY`.
-/// Such a value is currently either held directly in a populated
-/// [`ValueSlotRef`] or in a child `base::Value` thereof.
-pub struct DictValueRef<'a>(Pin<&'a mut rs_glue::ffi::Value>);
-
-impl<'a> DictValueRef<'a> {
-    /// Get a reference to the base::Value.
-    fn raw_mut(&mut self) -> Pin<&mut rs_glue::ffi::Value> {
-        self.0.as_mut()
-    }
-
-    /// Sets the value at this dictionary key to be a value of type
-    /// `base::Value::Type::NONE`.
-    pub fn set_none_key(&mut self, key: &str) {
-        rs_glue::ffi::ValueSetNoneKey(self.raw_mut(), key);
-    }
-
-    /// Sets the value at this dictionary key to a Boolean.
-    pub fn set_bool_key(&mut self, key: &str, val: bool) {
-        rs_glue::ffi::ValueSetBoolKey(self.raw_mut(), key, val);
-    }
-
-    /// Sets the value at this dictionary key to an integer.
-    pub fn set_integer_key(&mut self, key: &str, val: i32) {
-        rs_glue::ffi::ValueSetIntegerKey(self.raw_mut(), key, val);
-    }
-
-    /// Sets the value at this dictionary key to a double.
-    pub fn set_double_key(&mut self, key: &str, val: f64) {
-        rs_glue::ffi::ValueSetDoubleKey(self.raw_mut(), key, val);
-    }
-
-    /// Sets the value at this dictionary key to a string.
-    pub fn set_string_key(&mut self, key: &str, val: &str) {
-        rs_glue::ffi::ValueSetStringKey(self.raw_mut(), key, val);
-    }
-
-    /// Sets the value at this dictionary key to a new dictionary, and returns
-    /// a reference to it.
-    pub fn set_dict_key(&mut self, key: &str) -> DictValueRef {
-        rs_glue::ffi::ValueSetDictKey(self.raw_mut(), key).into()
-    }
-
-    /// Sets the value at this dictionary key to a new list, and returns a
-    /// reference to it.
-    pub fn set_list_key(&mut self, key: &str) -> ListValueRef {
-        rs_glue::ffi::ValueSetListKey(self.raw_mut(), key).into()
-    }
-}
-
-impl<'a> From<Pin<&'a mut rs_glue::ffi::Value>> for DictValueRef<'a> {
-    /// Wrap a reference to a C++ `base::Value` in a newtype wrapper to
-    /// indicate that it's of type DICTIONARY. This is not actually unsafe,
-    /// since any mistakes here will result in a deliberate crash due to
-    /// assertions on the C++ side, rather than memory safety errors.
-    fn from(value: Pin<&'a mut rs_glue::ffi::Value>) -> Self {
-        Self(value)
-    }
-}
-
-/// A reference to a C++ `base::Value` of type `base::Value::Type::LIST`.
-/// Such a value is currently either held directly in a populated
-/// [`ValueSlotRef`] or in a child `base::Value` thereof.
-pub struct ListValueRef<'a>(Pin<&'a mut rs_glue::ffi::Value>);
-
-impl<'a> ListValueRef<'a> {
-    /// Get a reference to the underlying base::Value.
-    fn raw_mut(&mut self) -> Pin<&mut rs_glue::ffi::Value> {
-        self.0.as_mut()
-    }
-
-    /// Appends a value of type `base::Value::Type::NONE`. Grows
-    /// the list as necessary.
-    pub fn append_none(&mut self) {
-        rs_glue::ffi::ValueAppendNone(self.raw_mut());
-    }
-
-    /// Appends a Boolean. Grows the list as necessary.
-    pub fn append_bool(&mut self, val: bool) {
-        self.raw_mut().ValueAppendBool(val)
-    }
-
-    /// Appends an integer. Grows the list as necessary.
-    pub fn append_integer(&mut self, val: i32) {
-        self.raw_mut().ValueAppendInteger(val)
-    }
-
-    /// Appends a double. Grows the list as necessary.
-    pub fn append_double(&mut self, val: f64) {
-        self.raw_mut().ValueAppendDouble(val)
-    }
-
-    /// Appends a string. Grows the list as necessary.
-    pub fn append_string(&mut self, val: &str) {
-        rs_glue::ffi::ValueAppendString(self.raw_mut(), val);
-    }
-
-    /// Appends a new dictionary, and returns a reference to it.
-    /// Grows the list as necessary.
-    pub fn append_dict(&mut self) -> DictValueRef {
-        rs_glue::ffi::ValueAppendDict(self.raw_mut()).into()
-    }
-
-    /// Appends a new list, and returns a reference to it. Grows
-    /// the list as necessary.
-    pub fn append_list(&mut self) -> ListValueRef {
-        rs_glue::ffi::ValueAppendList(self.raw_mut()).into()
-    }
-
-    /// Reserves space for a given number of elements within a list. This is
-    /// optional - lists will grow as necessary to accommodate the items you
-    /// add, so this just reduces the allocations necessary.
-    pub fn reserve_size(&mut self, len: usize) {
-        rs_glue::ffi::ValueReserveSize(self.raw_mut(), len);
-    }
-}
-
-impl<'a> From<Pin<&'a mut rs_glue::ffi::Value>> for ListValueRef<'a> {
-    /// Wrap a reference to a C++ `base::Value` in a newtype wrapper to
-    /// indicate that it's of type LIST. This is not actually unsafe, since
-    /// any mistakes here will result in a deliberate crash due to assertions
-    /// on the C++ side, rather than memory safety errors.
-    fn from(value: Pin<&'a mut rs_glue::ffi::Value>) -> Self {
-        Self(value)
-    }
-}
-
-/// A reference to a slot in which a `base::Value` can be constructed.
-/// Such a slot can only be created within C++ and passed to Rust; Rust
-/// can then create a `base::Value` therein.
-pub struct ValueSlotRef<'a>(Pin<&'a mut rs_glue::ffi::ValueSlot>);
-
-impl<'a> From<Pin<&'a mut rs_glue::ffi::ValueSlot>> for ValueSlotRef<'a> {
-    fn from(value: Pin<&'a mut rs_glue::ffi::ValueSlot>) -> Self {
-        Self(value)
-    }
-}
-
-impl<'a> From<&'a mut cxx::UniquePtr<rs_glue::ffi::ValueSlot>> for ValueSlotRef<'a> {
-    fn from(value: &'a mut cxx::UniquePtr<rs_glue::ffi::ValueSlot>) -> Self {
-        Self(value.pin_mut())
-    }
-}
-
-impl<'a> ValueSlotRef<'a> {
-    /// Return a mutable reference to the underlying raw value.
-    fn raw_mut(&mut self) -> Pin<&mut rs_glue::ffi::ValueSlot> {
-        self.0.as_mut()
-    }
-
-    /// Return a reference to the underlying raw value.
-    fn raw(&self) -> &rs_glue::ffi::ValueSlot {
-        &self.0
-    }
-
-    /// Creates a new `base::Value::Type::NONE` `base::Value` in this slot.
-    pub fn construct_none(&mut self) {
-        rs_glue::ffi::ConstructNoneValue(self.raw_mut());
-    }
-
-    /// Creates a new Boolean `base::Value` in this slot.
-    pub fn construct_bool(&mut self, val: bool) {
-        rs_glue::ffi::ConstructBoolValue(self.raw_mut(), val);
-    }
-
-    /// Creates a new integer `base::Value` in this slot.
-    pub fn construct_integer(&mut self, val: i32) {
-        rs_glue::ffi::ConstructIntegerValue(self.raw_mut(), val);
-    }
-
-    /// Creates a new double `base::Value` in this slot.
-    pub fn construct_double(&mut self, val: f64) {
-        rs_glue::ffi::ConstructDoubleValue(self.raw_mut(), val);
-    }
-
-    /// Creates a new string `base::Value` in this slot.
-    pub fn construct_string(&mut self, val: &str) {
-        rs_glue::ffi::ConstructStringValue(self.raw_mut(), val);
-    }
-
-    /// Creates a new dictionary `base::Value` in this slot.
-    pub fn construct_dict(&mut self) -> DictValueRef {
-        rs_glue::ffi::ConstructDictValue(self.raw_mut()).into()
-    }
-
-    /// Creates a new list `base::Value` in this slot.
-    pub fn construct_list(&mut self) -> ListValueRef {
-        rs_glue::ffi::ConstructListValue(self.raw_mut()).into()
-    }
-}
-
-/// Asks C++ code to dump this base::Value back to JSON.
-/// Primarily for testing the round-trip.
-impl<'a> std::fmt::Debug for ValueSlotRef<'a> {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
-        f.write_str(&rs_glue::ffi::DumpValueSlot(self.raw()))
-    }
-}
diff --git a/base/values_deserialization.rs b/base/values_deserialization.rs
deleted file mode 100644
index b561bd3..0000000
--- a/base/values_deserialization.rs
+++ /dev/null
@@ -1,223 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-use crate::values::{DictValueRef, ListValueRef, ValueSlotRef};
-use serde::de::{DeserializeSeed, Deserializer, Error, MapAccess, SeqAccess, Visitor};
-use std::convert::TryFrom;
-use std::fmt;
-
-/// Struct to check we're not recursing too deeply.
-#[derive(Clone)]
-struct RecursionDepthCheck(usize);
-
-impl RecursionDepthCheck {
-    /// Recurse a level and return an error if we've recursed too far.
-    fn recurse<E: Error>(&self) -> Result<RecursionDepthCheck, E> {
-        match self.0.checked_sub(1) {
-            Some(recursion_limit) => Ok(RecursionDepthCheck(recursion_limit)),
-            None => Err(Error::custom("recursion limit exceeded")),
-        }
-    }
-}
-
-/// What type of `base::Value` container we're deserializing into.
-enum DeserializationTarget<'elem, 'container> {
-    /// Deserialize into a brand new root `base::Value`.
-    NewValue { slot: ValueSlotRef<'container> },
-    /// Deserialize by appending to a list.
-    List { list: &'elem mut ListValueRef<'container> },
-    /// Deserialize by setting a dictionary key.
-    Dict { dict: &'elem mut DictValueRef<'container>, key: String },
-}
-
-/// serde deserializer for for `base::Value`.
-///
-/// serde is the "standard" framework for serializing and deserializing
-/// data formats in Rust. https://serde.rs/
-///
-/// This implements both the Visitor and Deserialize roles described
-/// here: https://serde.rs/impl-deserialize.html
-///
-/// One note, though. Normally serde instantiates a new object. The design
-/// of `base::Value` is that each sub-value (e.g. a list like this)
-/// needs to be deserialized into the parent value, which is pre-existing.
-/// To achieve this we use a feature of serde called 'stateful deserialization'
-/// (see https://docs.serde.rs/serde/de/trait.DeserializeSeed.html)
-///
-/// This struct stores that state.
-///
-/// Handily, this also enables us to store the desired recursion limit.
-///
-/// We use runtime dispatch (matching on an enum) to deserialize into a
-/// dictionary, list, etc. This may be slower than having three different
-/// `Visitor` implementations, where everything would be monomorphized
-/// and would probably disappear with inlining, but for now this is much
-/// less code.
-pub struct ValueVisitor<'elem, 'container> {
-    container: DeserializationTarget<'elem, 'container>,
-    recursion_depth_check: RecursionDepthCheck,
-}
-
-impl<'elem, 'container> ValueVisitor<'elem, 'container> {
-    /// Constructor - pass in an existing slot that can store a new
-    /// `base::Value`, then this visitor can be passed to serde deserialization
-    /// libraries to populate it with a tree of contents.
-    /// Any existing `base::Value` in the slot will be replaced.
-    pub fn new(slot: ValueSlotRef<'container>, mut max_depth: usize) -> Self {
-        max_depth += 1; // we will increment this counter when deserializing
-        // the initial `base::Value`. To match C++ behavior, we should
-        // only start counting for subsequent layers, hence decrement
-        // now.
-        Self {
-            container: DeserializationTarget::NewValue { slot },
-            recursion_depth_check: RecursionDepthCheck(max_depth),
-        }
-    }
-}
-
-impl<'de, 'elem, 'container> Visitor<'de> for ValueVisitor<'elem, 'container> {
-    // Because we are deserializing into a pre-existing object.
-    type Value = ();
-
-    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
-        formatter.write_str("any valid JSON")
-    }
-
-    fn visit_i32<E: serde::de::Error>(self, value: i32) -> Result<Self::Value, E> {
-        match self.container {
-            DeserializationTarget::NewValue { mut slot } => slot.construct_integer(value),
-            DeserializationTarget::List { list } => list.append_integer(value),
-            DeserializationTarget::Dict { dict, key } => dict.set_integer_key(&key, value),
-        };
-        Ok(())
-    }
-
-    fn visit_i8<E: serde::de::Error>(self, value: i8) -> Result<Self::Value, E> {
-        self.visit_i32(value as i32)
-    }
-
-    fn visit_bool<E: serde::de::Error>(self, value: bool) -> Result<Self::Value, E> {
-        match self.container {
-            DeserializationTarget::NewValue { mut slot } => slot.construct_bool(value),
-            DeserializationTarget::List { list } => list.append_bool(value),
-            DeserializationTarget::Dict { dict, key } => dict.set_bool_key(&key, value),
-        };
-        Ok(())
-    }
-
-    fn visit_i64<E: serde::de::Error>(self, value: i64) -> Result<Self::Value, E> {
-        // Here we match the behavior of the Chromium C++ JSON parser,
-        // which will attempt to create an integer base::Value but will
-        // overflow into a double if needs be.
-        // (See JSONParser::ConsumeNumber in json_parser.cc for equivalent,
-        // and json_reader_unittest.cc LargerIntIsLossy for a related test).
-        match i32::try_from(value) {
-            Ok(value) => self.visit_i32(value),
-            Err(_) => self.visit_f64(value as f64),
-        }
-    }
-
-    fn visit_u64<E: serde::de::Error>(self, value: u64) -> Result<Self::Value, E> {
-        // See visit_i64 comment.
-        match i32::try_from(value) {
-            Ok(value) => self.visit_i32(value),
-            Err(_) => self.visit_f64(value as f64),
-        }
-    }
-
-    fn visit_f64<E: serde::de::Error>(self, value: f64) -> Result<Self::Value, E> {
-        match self.container {
-            DeserializationTarget::NewValue { mut slot } => slot.construct_double(value),
-            DeserializationTarget::List { list } => list.append_double(value),
-            DeserializationTarget::Dict { dict, key } => dict.set_double_key(&key, value),
-        };
-        Ok(())
-    }
-
-    fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
-        match self.container {
-            DeserializationTarget::NewValue { mut slot } => slot.construct_string(value),
-            DeserializationTarget::List { list } => list.append_string(value),
-            DeserializationTarget::Dict { dict, key } => dict.set_string_key(&key, value),
-        };
-        Ok(())
-    }
-
-    fn visit_borrowed_str<E: serde::de::Error>(self, value: &'de str) -> Result<Self::Value, E> {
-        match self.container {
-            DeserializationTarget::NewValue { mut slot } => slot.construct_string(value),
-            DeserializationTarget::List { list } => list.append_string(value),
-            DeserializationTarget::Dict { dict, key } => dict.set_string_key(&key, value),
-        };
-        Ok(())
-    }
-
-    fn visit_string<E: serde::de::Error>(self, value: String) -> Result<Self::Value, E> {
-        self.visit_str(&value)
-    }
-
-    fn visit_none<E: serde::de::Error>(self) -> Result<Self::Value, E> {
-        match self.container {
-            DeserializationTarget::NewValue { mut slot } => slot.construct_none(),
-            DeserializationTarget::List { list } => list.append_none(),
-            DeserializationTarget::Dict { dict, key } => dict.set_none_key(&key),
-        };
-        Ok(())
-    }
-
-    fn visit_unit<E: serde::de::Error>(self) -> Result<Self::Value, E> {
-        self.visit_none()
-    }
-
-    fn visit_map<M>(mut self, mut access: M) -> Result<Self::Value, M::Error>
-    where
-        M: MapAccess<'de>,
-    {
-        let mut value = match self.container {
-            DeserializationTarget::NewValue { ref mut slot } => slot.construct_dict(),
-            DeserializationTarget::List { list } => list.append_dict(),
-            DeserializationTarget::Dict { dict, key } => dict.set_dict_key(&key),
-        };
-        // If it were exposed by values.h, we could use access.size_hint()
-        // to give a clue to the C++ about the size of the desired map.
-        while let Some(key) = access.next_key::<String>()? {
-            access.next_value_seed(ValueVisitor {
-                container: DeserializationTarget::Dict { dict: &mut value, key },
-                recursion_depth_check: self.recursion_depth_check.recurse()?,
-            })?;
-        }
-        Ok(())
-    }
-
-    fn visit_seq<S>(mut self, mut access: S) -> Result<Self::Value, S::Error>
-    where
-        S: SeqAccess<'de>,
-    {
-        let mut value = match self.container {
-            DeserializationTarget::NewValue { ref mut slot } => slot.construct_list(),
-            DeserializationTarget::List { list } => list.append_list(),
-            DeserializationTarget::Dict { dict, key } => dict.set_list_key(&key),
-        };
-        if let Some(size_hint) = access.size_hint() {
-            value.reserve_size(size_hint);
-        }
-        while let Some(_) = access.next_element_seed(ValueVisitor {
-            container: DeserializationTarget::List { list: &mut value },
-            recursion_depth_check: self.recursion_depth_check.recurse()?,
-        })? {}
-        Ok(())
-    }
-}
-
-impl<'de, 'elem, 'container> DeserializeSeed<'de> for ValueVisitor<'elem, 'container> {
-    // Because we are deserializing into a pre-existing object.
-    type Value = ();
-
-    fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
-    where
-        D: Deserializer<'de>,
-    {
-        deserializer.deserialize_any(self)
-    }
-}
diff --git a/base/values_unittest.rs b/base/values_unittest.rs
deleted file mode 100644
index 2c8d9a9..0000000
--- a/base/values_unittest.rs
+++ /dev/null
@@ -1,100 +0,0 @@
-use base::{NewValueSlotForTesting, ValueSlotRef};
-use rust_gtest_interop::prelude::*;
-
-#[gtest(RustValuesTest, AllocDealloc)]
-fn test_alloc_dealloc() {
-    NewValueSlotForTesting();
-}
-
-#[gtest(RustValuesTest, StartsNone)]
-fn test_starts_none() {
-    let mut v = NewValueSlotForTesting();
-    let v = ValueSlotRef::from(v.pin_mut());
-    expect_eq!(format!("{:?}", v), "(empty)");
-}
-
-#[gtest(RustValuesTest, SetDict)]
-fn test_set_dict() {
-    let mut v = NewValueSlotForTesting();
-    let mut v = ValueSlotRef::from(&mut v);
-    let mut d = v.construct_dict();
-    d.set_string_key("fish", "skate");
-    d.set_none_key("antlers");
-    d.set_bool_key("has_lungs", false);
-    d.set_integer_key("fins", 2);
-    d.set_double_key("bouyancy", 1.0);
-    let mut nested_list = d.set_list_key("scales");
-    nested_list.append_string("sea major");
-    let mut nested_dict = d.set_dict_key("taxonomy");
-    nested_dict.set_string_key("kingdom", "animalia");
-    nested_dict.set_string_key("phylum", "chordata");
-    // TODO(crbug.com/1282310): Use indoc to make this neater.
-    expect_eq!(
-        format!("{:?}", v),
-        "{\n   \
-            \"antlers\": null,\n   \
-            \"bouyancy\": 1.0,\n   \
-            \"fins\": 2,\n   \
-            \"fish\": \"skate\",\n   \
-            \"has_lungs\": false,\n   \
-            \"scales\": [ \"sea major\" ],\n   \
-            \"taxonomy\": {\n      \
-                \"kingdom\": \"animalia\",\n      \
-                \"phylum\": \"chordata\"\n   \
-            }\n\
-        }\n"
-    );
-}
-
-#[gtest(RustValuesTest, SetList)]
-fn test_set_list() {
-    let mut v = NewValueSlotForTesting();
-    let mut v = ValueSlotRef::from(&mut v);
-    let mut l = v.construct_list();
-    l.reserve_size(5);
-    l.append_bool(false);
-    l.append_none();
-    l.append_double(2.0);
-    l.append_integer(4);
-    let mut nested_list = l.append_list();
-    nested_list.append_none();
-    let mut nested_dict = l.append_dict();
-    nested_dict.set_string_key("a", "b");
-    l.append_string("hello");
-    expect_eq!(
-        format!("{:?}", v),
-        "[ false, null, 2.0, 4, [ null ], {\n   \
-            \"a\": \"b\"\n\
-        }, \"hello\" ]\n"
-    );
-}
-
-fn expect_simple_value_matches<F>(f: F, expected: &str)
-where
-    F: FnOnce(&mut ValueSlotRef),
-{
-    let mut v = NewValueSlotForTesting();
-    let mut v = ValueSlotRef::from(&mut v);
-    f(&mut v);
-    expect_eq!(format!("{:?}", v).trim_end(), expected);
-}
-
-#[gtest(RustValuesTest, SetSimpleOptionalValues)]
-fn test_set_simple_optional_values() {
-    expect_simple_value_matches(|v| v.construct_none(), "null");
-    expect_simple_value_matches(|v| v.construct_bool(true), "true");
-    expect_simple_value_matches(|v| v.construct_integer(3), "3");
-    expect_simple_value_matches(|v| v.construct_double(3.1), "3.1");
-    expect_simple_value_matches(|v| v.construct_string("a"), "\"a\"");
-}
-
-#[gtest(RustValuesTest, ReuseSlot)]
-fn test_reuse_slot() {
-    let mut v = NewValueSlotForTesting();
-    let mut v = ValueSlotRef::from(&mut v);
-    v.construct_none();
-    let mut d = v.construct_dict();
-    d.set_integer_key("a", 3);
-    v.construct_integer(7);
-    expect_eq!(format!("{:?}", v).trim_end(), "7");
-}
diff --git a/build/config/compiler/compiler.gni b/build/config/compiler/compiler.gni
index e0d44ee..fa665e3f 100644
--- a/build/config/compiler/compiler.gni
+++ b/build/config/compiler/compiler.gni
@@ -151,8 +151,8 @@
   # Use offsets rather than pointers in vtables in order to reduce the number of
   # relocations. This is safe to enable only when all C++ code is built with the
   # flag set to the same value.
-  use_relative_vtables_abi = is_android && current_cpu == "arm64" &&
-                             use_custom_libcxx && !is_component_build
+  use_relative_vtables_abi =
+      is_android && use_custom_libcxx && !is_component_build
 }
 
 assert(!is_cfi || use_thin_lto, "CFI requires ThinLTO")
diff --git a/build/config/rust.gni b/build/config/rust.gni
index d0fd6962..e7032a6c 100644
--- a/build/config/rust.gni
+++ b/build/config/rust.gni
@@ -16,6 +16,11 @@
   # is false.
   enable_rust = false
 
+  # Individual Rust components.
+  #
+  # The base::JSONReader implementation.
+  enable_rust_json = true
+
   # Use experimental Rust toolchain built in-tree. See //tools/rust. For now,
   # only use it for linux targets. The package only has prebuilt libs for linux.
   # More targets will be added later.
@@ -47,11 +52,10 @@
   # Rust toolchain. Typically used with 'use_unverified_rust_toolchain' = true
   removed_rust_stdlib_libs = []
 
-  # Use LTO when using rustc to link binaries. Experimental. Currently incompatible
-  # with the options we use in our C++ toolchain to split LTO units.
-  # This has no effect on the production of normal Chrome binaries, which are
-  # linked by clang/lld rather than rustc.
-  # https://crbug.com/1229419
+  # Use LTO when using rustc to link binaries. Experimental. Currently
+  # incompatible with the options we use in our C++ toolchain to split LTO
+  # units. This has no effect on the production of normal Chrome binaries, which
+  # are linked by clang/lld rather than rustc. https://crbug.com/1229419
   use_lto_in_rustc_linking = false
 
   # Use goma for Rust builds. Experimental. The only known problem is
diff --git a/build/fuchsia/linux_internal.sdk.sha1 b/build/fuchsia/linux_internal.sdk.sha1
index 9acb880..438d539 100644
--- a/build/fuchsia/linux_internal.sdk.sha1
+++ b/build/fuchsia/linux_internal.sdk.sha1
@@ -1 +1 @@
-10.20221117.0.1
+10.20221117.1.1
diff --git a/build/rust/BUILD.gn b/build/rust/BUILD.gn
index feae363..29f8a1a 100644
--- a/build/rust/BUILD.gn
+++ b/build/rust/BUILD.gn
@@ -2,58 +2,68 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-config("edition_2021") {
-  rustflags = [ "--edition=2021" ]
+import("//build/buildflag_header.gni")
+import("//build/config/rust.gni")
+
+buildflag_header("rust_buildflags") {
+  header = "rust_buildflags.h"
+  flags = [ "TOOLCHAIN_HAS_RUST=$toolchain_has_rust" ]
 }
 
-config("edition_2018") {
-  rustflags = [ "--edition=2018" ]
-}
-
-config("edition_2015") {
-  rustflags = [ "--edition=2015" ]
-}
-
-# The required dependencies for cxx-generated bindings, that must be included
-# on the C++ side.
-static_library("cxx_cppdeps") {
-  sources = [
-    "//third_party/rust/cxx/v1/crate/include/cxx.h",
-    "//third_party/rust/cxx/v1/crate/src/cxx.cc",
-  ]
-
-  defines = [ "RUST_CXX_NO_EXCEPTIONS" ]
-
-  if (is_win) {
-    defines += [ "CXX_RS_EXPORT=__declspec(dllexport)" ]
-  } else {
-    defines += [ "CXX_RS_EXPORT=__attribute__((visibility(\"default\")))" ]
+if (toolchain_has_rust) {
+  config("edition_2021") {
+    rustflags = [ "--edition=2021" ]
   }
 
-  # Depending on the C++ bindings side of cxx then requires also depending
-  # on the Rust bindings, since one calls the other. And the Rust bindings
-  # require the Rust standard library.
-  # Normally the Rust stdlib is brought in as a dependency by depending
-  # on any first-party Rust target. But in this case, it's conceivable
-  # that pure-C++ targets will not depend on any 1p Rust code so we'll add
-  # the Rust stdlib explicitly.
-  deps = [
-    ":cxx_rustdeps",
-    "//build/rust/std",
-  ]
-}
+  config("edition_2018") {
+    rustflags = [ "--edition=2018" ]
+  }
 
-# The required dependencies for cxx-generated bindings, that must be included
-# on the Rust side.
-group("cxx_rustdeps") {
-  public_deps = [ "//third_party/rust/cxx/v1:lib" ]
-}
+  config("edition_2015") {
+    rustflags = [ "--edition=2015" ]
+  }
 
-# Enables code behind #[cfg(test)]. This should only be used for targets where
-# testonly=true.
-config("test") {
-  rustflags = [
-    "--cfg",
-    "test",
-  ]
+  # The required dependencies for cxx-generated bindings, that must be included
+  # on the C++ side.
+  static_library("cxx_cppdeps") {
+    sources = [
+      "//third_party/rust/cxx/v1/crate/include/cxx.h",
+      "//third_party/rust/cxx/v1/crate/src/cxx.cc",
+    ]
+
+    defines = [ "RUST_CXX_NO_EXCEPTIONS" ]
+
+    if (is_win) {
+      defines += [ "CXX_RS_EXPORT=__declspec(dllexport)" ]
+    } else {
+      defines += [ "CXX_RS_EXPORT=__attribute__((visibility(\"default\")))" ]
+    }
+
+    # Depending on the C++ bindings side of cxx then requires also depending
+    # on the Rust bindings, since one calls the other. And the Rust bindings
+    # require the Rust standard library.
+    # Normally the Rust stdlib is brought in as a dependency by depending
+    # on any first-party Rust target. But in this case, it's conceivable
+    # that pure-C++ targets will not depend on any 1p Rust code so we'll add
+    # the Rust stdlib explicitly.
+    deps = [
+      ":cxx_rustdeps",
+      "//build/rust/std",
+    ]
+  }
+
+  # The required dependencies for cxx-generated bindings, that must be included
+  # on the Rust side.
+  group("cxx_rustdeps") {
+    public_deps = [ "//third_party/rust/cxx/v1:lib" ]
+  }
+
+  # Enables code behind #[cfg(test)]. This should only be used for targets where
+  # testonly=true.
+  config("test") {
+    rustflags = [
+      "--cfg",
+      "test",
+    ]
+  }
 }
diff --git a/build/rust/cargo_crate.gni b/build/rust/cargo_crate.gni
index de65a05..da0550c 100644
--- a/build/rust/cargo_crate.gni
+++ b/build/rust/cargo_crate.gni
@@ -126,9 +126,25 @@
                               ]) ]
   }
 
-  # The main target, either a Rust source set or an executable
+  # The main target, either a Rust source set or an executable.
   rust_target(target_name) {
-    # Work out what we're building
+    forward_variables_from(invoker,
+                           "*",
+                           TESTONLY_AND_VISIBILITY + [
+                                 "build_root",
+                                 "build_deps",
+                                 "build_sources",
+                                 "build_script_inputs",
+                                 "build_script_outputs",
+                                 "output_dir",
+                                 "unit_test_target",
+                                 "target_type",
+                                 "configs",
+                                 "rustenv",
+                               ])
+    forward_variables_from(invoker, TESTONLY_AND_VISIBILITY)
+
+    # Work out what we're building.
     crate_type = "rlib"
     if (defined(invoker.crate_type)) {
       crate_type = invoker.crate_type
@@ -162,22 +178,6 @@
     # and needs to be unique within the name of the binary itself.
     unit_test_target = "${_crate_name}_${orig_target_name}_unittests"
 
-    forward_variables_from(invoker,
-                           "*",
-                           [
-                             "build_root",
-                             "build_deps",
-                             "build_sources",
-                             "build_script_inputs",
-                             "build_script_outputs",
-                             "output_dir",
-                             "unit_test_target",
-                             "target_type",
-                             "visibility",
-                             "testonly",
-                             "configs",
-                           ])
-
     if (defined(invoker.build_root)) {
       # Uh-oh, we have a build script
       if (!defined(deps)) {
diff --git a/build/rust/rust_target.gni b/build/rust/rust_target.gni
index 2600528..1738bf2 100644
--- a/build/rust/rust_target.gni
+++ b/build/rust/rust_target.gni
@@ -247,7 +247,6 @@
                  "_test_deps",
                  "_testonly",
                  "_visibility",
-                 "proc_macro_target",
                ])
   } else {
     group(_target_name) {
diff --git a/cc/layers/heads_up_display_layer_impl.cc b/cc/layers/heads_up_display_layer_impl.cc
index b24cfcfe6..d1156e6 100644
--- a/cc/layers/heads_up_display_layer_impl.cc
+++ b/cc/layers/heads_up_display_layer_impl.cc
@@ -362,8 +362,7 @@
     const auto& size = pool_resource.size();
     auto display_item_list = base::MakeRefCounted<DisplayItemList>(
         DisplayItemList::kTopLevelDisplayItemList);
-    RecordPaintCanvas canvas(display_item_list.get(),
-                             SkRect::MakeIWH(size.width(), size.height()));
+    RecordPaintCanvas canvas(display_item_list.get());
     display_item_list->StartPaint();
     DrawHudContents(&canvas);
     display_item_list->EndPaintOfUnpaired(gfx::Rect(size));
diff --git a/cc/paint/paint_recorder.cc b/cc/paint/paint_recorder.cc
index 9cf3bcb..12bdc9e0 100644
--- a/cc/paint/paint_recorder.cc
+++ b/cc/paint/paint_recorder.cc
@@ -5,53 +5,65 @@
 #include "cc/paint/paint_recorder.h"
 
 #include "cc/paint/display_item_list.h"
-#include "ui/gfx/geometry/skia_conversions.h"
 
 namespace cc {
 
-PaintRecorder::PaintRecorder() {
-  display_item_list_ = base::MakeRefCounted<DisplayItemList>(
-      DisplayItemList::kToBeReleasedAsPaintOpBuffer);
-}
-PaintRecorder::~PaintRecorder() = default;
+PaintRecorderBase::PaintRecorderBase()
+    : display_item_list_(base::MakeRefCounted<DisplayItemList>(
+          DisplayItemList::kToBeReleasedAsPaintOpBuffer)) {}
 
-PaintCanvas* PaintRecorder::beginRecording(const SkRect& bounds) {
+PaintRecorderBase::~PaintRecorderBase() = default;
+
+void PaintRecorderBase::beginRecording() {
+  DCHECK(!is_recording_);
+  is_recording_ = true;
+
+  // The subclass must create canvas_ before calling this method.
+  DCHECK(canvas_);
   display_item_list_->StartPaint();
-  canvas_ = CreateCanvas(display_item_list_.get(), bounds);
-  return getRecordingCanvas();
 }
 
-sk_sp<PaintRecord> PaintRecorder::finishRecordingAsPicture() {
-  // SkPictureRecorder users expect that their saves are automatically
-  // closed for them.
-  //
-  // NOTE: Blink paint in general doesn't appear to need this, but the
-  // RecordingImageBufferSurface::fallBackToRasterCanvas finishing off the
-  // current frame depends on this.  Maybe we could remove this assumption and
-  // just have callers do it.
+sk_sp<PaintRecord> PaintRecorderBase::finishRecordingAsPicture() {
+  DCHECK(canvas_);
+  DCHECK(is_recording_);
+  is_recording_ = false;
+
+  // Some users expect that their saves are automatically closed for them.
+  // Maybe we could remove this assumption and just have callers do it.
+  // canvas_ is not reset in case it can be reused for the next recording.
   canvas_->restoreToCount(1);
 
-  // Some users (e.g. printing) use the existence of the recording canvas
-  // to know if recording is finished, so reset it here.
-  canvas_.reset();
+  if (canvas_->NeedsFlush())
+    canvas_.reset();
 
   // The rect doesn't matter, since we just release the record.
   display_item_list_->EndPaintOfUnpaired(gfx::Rect());
   return display_item_list_->FinalizeAndReleaseAsRecord();
 }
 
-std::unique_ptr<RecordPaintCanvas> PaintRecorder::CreateCanvas(
-    DisplayItemList* list,
-    const SkRect& bounds) {
-  return std::make_unique<RecordPaintCanvas>(list, bounds);
-}
-
-bool PaintRecorder::ListHasDrawOps() const {
+bool PaintRecorderBase::ListHasDrawOps() const {
   return display_item_list_->has_draw_ops();
 }
 
-size_t PaintRecorder::num_paint_ops() const {
+size_t PaintRecorderBase::num_paint_ops() const {
   return display_item_list_->num_paint_ops();
 }
 
+PaintCanvas* PaintRecorder::beginRecording() {
+  if (!canvas_)
+    canvas_ = std::make_unique<RecordPaintCanvas>(display_item_list_.get());
+  PaintRecorderBase::beginRecording();
+  return canvas_.get();
+}
+
+PaintCanvas* InspectablePaintRecorder::beginRecording(const gfx::Size& size) {
+  if (!canvas_ || size != size_) {
+    canvas_ = std::make_unique<InspectableRecordPaintCanvas>(
+        display_item_list_.get(), size);
+  }
+  size_ = size;
+  PaintRecorderBase::beginRecording();
+  return canvas_.get();
+}
+
 }  // namespace cc
diff --git a/cc/paint/paint_recorder.h b/cc/paint/paint_recorder.h
index 22970454..8da90b7 100644
--- a/cc/paint/paint_recorder.h
+++ b/cc/paint/paint_recorder.h
@@ -10,31 +10,16 @@
 #include "cc/paint/display_item_list.h"
 #include "cc/paint/paint_record.h"
 #include "cc/paint/record_paint_canvas.h"
+#include "ui/gfx/geometry/size.h"
 
 namespace cc {
 
 class DisplayItemList;
 
-class CC_PAINT_EXPORT PaintRecorder {
+class CC_PAINT_EXPORT PaintRecorderBase {
  public:
-  PaintRecorder();
-  PaintRecorder(const PaintRecorder&) = delete;
-  virtual ~PaintRecorder();
-
-  PaintRecorder& operator=(const PaintRecorder&) = delete;
-
-  PaintCanvas* beginRecording(const SkRect& bounds);
-
-  // TODO(enne): should make everything go through the non-rect version.
-  // See comments in RecordPaintCanvas ctor for why.
-  PaintCanvas* beginRecording(SkScalar width, SkScalar height) {
-    return beginRecording(SkRect::MakeWH(width, height));
-  }
-
-  // Only valid while recording.
-  ALWAYS_INLINE RecordPaintCanvas* getRecordingCanvas() {
-    return canvas_.get();
-  }
+  PaintRecorderBase(const PaintRecorderBase&) = delete;
+  PaintRecorderBase& operator=(const PaintRecorderBase&) = delete;
 
   sk_sp<PaintRecord> finishRecordingAsPicture();
 
@@ -46,15 +31,42 @@
   size_t TotalOpCount() const { return display_item_list_->TotalOpCount(); }
   size_t OpBytesUsed() const { return display_item_list_->OpBytesUsed(); }
 
- protected:
-  virtual std::unique_ptr<RecordPaintCanvas> CreateCanvas(DisplayItemList* list,
-                                                          const SkRect& bounds);
+  // Only valid while recording.
+  PaintCanvas* getRecordingCanvas() {
+    return is_recording_ ? canvas_.get() : nullptr;
+  }
 
- private:
+ protected:
+  PaintRecorderBase();
+  ~PaintRecorderBase();
+
+  // The subclass must create `canvas_` before calling this method.
+  void beginRecording();
+
+  bool is_recording_ = false;
   scoped_refptr<DisplayItemList> display_item_list_;
   std::unique_ptr<RecordPaintCanvas> canvas_;
 };
 
+class CC_PAINT_EXPORT PaintRecorder : public PaintRecorderBase {
+ public:
+  // Begins recording. The returned PaintCanvas doesn't support inspection of
+  // the current clip and the CTM during recording.
+  PaintCanvas* beginRecording();
+};
+
+class CC_PAINT_EXPORT InspectablePaintRecorder : public PaintRecorderBase {
+ public:
+  // Begins recording. The returned PaintCanvas supports inspection of the
+  // current clip and the CTM during recording. `size` doesn't affect the
+  // recorded results because all operations will be recorded regardless of it,
+  // but it determines the top-level device clip.
+  PaintCanvas* beginRecording(const gfx::Size& size);
+
+ private:
+  gfx::Size size_;
+};
+
 }  // namespace cc
 
 #endif  // CC_PAINT_PAINT_RECORDER_H_
diff --git a/cc/paint/record_paint_canvas.cc b/cc/paint/record_paint_canvas.cc
index 6f378a6..0216b5b 100644
--- a/cc/paint/record_paint_canvas.cc
+++ b/cc/paint/record_paint_canvas.cc
@@ -18,18 +18,12 @@
 
 namespace cc {
 
-RecordPaintCanvas::RecordPaintCanvas(DisplayItemList* list,
-                                     const SkRect& bounds)
-    : list_(list), recording_bounds_(bounds) {
+RecordPaintCanvas::RecordPaintCanvas(DisplayItemList* list) : list_(list) {
   DCHECK(list_);
 }
 
 RecordPaintCanvas::~RecordPaintCanvas() = default;
 
-SkImageInfo RecordPaintCanvas::imageInfo() const {
-  return GetCanvas()->imageInfo();
-}
-
 template <typename T, typename... Args>
 size_t RecordPaintCanvas::push(Args&&... args) {
 #if DCHECK_IS_ON()
@@ -70,52 +64,44 @@
 
 int RecordPaintCanvas::save() {
   push<SaveOp>();
-  return GetCanvas()->save();
+  return save_count_++;
 }
 
 int RecordPaintCanvas::saveLayer(const SkRect* bounds,
                                  const PaintFlags* flags) {
-  if (flags) {
-    if (flags->IsSimpleOpacity()) {
-      // TODO(enne): maybe more callers should know this and call
-      // saveLayerAlpha instead of needing to check here.
-      uint8_t alpha = SkColorGetA(flags->getColor());
-      return saveLayerAlpha(bounds, alpha);
-    }
-
-    // TODO(enne): it appears that image filters affect matrices and color
-    // matrices affect transparent flags on SkCanvas layers, but it's not clear
-    // whether those are actually needed and we could just skip ToSkPaint here.
-    push<SaveLayerOp>(bounds, flags);
-    SkPaint paint = flags->ToSkPaint();
-    return GetCanvas()->saveLayer(bounds, &paint);
+  if (flags && flags->IsSimpleOpacity()) {
+    // TODO(enne): maybe more callers should know this and call
+    // saveLayerAlpha instead of needing to check here.
+    uint8_t alpha = SkColorGetA(flags->getColor());
+    return saveLayerAlpha(bounds, alpha);
   }
+  return saveLayerInternal(bounds, flags);
+}
+
+int RecordPaintCanvas::saveLayerInternal(const SkRect* bounds,
+                                         const PaintFlags* flags) {
   push<SaveLayerOp>(bounds, flags);
-  return GetCanvas()->saveLayer(bounds, nullptr);
+  return save_count_++;
 }
 
 int RecordPaintCanvas::saveLayerAlpha(const SkRect* bounds, uint8_t alpha) {
   push<SaveLayerAlphaOp>(bounds, static_cast<float>(alpha / 255.0f));
-  return GetCanvas()->saveLayerAlpha(bounds, alpha);
+  return save_count_++;
 }
 
 void RecordPaintCanvas::restore() {
   push<RestoreOp>();
-  GetCanvas()->restore();
+  --save_count_;
+  DCHECK_GE(save_count_, 1);
 }
 
 int RecordPaintCanvas::getSaveCount() const {
-  return GetCanvas()->getSaveCount();
+  return save_count_;
 }
 
 void RecordPaintCanvas::restoreToCount(int save_count) {
-  if (!canvas_) {
-    DCHECK_EQ(save_count, 1);
-    return;
-  }
-
   DCHECK_GE(save_count, 1);
-  int diff = GetCanvas()->getSaveCount() - save_count;
+  int diff = getSaveCount() - save_count;
   DCHECK_GE(diff, 0);
   for (int i = 0; i < diff; ++i)
     restore();
@@ -123,68 +109,59 @@
 
 void RecordPaintCanvas::translate(SkScalar dx, SkScalar dy) {
   push<TranslateOp>(dx, dy);
-  GetCanvas()->translate(dx, dy);
 }
 
 void RecordPaintCanvas::scale(SkScalar sx, SkScalar sy) {
   push<ScaleOp>(sx, sy);
-  GetCanvas()->scale(sx, sy);
 }
 
 void RecordPaintCanvas::rotate(SkScalar degrees) {
   push<RotateOp>(degrees);
-  GetCanvas()->rotate(degrees);
 }
 
 void RecordPaintCanvas::concat(const SkMatrix& matrix) {
-  SkM44 m = SkM44(matrix);
-  push<ConcatOp>(m);
-  GetCanvas()->concat(m);
+  concat(SkM44(matrix));
 }
 
 void RecordPaintCanvas::concat(const SkM44& matrix) {
   push<ConcatOp>(matrix);
-  GetCanvas()->concat(matrix);
 }
 
 void RecordPaintCanvas::setMatrix(const SkMatrix& matrix) {
-  SkM44 m = SkM44(matrix);
-  push<SetMatrixOp>(m);
-  GetCanvas()->setMatrix(m);
+  setMatrix(SkM44(matrix));
 }
 
 void RecordPaintCanvas::setMatrix(const SkM44& matrix) {
   push<SetMatrixOp>(matrix);
-  GetCanvas()->setMatrix(matrix);
 }
 
 void RecordPaintCanvas::clipRect(const SkRect& rect,
                                  SkClipOp op,
                                  bool antialias) {
   push<ClipRectOp>(rect, op, antialias);
-  GetCanvas()->clipRect(rect, op, antialias);
 }
 
 void RecordPaintCanvas::clipRRect(const SkRRect& rrect,
                                   SkClipOp op,
                                   bool antialias) {
-  // TODO(enne): does this happen? Should the caller know this?
   if (rrect.isRect()) {
     clipRect(rrect.getBounds(), op, antialias);
     return;
   }
+  clipRRectInternal(rrect, op, antialias);
+}
+
+void RecordPaintCanvas::clipRRectInternal(const SkRRect& rrect,
+                                          SkClipOp op,
+                                          bool antialias) {
   push<ClipRRectOp>(rrect, op, antialias);
-  GetCanvas()->clipRRect(rrect, op, antialias);
 }
 
 void RecordPaintCanvas::clipPath(const SkPath& path,
                                  SkClipOp op,
                                  bool antialias,
                                  UsePaintCache use_paint_cache) {
-  if (!path.isInverseFillType() &&
-      GetCanvas()->getTotalMatrix().rectStaysRect()) {
-    // TODO(enne): do these cases happen? should the caller know that this isn't
-    // a path?
+  if (!path.isInverseFillType()) {
     SkRect rect;
     if (path.isRect(&rect)) {
       clipRect(rect, op, antialias);
@@ -201,30 +178,54 @@
       return;
     }
   }
+  clipPathInternal(path, op, antialias, use_paint_cache);
+}
 
+void RecordPaintCanvas::clipPathInternal(const SkPath& path,
+                                         SkClipOp op,
+                                         bool antialias,
+                                         UsePaintCache use_paint_cache) {
   push<ClipPathOp>(path, op, antialias, use_paint_cache);
-  GetCanvas()->clipPath(path, op, antialias);
-  return;
+}
+
+SkImageInfo RecordPaintCanvas::imageInfo() const {
+  NOTREACHED();
+  return SkImageInfo();
 }
 
 SkRect RecordPaintCanvas::getLocalClipBounds() const {
-  DCHECK(InitializedWithRecordingBounds());
-  return GetCanvas()->getLocalClipBounds();
+  NOTREACHED();
+  return SkRect();
 }
 
 bool RecordPaintCanvas::getLocalClipBounds(SkRect* bounds) const {
-  DCHECK(InitializedWithRecordingBounds());
-  return GetCanvas()->getLocalClipBounds(bounds);
+  NOTREACHED();
+  return false;
 }
 
 SkIRect RecordPaintCanvas::getDeviceClipBounds() const {
-  DCHECK(InitializedWithRecordingBounds());
-  return GetCanvas()->getDeviceClipBounds();
+  NOTREACHED();
+  return SkIRect();
 }
 
 bool RecordPaintCanvas::getDeviceClipBounds(SkIRect* bounds) const {
-  DCHECK(InitializedWithRecordingBounds());
-  return GetCanvas()->getDeviceClipBounds(bounds);
+  NOTREACHED();
+  return false;
+}
+
+bool RecordPaintCanvas::isClipEmpty() const {
+  NOTREACHED();
+  return true;
+}
+
+SkMatrix RecordPaintCanvas::getTotalMatrix() const {
+  NOTREACHED();
+  return SkMatrix();
+}
+
+SkM44 RecordPaintCanvas::getLocalToDevice() const {
+  NOTREACHED();
+  return SkM44();
 }
 
 void RecordPaintCanvas::drawColor(SkColor4f color, SkBlendMode mode) {
@@ -341,19 +342,6 @@
   push<DrawRecordOp>(record);
 }
 
-bool RecordPaintCanvas::isClipEmpty() const {
-  DCHECK(InitializedWithRecordingBounds());
-  return GetCanvas()->isClipEmpty();
-}
-
-SkMatrix RecordPaintCanvas::getTotalMatrix() const {
-  return GetCanvas()->getTotalMatrix();
-}
-
-SkM44 RecordPaintCanvas::getLocalToDevice() const {
-  return GetCanvas()->getLocalToDevice();
-}
-
 void RecordPaintCanvas::Annotate(AnnotationType type,
                                  const SkRect& rect,
                                  sk_sp<SkData> data) {
@@ -368,34 +356,130 @@
   push<SetNodeIdOp>(node_id);
 }
 
-const SkNoDrawCanvas* RecordPaintCanvas::GetCanvas() const {
-  return const_cast<RecordPaintCanvas*>(this)->GetCanvas();
+InspectableRecordPaintCanvas::InspectableRecordPaintCanvas(
+    DisplayItemList* list,
+    const gfx::Size& size)
+    : RecordPaintCanvas(list), canvas_(size.width(), size.height()) {}
+
+InspectableRecordPaintCanvas::~InspectableRecordPaintCanvas() = default;
+
+int InspectableRecordPaintCanvas::save() {
+  return CheckSaveCount(RecordPaintCanvas::save(), canvas_.save());
 }
 
-SkNoDrawCanvas* RecordPaintCanvas::GetCanvas() {
-  if (canvas_)
-    return &*canvas_;
-
-  // Size the canvas to be large enough to contain the |recording_bounds|, which
-  // may not be positioned at the origin.
-  SkIRect enclosing_rect = recording_bounds_.roundOut();
-  canvas_.emplace(enclosing_rect.right(), enclosing_rect.bottom());
-
-  // This is part of the "recording canvases have a size, but why" dance.
-  // By creating a canvas of size (right x bottom) and then clipping it,
-  // It makes getDeviceClipBounds return the original cull rect, which code
-  // in GraphicsContextCanvas on Mac expects.  (Just creating an SkNoDrawCanvas
-  // with the recording_bounds_ makes a canvas of size (width x height) instead
-  // which is incorrect.  SkRecorder cheats with private resetForNextCanvas.
-  canvas_->clipRect(recording_bounds_, SkClipOp::kIntersect, false);
-  return &*canvas_;
+int InspectableRecordPaintCanvas::saveLayerInternal(const SkRect* bounds,
+                                                    const PaintFlags* flags) {
+  int canvas_prev_save_count;
+  // TODO(enne): it appears that image filters affect matrices and color
+  // matrices affect transparent flags on SkCanvas layers, but it's not clear
+  // whether those are actually needed and we could just skip ToSkPaint here.
+  if (flags) {
+    SkPaint paint = flags->ToSkPaint();
+    canvas_prev_save_count = canvas_.saveLayer(bounds, &paint);
+  } else {
+    canvas_prev_save_count = canvas_.saveLayer(bounds, nullptr);
+  }
+  return CheckSaveCount(RecordPaintCanvas::saveLayerInternal(bounds, flags),
+                        canvas_prev_save_count);
 }
 
-bool RecordPaintCanvas::InitializedWithRecordingBounds() const {
-  // If the RecordPaintCanvas is initialized with an empty bounds then
-  // the various clip related functions are not valid and should not
-  // be called.
-  return !recording_bounds_.isEmpty();
+int InspectableRecordPaintCanvas::saveLayerAlpha(const SkRect* bounds,
+                                                 uint8_t alpha) {
+  return CheckSaveCount(RecordPaintCanvas::saveLayerAlpha(bounds, alpha),
+                        canvas_.saveLayerAlpha(bounds, alpha));
+}
+
+void InspectableRecordPaintCanvas::restore() {
+  RecordPaintCanvas::restore();
+  canvas_.restore();
+  DCHECK_EQ(getSaveCount(), canvas_.getSaveCount());
+}
+
+int InspectableRecordPaintCanvas::CheckSaveCount(int super_prev_save_count,
+                                                 int canvas_prev_save_count) {
+  DCHECK_EQ(super_prev_save_count, canvas_prev_save_count);
+  DCHECK_EQ(getSaveCount(), canvas_.getSaveCount());
+  return super_prev_save_count;
+}
+
+void InspectableRecordPaintCanvas::translate(SkScalar dx, SkScalar dy) {
+  RecordPaintCanvas::translate(dx, dy);
+  canvas_.translate(dx, dy);
+}
+
+void InspectableRecordPaintCanvas::scale(SkScalar sx, SkScalar sy) {
+  RecordPaintCanvas::scale(sx, sy);
+  canvas_.scale(sx, sy);
+}
+
+void InspectableRecordPaintCanvas::rotate(SkScalar degrees) {
+  RecordPaintCanvas::rotate(degrees);
+  canvas_.rotate(degrees);
+}
+
+void InspectableRecordPaintCanvas::concat(const SkM44& matrix) {
+  RecordPaintCanvas::concat(matrix);
+  canvas_.concat(matrix);
+}
+
+void InspectableRecordPaintCanvas::setMatrix(const SkM44& matrix) {
+  RecordPaintCanvas::setMatrix(matrix);
+  canvas_.setMatrix(matrix);
+}
+
+void InspectableRecordPaintCanvas::clipRect(const SkRect& rect,
+                                            SkClipOp op,
+                                            bool antialias) {
+  RecordPaintCanvas::clipRect(rect, op, antialias);
+  canvas_.clipRect(rect, op, antialias);
+}
+
+void InspectableRecordPaintCanvas::clipRRectInternal(const SkRRect& rrect,
+                                                     SkClipOp op,
+                                                     bool antialias) {
+  RecordPaintCanvas::clipRRectInternal(rrect, op, antialias);
+  canvas_.clipRRect(rrect, op, antialias);
+}
+
+void InspectableRecordPaintCanvas::clipPathInternal(
+    const SkPath& path,
+    SkClipOp op,
+    bool antialias,
+    UsePaintCache use_paint_cache) {
+  RecordPaintCanvas::clipPathInternal(path, op, antialias, use_paint_cache);
+  canvas_.clipPath(path, op, antialias);
+}
+
+SkImageInfo InspectableRecordPaintCanvas::imageInfo() const {
+  return canvas_.imageInfo();
+}
+
+SkRect InspectableRecordPaintCanvas::getLocalClipBounds() const {
+  return canvas_.getLocalClipBounds();
+}
+
+bool InspectableRecordPaintCanvas::getLocalClipBounds(SkRect* bounds) const {
+  return canvas_.getLocalClipBounds(bounds);
+}
+
+SkIRect InspectableRecordPaintCanvas::getDeviceClipBounds() const {
+  return canvas_.getDeviceClipBounds();
+}
+
+bool InspectableRecordPaintCanvas::getDeviceClipBounds(SkIRect* bounds) const {
+  return canvas_.getDeviceClipBounds(bounds);
+}
+
+bool InspectableRecordPaintCanvas::isClipEmpty() const {
+  return canvas_.isClipEmpty();
+}
+
+SkMatrix InspectableRecordPaintCanvas::getTotalMatrix() const {
+  return canvas_.getTotalMatrix();
+}
+
+SkM44 InspectableRecordPaintCanvas::getLocalToDevice() const {
+  return canvas_.getLocalToDevice();
 }
 
 }  // namespace cc
diff --git a/cc/paint/record_paint_canvas.h b/cc/paint/record_paint_canvas.h
index 456b6d1e..7d27edc 100644
--- a/cc/paint/record_paint_canvas.h
+++ b/cc/paint/record_paint_canvas.h
@@ -20,52 +20,63 @@
 class DisplayItemList;
 class PaintFlags;
 
+// This implementation of PaintCanvas records paint operations into the given
+// DisplayItemList. The methods that inspect the current clip or CTM are not
+// implemented (DCHECK will fail if called). Use InspectableRecordPaintCanvas
+// instead if the client needs to call those methods.
 class CC_PAINT_EXPORT RecordPaintCanvas : public PaintCanvas {
  public:
-  RecordPaintCanvas(DisplayItemList* list, const SkRect& bounds);
-  RecordPaintCanvas(const RecordPaintCanvas&) = delete;
+  explicit RecordPaintCanvas(DisplayItemList* list);
   ~RecordPaintCanvas() override;
 
+  RecordPaintCanvas(const RecordPaintCanvas&) = delete;
   RecordPaintCanvas& operator=(const RecordPaintCanvas&) = delete;
 
-  SkImageInfo imageInfo() const override;
-
   void* accessTopLayerPixels(SkImageInfo* info,
                              size_t* rowBytes,
                              SkIPoint* origin = nullptr) override;
 
   void flush() override;
+  bool NeedsFlush() const override;
 
   int save() override;
-  int saveLayer(const SkRect* bounds, const PaintFlags* flags) override;
+  int saveLayer(const SkRect* bounds, const PaintFlags* flags) final;
   int saveLayerAlpha(const SkRect* bounds, uint8_t alpha) override;
-
   void restore() override;
-  int getSaveCount() const override;
+  int getSaveCount() const final;
   void restoreToCount(int save_count) override;
+
   void translate(SkScalar dx, SkScalar dy) override;
   void scale(SkScalar sx, SkScalar sy) override;
   void rotate(SkScalar degrees) override;
   // TODO(crbug.com/1167153): The concat and setMatrix methods that take an
   // SkMatrix should be removed in favor of the SkM44 versions.
-  void concat(const SkMatrix& matrix) override;
-  void setMatrix(const SkMatrix& matrix) override;
+  void concat(const SkMatrix& matrix) final;
+  void setMatrix(const SkMatrix& matrix) final;
   void concat(const SkM44& matrix) override;
   void setMatrix(const SkM44& matrix) override;
 
   void clipRect(const SkRect& rect, SkClipOp op, bool antialias) override;
-  void clipRRect(const SkRRect& rrect, SkClipOp op, bool antialias) override;
+  void clipRRect(const SkRRect& rrect, SkClipOp op, bool antialias) final;
   void clipPath(const SkPath& path,
                 SkClipOp op,
                 bool antialias,
-                UsePaintCache use_paint_cache) override;
+                UsePaintCache use_paint_cache) final;
+
+  // These state-query functions can be called only if `size` is not empty in
+  // the constructor. With this restriction, we don't need to create
+  // SkNoDrawCanvas for clients that only need recording.
+  SkImageInfo imageInfo() const override;
   SkRect getLocalClipBounds() const override;
   bool getLocalClipBounds(SkRect* bounds) const override;
   SkIRect getDeviceClipBounds() const override;
   bool getDeviceClipBounds(SkIRect* bounds) const override;
+  bool isClipEmpty() const override;
+  SkMatrix getTotalMatrix() const override;
+  SkM44 getLocalToDevice() const override;
+
   void drawColor(SkColor4f color, SkBlendMode mode) override;
   void clear(SkColor4f color) override;
-
   void drawLine(SkScalar x0,
                 SkScalar y0,
                 SkScalar x1,
@@ -111,27 +122,21 @@
                     SkScalar y,
                     NodeId node_id,
                     const PaintFlags& flags) override;
-
   void drawPicture(sk_sp<const PaintRecord> record) override;
 
-  bool isClipEmpty() const override;
-  SkMatrix getTotalMatrix() const override;
-  SkM44 getLocalToDevice() const override;
-
   void Annotate(AnnotationType type,
                 const SkRect& rect,
                 sk_sp<SkData> data) override;
   void recordCustomData(uint32_t id) override;
   void setNodeId(int) override;
 
-  bool NeedsFlush() const override;
-
   // Don't shadow non-virtual helper functions.
+  using PaintCanvas::clipPath;
   using PaintCanvas::clipRect;
   using PaintCanvas::clipRRect;
-  using PaintCanvas::clipPath;
   using PaintCanvas::drawColor;
   using PaintCanvas::drawImage;
+  using PaintCanvas::drawPath;
   using PaintCanvas::drawPicture;
 
 #if DCHECK_IS_ON()
@@ -170,33 +175,75 @@
 #endif
   };
 
+ protected:
+  virtual int saveLayerInternal(const SkRect* bounds, const PaintFlags* flags);
+  virtual void clipRRectInternal(const SkRRect& rrect,
+                                 SkClipOp op,
+                                 bool antialias);
+  virtual void clipPathInternal(const SkPath& path,
+                                SkClipOp op,
+                                bool antialias,
+                                UsePaintCache use_paint_cache);
+
  private:
   template <typename T, typename... Args>
   size_t push(Args&&... args);
 
-  const SkNoDrawCanvas* GetCanvas() const;
-  SkNoDrawCanvas* GetCanvas();
-
-  bool InitializedWithRecordingBounds() const;
-
   DisplayItemList* list_;
+  int save_count_ = 1;
 
-  // TODO(enne): Although RecordPaintCanvas is mostly a write-only interface
-  // where paint commands are stored, occasionally users of PaintCanvas want
-  // to ask stateful questions mid-stream of clip and transform state.
-  // To avoid duplicating all this code (for now?), just forward to an SkCanvas
-  // that's not backed by anything but can answer these questions.
-  //
-  // This is mutable so that const functions (e.g. quickReject) that may
-  // lazy initialize the canvas can still be const.
-  mutable absl::optional<SkNoDrawCanvas> canvas_;
-  SkRect recording_bounds_;
   bool needs_flush_ = false;
 #if DCHECK_IS_ON()
   unsigned disable_flush_check_scope_ = 0;
 #endif
 };
 
+// Besides the recording functions, this implementation of PaintCanvas allows
+// inspection of the current clip and CTM during recording.
+class CC_PAINT_EXPORT InspectableRecordPaintCanvas : public RecordPaintCanvas {
+ public:
+  InspectableRecordPaintCanvas(DisplayItemList* list, const gfx::Size& size);
+  ~InspectableRecordPaintCanvas() override;
+
+  int save() override;
+  int saveLayerAlpha(const SkRect* bounds, uint8_t alpha) override;
+  void restore() override;
+
+  void translate(SkScalar dx, SkScalar dy) override;
+  void scale(SkScalar sx, SkScalar sy) override;
+  void rotate(SkScalar degrees) override;
+  void concat(const SkM44& matrix) override;
+  void setMatrix(const SkM44& matrix) override;
+
+  void clipRect(const SkRect& rect, SkClipOp op, bool antialias) override;
+
+  SkImageInfo imageInfo() const override;
+  SkRect getLocalClipBounds() const override;
+  bool getLocalClipBounds(SkRect* bounds) const override;
+  SkIRect getDeviceClipBounds() const override;
+  bool getDeviceClipBounds(SkIRect* bounds) const override;
+  bool isClipEmpty() const override;
+  SkMatrix getTotalMatrix() const override;
+  SkM44 getLocalToDevice() const override;
+
+  // Don't shadow non-virtual helper functions.
+  using RecordPaintCanvas::clipRect;
+
+ private:
+  int saveLayerInternal(const SkRect* bounds, const PaintFlags* flags) override;
+  void clipRRectInternal(const SkRRect& rrect,
+                         SkClipOp op,
+                         bool antialias) override;
+  void clipPathInternal(const SkPath& path,
+                        SkClipOp op,
+                        bool antialias,
+                        UsePaintCache use_paint_cache) override;
+
+  int CheckSaveCount(int super_prev_save_count, int canvas_prev_save_count);
+
+  SkNoDrawCanvas canvas_;
+};
+
 }  // namespace cc
 
 #endif  // CC_PAINT_RECORD_PAINT_CANVAS_H_
diff --git a/cc/paint/skia_paint_canvas_unittest.cc b/cc/paint/skia_paint_canvas_unittest.cc
index 7e090f9..b772c33 100644
--- a/cc/paint/skia_paint_canvas_unittest.cc
+++ b/cc/paint/skia_paint_canvas_unittest.cc
@@ -37,7 +37,7 @@
   PaintRecorder recorder;
   SkRect rect = SkRect::MakeWH(10, 10);
   PaintFlags flags;
-  recorder.beginRecording(rect);
+  recorder.beginRecording();
   for (int i = 0; i < 11; i++)
     recorder.getRecordingCanvas()->drawRect(rect, flags);
   auto record = recorder.finishRecordingAsPicture();
diff --git a/cc/paint/solid_color_analyzer_unittest.cc b/cc/paint/solid_color_analyzer_unittest.cc
index fa2cadd27..24279173 100644
--- a/cc/paint/solid_color_analyzer_unittest.cc
+++ b/cc/paint/solid_color_analyzer_unittest.cc
@@ -37,10 +37,10 @@
   }
 
   void Initialize(const gfx::Rect& rect = gfx::Rect(0, 0, 100, 100)) {
-    canvas_.emplace(display_item_list_.get(), gfx::RectToSkRect(rect));
+    canvas_.emplace(display_item_list_.get());
     rect_ = rect;
   }
-  RecordPaintCanvas* canvas() { return &*canvas_; }
+  PaintCanvas* canvas() { return &*canvas_; }
 
   bool IsSolidColor(int max_ops_to_analyze = 1) {
     Finalize();
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 259e89e..45a1f43 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -4330,7 +4330,7 @@
     "//chrome/browser/password_manager/android:test_support",
     "//chrome/browser/subresource_filter:android_test_support",
     "//chrome/browser/supervised_user:test_support",
-    "//components/autofill_assistant/browser:android_test_support",
+    "//components/autofill_assistant/browser:test_support",
     "//components/crash/android:crash_android",
     "//components/external_intents/android:test_support",
     "//components/minidump_uploader",
diff --git a/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/v2/TestFeedServer.java b/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/v2/TestFeedServer.java
index a11c73e..dddeeaf8 100644
--- a/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/v2/TestFeedServer.java
+++ b/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/v2/TestFeedServer.java
@@ -23,6 +23,8 @@
  */
 public class TestFeedServer implements WebServer.RequestHandler {
     private static final String TAG = "TestFeedServer";
+    private static final String FEED_RESPONSE_BINARYPB_PATH =
+            "/chrome/test/data/android/feed/v2/feed_query_normal_response.binarypb";
     private WebServer mServer;
     private boolean mReceivedQueryRequest;
 
@@ -32,6 +34,8 @@
             TestThreadUtils.runOnUiThreadBlocking(() -> {
                 UserPrefs.get(Profile.getLastUsedRegularProfile())
                         .setString(Pref.HOST_OVERRIDE_HOST, getBaseUrl());
+                UserPrefs.get(Profile.getLastUsedRegularProfile())
+                        .setString(Pref.DISCOVER_API_ENDPOINT_OVERRIDE, getBaseUrl());
             });
             mServer.setRequestHandler(this);
         } catch (Exception e) {
@@ -47,10 +51,6 @@
         return mServer.getBaseUrl();
     }
 
-    public boolean receivedQueryRequest() {
-        return mReceivedQueryRequest;
-    }
-
     @Override
     public void handleRequest(WebServer.HTTPRequest request, OutputStream output) {
         try {
@@ -63,13 +63,16 @@
     private void tryHandleRequest(WebServer.HTTPRequest request, OutputStream output)
             throws IOException {
         if (request.getMethod().equals("GET") && request.getURI().contains("/FeedQuery?")) {
-            mReceivedQueryRequest = true;
-
-            WebServer.writeResponse(output, WebServer.STATUS_OK,
-                    feedQueryResponse("/chrome/test/data/android/feed/v2/"
-                            + "feed_query_normal_response.binarypb"));
+            WebServer.writeResponse(
+                    output, WebServer.STATUS_OK, feedQueryResponse(FEED_RESPONSE_BINARYPB_PATH));
             return;
         }
+        if (request.getURI().contains("queryInteractiveFeed")) {
+            WebServer.writeResponse(output, WebServer.STATUS_OK,
+                    readFile(UrlUtils.getIsolatedTestFilePath(FEED_RESPONSE_BINARYPB_PATH)));
+            return;
+        }
+
         // Note: Support could be added for NextPageQuery and actions:upload.
         Log.e(TAG, "Unhandled request: " + request);
     }
@@ -78,6 +81,7 @@
         RandomAccessFile file = new RandomAccessFile(filePath, "r");
         byte[] bytes = new byte[(int) file.length()];
         int bytesRead = file.read(bytes);
+        file.close();
         if (bytesRead != bytes.length) {
             return Arrays.copyOfRange(bytes, 0, bytesRead);
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/WarmupManager.java b/chrome/android/java/src/org/chromium/chrome/browser/WarmupManager.java
index 137a9bf..160c193 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/WarmupManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/WarmupManager.java
@@ -7,7 +7,6 @@
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.net.Uri;
-import android.os.SystemClock;
 import android.view.ContextThemeWrapper;
 import android.view.InflateException;
 import android.view.View;
@@ -15,7 +14,6 @@
 import android.view.ViewStub;
 import android.widget.FrameLayout;
 
-import androidx.annotation.IntDef;
 import androidx.annotation.VisibleForTesting;
 
 import org.chromium.base.Log;
@@ -23,7 +21,6 @@
 import org.chromium.base.TraceEvent;
 import org.chromium.base.annotations.NativeMethods;
 import org.chromium.base.library_loader.LibraryLoader;
-import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.task.AsyncTask;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.crash.ChromePureJavaExceptionReporter;
@@ -34,8 +31,6 @@
 import org.chromium.content_public.browser.WebContentsObserver;
 import org.chromium.ui.LayoutInflaterUtils;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.net.InetAddress;
 import java.net.MalformedURLException;
 import java.net.URL;
@@ -54,39 +49,12 @@
 public class WarmupManager {
     private static final String TAG = "WarmupManager";
 
-    @VisibleForTesting
-    static final String WEBCONTENTS_STATUS_HISTOGRAM = "CustomTabs.SpareWebContents.Status2";
-
-    public static final boolean FOR_CCT = true;
-
-    // See CustomTabs.SpareWebContentsStatus histogram. Append-only.
-    @IntDef({WebContentsStatus.CREATED, WebContentsStatus.USED, WebContentsStatus.KILLED,
-            WebContentsStatus.DESTROYED, WebContentsStatus.STOLEN})
-    @Retention(RetentionPolicy.SOURCE)
-    @interface WebContentsStatus {
-        @VisibleForTesting
-        int CREATED = 0;
-        @VisibleForTesting
-        int USED = 1;
-        @VisibleForTesting
-        int KILLED = 2;
-        @VisibleForTesting
-        int DESTROYED = 3;
-        @VisibleForTesting
-        int STOLEN = 4;
-        int NUM_ENTRIES = 5;
-    }
-
     /**
      * Observes spare WebContents deaths. In case of death, records stats, and cleanup the objects.
      */
     private class RenderProcessGoneObserver extends WebContentsObserver {
         @Override
         public void renderProcessGone() {
-            long elapsed = SystemClock.elapsedRealtime() - mWebContentsCreationTimeMs;
-            RecordHistogram.recordLongTimesHistogram(
-                    "CustomTabs.SpareWebContents.TimeBeforeDeath", elapsed);
-            recordWebContentsStatus(WebContentsStatus.KILLED);
             destroySpareWebContentsInternal();
         }
     }
@@ -101,9 +69,7 @@
     private ViewGroup mMainView;
     @VisibleForTesting
     WebContents mSpareWebContents;
-    private long mWebContentsCreationTimeMs;
     private RenderProcessGoneObserver mObserver;
-    private boolean mWebContentsCreatedForCCT;
 
     /**
      * @return The singleton instance for the WarmupManager, creating one if necessary.
@@ -314,20 +280,15 @@
      *
      * This creates a renderer that is suitable for any navigation. It can be picked up by any tab.
      * Can be called multiple times, and must be called from the UI thread.
-     *
-     * @param forCCT Whether this WebContents is being created for CCT.
      */
-    public void createSpareWebContents(boolean forCCT) {
+    public void createSpareWebContents() {
         ThreadUtils.assertOnUiThread();
         if (!LibraryLoader.getInstance().isInitialized() || mSpareWebContents != null) return;
 
-        mWebContentsCreatedForCCT = forCCT;
         mSpareWebContents = new WebContentsFactory().createWebContentsWithWarmRenderer(
                 Profile.getLastUsedRegularProfile(), true /* initiallyHidden */);
         mObserver = new RenderProcessGoneObserver();
         mSpareWebContents.addObserver(mObserver);
-        mWebContentsCreationTimeMs = SystemClock.elapsedRealtime();
-        recordWebContentsStatus(WebContentsStatus.CREATED);
     }
 
     /**
@@ -336,7 +297,6 @@
     public void destroySpareWebContents() {
         ThreadUtils.assertOnUiThread();
         if (mSpareWebContents == null) return;
-        recordWebContentsStatus(WebContentsStatus.DESTROYED);
         destroySpareWebContentsInternal();
     }
 
@@ -348,8 +308,7 @@
      *
      * @return a WebContents, or null.
      */
-    public WebContents takeSpareWebContents(
-            boolean incognito, boolean initiallyHidden, boolean forCCT) {
+    public WebContents takeSpareWebContents(boolean incognito, boolean initiallyHidden) {
         ThreadUtils.assertOnUiThread();
         if (incognito) return null;
         WebContents result = mSpareWebContents;
@@ -358,8 +317,6 @@
         result.removeObserver(mObserver);
         mObserver = null;
         if (!initiallyHidden) result.onShow();
-        recordWebContentsStatus(mWebContentsCreatedForCCT == forCCT ? WebContentsStatus.USED
-                                                                    : WebContentsStatus.STOLEN);
         return result;
     }
 
@@ -377,12 +334,6 @@
         mObserver = null;
     }
 
-    private void recordWebContentsStatus(@WebContentsStatus int status) {
-        if (!mWebContentsCreatedForCCT) return;
-        RecordHistogram.recordEnumeratedHistogram(
-                WEBCONTENTS_STATUS_HISTOGRAM, status, WebContentsStatus.NUM_ENTRIES);
-    }
-
     @NativeMethods
     interface Natives {
         void startPreconnectPredictorInitialization(Profile profile);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/PowerBookmarkShoppingItemRow.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/PowerBookmarkShoppingItemRow.java
index f43761fd..9c51496 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/PowerBookmarkShoppingItemRow.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/PowerBookmarkShoppingItemRow.java
@@ -30,6 +30,7 @@
 import org.chromium.components.payments.CurrencyFormatter;
 import org.chromium.components.power_bookmarks.PowerBookmarkMeta;
 import org.chromium.components.power_bookmarks.ProductPrice;
+import org.chromium.components.power_bookmarks.ShoppingSpecifics;
 
 import java.util.Locale;
 
@@ -73,16 +74,17 @@
         PowerBookmarkMeta meta = mBookmarkModel.getPowerBookmarkMeta(bookmarkId);
         assert meta != null;
 
-        // TODO(crbug.com/1243383): Pull price updates once they're available.
-        ProductPrice originalPrice = meta.getShoppingSpecifics().getCurrentPrice();
+        ShoppingSpecifics specifics = meta.getShoppingSpecifics();
+        ProductPrice currentPrice = specifics.getCurrentPrice();
+        ProductPrice previousPrice = specifics.getPreviousPrice();
         mSubscription = PowerBookmarkUtils.createCommerceSubscriptionForPowerBookmarkMeta(meta);
         mCurrencyFormatter =
-                new CurrencyFormatter(originalPrice.getCurrencyCode(), Locale.getDefault());
+                new CurrencyFormatter(currentPrice.getCurrencyCode(), Locale.getDefault());
 
-        boolean mIsPriceTrackingEnabled =
-                meta != null && meta.getShoppingSpecifics().getIsPriceTracked();
+        boolean mIsPriceTrackingEnabled = specifics.getIsPriceTracked();
         initPriceTrackingUI(meta.getLeadImage().getUrl(), mIsPriceTrackingEnabled,
-                originalPrice.getAmountMicros(), originalPrice.getAmountMicros());
+                previousPrice.getAmountMicros(), currentPrice.getAmountMicros());
+
         return bookmarkItem;
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
index 1e572fd..fc302d1c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
@@ -1704,7 +1704,7 @@
 
     public static void createSpareWebContents() {
         if (SysUtils.isLowEndDevice()) return;
-        WarmupManager.getInstance().createSpareWebContents(WarmupManager.FOR_CCT);
+        WarmupManager.getInstance().createSpareWebContents();
     }
 
     public boolean receiveFile(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabController.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabController.java
index 1e697a8..61f29aa 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabController.java
@@ -383,8 +383,8 @@
             return webContents;
         }
 
-        webContents = mWarmupManager.takeSpareWebContents(mIntentDataProvider.isIncognito(),
-                false /*initiallyHidden*/, WarmupManager.FOR_CCT);
+        webContents = mWarmupManager.takeSpareWebContents(
+                mIntentDataProvider.isIncognito(), false /*initiallyHidden*/);
         if (webContents != null) {
             recordWebContentsStateOnLaunch(WebContentsState.SPARE_WEBCONTENTS);
             return webContents;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/segmentation_platform/PriceTrackingActionProviderTest.java b/chrome/android/java/src/org/chromium/chrome/browser/segmentation_platform/PriceTrackingActionProviderTest.java
index 4804b055..dbde53c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/segmentation_platform/PriceTrackingActionProviderTest.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/segmentation_platform/PriceTrackingActionProviderTest.java
@@ -10,6 +10,8 @@
 
 import android.os.Handler;
 
+import com.google.common.base.Optional;
+
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
@@ -80,7 +82,8 @@
     }
 
     private void setPriceTrackingBackendResult(boolean hasProductInfo) {
-        ProductInfo testProductInfo = new ProductInfo(null, null, 0, 0, null, 0, null);
+        ProductInfo testProductInfo =
+                new ProductInfo(null, null, 0, 0, null, 0, null, Optional.absent());
         Mockito.doAnswer(invocation -> {
                    ProductInfoCallback callback = invocation.getArgument(1);
                    callback.onResult(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java
index 45efc10..8c7a6ec 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java
@@ -558,8 +558,8 @@
 
         if (mPendingLoadParams != null) {
             assert isFrozen();
-            WebContents webContents = WarmupManager.getInstance().takeSpareWebContents(
-                    isIncognito(), isHidden(), isCustomTab());
+            WebContents webContents =
+                    WarmupManager.getInstance().takeSpareWebContents(isIncognito(), isHidden());
             if (webContents == null) {
                 Profile profile =
                         IncognitoUtils.getProfileFromWindowAndroid(mWindowAndroid, isIncognito());
@@ -917,7 +917,7 @@
             boolean creatingWebContents = webContents == null;
             if (creatingWebContents) {
                 webContents = WarmupManager.getInstance().takeSpareWebContents(
-                        isIncognito(), initiallyHidden, isCustomTab());
+                        isIncognito(), initiallyHidden);
                 if (webContents == null) {
                     Profile profile = IncognitoUtils.getProfileFromWindowAndroid(
                             mWindowAndroid, isIncognito());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/WarmupManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/WarmupManagerTest.java
index 5bdf1a2..369dfe34 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/WarmupManagerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/WarmupManagerTest.java
@@ -24,7 +24,6 @@
 import org.chromium.base.test.params.ParameterSet;
 import org.chromium.base.test.params.ParameterizedRunner;
 import org.chromium.base.test.util.CriteriaHelper;
-import org.chromium.base.test.util.MetricsUtils;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
 import org.chromium.chrome.browser.profiles.OTRProfileID;
@@ -133,10 +132,9 @@
         final AtomicReference<WebContents> webContentsReference = new AtomicReference<>();
 
         PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT, () -> {
-            mWarmupManager.createSpareWebContents(!WarmupManager.FOR_CCT);
+            mWarmupManager.createSpareWebContents();
             Assert.assertTrue(mWarmupManager.hasSpareWebContents());
-            WebContents webContents =
-                    mWarmupManager.takeSpareWebContents(false, false, !WarmupManager.FOR_CCT);
+            WebContents webContents = mWarmupManager.takeSpareWebContents(false, false);
             Assert.assertNotNull(webContents);
             Assert.assertFalse(mWarmupManager.hasSpareWebContents());
 
@@ -164,9 +162,8 @@
     @SmallTest
     @UiThreadTest
     public void testTakeSpareWebContents() {
-        mWarmupManager.createSpareWebContents(!WarmupManager.FOR_CCT);
-        WebContents webContents =
-                mWarmupManager.takeSpareWebContents(false, false, !WarmupManager.FOR_CCT);
+        mWarmupManager.createSpareWebContents();
+        WebContents webContents = mWarmupManager.takeSpareWebContents(false, false);
         Assert.assertNotNull(webContents);
         Assert.assertFalse(mWarmupManager.hasSpareWebContents());
         webContents.destroy();
@@ -176,12 +173,11 @@
     @SmallTest
     @UiThreadTest
     public void testTakeSpareWebContentsChecksArguments() {
-        mWarmupManager.createSpareWebContents(!WarmupManager.FOR_CCT);
-        Assert.assertNull(mWarmupManager.takeSpareWebContents(true, false, !WarmupManager.FOR_CCT));
-        Assert.assertNull(mWarmupManager.takeSpareWebContents(true, true, !WarmupManager.FOR_CCT));
+        mWarmupManager.createSpareWebContents();
+        Assert.assertNull(mWarmupManager.takeSpareWebContents(true, false));
+        Assert.assertNull(mWarmupManager.takeSpareWebContents(true, true));
         Assert.assertTrue(mWarmupManager.hasSpareWebContents());
-        Assert.assertNotNull(
-                mWarmupManager.takeSpareWebContents(false, true, !WarmupManager.FOR_CCT));
+        Assert.assertNotNull(mWarmupManager.takeSpareWebContents(false, true));
         Assert.assertFalse(mWarmupManager.hasSpareWebContents());
     }
 
@@ -189,64 +185,9 @@
     @SmallTest
     @UiThreadTest
     public void testClearsDeadWebContents() {
-        mWarmupManager.createSpareWebContents(!WarmupManager.FOR_CCT);
+        mWarmupManager.createSpareWebContents();
         WebContentsUtils.simulateRendererKilled(mWarmupManager.mSpareWebContents);
-        Assert.assertNull(
-                mWarmupManager.takeSpareWebContents(false, false, !WarmupManager.FOR_CCT));
-    }
-
-    @Test
-    @SmallTest
-    @UiThreadTest
-    public void testRecordWebContentsStatus() {
-        String name = WarmupManager.WEBCONTENTS_STATUS_HISTOGRAM;
-        MetricsUtils.HistogramDelta createdDelta =
-                new MetricsUtils.HistogramDelta(name, WarmupManager.WebContentsStatus.CREATED);
-        MetricsUtils.HistogramDelta usedDelta =
-                new MetricsUtils.HistogramDelta(name, WarmupManager.WebContentsStatus.USED);
-        MetricsUtils.HistogramDelta killedDelta =
-                new MetricsUtils.HistogramDelta(name, WarmupManager.WebContentsStatus.KILLED);
-        MetricsUtils.HistogramDelta destroyedDelta =
-                new MetricsUtils.HistogramDelta(name, WarmupManager.WebContentsStatus.DESTROYED);
-        MetricsUtils.HistogramDelta stolenDelta =
-                new MetricsUtils.HistogramDelta(name, WarmupManager.WebContentsStatus.STOLEN);
-
-        // Created, used.
-        mWarmupManager.createSpareWebContents(WarmupManager.FOR_CCT);
-        Assert.assertEquals(1, createdDelta.getDelta());
-        Assert.assertNotNull(
-                mWarmupManager.takeSpareWebContents(false, false, WarmupManager.FOR_CCT));
-        Assert.assertEquals(1, usedDelta.getDelta());
-
-        // Created, killed.
-        mWarmupManager.createSpareWebContents(WarmupManager.FOR_CCT);
-        Assert.assertEquals(2, createdDelta.getDelta());
-        Assert.assertNotNull(mWarmupManager.mSpareWebContents);
-        WebContentsUtils.simulateRendererKilled(mWarmupManager.mSpareWebContents);
-        Assert.assertEquals(1, killedDelta.getDelta());
-        Assert.assertNull(mWarmupManager.takeSpareWebContents(false, false, WarmupManager.FOR_CCT));
-
-        // Created, destroyed.
-        mWarmupManager.createSpareWebContents(WarmupManager.FOR_CCT);
-        Assert.assertEquals(3, createdDelta.getDelta());
-        Assert.assertNotNull(mWarmupManager.mSpareWebContents);
-        mWarmupManager.destroySpareWebContents();
-        Assert.assertEquals(1, destroyedDelta.getDelta());
-
-        // Created, stolen.
-        mWarmupManager.createSpareWebContents(WarmupManager.FOR_CCT);
-        Assert.assertEquals(4, createdDelta.getDelta());
-        Assert.assertNotNull(
-                mWarmupManager.takeSpareWebContents(false, false, !WarmupManager.FOR_CCT));
-        Assert.assertEquals(1, stolenDelta.getDelta());
-
-        // Created, used, not for CCT.
-        mWarmupManager.createSpareWebContents(!WarmupManager.FOR_CCT);
-        Assert.assertEquals(4, createdDelta.getDelta());
-        Assert.assertNotNull(
-                mWarmupManager.takeSpareWebContents(false, false, !WarmupManager.FOR_CCT));
-        Assert.assertEquals(1, stolenDelta.getDelta());
-        Assert.assertEquals(1, usedDelta.getDelta());
+        Assert.assertNull(mWarmupManager.takeSpareWebContents(false, false));
     }
 
     /** Checks that the View inflation works. */
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/app/bookmarks/BookmarkTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/app/bookmarks/BookmarkTest.java
index edcde3b..b37b8714 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/app/bookmarks/BookmarkTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/app/bookmarks/BookmarkTest.java
@@ -271,6 +271,7 @@
 
     @Test
     @SmallTest
+    @DisabledTest(message = "crbug.com/1385530")
     public void testAddBookmark() throws Exception {
         mActivityTestRule.loadUrl(mTestPage);
         // Check partner bookmarks are lazily loaded.
@@ -1160,6 +1161,7 @@
 
     @Test
     @MediumTest
+    @DisabledTest(message = "crbug.com/1385530")
     public void testMoveButtonsGoneForPartnerBookmarks() throws Exception {
         loadFakePartnerBookmarkShimForTesting();
         BookmarkPromoHeader.forcePromoStateForTests(SyncPromoState.NO_PROMO);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkPersonalizedSigninPromoTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkPersonalizedSigninPromoTest.java
index f727570..54357ba8 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkPersonalizedSigninPromoTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkPersonalizedSigninPromoTest.java
@@ -57,6 +57,10 @@
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 @Batch(Batch.PER_CLASS)
 public class BookmarkPersonalizedSigninPromoTest {
+    private static final String CONTINUED_HISTOGRAM_NAME =
+            "Signin.SyncPromo.Continued.Count.Bookmarks";
+    private static final String SHOWN_HISTOGRAM_NAME = "Signin.SyncPromo.Shown.Count.Bookmarks";
+
     private final AccountManagerTestRule mAccountManagerTestRule = new AccountManagerTestRule();
 
     private final BookmarkTestRule mBookmarkTestRule = new BookmarkTestRule();
@@ -97,53 +101,59 @@
     @Test
     @MediumTest
     public void testSigninButtonDefaultAccount() {
+        final HistogramDelta continuedHistogram = new HistogramDelta(CONTINUED_HISTOGRAM_NAME, 1);
         final CoreAccountInfo accountInfo =
                 mAccountManagerTestRule.addAccount(AccountManagerTestRule.TEST_ACCOUNT_EMAIL);
-        HistogramDelta signinHistogram =
-                new HistogramDelta("Signin.SyncPromo.Continued.Count.Bookmarks", 1);
         showBookmarkManagerAndCheckSigninPromoIsDisplayed();
 
         onView(allOf(withId(R.id.sync_promo_signin_button), activeInRecyclerView()))
                 .perform(click());
-
+        Assert.assertEquals(1, continuedHistogram.getDelta());
+        Assert.assertEquals(
+                mMockSyncConsentActivityLauncher, SyncConsentActivityLauncherImpl.get());
         verify(mMockSyncConsentActivityLauncher)
                 .launchActivityForPromoDefaultFlow(any(Activity.class),
                         eq(SigninAccessPoint.BOOKMARK_MANAGER), eq(accountInfo.getEmail()));
-        Assert.assertEquals(1, signinHistogram.getDelta());
     }
 
     @Test
     @MediumTest
     public void testSigninButtonNotDefaultAccount() {
-        HistogramDelta signinHistogram =
-                new HistogramDelta("Signin.SyncPromo.Continued.Count.Bookmarks", 1);
-        CoreAccountInfo accountInfo =
+        HistogramDelta continuedHistogram = new HistogramDelta(CONTINUED_HISTOGRAM_NAME, 1);
+        final CoreAccountInfo accountInfo =
                 mAccountManagerTestRule.addAccount(AccountManagerTestRule.TEST_ACCOUNT_EMAIL);
         showBookmarkManagerAndCheckSigninPromoIsDisplayed();
+
         onView(allOf(withId(R.id.sync_promo_choose_account_button), activeInRecyclerView()))
                 .perform(click());
+        Assert.assertEquals(1, continuedHistogram.getDelta());
+        Assert.assertEquals(
+                mMockSyncConsentActivityLauncher, SyncConsentActivityLauncherImpl.get());
         verify(mMockSyncConsentActivityLauncher)
                 .launchActivityForPromoChooseAccountFlow(any(Activity.class),
                         eq(SigninAccessPoint.BOOKMARK_MANAGER), eq(accountInfo.getEmail()));
-        Assert.assertEquals(1, signinHistogram.getDelta());
     }
 
     @Test
     @MediumTest
     public void testSigninButtonNewAccount() {
-        HistogramDelta signinHistogram =
-                new HistogramDelta("Signin.SyncPromo.Continued.Count.Bookmarks", 1);
+        final HistogramDelta continuedHistogram = new HistogramDelta(CONTINUED_HISTOGRAM_NAME, 1);
         showBookmarkManagerAndCheckSigninPromoIsDisplayed();
+
         onView(allOf(withId(R.id.sync_promo_signin_button), activeInRecyclerView()))
                 .perform(click());
+        Assert.assertEquals(1, continuedHistogram.getDelta());
+        Assert.assertEquals(
+                mMockSyncConsentActivityLauncher, SyncConsentActivityLauncherImpl.get());
         verify(mMockSyncConsentActivityLauncher)
                 .launchActivityForPromoAddAccountFlow(
                         any(Activity.class), eq(SigninAccessPoint.BOOKMARK_MANAGER));
-        Assert.assertEquals(1, signinHistogram.getDelta());
     }
 
     private void showBookmarkManagerAndCheckSigninPromoIsDisplayed() {
+        final HistogramDelta shownHistogram = new HistogramDelta(SHOWN_HISTOGRAM_NAME, 1);
         mBookmarkTestRule.showBookmarkManager(sActivityTestRule.getActivity());
+        Assert.assertEquals(1, shownHistogram.getDelta());
 
         // Profile data updates cause the signin promo to be recreated at the given index. The
         // RecyclerView's ViewGroup children may be stale, use activeInRecyclerView to filter to
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabsConnectionTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabsConnectionTest.java
index a005088..582f72b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabsConnectionTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabsConnectionTest.java
@@ -122,8 +122,7 @@
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             WarmupManager warmupManager = WarmupManager.getInstance();
             Assert.assertTrue(warmupManager.hasSpareWebContents());
-            WebContents webContents =
-                    warmupManager.takeSpareWebContents(false, false, WarmupManager.FOR_CCT);
+            WebContents webContents = warmupManager.takeSpareWebContents(false, false);
             Assert.assertNotNull(webContents);
             Assert.assertFalse(warmupManager.hasSpareWebContents());
             webContents.destroy();
@@ -365,13 +364,12 @@
         mCustomTabsConnection.mayLaunchUrl(token, Uri.parse(URL), null, urls);
         TestThreadUtils.runOnUiThreadBlocking(
                 ()
-                        -> Assert.assertNull(WarmupManager.getInstance().takeSpareWebContents(
-                                false, false, WarmupManager.FOR_CCT)));
+                        -> Assert.assertNull(
+                                WarmupManager.getInstance().takeSpareWebContents(false, false)));
     }
 
     private void assertSpareWebContentsNotNullAndDestroy() {
-        WebContents webContents = WarmupManager.getInstance().takeSpareWebContents(
-                false, false, WarmupManager.FOR_CCT);
+        WebContents webContents = WarmupManager.getInstance().takeSpareWebContents(false, false);
         Assert.assertNotNull(webContents);
         webContents.destroy();
     }
@@ -587,8 +585,8 @@
         Assert.assertTrue(mCustomTabsConnection.mayLaunchUrl(token, Uri.parse(URL), null, null));
         TestThreadUtils.runOnUiThreadBlocking(
                 ()
-                        -> Assert.assertNull(WarmupManager.getInstance().takeSpareWebContents(
-                                false, false, WarmupManager.FOR_CCT)));
+                        -> Assert.assertNull(
+                                WarmupManager.getInstance().takeSpareWebContents(false, false)));
     }
 
     @Test
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreatorTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreatorTest.java
index 07291d8..0407cc7c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreatorTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreatorTest.java
@@ -137,7 +137,7 @@
             @Override
             public void run() {
                 Tab currentTab = sActivityTestRule.getActivity().getActivityTab();
-                WarmupManager.getInstance().createSpareWebContents(!WarmupManager.FOR_CCT);
+                WarmupManager.getInstance().createSpareWebContents();
                 Assert.assertTrue(WarmupManager.getInstance().hasSpareWebContents());
                 sActivityTestRule.getActivity().getCurrentTabCreator().createNewTab(
                         new LoadUrlParams(mTestServer.getURL(TEST_PATH)),
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateUnitTest.java
index ce048f1..cc2fa588 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateUnitTest.java
@@ -27,6 +27,8 @@
 import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
 
+import com.google.common.base.Optional;
+
 import org.hamcrest.Matchers;
 import org.junit.After;
 import org.junit.Assert;
@@ -855,7 +857,8 @@
                 .when(mBookmarkModel)
                 .getBookmarksOfType(eq(PowerBookmarkType.SHOPPING));
         Long clusterId = 1L;
-        doReturn(new ShoppingService.ProductInfo("", new GURL(""), clusterId, 0, "", 0, ""))
+        doReturn(new ShoppingService.ProductInfo(
+                         "", new GURL(""), clusterId, 0, "", 0, "", Optional.absent()))
                 .when(mShoppingService)
                 .getAvailableProductInfoForUrl(any());
         PowerBookmarkMeta meta =
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityContentTestEnvironment.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityContentTestEnvironment.java
index 2b198eb..38ebce0 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityContentTestEnvironment.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityContentTestEnvironment.java
@@ -6,7 +6,6 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -244,8 +243,7 @@
 
     public WebContents prepareSpareWebcontents() {
         WebContents webContents = mock(WebContents.class);
-        when(warmupManager.takeSpareWebContents(
-                     anyBoolean(), anyBoolean(), eq(WarmupManager.FOR_CCT)))
+        when(warmupManager.takeSpareWebContents(anyBoolean(), anyBoolean()))
                 .thenReturn(webContents);
         return webContents;
     }
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index 8a24a85c..256a2be0 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -5974,6 +5974,12 @@
   <message name="IDS_PARENT_ACCESS_WEBVIEW_LOADING_MESSAGE" desc="Message shown while the parent access screen is loading.">
     Please wait...
   </message>
+  <message name="IDS_PARENT_ACCESS_ERROR_TITLE" desc="Title for the parent access dialog when an error has occurred.">
+    An error occurred
+  </message>
+  <message name="IDS_PARENT_ACCESS_ERROR_DESCRIPTION" desc="Message shown if the parent access dialog has failed to load for any reason.">
+    Please try again later
+  </message>
 
   <!-- Strings for gnubby U2FD authentication UI -->
   <message name="IDS_GNUBBY_NOTIFICATION_TITLE" desc="Title for notification informing user to press power button for gnubby U2FD.">
diff --git a/chrome/app/chromeos_strings_grdp/IDS_PARENT_ACCESS_ERROR_DESCRIPTION.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_PARENT_ACCESS_ERROR_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..bddbced1
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_PARENT_ACCESS_ERROR_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+f6ffefc573568cc1dad748cbf7ae7e3de27fafcc
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_PARENT_ACCESS_ERROR_TITLE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_PARENT_ACCESS_ERROR_TITLE.png.sha1
new file mode 100644
index 0000000..bddbced1
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_PARENT_ACCESS_ERROR_TITLE.png.sha1
@@ -0,0 +1 @@
+f6ffefc573568cc1dad748cbf7ae7e3de27fafcc
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index 7875c31d..1a387f9 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -40,6 +40,9 @@
   <message name="IDS_OS_SEARCH_RESULT_ROW_A11Y_RESULT_SELECTED" desc="ChromeVox alert to indicate the position number of a selected result in a list of search results and the selected result text itself, and that the user can press enter to navigate to section described by the search result.">
     Search result <ph name="LIST_POSITION">$1<ex>1</ex></ph> of <ph name="LIST_SIZE">$2<ex>2</ex></ph>: <ph name="SEARCH_RESULT_TEXT">$3<ex>Network Settings</ex></ph>. Press Enter to navigate to section.
   </message>
+  <message name="IDS_OS_SETTINGS_SEARCH_FEEDBACK_BUTTON" desc="Text description of feedback button in search.">
+    Report this search result
+  </message>
 
   <!-- About (OS Settings) -->
   <message name="IDS_OS_SETTINGS_ABOUT_PAGE_BUILD_DETAILS" desc="Label of the row button that clicks into Build details">
diff --git a/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_SEARCH_FEEDBACK_BUTTON.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_SEARCH_FEEDBACK_BUTTON.png.sha1
new file mode 100644
index 0000000..96ee7885
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_OS_SETTINGS_SEARCH_FEEDBACK_BUTTON.png.sha1
@@ -0,0 +1 @@
+b76c11440ad17275d3ec7f090dea082846f287f6
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index ab2c080..59d7132c 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -442,6 +442,8 @@
     "download/download_history.h",
     "download/download_item_model.cc",
     "download/download_item_model.h",
+    "download/download_item_warning_data.cc",
+    "download/download_item_warning_data.h",
     "download/download_manager_utils.cc",
     "download/download_manager_utils.h",
     "download/download_offline_content_provider.cc",
@@ -1996,6 +1998,7 @@
     "//chrome/browser/browsing_data:constants",
     "//chrome/browser/chrome_for_testing:buildflags",
     "//chrome/browser/devtools",
+    "//chrome/browser/enterprise/identifiers",
     "//chrome/browser/enterprise/platform_auth:features",
     "//chrome/browser/favicon",
     "//chrome/browser/feature_guide/notifications:public",
diff --git a/chrome/browser/accessibility/service/accessibility_service_router.cc b/chrome/browser/accessibility/service/accessibility_service_router.cc
index f44f729..84aacc8 100644
--- a/chrome/browser/accessibility/service/accessibility_service_router.cc
+++ b/chrome/browser/accessibility/service/accessibility_service_router.cc
@@ -5,20 +5,21 @@
 #include "chrome/browser/accessibility/service/accessibility_service_router.h"
 
 #include "content/public/browser/service_process_host.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
 
 namespace ax {
 
 AccessibilityServiceRouter::AccessibilityServiceRouter() = default;
 AccessibilityServiceRouter::~AccessibilityServiceRouter() = default;
 
-void AccessibilityServiceRouter::BindAutomationWithClient(
-    mojo::PendingRemote<mojom::AutomationClient> automation_client_remote,
-    mojo::PendingReceiver<mojom::Automation> automation_receiver) {
+void AccessibilityServiceRouter::BindAccessibilityServiceClient(
+    mojo::PendingRemote<mojom::AccessibilityServiceClient>
+        accessibility_service_client) {
   LaunchIfNotRunning();
 
   if (accessibility_service_.is_bound()) {
-    accessibility_service_->BindAutomation(std::move(automation_client_remote),
-                                           std::move(automation_receiver));
+    accessibility_service_->BindAccessibilityServiceClient(
+        std::move(accessibility_service_client));
   }
 }
 
diff --git a/chrome/browser/accessibility/service/accessibility_service_router.h b/chrome/browser/accessibility/service/accessibility_service_router.h
index 1ca4fd34..7b11fdd4 100644
--- a/chrome/browser/accessibility/service/accessibility_service_router.h
+++ b/chrome/browser/accessibility/service/accessibility_service_router.h
@@ -21,9 +21,9 @@
       delete;
   ~AccessibilityServiceRouter() override;
 
-  virtual void BindAutomationWithClient(
-      mojo::PendingRemote<mojom::AutomationClient> automation_client_remote,
-      mojo::PendingReceiver<mojom::Automation> automation_receiver);
+  virtual void BindAccessibilityServiceClient(
+      mojo::PendingRemote<mojom::AccessibilityServiceClient>
+          accessibility_service_client);
 
   virtual void BindAssistiveTechnologyController(
       mojo::PendingReceiver<mojom::AssistiveTechnologyController>
diff --git a/chrome/browser/apps/guest_view/web_view_browsertest.cc b/chrome/browser/apps/guest_view/web_view_browsertest.cc
index b9ebd21..e529410 100644
--- a/chrome/browser/apps/guest_view/web_view_browsertest.cc
+++ b/chrome/browser/apps/guest_view/web_view_browsertest.cc
@@ -63,6 +63,7 @@
 #include "components/version_info/version_info.h"
 #include "components/web_modal/web_contents_modal_dialog_manager.h"
 #include "content/public/browser/ax_event_notification_details.h"
+#include "content/public/browser/browser_accessibility_state.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/navigation_handle.h"
@@ -4577,21 +4578,17 @@
 }
 
 IN_PROC_BROWSER_TEST_P(WebViewAccessibilityTest, LoadWebViewAccessibility) {
+  content::BrowserAccessibilityState::GetInstance()->EnableAccessibility();
   LoadAppWithGuest("web_view/focus_accessibility");
   content::WebContents* web_contents = GetFirstAppWindowWebContents();
-  content::EnableAccessibilityForWebContents(web_contents);
-  content::WebContents* guest_web_contents = GetGuestWebContents();
-  content::EnableAccessibilityForWebContents(guest_web_contents);
   content::WaitForAccessibilityTreeToContainNodeWithName(web_contents,
                                                          "Guest button");
 }
 
 IN_PROC_BROWSER_TEST_P(WebViewAccessibilityTest, FocusAccessibility) {
+  content::BrowserAccessibilityState::GetInstance()->EnableAccessibility();
   LoadAppWithGuest("web_view/focus_accessibility");
   content::WebContents* web_contents = GetFirstAppWindowWebContents();
-  content::EnableAccessibilityForWebContents(web_contents);
-  content::WebContents* guest_web_contents = GetGuestWebContents();
-  content::EnableAccessibilityForWebContents(guest_web_contents);
 
   // Wait for focus to land on the "root web area" role, representing
   // focus on the main document itself.
@@ -4624,11 +4621,9 @@
 // The test was disabled. See crbug.com/1141313.
 IN_PROC_BROWSER_TEST_P(WebViewAccessibilityTest,
                        DISABLED_FocusAccessibilityNestedFrame) {
+  content::BrowserAccessibilityState::GetInstance()->EnableAccessibility();
   LoadAppWithGuest("web_view/focus_accessibility");
   content::WebContents* web_contents = GetFirstAppWindowWebContents();
-  content::EnableAccessibilityForWebContents(web_contents);
-  content::WebContents* guest_web_contents = GetGuestWebContents();
-  content::EnableAccessibilityForWebContents(guest_web_contents);
 
   // Wait for focus to land on the "root web area" role, representing
   // focus on the main document itself.
@@ -4708,11 +4703,10 @@
 };
 
 IN_PROC_BROWSER_TEST_P(WebViewAccessibilityTest, DISABLED_TouchAccessibility) {
+  content::BrowserAccessibilityState::GetInstance()->EnableAccessibility();
   LoadAppWithGuest("web_view/touch_accessibility");
   content::WebContents* web_contents = GetFirstAppWindowWebContents();
-  content::EnableAccessibilityForWebContents(web_contents);
   content::WebContents* guest_web_contents = GetGuestWebContents();
-  content::EnableAccessibilityForWebContents(guest_web_contents);
 
   // Listen for accessibility events on both WebContents.
   WebContentsAccessibilityEventWatcher main_event_watcher(
diff --git a/chrome/browser/apps/platform_apps/extension_app_shim_manager_delegate_mac.cc b/chrome/browser/apps/platform_apps/extension_app_shim_manager_delegate_mac.cc
index 7506f91..6b6db50 100644
--- a/chrome/browser/apps/platform_apps/extension_app_shim_manager_delegate_mac.cc
+++ b/chrome/browser/apps/platform_apps/extension_app_shim_manager_delegate_mac.cc
@@ -18,6 +18,7 @@
 #include "chrome/browser/apps/app_service/browser_app_launcher.h"
 #include "chrome/browser/apps/app_shim/app_shim_termination_manager.h"
 #include "chrome/browser/apps/platform_apps/app_window_registry_util.h"
+#include "chrome/browser/apps/platform_apps/platform_app_launch.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/extensions/launch_util.h"
 #include "chrome/browser/profiles/profile_manager.h"
@@ -192,6 +193,10 @@
   DCHECK(extension);
   extensions::RecordAppLaunchType(extension_misc::APP_LAUNCH_CMD_LINE_APP,
                                   extension->GetType());
+
+  if (apps::OpenDeprecatedApplicationPrompt(profile, app_id))
+    return;
+
   if (extension->is_hosted_app()) {
     auto params = CreateAppLaunchParamsUserContainer(
         profile, extension, WindowOpenDisposition::NEW_FOREGROUND_TAB,
diff --git a/chrome/browser/ash/BUILD.gn b/chrome/browser/ash/BUILD.gn
index 654c35c..7757704 100644
--- a/chrome/browser/ash/BUILD.gn
+++ b/chrome/browser/ash/BUILD.gn
@@ -1426,6 +1426,8 @@
     "login/auth/chrome_safe_mode_delegate.h",
     "login/challenge_response_auth_keys_loader.cc",
     "login/challenge_response_auth_keys_loader.h",
+    "login/choobe_flow_controller.cc",
+    "login/choobe_flow_controller.h",
     "login/chrome_restart_request.cc",
     "login/chrome_restart_request.h",
     "login/configuration_keys.cc",
@@ -2034,6 +2036,8 @@
     "ownership/owner_settings_service_ash.h",
     "ownership/owner_settings_service_ash_factory.cc",
     "ownership/owner_settings_service_ash_factory.h",
+    "ownership/ownership_histograms.cc",
+    "ownership/ownership_histograms.h",
     "pcie_peripheral/ash_usb_detector.cc",
     "pcie_peripheral/ash_usb_detector.h",
     "phonehub/browser_tabs_metadata_fetcher_impl.cc",
diff --git a/chrome/browser/ash/accessibility/dictation_browsertest.cc b/chrome/browser/ash/accessibility/dictation_browsertest.cc
index 7093f9b..af40a63c5 100644
--- a/chrome/browser/ash/accessibility/dictation_browsertest.cc
+++ b/chrome/browser/ash/accessibility/dictation_browsertest.cc
@@ -849,7 +849,7 @@
 IN_PROC_BROWSER_TEST_P(DictationTest, SmartCapitalization) {
   ToggleDictationWithKeystroke();
   WaitForRecognitionStarted();
-  SendFinalResultAndWaitForEditableValue("This", "This");
+  SendFinalResultAndWaitForEditableValue("this", "This");
   SendFinalResultAndWaitForEditableValue("Is", "This is");
   SendFinalResultAndWaitForEditableValue("a test.", "This is a test.");
   SendFinalResultAndWaitForEditableValue("you passed!",
@@ -1309,8 +1309,8 @@
 IN_PROC_BROWSER_TEST_P(DictationCommandsTest, NavStartTextSimple) {
   SendFinalResultAndWaitForEditableValue("Is good", "Is good");
   SendFinalResultAndWaitForCaretBoundsChanged("move to the start");
-  SendFinalResultAndWaitForEditableValue("the weather outside",
-                                         "the weather outside Is good");
+  SendFinalResultAndWaitForEditableValue("The weather outside",
+                                         "The weather outside Is good");
 }
 
 IN_PROC_BROWSER_TEST_P(DictationCommandsTest, NavStartTextMultiLineString) {
@@ -1320,8 +1320,8 @@
   std::string text = "Is good\n and we should go for a run.";
   SendFinalResultAndWaitForEditableValue(text, text);
   SendFinalResultAndWaitForCaretBoundsChanged("move to the start");
-  std::string expected = "the weather outside " + text;
-  SendFinalResultAndWaitForEditableValue("the weather outside", expected);
+  std::string expected = "The weather outside " + text;
+  SendFinalResultAndWaitForEditableValue("The weather outside", expected);
 }
 
 #if BUILDFLAG(IS_CHROMEOS) && defined(MEMORY_SANITIZER)
diff --git a/chrome/browser/ash/accessibility/service/accessibility_service_client.cc b/chrome/browser/ash/accessibility/service/accessibility_service_client.cc
index 4153fcd..6871ee4 100644
--- a/chrome/browser/ash/accessibility/service/accessibility_service_client.cc
+++ b/chrome/browser/ash/accessibility/service/accessibility_service_client.cc
@@ -21,6 +21,12 @@
   Reset();
 }
 
+void AccessibilityServiceClient::BindAutomation(
+    mojo::PendingRemote<ax::mojom::Automation> automation,
+    mojo::PendingReceiver<ax::mojom::AutomationClient> automation_client) {
+  automation_client_->Bind(std::move(automation), std::move(automation_client));
+}
+
 void AccessibilityServiceClient::SetProfile(content::BrowserContext* profile) {
   // If the profile has changed we will need to disconnect from the previous
   // service, get the service keyed to this profile, and if any features were
@@ -89,8 +95,7 @@
   }
 
   if (at_controller_.is_bound()) {
-    // TODO(crbug.com/1355633): Enable assistive technology with mojom.
-    // at_controller_->EnableAssistiveTechnology(type, enabled);
+    at_controller_->EnableAssistiveTechnology(enabled_features_);
     return;
   }
 
@@ -103,13 +108,15 @@
   if (!profile_)
     return;
 
+  automation_client_ = std::make_unique<AutomationClientImpl>();
+
   ax::AccessibilityServiceRouter* router =
       ax::AccessibilityServiceRouterFactory::GetForBrowserContext(
           static_cast<content::BrowserContext*>(profile_));
-  automation_client_ = std::make_unique<AutomationClientImpl>();
-  automation_client_->Bind(router);
   router->BindAssistiveTechnologyController(
       at_controller_.BindNewPipeAndPassReceiver(), enabled_features_);
+  router->BindAccessibilityServiceClient(
+      service_client_.BindNewPipeAndPassRemote());
 }
 
 }  // namespace ash
diff --git a/chrome/browser/ash/accessibility/service/accessibility_service_client.h b/chrome/browser/ash/accessibility/service/accessibility_service_client.h
index bc48c5b8..b887987 100644
--- a/chrome/browser/ash/accessibility/service/accessibility_service_client.h
+++ b/chrome/browser/ash/accessibility/service/accessibility_service_client.h
@@ -8,6 +8,7 @@
 #include <vector>
 
 #include "base/callback_helpers.h"
+#include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/accessibility/public/mojom/accessibility_service.mojom.h"
 
@@ -22,13 +23,21 @@
 // AccessibilityService process over mojom. It is responsible for communicating
 // to the service which features are running and binding helper classes for the
 // service.
-class AccessibilityServiceClient {
+// TODO(crbug.com/1355633): Move to ash/accessibility/service.
+class AccessibilityServiceClient
+    : public ax::mojom::AccessibilityServiceClient {
  public:
   AccessibilityServiceClient();
   AccessibilityServiceClient(const AccessibilityServiceClient&) = delete;
   AccessibilityServiceClient& operator=(const AccessibilityServiceClient&) =
       delete;
-  ~AccessibilityServiceClient();
+  ~AccessibilityServiceClient() override;
+
+  // ax::mojom::AccessibilityServiceClient:
+  void BindAutomation(mojo::PendingRemote<ax::mojom::Automation> automation,
+                      mojo::PendingReceiver<ax::mojom::AutomationClient>
+                          automation_client) override;
+
   void SetProfile(content::BrowserContext* profile);
 
   // Enables or disables accessibility features in the service.
@@ -59,6 +68,10 @@
 
   // Here is the remote to the AT Controller, used to toggle features.
   mojo::Remote<ax::mojom::AssistiveTechnologyController> at_controller_;
+
+  // This class receives mojom requests from the service via the interface
+  // AccessibilityServiceClient.
+  mojo::Receiver<ax::mojom::AccessibilityServiceClient> service_client_{this};
 };
 
 }  // namespace ash
diff --git a/chrome/browser/ash/accessibility/service/accessibility_service_client_browsertest.cc b/chrome/browser/ash/accessibility/service/accessibility_service_client_browsertest.cc
index a9cacfb..13d85eb5f 100644
--- a/chrome/browser/ash/accessibility/service/accessibility_service_client_browsertest.cc
+++ b/chrome/browser/ash/accessibility/service/accessibility_service_client_browsertest.cc
@@ -143,55 +143,60 @@
   // with the enabled AT type.
   client.SetChromeVoxEnabled(true);
   EXPECT_TRUE(ServiceHasATEnabled(AssistiveTechnologyType::kChromeVox));
-
-  // TODO(crbug.com/1355633): Enable this part of the test once the mojom for AT
-  // controller lands.
-  // client.SetSelectToSpeakEnabled(true);
-  // fake_service_->WaitForATChanged();
-  // EXPECT_TRUE(ServiceHasATEnabled(AssistiveTechnologyType::kSelectToSpeak));
-  // client.SetSwitchAccessEnabled(true);
-  // fake_service_->WaitForATChanged();
-  // EXPECT_TRUE(ServiceHasATEnabled(AssistiveTechnologyType::kSwitchAccess));
-  // client.SetAutoclickEnabled(true);
-  // fake_service_->WaitForATChanged();
-  // EXPECT_TRUE(ServiceHasATEnabled(AssistiveTechnologyType::kAutoClick));
-  // client.SetDictationEnabled(true);
-  // fake_service_->WaitForATChanged();
-  // EXPECT_TRUE(ServiceHasATEnabled(AssistiveTechnologyType::kDictation));
-  // client.SetMagnifierEnabled(true);
-  // fake_service_->WaitForATChanged();
-  // EXPECT_TRUE(ServiceHasATEnabled(AssistiveTechnologyType::kMagnifier));
-  // client.SetChromeVoxEnabled(false);
-  // fake_service_->WaitForATChanged();
-  // EXPECT_FALSE(ServiceHasATEnabled(AssistiveTechnologyType::kChromeVox));
-  // client.SetSelectToSpeakEnabled(false);
-  // fake_service_->WaitForATChanged();
-  // EXPECT_FALSE(ServiceHasATEnabled(AssistiveTechnologyType::kSelectToSpeak));
-  // client.SetSwitchAccessEnabled(false);
-  // fake_service_->WaitForATChanged();
-  // EXPECT_FALSE(ServiceHasATEnabled(AssistiveTechnologyType::kSwitchAccess));
-  // client.SetAutoclickEnabled(false);
-  // fake_service_->WaitForATChanged();
-  // EXPECT_FALSE(ServiceHasATEnabled(AssistiveTechnologyType::kAutoClick));
-  // client.SetDictationEnabled(false);
-  // fake_service_->WaitForATChanged();
-  // EXPECT_FALSE(ServiceHasATEnabled(AssistiveTechnologyType::kDictation));
-  // client.SetMagnifierEnabled(false);
-  // fake_service_->WaitForATChanged();
-  // EXPECT_FALSE(ServiceHasATEnabled(AssistiveTechnologyType::kMagnifier));
+  client.SetSelectToSpeakEnabled(true);
+  fake_service_->WaitForATChanged();
+  EXPECT_TRUE(ServiceHasATEnabled(AssistiveTechnologyType::kSelectToSpeak));
+  client.SetSwitchAccessEnabled(true);
+  fake_service_->WaitForATChanged();
+  EXPECT_TRUE(ServiceHasATEnabled(AssistiveTechnologyType::kSwitchAccess));
+  client.SetAutoclickEnabled(true);
+  fake_service_->WaitForATChanged();
+  EXPECT_TRUE(ServiceHasATEnabled(AssistiveTechnologyType::kAutoClick));
+  client.SetDictationEnabled(true);
+  fake_service_->WaitForATChanged();
+  EXPECT_TRUE(ServiceHasATEnabled(AssistiveTechnologyType::kDictation));
+  client.SetMagnifierEnabled(true);
+  fake_service_->WaitForATChanged();
+  EXPECT_TRUE(ServiceHasATEnabled(AssistiveTechnologyType::kMagnifier));
+  client.SetChromeVoxEnabled(false);
+  fake_service_->WaitForATChanged();
+  EXPECT_FALSE(ServiceHasATEnabled(AssistiveTechnologyType::kChromeVox));
+  client.SetSelectToSpeakEnabled(false);
+  fake_service_->WaitForATChanged();
+  EXPECT_FALSE(ServiceHasATEnabled(AssistiveTechnologyType::kSelectToSpeak));
+  client.SetSwitchAccessEnabled(false);
+  fake_service_->WaitForATChanged();
+  EXPECT_FALSE(ServiceHasATEnabled(AssistiveTechnologyType::kSwitchAccess));
+  client.SetAutoclickEnabled(false);
+  fake_service_->WaitForATChanged();
+  EXPECT_FALSE(ServiceHasATEnabled(AssistiveTechnologyType::kAutoClick));
+  client.SetDictationEnabled(false);
+  fake_service_->WaitForATChanged();
+  EXPECT_FALSE(ServiceHasATEnabled(AssistiveTechnologyType::kDictation));
+  client.SetMagnifierEnabled(false);
+  fake_service_->WaitForATChanged();
+  EXPECT_FALSE(ServiceHasATEnabled(AssistiveTechnologyType::kMagnifier));
 }
 
-IN_PROC_BROWSER_TEST_F(AccessibilityServiceClientTest, EnablesAutomation) {
+IN_PROC_BROWSER_TEST_F(AccessibilityServiceClientTest,
+                       SendsAutomationToTheService) {
   AccessibilityServiceClient client;
   client.SetProfile(browser()->profile());
   // Enable an assistive technology. The service will not be started until
   // some AT needs it.
   client.SetChromeVoxEnabled(true);
+  EXPECT_TRUE(ServiceHasATEnabled(AssistiveTechnologyType::kChromeVox));
 
-  // TODO(crbug.com/1355633): Enable this part of the test once the mojom for AT
-  // controller lands.
-  // fake_service_->EnableAutomationClient(true);
-  // // Expect an a11y event to have come through.
+  // The service may bind multiple Automations to the AutomationClient.
+  for (int i = 0; i < 3; i++) {
+    fake_service_->BindAnotherAutomation();
+  }
+
+  // TODO(crbug.com/1355633): Enable this part of the test once the mojom
+  // AutomationClient and Automation land.
+  // fake_service_->AutomationClientEnable();
+  // // Expect an a11y event to have come through three times, once for
+  // // each AutomationClient.
   // fake_service_->WaitForAutomationEvents();
 }
 }  // namespace ash
diff --git a/chrome/browser/ash/accessibility/service/automation_client_impl.cc b/chrome/browser/ash/accessibility/service/automation_client_impl.cc
index 8d4642b..161ed20a 100644
--- a/chrome/browser/ash/accessibility/service/automation_client_impl.cc
+++ b/chrome/browser/ash/accessibility/service/automation_client_impl.cc
@@ -20,18 +20,21 @@
       nullptr);
 }
 
-void AutomationClientImpl::Bind(ax::AccessibilityServiceRouter* router) {
+void AutomationClientImpl::Bind(
+    mojo::PendingRemote<ax::mojom::Automation> automation,
+    mojo::PendingReceiver<ax::mojom::AutomationClient> automation_client) {
   // Launches the service if it wasn't running yet.
   // Development note (crbug.com/1355633): Using the remote router means
   // extensions don't get a11y events when AutomationClientImpl is bound, so
   // accessibility features built as component extensions are broken when the
   // service is running.
-  DCHECK(!bound_);
-  bound_ = true;
-  extensions::AutomationEventRouter::GetInstance()->RegisterRemoteRouter(this);
-  router->BindAutomationWithClient(
-      automation_client_receiver_.BindNewPipeAndPassRemote(),
-      automation_.BindNewPipeAndPassReceiver());
+  if (!bound_) {
+    bound_ = true;
+    extensions::AutomationEventRouter::GetInstance()->RegisterRemoteRouter(
+        this);
+  }
+  automation_remotes_.Add(std::move(automation));
+  automation_client_receivers_.Add(this, std::move(automation_client));
 }
 
 void AutomationClientImpl::DispatchAccessibilityEvents(
@@ -40,45 +43,52 @@
     const gfx::Point& mouse_location,
     std::vector<ui::AXEvent> events) {
   DCHECK(tree_id != ui::AXTreeIDUnknown());
-  if (tree_id == ui::AXTreeIDUnknown() || !automation_.is_bound())
+  if (tree_id == ui::AXTreeIDUnknown())
     return;
   // TODO(crbug.com/1355633): Send to AccessibilityService.
-  // automation_->DispatchAccessibilityEvents(*tree_id.token(), updates,
-  //                                         mouse_location, events);
+  // for (auto& remote : automation_remotes_) {
+  //   remote->DispatchAccessibilityEvents(tree_id, updates, mouse_location,
+  //                                       events);
+  // }
 }
 
 void AutomationClientImpl::DispatchAccessibilityLocationChange(
     const ExtensionMsg_AccessibilityLocationChangeParams& params) {
   ui::AXTreeID tree_id = params.tree_id;
-  if (!tree_id.token() || !automation_.is_bound())
+  if (tree_id == ui::AXTreeIDUnknown())
     return;
   // TODO(crbug.com/1355633): Send to AccessibilityService.
-  // automation_->DispatchAccessibilityLocationChange(*tree_id.token(),
-  // params.id,
-  //                                                  params.new_location);
+  // for (auto& remote : automation_remotes_) {
+  //   remote->DispatchAccessibilityLocationChange(tree_id, params.id,
+  //                                               params.new_location);
+  // }
 }
 void AutomationClientImpl::DispatchTreeDestroyedEvent(ui::AXTreeID tree_id) {
-  if (!tree_id.token() || !automation_.is_bound())
+  if (tree_id == ui::AXTreeIDUnknown())
     return;
   // TODO(crbug.com/1355633): Send to AccessibilityService.
-  // automation_->DispatchTreeDestroyedEvent(*(tree_id.token()));
+  // for (auto& remote : automation_remotes_) {
+  //   remote->DispatchTreeDestroyedEvent(tree_id);
+  // }
 }
 
 void AutomationClientImpl::DispatchActionResult(
     const ui::AXActionData& data,
     bool result,
     content::BrowserContext* browser_context) {
-  if (!automation_.is_bound())
-    return;
   // TODO(crbug.com/1355633): Send to AccessibilityService.
-  // automation_->DispatchActionResult(data, result);
+  // for (auto& remote : automation_remotes_) {
+  //   remote->DispatchActionResult(data, result);
+  // }
 }
 
 void AutomationClientImpl::DispatchGetTextLocationDataResult(
     const ui::AXActionData& data,
     const absl::optional<gfx::Rect>& rect) {
   // TODO(crbug.com/1355633): Send to AccessibilityService.
-  // automation_->DispatchGetTextLocationDataResult(data, rect);
+  // for (auto& remote : automation_remotes_) {
+  //   remote->DispatchGetTextLocationDataResult(data, rect);
+  // }
 }
 
 void AutomationClientImpl::Enable() {
@@ -91,12 +101,11 @@
   AutomationManagerAura::GetInstance()->Disable();
 }
 
-void AutomationClientImpl::EnableTree(const base::UnguessableToken& tree_id) {
-  ui::AXTreeID ax_tree_id = ui::AXTreeID::FromToken(tree_id);
+void AutomationClientImpl::EnableTree(const ui::AXTreeID& tree_id) {
   // TODO(crbug.com/1355633): Refactor logic from extensions namespace to a
   // common location.
   extensions::AutomationInternalEnableTreeFunction::EnableTree(
-      ax_tree_id, /*extension_id=*/"");
+      tree_id, /*extension_id=*/"");
 }
 
 void AutomationClientImpl::PerformAction(const ui::AXActionData& data) {
diff --git a/chrome/browser/ash/accessibility/service/automation_client_impl.h b/chrome/browser/ash/accessibility/service/automation_client_impl.h
index 93e19373..5726f99a 100644
--- a/chrome/browser/ash/accessibility/service/automation_client_impl.h
+++ b/chrome/browser/ash/accessibility/service/automation_client_impl.h
@@ -6,13 +6,10 @@
 #define CHROME_BROWSER_ASH_ACCESSIBILITY_SERVICE_AUTOMATION_CLIENT_IMPL_H_
 
 #include "extensions/browser/api/automation_internal/automation_event_router_interface.h"
-#include "mojo/public/cpp/bindings/receiver.h"
-#include "mojo/public/cpp/bindings/remote.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
+#include "mojo/public/cpp/bindings/remote_set.h"
 #include "services/accessibility/public/mojom/accessibility_service.mojom.h"
-
-namespace ax {
-class AccessibilityServiceRouter;
-}  // namespace ax
+#include "ui/accessibility/mojom/ax_tree_id.mojom.h"
 
 namespace ash {
 
@@ -26,7 +23,9 @@
   AutomationClientImpl& operator=(const AutomationClientImpl&) = delete;
   ~AutomationClientImpl() override;
 
-  void Bind(ax::AccessibilityServiceRouter* router);
+  void Bind(
+      mojo::PendingRemote<ax::mojom::Automation> automation,
+      mojo::PendingReceiver<ax::mojom::AutomationClient> automation_client);
 
  private:
   // The following are called by the Accessibility service, passing information
@@ -34,7 +33,7 @@
   // TODO(crbug.com/1355633): Override from ax::mojom::AutomationClient:
   void Enable();
   void Disable();
-  void EnableTree(const base::UnguessableToken& tree_id);
+  void EnableTree(const ui::AXTreeID& tree_id);
   void PerformAction(const ui::AXActionData& data);
 
   // Receive accessibility information from AutomationEventRouter in ash and
@@ -54,14 +53,16 @@
       const ui::AXActionData& data,
       const absl::optional<gfx::Rect>& rect) override;
 
-  // Here is the remote to Automation in the service.
-  mojo::Remote<ax::mojom::Automation> automation_;
+  // Here are all the remote to Automation in the service.
+  mojo::RemoteSet<ax::mojom::Automation> automation_remotes_;
 
   // This class is the AutomationClient, receiving AutomationClient calls
-  // from the AccessibilityService, therefore it is the Receiver.
-  mojo::Receiver<ax::mojom::AutomationClient> automation_client_receiver_{this};
+  // from the AccessibilityService.
+  mojo::ReceiverSet<ax::mojom::AutomationClient> automation_client_receivers_;
 
   bool bound_ = false;
 };
+
 }  // namespace ash
+
 #endif  // CHROME_BROWSER_ACCESSIBILITY_ACCESSIBILITY_CLIENT_IMPL_H_
diff --git a/chrome/browser/ash/accessibility/service/fake_accessibility_service.cc b/chrome/browser/ash/accessibility/service/fake_accessibility_service.cc
index d93d67a..57ed1d4 100644
--- a/chrome/browser/ash/accessibility/service/fake_accessibility_service.cc
+++ b/chrome/browser/ash/accessibility/service/fake_accessibility_service.cc
@@ -14,12 +14,24 @@
 FakeAccessibilityService::FakeAccessibilityService() = default;
 FakeAccessibilityService::~FakeAccessibilityService() = default;
 
-void FakeAccessibilityService::BindAutomationWithClient(
-    mojo::PendingRemote<ax::mojom::AutomationClient>
-        accessibility_client_remote,
-    mojo::PendingReceiver<ax::mojom::Automation> automation_receiver) {
-  automation_client_remotes_.Add(std::move(accessibility_client_remote));
-  automation_receivers_.Add(this, std::move(automation_receiver));
+void FakeAccessibilityService::BindAccessibilityServiceClient(
+    mojo::PendingRemote<ax::mojom::AccessibilityServiceClient>
+        accessibility_service_client) {
+  accessibility_service_client_remote_.Bind(
+      std::move(accessibility_service_client));
+}
+
+void FakeAccessibilityService::BindAnotherAutomation() {
+  mojo::PendingRemote<ax::mojom::Automation> automation_remote;
+  automation_receivers_.Add(this,
+                            automation_remote.InitWithNewPipeAndPassReceiver());
+
+  mojo::PendingReceiver<ax::mojom::AutomationClient> automation_client_receiver;
+  automation_client_remotes_.Add(
+      automation_client_receiver.InitWithNewPipeAndPassRemote());
+
+  accessibility_service_client_remote_->BindAutomation(
+      std::move(automation_remote), std::move(automation_client_receiver));
 }
 
 void FakeAccessibilityService::BindAssistiveTechnologyController(
@@ -27,9 +39,7 @@
         at_controller_receiver,
     const std::vector<ax::mojom::AssistiveTechnologyType>& enabled_features) {
   at_controller_receivers_.Add(this, std::move(at_controller_receiver));
-  for (auto feature : enabled_features) {
-    EnableAssistiveTechnology(feature, /*enabled=*/true);
-  }
+  EnableAssistiveTechnology(enabled_features);
 }
 
 void FakeAccessibilityService::DispatchTreeDestroyedEvent(
@@ -65,13 +75,8 @@
 }
 
 void FakeAccessibilityService::EnableAssistiveTechnology(
-    ax::mojom::AssistiveTechnologyType type,
-    bool enabled) {
-  if (enabled)
-    enabled_ATs_.insert(type);
-  else
-    enabled_ATs_.erase(type);
-
+    const std::vector<ax::mojom::AssistiveTechnologyType>& enabled_features) {
+  enabled_ATs_ = std::set(enabled_features.begin(), enabled_features.end());
   if (change_ATs_closure_)
     std::move(change_ATs_closure_).Run();
 }
@@ -83,11 +88,10 @@
 }
 
 bool FakeAccessibilityService::IsBound() {
-  return automation_client_remotes_.size() > 0 &&
-         automation_client_remotes_.begin()->is_bound();
+  return accessibility_service_client_remote_.is_bound();
 }
 
-void FakeAccessibilityService::EnableAutomationClient(bool enabled) {
+void FakeAccessibilityService::AutomationClientEnable(bool enabled) {
   // TODO(crbug.com/1355633): Add once AutomationClient mojom is added.
   // for (auto& automation_client : automation_client_remotes_) {
   //   enabled ? automation_client->Enable() : automation_client->Disable();
diff --git a/chrome/browser/ash/accessibility/service/fake_accessibility_service.h b/chrome/browser/ash/accessibility/service/fake_accessibility_service.h
index ec5c238..8ae27c0 100644
--- a/chrome/browser/ash/accessibility/service/fake_accessibility_service.h
+++ b/chrome/browser/ash/accessibility/service/fake_accessibility_service.h
@@ -31,10 +31,9 @@
   ~FakeAccessibilityService() override;
 
   // AccessibilityServiceRouter:
-  void BindAutomationWithClient(
-      mojo::PendingRemote<ax::mojom::AutomationClient> automation_client_remote,
-      mojo::PendingReceiver<ax::mojom::Automation> automation_receiver)
-      override;
+  void BindAccessibilityServiceClient(
+      mojo::PendingRemote<ax::mojom::AccessibilityServiceClient>
+          accessibility_service_client) override;
   void BindAssistiveTechnologyController(
       mojo::PendingReceiver<ax::mojom::AssistiveTechnologyController>
           at_controller_receiver,
@@ -53,10 +52,10 @@
       int node_id,
       const ui::AXRelativeBounds& bounds);
 
-  // TODO(crbug.com/1355633): Override from
   // ax::mojom::AssistiveTechnologyController:
-  void EnableAssistiveTechnology(ax::mojom::AssistiveTechnologyType type,
-                                 bool enabled);
+  void EnableAssistiveTechnology(
+      const std::vector<ax::mojom::AssistiveTechnologyType>& enabled_features)
+      override;
 
   //
   // Methods for testing.
@@ -70,7 +69,11 @@
     return enabled_ATs_;
   }
 
-  void EnableAutomationClient(bool enabled);
+  // Allows tests to bind Automation multiple times, mimicking multiple
+  // V8 instances in the service.
+  void BindAnotherAutomation();
+
+  void AutomationClientEnable(bool enabled);
 
   void WaitForAutomationEvents();
 
@@ -80,10 +83,14 @@
   base::OnceClosure automation_events_closure_;
   std::vector<base::UnguessableToken> tree_destroyed_events_;
   std::vector<std::tuple<ui::AXActionData, bool>> action_results_;
+
   mojo::ReceiverSet<ax::mojom::Automation> automation_receivers_;
   mojo::RemoteSet<ax::mojom::AutomationClient> automation_client_remotes_;
+
   mojo::ReceiverSet<ax::mojom::AssistiveTechnologyController>
       at_controller_receivers_;
+  mojo::Remote<ax::mojom::AccessibilityServiceClient>
+      accessibility_service_client_remote_;
 };
 
 }  // namespace ash
diff --git a/chrome/browser/ash/crosapi/browser_manager.cc b/chrome/browser/ash/crosapi/browser_manager.cc
index 54ba80ed..a629409e 100644
--- a/chrome/browser/ash/crosapi/browser_manager.cc
+++ b/chrome/browser/ash/crosapi/browser_manager.cc
@@ -743,13 +743,13 @@
   browser_service_->service->GetActiveTabUrl(std::move(callback));
 }
 
-void BrowserManager::GetTabStripModelUrls(
+void BrowserManager::GetBrowserInformation(
     const std::string& window_unique_id,
-    GetTabStripModelUrlsCallback callback) {
+    GetBrowserInformationCallback callback) {
   crosapi::CrosapiManager::Get()
       ->crosapi_ash()
       ->desk_template_ash()
-      ->GetTabStripModelUrls(window_unique_id, std::move(callback));
+      ->GetBrowserInformation(window_unique_id, std::move(callback));
 }
 
 void BrowserManager::AddObserver(BrowserManagerObserver* observer) {
diff --git a/chrome/browser/ash/crosapi/browser_manager.h b/chrome/browser/ash/crosapi/browser_manager.h
index da013f885..173f0fc 100644
--- a/chrome/browser/ash/crosapi/browser_manager.h
+++ b/chrome/browser/ash/crosapi/browser_manager.h
@@ -257,12 +257,12 @@
   // Gets Url of the active tab from lacros if there is any.
   void GetActiveTabUrl(GetActiveTabUrlCallback callback);
 
-  using GetTabStripModelUrlsCallback =
+  using GetBrowserInformationCallback =
       base::OnceCallback<void(crosapi::mojom::DeskTemplateStatePtr)>;
   // Gets URLs and active indices of the tab strip models from the Lacros
   // browser window.
-  void GetTabStripModelUrls(const std::string& window_unique_id,
-                            GetTabStripModelUrlsCallback callback);
+  void GetBrowserInformation(const std::string& window_unique_id,
+                             GetBrowserInformationCallback callback);
 
   void AddObserver(BrowserManagerObserver* observer);
   void RemoveObserver(BrowserManagerObserver* observer);
diff --git a/chrome/browser/ash/crosapi/desk_template_ash.cc b/chrome/browser/ash/crosapi/desk_template_ash.cc
index eb8ff0d..5352c7c 100644
--- a/chrome/browser/ash/crosapi/desk_template_ash.cc
+++ b/chrome/browser/ash/crosapi/desk_template_ash.cc
@@ -26,16 +26,16 @@
   receivers_.Add(this, std::move(pending_receiver));
 }
 
-void DeskTemplateAsh::GetTabStripModelUrls(
+void DeskTemplateAsh::GetBrowserInformation(
     const std::string& window_unique_id,
     base::OnceCallback<void(crosapi::mojom::DeskTemplateStatePtr)> callback) {
   const auto current_serial = serial_++;
   calls_.emplace_back(current_serial, window_unique_id, remotes_.size(),
                       std::move(callback));
   for (const auto& remote : remotes_) {
-    remote->GetTabStripModelUrls(
+    remote->GetBrowserInformation(
         current_serial, window_unique_id,
-        base::BindOnce(&DeskTemplateAsh::OnGetTabStripModelUrlsFromRemote,
+        base::BindOnce(&DeskTemplateAsh::OnGetBrowserInformationFromRemote,
                        weak_factory_.GetWeakPtr()));
   }
 }
@@ -67,7 +67,7 @@
   remotes_.Add(mojo::Remote<mojom::DeskTemplateClient>(std::move(client)));
 }
 
-void DeskTemplateAsh::OnGetTabStripModelUrlsFromRemote(
+void DeskTemplateAsh::OnGetBrowserInformationFromRemote(
     uint32_t serial,
     const std::string& window_unique_id,
     mojom::DeskTemplateStatePtr state) {
diff --git a/chrome/browser/ash/crosapi/desk_template_ash.h b/chrome/browser/ash/crosapi/desk_template_ash.h
index 1bde099..93022486 100644
--- a/chrome/browser/ash/crosapi/desk_template_ash.h
+++ b/chrome/browser/ash/crosapi/desk_template_ash.h
@@ -29,7 +29,7 @@
 
   // Called by ash's internal desk template implementation.
   // Forwarded to Lacros.
-  void GetTabStripModelUrls(
+  void GetBrowserInformation(
       const std::string& window_unique_id,
       base::OnceCallback<void(crosapi::mojom::DeskTemplateStatePtr)> callback);
   void CreateBrowserWithRestoredData(
@@ -68,9 +68,9 @@
 
   // Receives the response from the single remote.  If the response contains
   // data, forwards it to Ash.
-  void OnGetTabStripModelUrlsFromRemote(uint32_t serial,
-                                        const std::string& window_unique_id,
-                                        mojom::DeskTemplateStatePtr state);
+  void OnGetBrowserInformationFromRemote(uint32_t serial,
+                                         const std::string& window_unique_id,
+                                         mojom::DeskTemplateStatePtr state);
 
   mojo::ReceiverSet<mojom::DeskTemplate> receivers_;
   // Each separate Lacros process owns its own remote.
diff --git a/chrome/browser/ash/crosapi/parent_access_ash.cc b/chrome/browser/ash/crosapi/parent_access_ash.cc
index 7818985..663a8a40 100644
--- a/chrome/browser/ash/crosapi/parent_access_ash.cc
+++ b/chrome/browser/ash/crosapi/parent_access_ash.cc
@@ -29,7 +29,7 @@
           crosapi::mojom::ParentAccessDeclinedResult::New());
       break;
 
-    case ash::ParentAccessDialog::Result::Status::kCancelled:
+    case ash::ParentAccessDialog::Result::Status::kCanceled:
       parent_access_result = mojom::ParentAccessResult::NewCancelled(
           crosapi::mojom::ParentAccessCancelledResult::New());
       break;
diff --git a/chrome/browser/ash/eche_app/eche_app_manager_factory.cc b/chrome/browser/ash/eche_app/eche_app_manager_factory.cc
index efc7b97..2d6cd7da 100644
--- a/chrome/browser/ash/eche_app/eche_app_manager_factory.cc
+++ b/chrome/browser/ash/eche_app/eche_app_manager_factory.cc
@@ -50,16 +50,6 @@
 
 namespace {
 
-// Enumeration of possible interactions with a PhoneHub notification. Keep in
-// sync with corresponding enum in tools/metrics/histograms/enums.xml. These
-// values are persisted to logs. Entries should not be renumbered and numeric
-// values should never be reused.
-enum class NotificationInteraction {
-  kUnknown = 0,
-  kOpenAppStreaming = 1,
-  kMaxValue = kOpenAppStreaming,
-};
-
 void EnsureStreamClose(Profile* profile) {
   EcheAppManager* eche_app_manager =
       EcheAppManagerFactory::GetForProfile(profile);
@@ -205,8 +195,6 @@
     const gfx::Image& icon) {
   LaunchWebApp(package_name, notification_id, visible_name, user_id, icon,
                profile);
-  base::UmaHistogramEnumeration("Eche.NotificationClicked",
-                                NotificationInteraction::kOpenAppStreaming);
   EcheAppManagerFactory::GetInstance()
       ->CloseConnectionOrLaunchErrorNotifications();
 }
diff --git a/chrome/browser/ash/extensions/input_method_api.cc b/chrome/browser/ash/extensions/input_method_api.cc
index 32c252d..8963aff 100644
--- a/chrome/browser/ash/extensions/input_method_api.cc
+++ b/chrome/browser/ash/extensions/input_method_api.cc
@@ -50,6 +50,8 @@
 namespace SwitchToLastUsedInputMethod =
     extensions::api::input_method_private::SwitchToLastUsedInputMethod;
 namespace SetXkbLayout = extensions::api::input_method_private::SetXkbLayout;
+namespace OpenOptionsPage =
+    extensions::api::input_method_private::OpenOptionsPage;
 namespace OnChanged = extensions::api::input_method_private::OnChanged;
 namespace OnDictionaryChanged =
     extensions::api::input_method_private::OnDictionaryChanged;
@@ -265,6 +267,35 @@
 }
 
 ExtensionFunction::ResponseAction
+InputMethodPrivateOpenOptionsPageFunction::Run() {
+  std::unique_ptr<OpenOptionsPage::Params> params(
+      OpenOptionsPage::Params::Create(args()));
+  EXTENSION_FUNCTION_VALIDATE(params.get());
+  scoped_refptr<ash::input_method::InputMethodManager::State> ime_state =
+      ash::input_method::InputMethodManager::Get()->GetActiveIMEState();
+  const ash::input_method::InputMethodDescriptor* ime =
+      ime_state->GetInputMethodFromId(params->input_method_id);
+  if (!ime)
+    return RespondNow(Error(InformativeError(
+        base::StringPrintf("%s Input Method: %s", kErrorInvalidInputMethod,
+                           params->input_method_id.c_str()),
+        static_function_name())));
+
+  content::WebContents* web_contents = GetSenderWebContents();
+  if (web_contents) {
+    const GURL& options_page_url = ime->options_page_url();
+    if (!options_page_url.is_empty()) {
+      Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
+      content::OpenURLParams url_params(options_page_url, content::Referrer(),
+                                        WindowOpenDisposition::SINGLETON_TAB,
+                                        ui::PAGE_TRANSITION_LINK, false);
+      browser->OpenURL(url_params);
+    }
+  }
+  return RespondNow(NoArguments());
+}
+
+ExtensionFunction::ResponseAction
 InputMethodPrivateGetSurroundingTextFunction::Run() {
   ui::TextInputTarget* input_context =
       ui::IMEBridge::Get()->GetInputContextHandler();
@@ -492,6 +523,7 @@
   registry.RegisterFunction<InputMethodPrivateAddWordToDictionaryFunction>();
   registry
       .RegisterFunction<InputMethodPrivateNotifyImeMenuItemActivatedFunction>();
+  registry.RegisterFunction<InputMethodPrivateOpenOptionsPageFunction>();
 }
 
 InputMethodAPI::~InputMethodAPI() = default;
diff --git a/chrome/browser/ash/extensions/input_method_api.h b/chrome/browser/ash/extensions/input_method_api.h
index d8c40fb..a4011ee 100644
--- a/chrome/browser/ash/extensions/input_method_api.h
+++ b/chrome/browser/ash/extensions/input_method_api.h
@@ -224,6 +224,26 @@
                              INPUTMETHODPRIVATE_HIDEINPUTVIEW)
 };
 
+// Implements the inputMethodPrivate.openOptionsPage method.
+class InputMethodPrivateOpenOptionsPageFunction : public ExtensionFunction {
+ public:
+  InputMethodPrivateOpenOptionsPageFunction() {}
+
+  InputMethodPrivateOpenOptionsPageFunction(
+      const InputMethodPrivateOpenOptionsPageFunction&) = delete;
+  InputMethodPrivateOpenOptionsPageFunction& operator=(
+      const InputMethodPrivateOpenOptionsPageFunction&) = delete;
+
+ protected:
+  ~InputMethodPrivateOpenOptionsPageFunction() override {}
+
+  ResponseAction Run() override;
+
+ private:
+  DECLARE_EXTENSION_FUNCTION("inputMethodPrivate.openOptionsPage",
+                             INPUTMETHODPRIVATE_OPENOPTIONSPAGE)
+};
+
 class InputMethodPrivateGetSurroundingTextFunction : public ExtensionFunction {
  public:
   InputMethodPrivateGetSurroundingTextFunction() {}
diff --git a/chrome/browser/ash/file_manager/file_manager_browsertest.cc b/chrome/browser/ash/file_manager/file_manager_browsertest.cc
index d938fb8..a07f1b6 100644
--- a/chrome/browser/ash/file_manager/file_manager_browsertest.cc
+++ b/chrome/browser/ash/file_manager/file_manager_browsertest.cc
@@ -182,6 +182,11 @@
     return *this;
   }
 
+  TestCase& EnableSearchV2() {
+    options.enable_search_v2 = true;
+    return *this;
+  }
+
   std::string GetFullName() const {
     std::string full_name = name;
 
@@ -227,6 +232,9 @@
     if (options.file_transfer_connector_report_only)
       full_name += "_ReportOnly";
 
+    if (options.enable_search_v2)
+      full_name += "_SearchV2";
+
     return full_name;
   }
 
@@ -1651,7 +1659,8 @@
                       TestCase("searchDownloadsClearSearch"),
                       TestCase("searchHidingViaTab"),
                       TestCase("searchHidingTextEntryField"),
-                      TestCase("searchButtonToggles")
+                      TestCase("searchButtonToggles"),
+                      TestCase("searchOptions").EnableSearchV2()
                       // TODO(b/189173190): Enable
                       // TestCase("searchQueryLaunchParam")
                       ));
diff --git a/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc b/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc
index c1cd796..2d72938b 100644
--- a/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc
+++ b/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc
@@ -1992,6 +1992,12 @@
     disabled_features.push_back(features::kFileTransferEnterpriseConnector);
   }
 
+  if (options.enable_search_v2) {
+    enabled_features.push_back(chromeos::features::kFilesSearchV2);
+  } else {
+    disabled_features.push_back(chromeos::features::kFilesSearchV2);
+  }
+
   // This is destroyed in |TearDown()|. We cannot initialize this in the
   // constructor due to this feature values' above dependence on virtual
   // method calls, but by convention subclasses of this fixture may initialize
diff --git a/chrome/browser/ash/file_manager/file_manager_browsertest_base.h b/chrome/browser/ash/file_manager/file_manager_browsertest_base.h
index 45536e5..322018e 100644
--- a/chrome/browser/ash/file_manager/file_manager_browsertest_base.h
+++ b/chrome/browser/ash/file_manager/file_manager_browsertest_base.h
@@ -135,6 +135,9 @@
 
     // Whether test should use report-only mode for the file transfer connector.
     bool file_transfer_connector_report_only = false;
+
+    // Whether tests should enable V2 of search.
+    bool enable_search_v2 = false;
   };
 
   FileManagerBrowserTestBase(const FileManagerBrowserTestBase&) = delete;
diff --git a/chrome/browser/ash/file_manager/file_manager_jstest.cc b/chrome/browser/ash/file_manager/file_manager_jstest.cc
index 7bd56a7..26d438b 100644
--- a/chrome/browser/ash/file_manager/file_manager_jstest.cc
+++ b/chrome/browser/ash/file_manager/file_manager_jstest.cc
@@ -294,6 +294,10 @@
   RunTestURL("widgets/xf_breadcrumb_unittest.js");
 }
 
+IN_PROC_BROWSER_TEST_F(FileManagerJsTest, XfSearchOptions) {
+  RunTestURL("widgets/xf_search_options_unittest.js");
+}
+
 IN_PROC_BROWSER_TEST_F(FileManagerJsTest, FileGridTest) {
   RunTestURL("foreground/js/ui/file_grid_unittest.js");
 }
diff --git a/chrome/browser/ash/input_method/input_method_manager_impl.cc b/chrome/browser/ash/input_method/input_method_manager_impl.cc
index 9871e5b..9da6d71 100644
--- a/chrome/browser/ash/input_method/input_method_manager_impl.cc
+++ b/chrome/browser/ash/input_method/input_method_manager_impl.cc
@@ -233,6 +233,19 @@
   return enabled_input_method_ids_.size();
 }
 
+const InputMethodDescriptor*
+InputMethodManagerImpl::StateImpl::GetInputMethodFromId(
+    const std::string& input_method_id) const {
+  const InputMethodDescriptor* ime =
+      manager_->util_.GetInputMethodDescriptorFromId(input_method_id);
+  if (!ime) {
+    const auto ix = available_input_methods_.find(input_method_id);
+    if (ix != available_input_methods_.end())
+      ime = &ix->second;
+  }
+  return ime;
+}
+
 void InputMethodManagerImpl::StateImpl::EnableLoginLayouts(
     const std::string& language_code,
     const std::vector<std::string>& initial_layouts) {
diff --git a/chrome/browser/ash/input_method/input_method_manager_impl.h b/chrome/browser/ash/input_method/input_method_manager_impl.h
index 7bd7193..e930bcf2 100644
--- a/chrome/browser/ash/input_method/input_method_manager_impl.h
+++ b/chrome/browser/ash/input_method/input_method_manager_impl.h
@@ -93,6 +93,8 @@
     std::unique_ptr<InputMethodDescriptors> GetEnabledInputMethods()
         const override;
     const std::vector<std::string>& GetEnabledInputMethodIds() const override;
+    const InputMethodDescriptor* GetInputMethodFromId(
+        const std::string& input_method_id) const override;
     size_t GetNumEnabledInputMethods() const override;
     void SetEnabledExtensionImes(std::vector<std::string>* ids) override;
     void SetInputMethodLoginDefault() override;
diff --git a/chrome/browser/ash/input_method/mock_input_method_manager_impl.cc b/chrome/browser/ash/input_method/mock_input_method_manager_impl.cc
index 14bd52b..57751524 100644
--- a/chrome/browser/ash/input_method/mock_input_method_manager_impl.cc
+++ b/chrome/browser/ash/input_method/mock_input_method_manager_impl.cc
@@ -37,6 +37,19 @@
   return result;
 }
 
+const InputMethodDescriptor*
+MockInputMethodManagerImpl::State::GetInputMethodFromId(
+    const std::string& input_method_id) const {
+  static const InputMethodDescriptor defaultInputMethod =
+      InputMethodUtil::GetFallbackInputMethodDescriptor();
+  for (const auto& enabled_input_method_id : enabled_input_method_ids) {
+    if (input_method_id == enabled_input_method_id) {
+      return &defaultInputMethod;
+    }
+  }
+  return nullptr;
+}
+
 InputMethodDescriptor MockInputMethodManagerImpl::State::GetCurrentInputMethod()
     const {
   InputMethodDescriptor descriptor =
diff --git a/chrome/browser/ash/input_method/mock_input_method_manager_impl.h b/chrome/browser/ash/input_method/mock_input_method_manager_impl.h
index 02bbe919..365874a0 100644
--- a/chrome/browser/ash/input_method/mock_input_method_manager_impl.h
+++ b/chrome/browser/ash/input_method/mock_input_method_manager_impl.h
@@ -32,6 +32,8 @@
     GetEnabledInputMethodsSortedByLocalizedDisplayNames() const override;
     std::unique_ptr<InputMethodDescriptors> GetEnabledInputMethods()
         const override;
+    const InputMethodDescriptor* GetInputMethodFromId(
+        const std::string& input_method_id) const override;
     InputMethodDescriptor GetCurrentInputMethod() const override;
 
     // The value GetCurrentInputMethod().id() will return.
diff --git a/chrome/browser/ash/login/choobe_flow_controller.cc b/chrome/browser/ash/login/choobe_flow_controller.cc
new file mode 100644
index 0000000..3562d48e
--- /dev/null
+++ b/chrome/browser/ash/login/choobe_flow_controller.cc
@@ -0,0 +1,93 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/login/choobe_flow_controller.h"
+
+#include <vector>
+
+#include "base/containers/flat_set.h"
+#include "base/notreached.h"
+#include "chrome/browser/ash/login/ui/login_display_host.h"
+#include "chrome/browser/ash/login/wizard_controller.h"
+#include "chrome/browser/ui/webui/ash/login/theme_selection_screen_handler.h"
+#include "chrome/grit/generated_resources.h"
+
+namespace ash {
+
+namespace {
+
+const int kMinScreensToShowChoobe = 3;
+const int kMaxScreensToShowChoobe = 10;
+
+const ChoobeFlowController::OptionalScreen kOptionalScreens[] = {
+    {ThemeSelectionScreenView::kScreenId,
+     "oobe-32:stars",
+     {"choobeThemeSelectionTileTitle",
+      IDS_OOBE_CHOOBE_THEME_SELECTION_TILE_TITLE}}};
+
+}  // namespace
+
+ChoobeFlowController::ChoobeFlowController() {}
+
+ChoobeFlowController::~ChoobeFlowController() {}
+
+void ChoobeFlowController::Start() {
+  if (is_choobe_flow_active_)
+    return;
+
+  chromeos::LoginDisplayHost* host = chromeos::LoginDisplayHost::default_host();
+  if (!host || !host->GetWizardController())
+    return;
+
+  for (auto screen : kOptionalScreens) {
+    BaseScreen* screen_obj =
+        host->GetWizardController()->GetScreen(screen.screen_id);
+    if (!screen_obj->ShouldBeSkipped(*host->GetWizardContext())) {
+      eligible_screens_.push_back(screen);
+    }
+  }
+
+  if (eligible_screens_.size() >= kMinScreensToShowChoobe &&
+      eligible_screens_.size() <= kMaxScreensToShowChoobe) {
+    is_choobe_flow_active_ = true;
+  }
+}
+
+void ChoobeFlowController::Stop() {
+  eligible_screens_.clear();
+  selected_screens_.clear();
+  is_choobe_flow_active_ = false;
+}
+
+std::vector<ChoobeFlowController::OptionalScreen>
+ChoobeFlowController::GetEligibleCHOOBEScreens() {
+  return eligible_screens_;
+}
+
+bool ChoobeFlowController::ShouldScreenBeSkipped(OobeScreenId screen_id) {
+  if (!is_choobe_flow_active_)
+    return false;
+  return selected_screens_.find(screen_id) == selected_screens_.end();
+}
+
+std::vector<ChoobeFlowController::OptionalScreenResource>
+ChoobeFlowController::GetOptionalScreensResources() {
+  std::vector<ChoobeFlowController::OptionalScreenResource> titles;
+  for (auto screen : kOptionalScreens) {
+    titles.push_back(screen.title_resource);
+  }
+  return titles;
+}
+
+void ChoobeFlowController::OnScreensSelected(base::Value::List screens) {
+  if (!is_choobe_flow_active_)
+    NOTREACHED() << "Screens should only be selected when is_choobe_active_";
+
+  for (auto& screen : screens) {
+    std::string cur = screen.GetString();
+    selected_screens_.insert(OobeScreenId(cur));
+  }
+}
+
+}  // namespace ash
diff --git a/chrome/browser/ash/login/choobe_flow_controller.h b/chrome/browser/ash/login/choobe_flow_controller.h
new file mode 100644
index 0000000..e823802
--- /dev/null
+++ b/chrome/browser/ash/login/choobe_flow_controller.h
@@ -0,0 +1,92 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ASH_LOGIN_CHOOBE_FLOW_CONTROLLER_H_
+#define CHROME_BROWSER_ASH_LOGIN_CHOOBE_FLOW_CONTROLLER_H_
+
+#include <vector>
+
+#include "base/containers/flat_set.h"
+#include "base/values.h"
+#include "chrome/browser/ash/login/oobe_screen.h"
+
+namespace ash {
+
+// Controls the CHOOBE flow which is a part of the onboarding flow.
+// CHOOBE Flow consists of a list of optional screens. The user can
+// specify which optional screens to go through from the CHOOBE screen.
+class ChoobeFlowController {
+ public:
+  // Resources of the strings used in the tiles shown in the CHOOBE
+  // screen. Resources will be added to the `LocalizedValuesBuilder`
+  // in `LocaleSwitchScreenHandler::DeclareLocalizedValues()`.
+  struct OptionalScreenResource {
+    const char* key;
+    int message_id;
+  };
+
+  // Optional screen which is part of CHOOBE. The screen tile will
+  // be shown in the CHOOBE screen if it is eligible for the user
+  // (`Screen::ShouldBeSkipped()` method returns `false`).
+  struct OptionalScreen {
+    StaticOobeScreenId screen_id;
+    const char* icon_id;
+    OptionalScreenResource title_resource;
+  };
+
+  ChoobeFlowController();
+
+  ChoobeFlowController(const ChoobeFlowController&) = delete;
+  ChoobeFlowController& operator=(const ChoobeFlowController&) = delete;
+
+  ~ChoobeFlowController();
+
+  // Called before the CHOOBE screen is shown:
+  //  * Populates the `eligible_screens_` vector with the optional
+  //    screens that the user can go through.
+  //  * Sets `is_choobe_flow_active_` to `true` if the number of eligible
+  //    screens falls in the allowed range for CHOOBE screen to be shown.
+  void Start();
+
+  // * Clears `eligible_screens_` and `selected_screens_`.
+  // * Sets `is_choobe_flow_active_` to `false` so that future calls to
+  //   `ShouldScreenBeSkipped(screen_id)` returns `false`.
+  void Stop();
+
+  // Returns `true` if CHOOBE is active and the user has selected the screen.
+  bool ShouldScreenBeSkipped(OobeScreenId screen_id);
+
+  // Returns screens that the user is eligible to go through.
+  std::vector<OptionalScreen> GetEligibleCHOOBEScreens();
+
+  // Returns string resources for all optional screens stored in
+  // `kNonFoundationalScreens`.
+  static std::vector<OptionalScreenResource> GetOptionalScreensResources();
+
+  // Populates `selected_screens_` with screens selected by the user.
+  void OnScreensSelected(base::Value::List screens);
+
+  bool IsChoobeFlowActive() { return is_choobe_flow_active_; }
+
+ private:
+  // Screens that the user can select in the CHOOBE screen. Populated by
+  // the `Start()` method.
+  std::vector<OptionalScreen> eligible_screens_;
+
+  // Screens that the user has selected. Populated by the `OnScreensSelected`
+  // method.
+  base::flat_set<OobeScreenId> selected_screens_;
+
+  bool is_choobe_flow_active_ = false;
+};
+
+}  // namespace ash
+
+// TODO(https://crbug.com/1164001): remove after //chrome/browser/chromeos
+// source migration is finished.
+namespace chromeos {
+using ::ash::ChoobeFlowController;
+}
+
+#endif  // CHROME_BROWSER_ASH_LOGIN_CHOOBE_FLOW_CONTROLLER_H_
diff --git a/chrome/browser/ash/login/wizard_controller.cc b/chrome/browser/ash/login/wizard_controller.cc
index c394e92..1702d98c 100644
--- a/chrome/browser/ash/login/wizard_controller.cc
+++ b/chrome/browser/ash/login/wizard_controller.cc
@@ -39,6 +39,7 @@
 #include "chrome/browser/ash/app_mode/web_app/web_kiosk_app_manager.h"
 #include "chrome/browser/ash/arc/arc_util.h"
 #include "chrome/browser/ash/customization/customization_document.h"
+#include "chrome/browser/ash/login/choobe_flow_controller.h"
 #include "chrome/browser/ash/login/configuration_keys.h"
 #include "chrome/browser/ash/login/demo_mode/demo_session.h"
 #include "chrome/browser/ash/login/demo_mode/demo_setup_controller.h"
@@ -2630,6 +2631,12 @@
   return auto_enrollment_controller_.get();
 }
 
+ChoobeFlowController* WizardController::GetChoobeFlowController() {
+  if (!choobe_flow_controller_)
+    choobe_flow_controller_ = std::make_unique<ChoobeFlowController>();
+  return choobe_flow_controller_.get();
+}
+
 void WizardController::MaybeTakeTPMOwnership() {
   if (wizard_context_->is_branded_build || switches::IsTpmDynamic())
     return;
diff --git a/chrome/browser/ash/login/wizard_controller.h b/chrome/browser/ash/login/wizard_controller.h
index fc96e60..45ea469 100644
--- a/chrome/browser/ash/login/wizard_controller.h
+++ b/chrome/browser/ash/login/wizard_controller.h
@@ -16,6 +16,7 @@
 #include "base/scoped_observation.h"
 #include "base/time/time.h"
 #include "chrome/browser/ash/accessibility/accessibility_manager.h"
+#include "chrome/browser/ash/login/choobe_flow_controller.h"
 #include "chrome/browser/ash/login/demo_mode/demo_session.h"
 #include "chrome/browser/ash/login/enrollment/auto_enrollment_check_screen.h"
 #include "chrome/browser/ash/login/enrollment/auto_enrollment_controller.h"
@@ -186,6 +187,10 @@
     return demo_setup_controller_.get();
   }
 
+  // Returns CHOOBE flow controller (lazily initialized one if it doesn't exist
+  // already).
+  ChoobeFlowController* GetChoobeFlowController();
+
   // Returns a pointer to the current screen or nullptr if there's no such
   // screen.
   BaseScreen* current_screen() const { return current_screen_; }
@@ -488,6 +493,7 @@
   void MaybeTakeTPMOwnership();
 
   std::unique_ptr<AutoEnrollmentController> auto_enrollment_controller_;
+  std::unique_ptr<ChoobeFlowController> choobe_flow_controller_;
   std::unique_ptr<ScreenManager> screen_manager_;
 
   // The `BaseScreen*` here point to the objects owned by the `screen_manager_`.
diff --git a/chrome/browser/ash/ownership/owner_key_loader.cc b/chrome/browser/ash/ownership/owner_key_loader.cc
index 4ac533f..efbfe7f7 100644
--- a/chrome/browser/ash/ownership/owner_key_loader.cc
+++ b/chrome/browser/ash/ownership/owner_key_loader.cc
@@ -6,6 +6,7 @@
 
 #include "base/check_is_test.h"
 #include "base/task/thread_pool.h"
+#include "chrome/browser/ash/ownership/ownership_histograms.h"
 #include "chrome/browser/ash/settings/device_settings_service.h"
 #include "chrome/browser/net/nss_service.h"
 #include "chrome/browser/net/nss_service_factory.h"
@@ -141,6 +142,22 @@
                                 std::move(worker_task)));
 }
 
+inline bool IsKeyPresent(
+    const scoped_refptr<ownership::PublicKey>& public_key) {
+  return public_key && !public_key->is_empty();
+}
+
+inline bool IsKeyPresent(
+    const scoped_refptr<ownership::PrivateKey>& public_key) {
+  return public_key && public_key->key();
+}
+
+inline bool AreKeysPresent(
+    const scoped_refptr<ownership::PublicKey>& public_key,
+    const scoped_refptr<ownership::PrivateKey>& private_key) {
+  return IsKeyPresent(public_key) && IsKeyPresent(private_key);
+}
+
 }  // namespace
 
 OwnerKeyLoader::OwnerKeyLoader(
@@ -170,6 +187,8 @@
 
   if (!device_settings_service_) {
     CHECK_IS_TEST();
+    RecordOwnerKeyEvent(OwnerKeyEvent::kDeviceSettingsServiceIsNull,
+                        /*success=*/false);
     return std::move(callback_).Run(/*public_key=*/nullptr,
                                     /*private_key=*/nullptr);
     // `this` might be deleted here.
@@ -192,12 +211,14 @@
   public_key_ = std::move(public_key);
 
   if (is_enterprise_managed_) {
+    RecordOwnerKeyEvent(OwnerKeyEvent::kManagedDevice,
+                        /*success=*/IsKeyPresent(public_key_));
     // Managed devices don't have private owner keys.
     return std::move(callback_).Run(std::move(public_key_), nullptr);
     // `this` might be deleted here.
   }
 
-  if (public_key_ && !public_key_->is_empty()) {
+  if (IsKeyPresent(public_key_)) {
     // Now check whether the current user has access to the private key
     // associated with the public key.
     return GetCertDbAndPostOnWorkerThread(
@@ -216,7 +237,9 @@
 void OwnerKeyLoader::OnPrivateKeyLoaded(
     scoped_refptr<ownership::PrivateKey> private_key) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  if (private_key && private_key->key()) {
+  if (IsKeyPresent(private_key)) {
+    RecordOwnerKeyEvent(OwnerKeyEvent::kOwnerHasKeys,
+                        /*success=*/AreKeysPresent(public_key_, private_key));
     // Success: both keys were loaded, the current user is the owner.
     return std::move(callback_).Run(std::move(public_key_),
                                     std::move(private_key));
@@ -233,6 +256,10 @@
   // device_settings_service_ indicates that the current user should become the
   // owner, generate a new owner key pair for them.
   if (device_settings_service_->GetWillEstablishConsumerOwnership()) {
+    // This should only happen on the first sign in when there's no previous
+    // public key.
+    RecordOwnerKeyEvent(OwnerKeyEvent::kEstablishingConsumerOwnership,
+                        /*success=*/!IsKeyPresent(public_key_));
     LOG(WARNING) << "Establishing consumer ownership.";
     return GenerateNewKey();
   }
@@ -247,9 +274,15 @@
     // If the policy says that the current user is the owner, generate a new key
     // pair for them.
     if (policy_data->username() == profile_->GetProfileUserName()) {
+      // Expect public key to be present. It's not likely to lose private and
+      // public keys simultaneously, they are stored independently.
+      RecordOwnerKeyEvent(OwnerKeyEvent::kRegeneratingOwnerKeyBasedOnPolicy,
+                          /*success=*/IsKeyPresent(public_key_));
       LOG(WARNING) << "The owner key was lost. Generating a new one.";
       return GenerateNewKey();
     } else {
+      RecordOwnerKeyEvent(OwnerKeyEvent::kUserNotAnOwnerBasedOnPolicy,
+                          /*success=*/IsKeyPresent(public_key_));
       // The current user is not the owner, just return the public key.
       return std::move(callback_).Run(std::move(public_key_), nullptr);
       // `this` might be deleted here.
@@ -261,10 +294,21 @@
       user_manager::UserManager::Get()->GetOwnerEmail();
   if (owner_email.has_value() &&
       owner_email.value() == profile_->GetProfileUserName()) {
+    // This brunch is more likely to be used before device policies are created
+    // for the first time, so expect the public key to not be present.
+    RecordOwnerKeyEvent(OwnerKeyEvent::kRegeneratingOwnerKeyBasedOnLocalState,
+                        /*success=*/!IsKeyPresent(public_key_));
     LOG(WARNING) << "Generating new owner key based on local state data.";
     return GenerateNewKey();
+  } else if (owner_email.has_value() &&
+             owner_email.value() != profile_->GetProfileUserName()) {
+    RecordOwnerKeyEvent(OwnerKeyEvent::kUserNotAnOwnerBasedOnLocalState,
+                        /*success=*/IsKeyPresent(public_key_));
+    return std::move(callback_).Run(std::move(public_key_), nullptr);
   }
 
+  RecordOwnerKeyEvent(OwnerKeyEvent::kUnsureUserNotAnOwner,
+                      /*success=*/IsKeyPresent(public_key_));
   // The current user doesn't seem to be the owner, just return the public key
   // (or nullptr, if it wasn't successfully loaded earlier).
   return std::move(callback_).Run(std::move(public_key_), nullptr);
@@ -283,7 +327,10 @@
     scoped_refptr<ownership::PublicKey> public_key,
     scoped_refptr<ownership::PrivateKey> private_key) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  if (private_key && private_key->key()) {
+  if (AreKeysPresent(public_key, private_key)) {
+    RecordOwnerKeyEvent(OwnerKeyEvent::kOwnerKeyGenerated,
+                        /*success=*/(generate_attempt_counter_ == 0));
+
     LOG(WARNING) << "New owner key pair was generated.";
     return std::move(callback_).Run(std::move(public_key),
                                     std::move(private_key));
@@ -296,6 +343,10 @@
     return GenerateNewKey();
   }
 
+  // This case is not a success in general, but record whether the user will at
+  // least get the old public key.
+  RecordOwnerKeyEvent(OwnerKeyEvent::kFailedToGenerateOwnerKey,
+                      /*success=*/IsKeyPresent(public_key_));
   LOG(ERROR) << "Failed to generate new owner key.";
   // Return at least the public key, if it was loaded. If Chrome is taking
   // ownership for the first time, it should be null. If recovering from a lost
diff --git a/chrome/browser/ash/ownership/owner_key_loader_unittest.cc b/chrome/browser/ash/ownership/owner_key_loader_unittest.cc
index f5ad237..b8026b2 100644
--- a/chrome/browser/ash/ownership/owner_key_loader_unittest.cc
+++ b/chrome/browser/ash/ownership/owner_key_loader_unittest.cc
@@ -7,7 +7,9 @@
 #include <memory>
 #include <vector>
 
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/test_future.h"
+#include "chrome/browser/ash/ownership/ownership_histograms.h"
 #include "chrome/browser/ash/policy/core/device_policy_builder.h"
 #include "chrome/browser/ash/settings/device_settings_service.h"
 #include "chrome/browser/net/fake_nss_service.h"
@@ -23,6 +25,8 @@
 
 using PublicKeyRefPtr = scoped_refptr<ownership::PublicKey>;
 using PrivateKeyRefPtr = scoped_refptr<ownership::PrivateKey>;
+using base::Bucket;
+using testing::ElementsAre;
 
 namespace ash {
 
@@ -85,6 +89,7 @@
   ash::DeviceSettingsService device_settings_service_;
   std::unique_ptr<OwnerKeyLoader> key_loader_;
   base::test::TestFuture<PublicKeyRefPtr, PrivateKeyRefPtr> result_observer_;
+  base::HistogramTester histogram_tester_;
 };
 
 // Test that the first user generates a new owner key.
@@ -99,6 +104,12 @@
   EXPECT_TRUE(!result_observer_.Get<PublicKeyRefPtr>()->is_empty());
   ASSERT_TRUE(result_observer_.Get<PrivateKeyRefPtr>());
   EXPECT_TRUE(result_observer_.Get<PrivateKeyRefPtr>()->key());
+
+  EXPECT_THAT(
+      histogram_tester_.GetAllSamples(kOwnerKeyHistogramName),
+      ElementsAre(
+          Bucket(OwnerKeyUmaEvent::kEstablishingConsumerOwnershipSuccess, 1),
+          Bucket(OwnerKeyUmaEvent::kOwnerKeyGeneratedSuccess, 1)));
 }
 
 // Test that the first user generates owner key after a crash. If during the
@@ -117,6 +128,14 @@
   EXPECT_TRUE(!result_observer_.Get<PublicKeyRefPtr>()->is_empty());
   ASSERT_TRUE(result_observer_.Get<PrivateKeyRefPtr>());
   EXPECT_TRUE(result_observer_.Get<PrivateKeyRefPtr>()->key());
+
+  EXPECT_THAT(
+      histogram_tester_.GetAllSamples(kOwnerKeyHistogramName),
+      ElementsAre(
+          Bucket(
+              OwnerKeyUmaEvent::kRegeneratingOwnerKeyBasedOnLocalStateSuccess,
+              1),
+          Bucket(OwnerKeyUmaEvent::kOwnerKeyGeneratedSuccess, 1)));
 }
 
 // Test that the second user doesn't try to generate a new owner key.
@@ -134,6 +153,10 @@
   EXPECT_EQ(result_observer_.Get<PublicKeyRefPtr>()->data(),
             ExtractBytes(signing_key));
   EXPECT_FALSE(result_observer_.Get<PrivateKeyRefPtr>());
+
+  EXPECT_THAT(histogram_tester_.GetAllSamples(kOwnerKeyHistogramName),
+              ElementsAre(Bucket(
+                  OwnerKeyUmaEvent::kUserNotAnOwnerBasedOnPolicySuccess, 1)));
 }
 
 // Test that an owner user gets recognized as the owner when it's mentioned in
@@ -152,6 +175,9 @@
             ExtractBytes(signing_key));
   ASSERT_TRUE(result_observer_.Get<PrivateKeyRefPtr>());
   EXPECT_TRUE(result_observer_.Get<PrivateKeyRefPtr>()->key());
+
+  EXPECT_THAT(histogram_tester_.GetAllSamples(kOwnerKeyHistogramName),
+              ElementsAre(Bucket(OwnerKeyUmaEvent::kOwnerHasKeysSuccess, 1)));
 }
 
 // Test that even without existing device policies the owner key gets loaded
@@ -170,6 +196,9 @@
             ExtractBytes(signing_key));
   ASSERT_TRUE(result_observer_.Get<PrivateKeyRefPtr>());
   EXPECT_TRUE(result_observer_.Get<PrivateKeyRefPtr>()->key());
+
+  EXPECT_THAT(histogram_tester_.GetAllSamples(kOwnerKeyHistogramName),
+              ElementsAre(Bucket(OwnerKeyUmaEvent::kOwnerHasKeysSuccess, 1)));
 }
 
 // Test that the second user is not falsely recognized as the owner even if
@@ -187,6 +216,10 @@
   EXPECT_EQ(result_observer_.Get<PublicKeyRefPtr>()->data(),
             ExtractBytes(signing_key));
   EXPECT_FALSE(result_observer_.Get<PrivateKeyRefPtr>());
+
+  EXPECT_THAT(
+      histogram_tester_.GetAllSamples(kOwnerKeyHistogramName),
+      ElementsAre(Bucket(OwnerKeyUmaEvent::kUnsureUserNotAnOwnerSuccess, 1)));
 }
 
 // Test that an owner user still gets recognized as the owner when it's
@@ -207,6 +240,13 @@
             ExtractBytes(signing_key));
   ASSERT_TRUE(result_observer_.Get<PrivateKeyRefPtr>());
   EXPECT_TRUE(result_observer_.Get<PrivateKeyRefPtr>()->key());
+
+  EXPECT_THAT(
+      histogram_tester_.GetAllSamples(kOwnerKeyHistogramName),
+      ElementsAre(
+          Bucket(OwnerKeyUmaEvent::kRegeneratingOwnerKeyBasedOnPolicySuccess,
+                 1),
+          Bucket(OwnerKeyUmaEvent::kOwnerKeyGeneratedSuccess, 1)));
 }
 
 // Test that an owner user still gets recognized as the owner when it's
@@ -230,6 +270,14 @@
             ExtractBytes(signing_key));
   ASSERT_TRUE(result_observer_.Get<PrivateKeyRefPtr>());
   EXPECT_TRUE(result_observer_.Get<PrivateKeyRefPtr>()->key());
+
+  EXPECT_THAT(
+      histogram_tester_.GetAllSamples(kOwnerKeyHistogramName),
+      ElementsAre(
+          // "Fail" means that the existence of the public key is unexpected.
+          Bucket(OwnerKeyUmaEvent::kRegeneratingOwnerKeyBasedOnLocalStateFail,
+                 1),
+          Bucket(OwnerKeyUmaEvent::kOwnerKeyGeneratedSuccess, 1)));
 }
 
 // Test that OwnerKeyLoader makes several attempts to generate the owner key
@@ -245,6 +293,13 @@
   ASSERT_TRUE(!result_observer_.Get<PublicKeyRefPtr>()->is_empty());
   ASSERT_TRUE(result_observer_.Get<PrivateKeyRefPtr>());
   EXPECT_TRUE(result_observer_.Get<PrivateKeyRefPtr>()->key());
+
+  EXPECT_THAT(
+      histogram_tester_.GetAllSamples(kOwnerKeyHistogramName),
+      ElementsAre(
+          Bucket(OwnerKeyUmaEvent::kEstablishingConsumerOwnershipSuccess, 1),
+          // "Fail" means that there were generation errors before it succeeded.
+          Bucket(OwnerKeyUmaEvent::kOwnerKeyGeneratedFail, 1)));
 }
 
 // Test that OwnerKeyLoader gives up to generate the owner key pair after a
@@ -258,6 +313,12 @@
 
   EXPECT_FALSE(result_observer_.Get<PublicKeyRefPtr>());
   EXPECT_FALSE(result_observer_.Get<PrivateKeyRefPtr>());
+
+  EXPECT_THAT(
+      histogram_tester_.GetAllSamples(kOwnerKeyHistogramName),
+      ElementsAre(
+          Bucket(OwnerKeyUmaEvent::kEstablishingConsumerOwnershipSuccess, 1),
+          Bucket(OwnerKeyUmaEvent::kFailedToGenerateOwnerKeyFail, 1)));
 }
 
 // Test that enterprise devices don't attempt to load private key. The signing
@@ -281,6 +342,9 @@
   ASSERT_TRUE(!result_observer_.Get<PublicKeyRefPtr>()->is_empty());
   // Check that the private key wasn't loaded.
   EXPECT_FALSE(result_observer_.Get<PrivateKeyRefPtr>());
+
+  EXPECT_THAT(histogram_tester_.GetAllSamples(kOwnerKeyHistogramName),
+              ElementsAre(Bucket(OwnerKeyUmaEvent::kManagedDeviceSuccess, 1)));
 }
 
 }  // namespace ash
diff --git a/chrome/browser/ash/ownership/owner_settings_service_ash.cc b/chrome/browser/ash/ownership/owner_settings_service_ash.cc
index 363f7437..6a60211 100644
--- a/chrome/browser/ash/ownership/owner_settings_service_ash.cc
+++ b/chrome/browser/ash/ownership/owner_settings_service_ash.cc
@@ -26,6 +26,7 @@
 #include "base/threading/thread_checker.h"
 #include "chrome/browser/ash/login/session/user_session_manager.h"
 #include "chrome/browser/ash/ownership/owner_settings_service_ash_factory.h"
+#include "chrome/browser/ash/ownership/ownership_histograms.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/ash/settings/about_flags.h"
 #include "chrome/browser/ash/settings/cros_settings.h"
@@ -774,12 +775,16 @@
       task_runner.get(), std::move(policy),
       base::BindOnce(&OwnerSettingsServiceAsh::OnPolicyAssembledAndSigned,
                      store_settings_factory_.GetWeakPtr()));
+  RecordOwnerKeyEvent(OwnerKeyEvent::kStartSigningPolicy, /*success=*/rv);
   if (!rv)
     ReportStatusAndContinueStoring(false /* success */);
 }
 
 void OwnerSettingsServiceAsh::OnPolicyAssembledAndSigned(
     std::unique_ptr<em::PolicyFetchResponse> policy_response) {
+  RecordOwnerKeyEvent(OwnerKeyEvent::kSignedPolicy,
+                      /*success=*/policy_response.get());
+
   if (!policy_response.get() || !device_settings_service_) {
     ReportStatusAndContinueStoring(false /* success */);
     return;
@@ -791,6 +796,8 @@
 }
 
 void OwnerSettingsServiceAsh::OnSignedPolicyStored(bool success) {
+  RecordOwnerKeyEvent(OwnerKeyEvent::kStoredPolicy, success);
+
   CHECK(device_settings_service_);
   ReportStatusAndContinueStoring(success &&
                                  device_settings_service_->status() ==
diff --git a/chrome/browser/ash/ownership/owner_settings_service_ash_unittest.cc b/chrome/browser/ash/ownership/owner_settings_service_ash_unittest.cc
index bf77a3c..b929538a 100644
--- a/chrome/browser/ash/ownership/owner_settings_service_ash_unittest.cc
+++ b/chrome/browser/ash/ownership/owner_settings_service_ash_unittest.cc
@@ -16,6 +16,7 @@
 #include "base/test/test_future.h"
 #include "base/values.h"
 #include "chrome/browser/ash/ownership/owner_settings_service_ash_factory.h"
+#include "chrome/browser/ash/ownership/ownership_histograms.h"
 #include "chrome/browser/ash/settings/device_settings_provider.h"
 #include "chrome/browser/ash/settings/device_settings_test_helper.h"
 #include "chrome/common/chrome_paths.h"
@@ -27,6 +28,8 @@
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace em = enterprise_management;
+using base::Bucket;
+using testing::ElementsAre;
 
 namespace ash {
 
@@ -95,10 +98,8 @@
 class OwnerSettingsServiceAshTest : public DeviceSettingsTestBase {
  public:
   OwnerSettingsServiceAshTest()
-      : service_(nullptr),
-        local_state_(TestingBrowserProcess::GetGlobal()),
-        user_data_dir_override_(chrome::DIR_USER_DATA),
-        management_settings_set_(false) {}
+      : local_state_(TestingBrowserProcess::GetGlobal()),
+        user_data_dir_override_(chrome::DIR_USER_DATA) {}
 
   OwnerSettingsServiceAshTest(const OwnerSettingsServiceAshTest&) = delete;
   OwnerSettingsServiceAshTest& operator=(const OwnerSettingsServiceAshTest&) =
@@ -153,17 +154,28 @@
   }
 
  protected:
-  OwnerSettingsServiceAsh* service_;
+  OwnerSettingsServiceAsh* service_ = nullptr;
   ScopedTestingLocalState local_state_;
   std::unique_ptr<DeviceSettingsProvider> provider_;
   base::ScopedPathOverride user_data_dir_override_;
-  bool management_settings_set_;
+  bool management_settings_set_ = false;
+  base::HistogramTester histogram_tester_;
 };
 
 TEST_F(OwnerSettingsServiceAshTest, SingleSetTest) {
   TestSingleSet(service_, kReleaseChannel, base::Value("dev-channel"));
   TestSingleSet(service_, kReleaseChannel, base::Value("beta-channel"));
   TestSingleSet(service_, kReleaseChannel, base::Value("stable-channel"));
+
+  EXPECT_LE(1, histogram_tester_.GetBucketCount(
+                   kOwnerKeyHistogramName,
+                   OwnerKeyUmaEvent::kStartSigningPolicySuccess));
+  EXPECT_LE(
+      1, histogram_tester_.GetBucketCount(
+             kOwnerKeyHistogramName, OwnerKeyUmaEvent::kSignedPolicySuccess));
+  EXPECT_LE(
+      1, histogram_tester_.GetBucketCount(
+             kOwnerKeyHistogramName, OwnerKeyUmaEvent::kStoredPolicySuccess));
 }
 
 TEST_F(OwnerSettingsServiceAshTest, MultipleSetTest) {
diff --git a/chrome/browser/ash/ownership/ownership_histograms.cc b/chrome/browser/ash/ownership/ownership_histograms.cc
new file mode 100644
index 0000000..e65a3c3
--- /dev/null
+++ b/chrome/browser/ash/ownership/ownership_histograms.cc
@@ -0,0 +1,85 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/ownership/ownership_histograms.h"
+
+#include "base/metrics/histogram_functions.h"
+
+namespace ash {
+namespace {
+
+OwnerKeyUmaEvent ConvertToUmaEvent(OwnerKeyEvent event, bool success) {
+  if (success) {
+    switch (event) {
+      case OwnerKeyEvent::kDeviceSettingsServiceIsNull:
+        return OwnerKeyUmaEvent::kDeviceSettingsServiceIsNull;
+      case OwnerKeyEvent::kManagedDevice:
+        return OwnerKeyUmaEvent::kManagedDeviceSuccess;
+      case OwnerKeyEvent::kOwnerHasKeys:
+        return OwnerKeyUmaEvent::kOwnerHasKeysSuccess;
+      case OwnerKeyEvent::kEstablishingConsumerOwnership:
+        return OwnerKeyUmaEvent::kEstablishingConsumerOwnershipSuccess;
+      case OwnerKeyEvent::kRegeneratingOwnerKeyBasedOnPolicy:
+        return OwnerKeyUmaEvent::kRegeneratingOwnerKeyBasedOnPolicySuccess;
+      case OwnerKeyEvent::kUserNotAnOwnerBasedOnPolicy:
+        return OwnerKeyUmaEvent::kUserNotAnOwnerBasedOnPolicySuccess;
+      case OwnerKeyEvent::kRegeneratingOwnerKeyBasedOnLocalState:
+        return OwnerKeyUmaEvent::kRegeneratingOwnerKeyBasedOnLocalStateSuccess;
+      case OwnerKeyEvent::kUserNotAnOwnerBasedOnLocalState:
+        return OwnerKeyUmaEvent::kUserNotAnOwnerBasedOnLocalStateSuccess;
+      case OwnerKeyEvent::kUnsureUserNotAnOwner:
+        return OwnerKeyUmaEvent::kUnsureUserNotAnOwnerSuccess;
+      case OwnerKeyEvent::kOwnerKeyGenerated:
+        return OwnerKeyUmaEvent::kOwnerKeyGeneratedSuccess;
+      case OwnerKeyEvent::kFailedToGenerateOwnerKey:
+        return OwnerKeyUmaEvent::kFailedToGenerateOwnerKeySuccess;
+      case OwnerKeyEvent::kStartSigningPolicy:
+        return OwnerKeyUmaEvent::kStartSigningPolicySuccess;
+      case OwnerKeyEvent::kSignedPolicy:
+        return OwnerKeyUmaEvent::kSignedPolicySuccess;
+      case OwnerKeyEvent::kStoredPolicy:
+        return OwnerKeyUmaEvent::kStoredPolicySuccess;
+    }
+  } else {
+    switch (event) {
+      case OwnerKeyEvent::kDeviceSettingsServiceIsNull:
+        return OwnerKeyUmaEvent::kDeviceSettingsServiceIsNull;
+      case OwnerKeyEvent::kManagedDevice:
+        return OwnerKeyUmaEvent::kManagedDeviceFail;
+      case OwnerKeyEvent::kOwnerHasKeys:
+        return OwnerKeyUmaEvent::kOwnerHasKeysFail;
+      case OwnerKeyEvent::kEstablishingConsumerOwnership:
+        return OwnerKeyUmaEvent::kEstablishingConsumerOwnershipFail;
+      case OwnerKeyEvent::kRegeneratingOwnerKeyBasedOnPolicy:
+        return OwnerKeyUmaEvent::kRegeneratingOwnerKeyBasedOnPolicyFail;
+      case OwnerKeyEvent::kUserNotAnOwnerBasedOnPolicy:
+        return OwnerKeyUmaEvent::kUserNotAnOwnerBasedOnPolicyFail;
+      case OwnerKeyEvent::kRegeneratingOwnerKeyBasedOnLocalState:
+        return OwnerKeyUmaEvent::kRegeneratingOwnerKeyBasedOnLocalStateFail;
+      case OwnerKeyEvent::kUserNotAnOwnerBasedOnLocalState:
+        return OwnerKeyUmaEvent::kUserNotAnOwnerBasedOnLocalStateFail;
+      case OwnerKeyEvent::kUnsureUserNotAnOwner:
+        return OwnerKeyUmaEvent::kUnsureUserNotAnOwnerFail;
+      case OwnerKeyEvent::kOwnerKeyGenerated:
+        return OwnerKeyUmaEvent::kOwnerKeyGeneratedFail;
+      case OwnerKeyEvent::kFailedToGenerateOwnerKey:
+        return OwnerKeyUmaEvent::kFailedToGenerateOwnerKeyFail;
+      case OwnerKeyEvent::kStartSigningPolicy:
+        return OwnerKeyUmaEvent::kStartSigningPolicyFail;
+      case OwnerKeyEvent::kSignedPolicy:
+        return OwnerKeyUmaEvent::kSignedPolicyFail;
+      case OwnerKeyEvent::kStoredPolicy:
+        return OwnerKeyUmaEvent::kStoredPolicyFail;
+    }
+  }
+}
+
+}  // namespace
+
+void RecordOwnerKeyEvent(OwnerKeyEvent event, bool success) {
+  base::UmaHistogramEnumeration(kOwnerKeyHistogramName,
+                                ConvertToUmaEvent(event, success));
+}
+
+}  // namespace ash
diff --git a/chrome/browser/ash/ownership/ownership_histograms.h b/chrome/browser/ash/ownership/ownership_histograms.h
new file mode 100644
index 0000000..66888d53
--- /dev/null
+++ b/chrome/browser/ash/ownership/ownership_histograms.h
@@ -0,0 +1,138 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ASH_OWNERSHIP_OWNERSHIP_HISTOGRAMS_H_
+#define CHROME_BROWSER_ASH_OWNERSHIP_OWNERSHIP_HISTOGRAMS_H_
+
+namespace ash {
+
+// Events related to owner key loading, generation and usage.
+enum class OwnerKeyEvent {
+  // DeviceSettingsService was null, owner key was not loaded.
+  kDeviceSettingsServiceIsNull,
+  // Managed device finished key loading.
+  kManagedDevice,
+  // Consumer device finished key loading.
+  kOwnerHasKeys,
+  // ChromeOS decided to establish consumer ownership.
+  kEstablishingConsumerOwnership,
+  // ChromeOS decided to re-generate the lost owner key based on the data from
+  // device policies.
+  kRegeneratingOwnerKeyBasedOnPolicy,
+  // A user was categorized as not an owner based on the data from device
+  // policies.
+  kUserNotAnOwnerBasedOnPolicy,
+  // ChromeOS decided to re-generate the lost owner key based on the data from
+  // local state.
+  kRegeneratingOwnerKeyBasedOnLocalState,
+  // A user was categorized as not an owner based on the data from local state.
+  kUserNotAnOwnerBasedOnLocalState,
+  // ChromeOS assumed that a user is not an owner based on the lack of data.
+  kUnsureUserNotAnOwner,
+  // New owner key was generated.
+  kOwnerKeyGenerated,
+  // Failed to generate new owner key.
+  kFailedToGenerateOwnerKey,
+  // Started signing policies.
+  kStartSigningPolicy,
+  // Finished signing policies.
+  kSignedPolicy,
+  // Finished storing policies.
+  kStoredPolicy,
+};
+
+// Combines `event` and `success` to produce a more specific UMA event and
+// records it. `success`==true generally means that the event happened as
+// expected and `success`==false means that something related to the event went
+// wrong or unexpectedly (see comments for the UMA events for more details).
+void RecordOwnerKeyEvent(OwnerKeyEvent event, bool success);
+
+// PUBLIC ONLY FOR TESTING:
+
+// The path for the OwnerKeyUmaEvent histogram. Accessible for testing. Prefer
+// using through the  `RecordOwnerKeyEvent` method.
+constexpr char kOwnerKeyHistogramName[] = "ChromeOS.Ownership.OwnerKeyUmaEvent";
+
+// Events related to owner key loading, generation and usage that are sent to
+// UMA. Produced from the events above by combining with a success/failure
+// status. These values are persisted to histograms. Entries should not be
+// renumbered and numeric values should never be reused.
+enum class OwnerKeyUmaEvent {
+  // DeviceSettingsService was null, owner key was not loaded.
+  kDeviceSettingsServiceIsNull = 0,
+  // Managed device successfully loaded the public owner key.
+  kManagedDeviceSuccess = 1,
+  // Managed device failed to load the public owner key.
+  kManagedDeviceFail = 2,
+  // Consumer owner user successfully loaded both public and private keys.
+  kOwnerHasKeysSuccess = 3,
+  // Consumer owner received both public and private keys, but at least one of
+  // them wasn't actually loaded.
+  kOwnerHasKeysFail = 4,
+  // ChromeOS decided to establish consumer ownership when there was no existing
+  // public key.
+  kEstablishingConsumerOwnershipSuccess = 5,
+  // ChromeOS decided to establish consumer ownership when there was an existing
+  // public key.
+  kEstablishingConsumerOwnershipFail = 6,
+  // ChromeOS decided to re-generate the lost owner key based on the data from
+  // device policies after the public key was found (the private part is what
+  // was lost).
+  kRegeneratingOwnerKeyBasedOnPolicySuccess = 7,
+  // ChromeOS decided to re-generate the lost owner key based on the data from
+  // device policies and the public key was also not found. (Strictly speaking
+  // not a failure, but still an unusual situation).
+  kRegeneratingOwnerKeyBasedOnPolicyFail = 8,
+  // A user was categorized as not an owner based on the data from device
+  // policies, the public key was successfully loaded.
+  kUserNotAnOwnerBasedOnPolicySuccess = 9,
+  // A user was categorized as not an owner based on the data from device
+  // policies, the public key failed to load.
+  kUserNotAnOwnerBasedOnPolicyFail = 10,
+  // ChromeOS decided to re-generate the lost owner key based on the data from
+  // local state and the public key was not present.
+  kRegeneratingOwnerKeyBasedOnLocalStateSuccess = 11,
+  // ChromeOS decided to re-generate the lost owner key based on the data from
+  // local state after the public key was found (in such a case device policies
+  // should be used, relying on local state is unexpected).
+  kRegeneratingOwnerKeyBasedOnLocalStateFail = 12,
+  // A user was categorized as not an owner based on the data from local state,
+  // the public key was successfully loaded.
+  kUserNotAnOwnerBasedOnLocalStateSuccess = 13,
+  // A user was categorized as not an owner based on the data from local state,
+  // the public key failed to load.
+  kUserNotAnOwnerBasedOnLocalStateFail = 14,
+  // ChromeOS assumed that a user is not an owner based on the lack of data, the
+  // public key was successfully loaded.
+  kUnsureUserNotAnOwnerSuccess = 15,
+  // ChromeOS assumed that a user is not an owner based on the lack of data, the
+  // public key failed to load.
+  kUnsureUserNotAnOwnerFail = 16,
+  // New owner key was generated on the first attempt.
+  kOwnerKeyGeneratedSuccess = 17,
+  // New owner key was generated after 1+ failures.
+  kOwnerKeyGeneratedFail = 18,
+  // Failed to generate new owner key, at least the old public key was returned.
+  kFailedToGenerateOwnerKeySuccess = 19,
+  // Failed to generate new owner key, the old public key also failed to load
+  // (or was not present).
+  kFailedToGenerateOwnerKeyFail = 20,
+  // Successfully started signing policies.
+  kStartSigningPolicySuccess = 21,
+  // Failed to start signing policies.
+  kStartSigningPolicyFail = 22,
+  // Successfully signed policies.
+  kSignedPolicySuccess = 23,
+  // Failed to sign policies.
+  kSignedPolicyFail = 24,
+  // Successfully stored policies.
+  kStoredPolicySuccess = 25,
+  // Failed to store policies.
+  kStoredPolicyFail = 26,
+  kMaxValue = kStoredPolicyFail,
+};
+
+}  // namespace ash
+
+#endif  // CHROME_BROWSER_ASH_OWNERSHIP_OWNERSHIP_HISTOGRAMS_H_
diff --git a/chrome/browser/ash/web_applications/help_app/help_app_untrusted_ui_config.cc b/chrome/browser/ash/web_applications/help_app/help_app_untrusted_ui_config.cc
index 4a0d388..ca848171 100644
--- a/chrome/browser/ash/web_applications/help_app/help_app_untrusted_ui_config.cc
+++ b/chrome/browser/ash/web_applications/help_app/help_app_untrusted_ui_config.cc
@@ -60,18 +60,18 @@
   source->AddString("chromeOSVersion", base::SysInfo::OperatingSystemVersion());
   source->AddString("chromeVersion", chrome::kChromeVersion);
   source->AddInteger("channel", static_cast<int>(chrome::GetChannel()));
-  std::string customization_id;
-  std::string hwid;
   chromeos::system::StatisticsProvider* provider =
       chromeos::system::StatisticsProvider::GetInstance();
   // MachineStatistics may not exist for browser tests, but it is fine for these
   // to be empty strings.
-  provider->GetMachineStatistic(chromeos::system::kCustomizationIdKey,
-                                &customization_id);
-  provider->GetMachineStatistic(chromeos::system::kHardwareClassKey, &hwid);
-  source->AddString("customizationId", customization_id);
+  const absl::optional<base::StringPiece> customization_id =
+      provider->GetMachineStatistic(chromeos::system::kCustomizationIdKey);
+  const absl::optional<base::StringPiece> hwid =
+      provider->GetMachineStatistic(chromeos::system::kHardwareClassKey);
+  source->AddString("customizationId",
+                    std::string(customization_id.value_or("")));
   source->AddString("deviceName", ui::GetChromeOSDeviceName());
-  source->AddString("hwid", hwid);
+  source->AddString("hwid", std::string(hwid.value_or("")));
   source->AddString("deviceHelpContentId",
                     base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
                         "device-help-content-id"));
diff --git a/chrome/browser/ash/web_applications/print_management_web_app_info.cc b/chrome/browser/ash/web_applications/print_management_web_app_info.cc
index 6cad792..7859817 100644
--- a/chrome/browser/ash/web_applications/print_management_web_app_info.cc
+++ b/chrome/browser/ash/web_applications/print_management_web_app_info.cc
@@ -52,7 +52,7 @@
 }
 
 bool PrintManagementSystemAppDelegate::ShouldShowInLauncher() const {
-  return false;
+  return true;
 }
 gfx::Size PrintManagementSystemAppDelegate::GetMinimumWindowSize() const {
   return {600, 320};
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index 8ebf54b5..17b0776 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -310,6 +310,7 @@
         <include name="IDR_PARENT_ACCESS_APP_JS" file="${root_gen_dir}\chrome\browser\resources\chromeos\parent_access\parent_access_app.js" use_base_dir="false" type="BINDATA" />
         <include name="IDR_PARENT_ACCESS_UI_JS" file="${root_gen_dir}\chrome\browser\resources\chromeos\parent_access\parent_access_ui.js" use_base_dir="false" type="BINDATA" />
         <include name="IDR_PARENT_ACCESS_AFTER_JS" file="${root_gen_dir}\chrome\browser\resources\chromeos\parent_access\parent_access_after.js" use_base_dir="false" type="BINDATA" />
+        <include name="IDR_PARENT_ACCESS_ERROR_JS" file="${root_gen_dir}\chrome\browser\resources\chromeos\parent_access\parent_access_error.js" use_base_dir="false" type="BINDATA" />
         <include name="IDR_PARENT_ACCESS_WEBVIEW_MANAGER_JS" file="resources\chromeos\parent_access\webview_manager.js" type="BINDATA" />
         <include name="IDR_LOCAL_WEB_APPROVALS_AFTER_JS" file="${root_gen_dir}\chrome\browser\resources\chromeos\parent_access\flows\local_web_approvals_after.js" use_base_dir="false" type="BINDATA" />
         <include name="IDR_PARENT_ACCESS_CONTROLLER_JS" file="resources\chromeos\parent_access\parent_access_controller.js" type="BINDATA" />
diff --git a/chrome/browser/chrome_navigation_browsertest.cc b/chrome/browser/chrome_navigation_browsertest.cc
index 3081e70..babc1f61 100644
--- a/chrome/browser/chrome_navigation_browsertest.cc
+++ b/chrome/browser/chrome_navigation_browsertest.cc
@@ -1181,6 +1181,17 @@
     ChromeNavigationBrowserTest::SetUpOnMainThread();
   }
 
+  void OpenPopup(content::WebContents* creating_contents, GURL destination) {
+    content::TestNavigationObserver popup_waiter(destination);
+    popup_waiter.StartWatchingNewWebContents();
+    EXPECT_TRUE(
+        content::EvalJs(creating_contents,
+                        content::JsReplace("!!window.open($1);", destination))
+            .ExtractBool());
+    popup_waiter.WaitForNavigationFinished();
+    EXPECT_TRUE(popup_waiter.last_navigation_succeeded());
+  }
+
  private:
   net::EmbeddedTestServer https_server_;
 };
@@ -1200,16 +1211,7 @@
   // Open a popup for chrome.google.com and ensure that it's isolated in a
   // different SiteInstance and process from the previous google.com page.
   const GURL webstore_origin_url("https://chrome.google.com/title1.html");
-  {
-    content::TestNavigationObserver popup_waiter(webstore_origin_url);
-    popup_waiter.StartWatchingNewWebContents();
-    EXPECT_TRUE(content::EvalJs(initial_web_contents,
-                                content::JsReplace("!!window.open($1);",
-                                                   webstore_origin_url))
-                    .ExtractBool());
-    popup_waiter.WaitForNavigationFinished();
-    EXPECT_TRUE(popup_waiter.last_navigation_succeeded());
-  }
+  OpenPopup(initial_web_contents, webstore_origin_url);
   EXPECT_EQ(2, browser()->tab_strip_model()->count());
   content::WebContents* popup =
       browser()->tab_strip_model()->GetActiveWebContents();
@@ -1227,16 +1229,8 @@
   // Now navigate the popup to the full web store URL. This will again cause it
   // to be isolated in a different SiteInstance and process from the previous
   // pages, but also now cause a BrowsingInstance swap.
-  {
-    const GURL webstore_url(
-        "https://chrome.google.com/webstore/mock_store.html");
-    content::TestNavigationObserver navigation_waiter(popup);
-    EXPECT_TRUE(
-        ExecuteScript(popup, "location = '" + webstore_url.spec() + "';"));
-    navigation_waiter.WaitForNavigationFinished();
-    EXPECT_TRUE(navigation_waiter.last_navigation_succeeded());
-    EXPECT_EQ(webstore_url, popup->GetLastCommittedURL());
-  }
+  const GURL webstore_url("https://chrome.google.com/webstore/mock_store.html");
+  EXPECT_TRUE(content::NavigateToURLFromRenderer(popup, webstore_url));
   scoped_refptr<content::SiteInstance> webstore_instance(
       popup->GetPrimaryMainFrame()->GetSiteInstance());
   EXPECT_NE(webstore_instance, popup_instance);
@@ -1246,6 +1240,14 @@
   EXPECT_FALSE(webstore_instance->IsRelatedSiteInstance(popup_instance.get()));
   EXPECT_FALSE(
       webstore_instance->IsRelatedSiteInstance(initial_instance.get()));
+
+  // Finally navigate the popup back away from the web store URL. This will lead
+  // to another new process and BrowsingInstance swap.
+  EXPECT_TRUE(content::NavigateToURLFromRenderer(popup, first_url));
+  scoped_refptr<content::SiteInstance> final_instance(
+      popup->GetPrimaryMainFrame()->GetSiteInstance());
+  EXPECT_NE(final_instance->GetProcess(), webstore_instance->GetProcess());
+  EXPECT_FALSE(final_instance->IsRelatedSiteInstance(webstore_instance.get()));
 }
 
 // Make sure that the new Chrome Web Store URL used in production
@@ -1266,16 +1268,7 @@
   // a BrowsingInstance swap at this point.
   const GURL webstore_origin_url(
       "https://chromewebstore.google.com/title1.html");
-  {
-    content::TestNavigationObserver popup_waiter(webstore_origin_url);
-    popup_waiter.StartWatchingNewWebContents();
-    EXPECT_TRUE(content::EvalJs(initial_web_contents,
-                                content::JsReplace("!!window.open($1);",
-                                                   webstore_origin_url))
-                    .ExtractBool());
-    popup_waiter.WaitForNavigationFinished();
-    EXPECT_TRUE(popup_waiter.last_navigation_succeeded());
-  }
+  OpenPopup(initial_web_contents, webstore_origin_url);
   EXPECT_EQ(2, browser()->tab_strip_model()->count());
   content::WebContents* popup =
       browser()->tab_strip_model()->GetActiveWebContents();
@@ -1287,6 +1280,14 @@
   EXPECT_NE(initial_instance, popup_instance);
   EXPECT_NE(initial_instance->GetProcess(), popup_instance->GetProcess());
   EXPECT_FALSE(initial_instance->IsRelatedSiteInstance(popup_instance.get()));
+
+  // Navigating the popup away from the webstore should cause another new
+  // process and BrowsingInstance swap.
+  EXPECT_TRUE(content::NavigateToURLFromRenderer(popup, first_url));
+  scoped_refptr<content::SiteInstance> final_instance(
+      popup->GetPrimaryMainFrame()->GetSiteInstance());
+  EXPECT_NE(final_instance->GetProcess(), popup_instance->GetProcess());
+  EXPECT_FALSE(final_instance->IsRelatedSiteInstance(popup_instance.get()));
 }
 
 class WebstoreOverrideIsolationBrowserTest
@@ -1316,16 +1317,7 @@
   // Open a popup for chrome.foo.com and ensure that it's isolated in a
   // different SiteInstance and process from the rest of foo.com.
   const GURL webstore_origin_url("https://chrome.foo.com/title1.html");
-  {
-    content::TestNavigationObserver popup_waiter(webstore_origin_url);
-    popup_waiter.StartWatchingNewWebContents();
-    EXPECT_TRUE(content::EvalJs(initial_web_contents,
-                                content::JsReplace("!!window.open($1);",
-                                                   webstore_origin_url))
-                    .ExtractBool());
-    popup_waiter.WaitForNavigationFinished();
-    EXPECT_TRUE(popup_waiter.last_navigation_succeeded());
-  }
+  OpenPopup(initial_web_contents, webstore_origin_url);
   EXPECT_EQ(2, browser()->tab_strip_model()->count());
   content::WebContents* popup =
       browser()->tab_strip_model()->GetActiveWebContents();
@@ -1343,15 +1335,8 @@
 
   // Now navigate the popup to the full web store URL and confirm that this
   // causes a BrowsingInstance swap.
-  {
-    const GURL webstore_url("https://chrome.foo.com/frame_tree/simple.htm");
-    content::TestNavigationObserver navigation_waiter(popup);
-    EXPECT_TRUE(
-        ExecuteScript(popup, "location = '" + webstore_url.spec() + "';"));
-    navigation_waiter.WaitForNavigationFinished();
-    EXPECT_TRUE(navigation_waiter.last_navigation_succeeded());
-    EXPECT_EQ(webstore_url, popup->GetLastCommittedURL());
-  }
+  const GURL webstore_url("https://chrome.foo.com/frame_tree/simple.htm");
+  EXPECT_TRUE(content::NavigateToURLFromRenderer(popup, webstore_url));
   scoped_refptr<content::SiteInstance> webstore_instance(
       popup->GetPrimaryMainFrame()->GetSiteInstance());
   EXPECT_NE(webstore_instance, popup_instance);
@@ -1359,6 +1344,14 @@
   EXPECT_FALSE(webstore_instance->IsRelatedSiteInstance(popup_instance.get()));
   EXPECT_FALSE(
       webstore_instance->IsRelatedSiteInstance(initial_instance.get()));
+
+  // Finally navigate the popup back away from the web store URL. This will lead
+  // to another new process and BrowsingInstance swap.
+  EXPECT_TRUE(content::NavigateToURLFromRenderer(popup, first_url));
+  scoped_refptr<content::SiteInstance> final_instance(
+      popup->GetPrimaryMainFrame()->GetSiteInstance());
+  EXPECT_NE(final_instance->GetProcess(), webstore_instance->GetProcess());
+  EXPECT_FALSE(final_instance->IsRelatedSiteInstance(webstore_instance.get()));
 }
 
 // Check that it's possible to navigate to a chrome scheme URL from a crashed
diff --git a/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorCoordinator.java b/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorCoordinator.java
index 3f9667a..a90ea4d 100644
--- a/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorCoordinator.java
+++ b/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorCoordinator.java
@@ -47,8 +47,6 @@
  */
 public class CreatorCoordinator
         implements FeedAutoplaySettingsDelegate, FeedContentFirstLoadWatcher {
-    private static final String NATIVE_CONTENT_ID = "0";
-
     private final ViewGroup mViewGroup;
     private CreatorMediator mMediator;
     private Activity mActivity;
diff --git a/chrome/browser/download/bubble/download_bubble_controller.cc b/chrome/browser/download/bubble/download_bubble_controller.cc
index 5cd8aad..c13525e9 100644
--- a/chrome/browser/download/bubble/download_bubble_controller.cc
+++ b/chrome/browser/download/bubble/download_bubble_controller.cc
@@ -16,6 +16,7 @@
 #include "chrome/browser/download/download_core_service.h"
 #include "chrome/browser/download/download_core_service_factory.h"
 #include "chrome/browser/download/download_item_model.h"
+#include "chrome/browser/download/download_item_warning_data.h"
 #include "chrome/browser/download/offline_item_model_manager.h"
 #include "chrome/browser/download/offline_item_model_manager_factory.h"
 #include "chrome/browser/download/offline_item_utils.h"
@@ -380,11 +381,22 @@
 
 void DownloadBubbleUIController::ProcessDownloadButtonPress(
     DownloadUIModel* model,
-    DownloadCommands::Command command) {
+    DownloadCommands::Command command,
+    bool is_main_view) {
   DownloadCommands commands(model->GetWeakPtr());
   base::UmaHistogramExactLinear("Download.Bubble.ProcessedCommand", command,
                                 DownloadCommands::MAX + 1);
   switch (command) {
+    case DownloadCommands::KEEP:
+    case DownloadCommands::DISCARD:
+      DownloadItemWarningData::AddWarningActionEvent(
+          model->GetDownloadItem(),
+          is_main_view ? DownloadItemWarningData::BUBBLE_MAINPAGE
+                       : DownloadItemWarningData::BUBBLE_SUBPAGE,
+          command == DownloadCommands::KEEP ? DownloadItemWarningData::PROCEED
+                                            : DownloadItemWarningData::DISCARD);
+      commands.ExecuteCommand(command);
+      break;
     case DownloadCommands::REVIEW:
       model->ReviewScanningVerdict(
           browser_->tab_strip_model()->GetActiveWebContents());
@@ -402,8 +414,6 @@
     case DownloadCommands::OPEN_WHEN_COMPLETE:
     case DownloadCommands::SHOW_IN_FOLDER:
     case DownloadCommands::ALWAYS_OPEN_TYPE:
-    case DownloadCommands::KEEP:
-    case DownloadCommands::DISCARD:
       commands.ExecuteCommand(command);
       break;
     default:
diff --git a/chrome/browser/download/bubble/download_bubble_controller.h b/chrome/browser/download/bubble/download_bubble_controller.h
index 7d3875e..63d805d 100644
--- a/chrome/browser/download/bubble/download_bubble_controller.h
+++ b/chrome/browser/download/bubble/download_bubble_controller.h
@@ -66,7 +66,8 @@
 
   // Process button press on the bubble.
   void ProcessDownloadButtonPress(DownloadUIModel* model,
-                                  DownloadCommands::Command command);
+                                  DownloadCommands::Command command,
+                                  bool is_main_view);
 
   // Notify when a new download is ready to be shown on UI, and if the window
   // this controller belongs to should show the partial view.
diff --git a/chrome/browser/download/download_item_warning_data.cc b/chrome/browser/download/download_item_warning_data.cc
new file mode 100644
index 0000000..4b76bf5
--- /dev/null
+++ b/chrome/browser/download/download_item_warning_data.cc
@@ -0,0 +1,141 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/download/download_item_warning_data.h"
+
+#include "components/download/public/common/download_item.h"
+
+using download::DownloadItem;
+using WarningSurface = DownloadItemWarningData::WarningSurface;
+using WarningAction = DownloadItemWarningData::WarningAction;
+using WarningActionEvent = DownloadItemWarningData::WarningActionEvent;
+using ClientSafeBrowsingReportRequest =
+    safe_browsing::ClientSafeBrowsingReportRequest;
+
+namespace {
+constexpr int kWarningActionEventMaxLength = 20;
+}
+
+// static
+const char DownloadItemWarningData::kKey[] = "DownloadItemWarningData key";
+
+// static
+std::vector<WarningActionEvent> DownloadItemWarningData::GetWarningActionEvents(
+    const DownloadItem* download) {
+  DownloadItemWarningData* data =
+      static_cast<DownloadItemWarningData*>(download->GetUserData(kKey));
+  if (!data || data->warning_first_shown_time_.is_null()) {
+    return {};
+  }
+  return data->action_events_;
+}
+
+// static
+void DownloadItemWarningData::AddWarningActionEvent(DownloadItem* download,
+                                                    WarningSurface surface,
+                                                    WarningAction action) {
+  // TODO(crbug.com/1363368): Add a histogram to log the result before return.
+  if (!download) {
+    return;
+  }
+  DownloadItemWarningData* data =
+      static_cast<DownloadItemWarningData*>(download->GetUserData(kKey));
+  if (!data) {
+    data = new DownloadItemWarningData();
+    download->SetUserData(kKey, base::WrapUnique(data));
+  }
+  if (action == WarningAction::SHOWN) {
+    if (data->warning_first_shown_time_.is_null()) {
+      data->warning_first_shown_time_ = base::Time::Now();
+    }
+    return;
+  }
+  if (data->warning_first_shown_time_.is_null()) {
+    return;
+  }
+  if (data->action_events_.size() >= kWarningActionEventMaxLength) {
+    return;
+  }
+  int64_t action_latency =
+      (base::Time::Now() - data->warning_first_shown_time_).InMilliseconds();
+  bool is_terminal_action =
+      (action == PROCEED || action == DISCARD) ? true : false;
+  DCHECK_NE(WarningAction::SHOWN, action);
+  data->action_events_.emplace_back(surface, action, action_latency,
+                                    is_terminal_action);
+}
+
+// static
+ClientSafeBrowsingReportRequest::DownloadWarningAction
+DownloadItemWarningData::ConstructCsbrrDownloadWarningAction(
+    const WarningActionEvent& event) {
+  ClientSafeBrowsingReportRequest::DownloadWarningAction action;
+  switch (event.surface) {
+    case DownloadItemWarningData::BUBBLE_MAINPAGE:
+      action.set_surface(ClientSafeBrowsingReportRequest::
+                             DownloadWarningAction::BUBBLE_MAINPAGE);
+      break;
+    case DownloadItemWarningData::BUBBLE_SUBPAGE:
+      action.set_surface(ClientSafeBrowsingReportRequest::
+                             DownloadWarningAction::BUBBLE_SUBPAGE);
+      break;
+    case DownloadItemWarningData::DOWNLOADS_PAGE:
+      action.set_surface(ClientSafeBrowsingReportRequest::
+                             DownloadWarningAction::DOWNLOADS_PAGE);
+      break;
+    case DownloadItemWarningData::DOWNLOAD_PROMPT:
+      action.set_surface(ClientSafeBrowsingReportRequest::
+                             DownloadWarningAction::DOWNLOAD_PROMPT);
+      break;
+  }
+  switch (event.action) {
+    case DownloadItemWarningData::PROCEED:
+      action.set_action(
+          ClientSafeBrowsingReportRequest::DownloadWarningAction::PROCEED);
+      break;
+    case DownloadItemWarningData::DISCARD:
+      action.set_action(
+          ClientSafeBrowsingReportRequest::DownloadWarningAction::DISCARD);
+      break;
+    case DownloadItemWarningData::KEEP:
+      action.set_action(
+          ClientSafeBrowsingReportRequest::DownloadWarningAction::KEEP);
+      break;
+    case DownloadItemWarningData::CLOSE:
+      action.set_action(
+          ClientSafeBrowsingReportRequest::DownloadWarningAction::CLOSE);
+      break;
+    case DownloadItemWarningData::CANCEL:
+      action.set_action(
+          ClientSafeBrowsingReportRequest::DownloadWarningAction::CANCEL);
+      break;
+    case DownloadItemWarningData::DISMISS:
+      action.set_action(
+          ClientSafeBrowsingReportRequest::DownloadWarningAction::DISMISS);
+      break;
+    case DownloadItemWarningData::BACK:
+      action.set_action(
+          ClientSafeBrowsingReportRequest::DownloadWarningAction::BACK);
+      break;
+    case DownloadItemWarningData::SHOWN:
+      NOTREACHED();
+      break;
+  }
+  action.set_is_terminal_action(event.is_terminal_action);
+  action.set_interval_msec(event.action_latency_msec);
+  return action;
+}
+
+DownloadItemWarningData::DownloadItemWarningData() = default;
+
+DownloadItemWarningData::~DownloadItemWarningData() = default;
+
+WarningActionEvent::WarningActionEvent(WarningSurface surface,
+                                       WarningAction action,
+                                       int64_t action_latency_msec,
+                                       bool is_terminal_action)
+    : surface(surface),
+      action(action),
+      action_latency_msec(action_latency_msec),
+      is_terminal_action(is_terminal_action) {}
diff --git a/chrome/browser/download/download_item_warning_data.h b/chrome/browser/download/download_item_warning_data.h
new file mode 100644
index 0000000..9e06456
--- /dev/null
+++ b/chrome/browser/download/download_item_warning_data.h
@@ -0,0 +1,106 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_DOWNLOAD_DOWNLOAD_ITEM_WARNING_DATA_H_
+#define CHROME_BROWSER_DOWNLOAD_DOWNLOAD_ITEM_WARNING_DATA_H_
+
+#include <vector>
+
+#include "base/supports_user_data.h"
+#include "base/time/time.h"
+#include "components/safe_browsing/core/common/proto/csd.pb.h"
+
+namespace download {
+class DownloadItem;
+}
+
+// Per DownloadItem data for storing warning events on download warnings. The
+// data is only set if a warning is shown. These events are added to Safe
+// Browsing reports.
+class DownloadItemWarningData : public base::SupportsUserData::Data {
+ public:
+  // The surface that the warning is shown. See
+  // go/chrome-download-warning-surfaces for details.
+  enum WarningSurface {
+    // Applicable actions: DISCARD
+    BUBBLE_MAINPAGE = 1,
+    // Applicable actions: PROCEED, DISCARD, DISMISS, CLOSE, BACK
+    BUBBLE_SUBPAGE = 2,
+    // Applicable actions: DISCARD, KEEP
+    DOWNLOADS_PAGE = 3,
+    // Applicable actions: PROCEED, CANCEL, CLOSE
+    DOWNLOAD_PROMPT = 4,
+  };
+
+  // Users action on the warning surface.
+  enum WarningAction {
+    // The warning is shown. This is a special action that may not be triggered
+    // by user. We will use this action as the anchor to track the latency of
+    // other actions.
+    SHOWN = 0,
+    // The user clicks proceed, which means the user decides to bypass the
+    // warning.
+    PROCEED = 1,
+    // The user clicks discard, which means the user decides to obey the
+    // warning and the dangerous download is deleted from disk.
+    DISCARD = 2,
+    // The user has clicked the keep button on the surface.
+    KEEP = 3,
+    // The user has clicked the close button on the surface.
+    CLOSE = 4,
+    // The user clicks cancel on the download prompt.
+    CANCEL = 5,
+    // The user has dismissed the bubble by clicking anywhere outside
+    // the bubble.
+    DISMISS = 6,
+    // The user has clicked the back button on the bubble subpage to go back
+    // to the bubble main page.
+    BACK = 7
+  };
+
+  struct WarningActionEvent {
+    WarningSurface surface;
+    WarningAction action;
+    // The latency between when the warning is shown for the first time and when
+    // this event has happened.
+    int64_t action_latency_msec;
+    // A terminal action means that the warning disappears after this event,
+    // the download is either deleted or saved.
+    bool is_terminal_action = false;
+
+    WarningActionEvent(WarningSurface surface,
+                       WarningAction action,
+                       int64_t action_latency_msec,
+                       bool is_terminal_action);
+  };
+
+  ~DownloadItemWarningData() override;
+
+  // Gets all warning actions associated with this `download`. Returns an empty
+  // vector if there's no warning data or there is no warning shown for this
+  // `download`.
+  static std::vector<WarningActionEvent> GetWarningActionEvents(
+      const download::DownloadItem* download);
+
+  // Adds an `action` triggered on `surface` for `download`. It may not be added
+  // if `download` is null or the length of events associated with this
+  // `download` exceeds the limit.
+  static void AddWarningActionEvent(download::DownloadItem* download,
+                                    WarningSurface surface,
+                                    WarningAction action);
+
+  // Converts an `event` to the Safe Browsing report proto format.
+  static safe_browsing::ClientSafeBrowsingReportRequest::DownloadWarningAction
+  ConstructCsbrrDownloadWarningAction(const WarningActionEvent& event);
+
+ private:
+  DownloadItemWarningData();
+
+  static const char kKey[];
+
+  base::Time warning_first_shown_time_;
+  std::vector<WarningActionEvent> action_events_;
+};
+
+#endif  // CHROME_BROWSER_DOWNLOAD_DOWNLOAD_ITEM_WARNING_DATA_H_
diff --git a/chrome/browser/download/download_item_warning_data_unittest.cc b/chrome/browser/download/download_item_warning_data_unittest.cc
new file mode 100644
index 0000000..ebd0af6
--- /dev/null
+++ b/chrome/browser/download/download_item_warning_data_unittest.cc
@@ -0,0 +1,141 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/download/download_item_warning_data.h"
+
+#include <vector>
+
+#include "base/test/task_environment.h"
+#include "components/download/public/common/mock_download_item.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using WarningSurface = DownloadItemWarningData::WarningSurface;
+using WarningAction = DownloadItemWarningData::WarningAction;
+using WarningActionEvent = DownloadItemWarningData::WarningActionEvent;
+
+class DownloadItemWarningDataTest : public testing::Test {
+ public:
+  DownloadItemWarningDataTest() = default;
+
+ protected:
+  void FastForwardAndAddEvent(base::TimeDelta time_delta,
+                              WarningSurface surface,
+                              WarningAction action) {
+    task_environment_.FastForwardBy(time_delta);
+    DownloadItemWarningData::AddWarningActionEvent(&download_, surface, action);
+  }
+
+  std::vector<WarningActionEvent> GetEvents() {
+    return DownloadItemWarningData::GetWarningActionEvents(&download_);
+  }
+
+  bool VerifyEventValue(const WarningActionEvent& actual_event,
+                        WarningSurface expected_surface,
+                        WarningAction expected_action,
+                        int64_t expected_latency,
+                        bool expected_is_terminal_action) {
+    bool success = true;
+    if (actual_event.surface != expected_surface) {
+      success = false;
+      ADD_FAILURE() << "Warning action event should have surface "
+                    << expected_surface << ", but found "
+                    << actual_event.surface;
+    }
+    if (actual_event.action != expected_action) {
+      success = false;
+      ADD_FAILURE() << "Warning action event should have action "
+                    << expected_action << ", but found " << actual_event.action;
+    }
+    if (actual_event.action_latency_msec != expected_latency) {
+      success = false;
+      ADD_FAILURE() << "Warning action event should have latency "
+                    << expected_latency << ", but found "
+                    << actual_event.action_latency_msec;
+    }
+    if (actual_event.is_terminal_action != expected_is_terminal_action) {
+      success = false;
+      ADD_FAILURE() << "Warning action event should have is_terminal_action "
+                    << expected_is_terminal_action << ", but found "
+                    << actual_event.is_terminal_action;
+    }
+    return success;
+  }
+
+  base::test::TaskEnvironment task_environment_{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+  download::MockDownloadItem download_;
+};
+
+TEST_F(DownloadItemWarningDataTest, GetEvents) {
+  FastForwardAndAddEvent(base::Seconds(0), WarningSurface::BUBBLE_MAINPAGE,
+                         WarningAction::SHOWN);
+  FastForwardAndAddEvent(base::Seconds(5), WarningSurface::BUBBLE_SUBPAGE,
+                         WarningAction::CLOSE);
+  FastForwardAndAddEvent(base::Seconds(10), WarningSurface::DOWNLOAD_PROMPT,
+                         WarningAction::CANCEL);
+  FastForwardAndAddEvent(base::Seconds(15), WarningSurface::DOWNLOADS_PAGE,
+                         WarningAction::DISCARD);
+
+  std::vector<WarningActionEvent> events = GetEvents();
+
+  ASSERT_EQ(3u, events.size());
+  EXPECT_TRUE(VerifyEventValue(events[0], WarningSurface::BUBBLE_SUBPAGE,
+                               WarningAction::CLOSE, /*expected_latency=*/5000,
+                               /*expected_is_terminal_action=*/false));
+  EXPECT_TRUE(VerifyEventValue(
+      events[1], WarningSurface::DOWNLOAD_PROMPT, WarningAction::CANCEL,
+      /*expected_latency=*/15000, /*expected_is_terminal_action=*/false));
+  EXPECT_TRUE(VerifyEventValue(
+      events[2], WarningSurface::DOWNLOADS_PAGE, WarningAction::DISCARD,
+      /*expected_latency=*/30000, /*expected_is_terminal_action=*/true));
+}
+
+TEST_F(DownloadItemWarningDataTest, GetEvents_WarningShownNotLogged) {
+  FastForwardAndAddEvent(base::Seconds(5), WarningSurface::BUBBLE_SUBPAGE,
+                         WarningAction::PROCEED);
+
+  std::vector<WarningActionEvent> events = GetEvents();
+
+  // The events are not logged because the SHOWN event is not logged, so there
+  // is no anchor event to calculate the latency.
+  EXPECT_EQ(0u, events.size());
+}
+
+TEST_F(DownloadItemWarningDataTest, GetEvents_MultipleWarningShownLogged) {
+  FastForwardAndAddEvent(base::Seconds(0), WarningSurface::BUBBLE_MAINPAGE,
+                         WarningAction::SHOWN);
+  FastForwardAndAddEvent(base::Seconds(5), WarningSurface::BUBBLE_MAINPAGE,
+                         WarningAction::SHOWN);
+  FastForwardAndAddEvent(base::Seconds(5), WarningSurface::BUBBLE_SUBPAGE,
+                         WarningAction::DISCARD);
+
+  std::vector<WarningActionEvent> events = GetEvents();
+
+  ASSERT_EQ(1u, events.size());
+  // The expected latency should be 10000 instead of 5000 because the latency
+  // should be anchored to the first shown event.
+  EXPECT_TRUE(VerifyEventValue(events[0], WarningSurface::BUBBLE_SUBPAGE,
+                               WarningAction::DISCARD,
+                               /*expected_latency=*/10000,
+                               /*expected_is_terminal_action=*/true));
+}
+
+TEST_F(DownloadItemWarningDataTest, GetEvents_ExceedEventMaxLength) {
+  FastForwardAndAddEvent(base::Seconds(0), WarningSurface::BUBBLE_MAINPAGE,
+                         WarningAction::SHOWN);
+  for (int i = 0; i <= 30; i++) {
+    FastForwardAndAddEvent(base::Seconds(5), WarningSurface::BUBBLE_SUBPAGE,
+                           WarningAction::CLOSE);
+  }
+
+  std::vector<WarningActionEvent> events = GetEvents();
+
+  ASSERT_EQ(20u, events.size());
+  // The newer events should be ignored, so the expected latency of the last
+  // event should be the 20th event.
+  EXPECT_TRUE(VerifyEventValue(events[19], WarningSurface::BUBBLE_SUBPAGE,
+                               WarningAction::CLOSE,
+                               /*expected_latency=*/20 * 5000,
+                               /*expected_is_terminal_action=*/false));
+}
diff --git a/chrome/browser/extensions/api/developer_private/developer_private_api_unittest.cc b/chrome/browser/extensions/api/developer_private/developer_private_api_unittest.cc
index 17953dc..733f59b 100644
--- a/chrome/browser/extensions/api/developer_private/developer_private_api_unittest.cc
+++ b/chrome/browser/extensions/api/developer_private/developer_private_api_unittest.cc
@@ -238,7 +238,7 @@
   // A wrapper around extension_function_test_utils::RunFunction that runs with
   // the associated browser, no flags, and can take stack-allocated arguments.
   bool RunFunction(const scoped_refptr<ExtensionFunction>& function,
-                   const base::ListValue& args);
+                   const base::Value::List& args);
 
   // Loads an unpacked extension that is backed by a real directory, allowing
   // it to be reloaded.
@@ -255,7 +255,7 @@
                                 bool expected_default_value);
 
   testing::AssertionResult TestPackExtensionFunction(
-      const base::ListValue& args,
+      const base::Value::List& args,
       api::developer_private::PackStatus expected_status,
       int expected_flags);
 
@@ -294,11 +294,9 @@
 
 bool DeveloperPrivateApiUnitTest::RunFunction(
     const scoped_refptr<ExtensionFunction>& function,
-    const base::ListValue& args) {
+    const base::Value::List& args) {
   return extension_function_test_utils::RunFunction(
-      function.get(),
-      base::ListValue::From(base::Value::ToUniquePtrValue(args.Clone())),
-      browser(), api_test_utils::NONE);
+      function.get(), args.Clone(), browser(), api_test_utils::NONE);
 }
 
 const Extension* DeveloperPrivateApiUnitTest::LoadUnpackedExtension() {
@@ -353,8 +351,8 @@
     parameters.Set("extensionId", extension_id);
     parameters.Set(key, true);
 
-    base::ListValue args;
-    args.Append(base::Value(std::move(parameters)));
+    base::Value::List args;
+    args.Append(std::move(parameters));
     auto function = base::MakeRefCounted<
         api::DeveloperPrivateUpdateExtensionConfigurationFunction>();
     EXPECT_FALSE(RunFunction(function, args)) << key;
@@ -378,8 +376,8 @@
     parameters.Set("extensionId", extension_id);
     parameters.Set(key, false);
 
-    base::ListValue args;
-    args.Append(base::Value(std::move(parameters)));
+    base::Value::List args;
+    args.Append(std::move(parameters));
 
     ExtensionFunction::ScopedUserGestureForTests scoped_user_gesture;
     auto function = base::MakeRefCounted<
@@ -390,7 +388,7 @@
 }
 
 testing::AssertionResult DeveloperPrivateApiUnitTest::TestPackExtensionFunction(
-    const base::ListValue& args,
+    const base::Value::List& args,
     api::developer_private::PackStatus expected_status,
     int expected_flags) {
   auto function =
@@ -423,18 +421,18 @@
     bool dev_mode) {
   auto function = base::MakeRefCounted<
       api::DeveloperPrivateUpdateProfileConfigurationFunction>();
-  std::unique_ptr<base::ListValue> args =
+  base::Value::List args =
       ListBuilder()
           .Append(DictionaryBuilder().Set("inDeveloperMode", dev_mode).Build())
-          .Build();
-  EXPECT_TRUE(RunFunction(function, *args)) << function->GetError();
+          .BuildList();
+  EXPECT_TRUE(RunFunction(function, args)) << function->GetError();
 }
 
 void DeveloperPrivateApiUnitTest::GetProfileConfiguration(
     std::unique_ptr<api::developer_private::ProfileInfo>* profile_info) {
   auto function = base::MakeRefCounted<
       api::DeveloperPrivateGetProfileConfigurationFunction>();
-  base::ListValue args;
+  base::Value::List args;
   EXPECT_TRUE(RunFunction(function, args)) << function->GetError();
 
   ASSERT_TRUE(function->GetResultList());
@@ -526,7 +524,7 @@
   const Extension* extension = LoadUnpackedExtension();
   std::string extension_id = extension->id();
   auto function = base::MakeRefCounted<api::DeveloperPrivateReloadFunction>();
-  base::ListValue reload_args;
+  base::Value::List reload_args;
   reload_args.Append(extension_id);
 
   TestExtensionRegistryObserver registry_observer(registry());
@@ -560,7 +558,7 @@
       << "pem should not exist before the test is run!";
 
   // First, test a directory that should pack properly.
-  base::ListValue pack_args;
+  base::Value::List pack_args;
   pack_args.Append(temp_root_path.AsUTF8Unsafe());
   EXPECT_TRUE(TestPackExtensionFunction(
       pack_args, api::developer_private::PACK_STATUS_SUCCESS, 0));
@@ -587,8 +585,7 @@
   // get an error.
   base::DeleteFile(crx_path);
   // Remove the pem key and flags arguments.
-  pack_args.GetList().erase(pack_args.GetList().begin() + 1,
-                            pack_args.GetList().begin() + 3);
+  pack_args.erase(pack_args.begin() + 1, pack_args.begin() + 3);
   EXPECT_TRUE(TestPackExtensionFunction(
       pack_args, api::developer_private::PACK_STATUS_ERROR, 0));
 }
@@ -603,7 +600,7 @@
   api::EntryPicker::SkipPickerAndAlwaysSelectPathForTest(&expected_dir_path);
 
   // Try selecting a directory.
-  base::ListValue choose_args;
+  base::Value::List choose_args;
   choose_args.Append("FOLDER");
   choose_args.Append("LOAD");
   auto function =
@@ -622,7 +619,7 @@
   base::FilePath expected_file_path =
       data_dir().AppendASCII("simple_with_popup.pem");
   api::EntryPicker::SkipPickerAndAlwaysSelectPathForTest(&expected_file_path);
-  choose_args.ClearList();
+  choose_args.clear();
   choose_args.Append("FILE");
   choose_args.Append("PEM");
   function = base::MakeRefCounted<api::DeveloperPrivateChoosePathFunction>();
@@ -657,7 +654,8 @@
       base::MakeRefCounted<api::DeveloperPrivateLoadUnpackedFunction>();
   function->SetRenderFrameHost(web_contents->GetPrimaryMainFrame());
   ExtensionIdSet current_ids = registry()->enabled_extensions().GetIDs();
-  EXPECT_TRUE(RunFunction(function, base::ListValue())) << function->GetError();
+  EXPECT_TRUE(RunFunction(function, base::Value::List()))
+      << function->GetError();
   // We should have added one new extension.
   ExtensionIdSet id_difference = base::STLSetDifference<ExtensionIdSet>(
       registry()->enabled_extensions().GetIDs(), current_ids);
@@ -673,10 +671,10 @@
   // Try loading a bad extension (it should fail, and we should get an error).
   function = base::MakeRefCounted<api::DeveloperPrivateLoadUnpackedFunction>();
   function->SetRenderFrameHost(web_contents->GetPrimaryMainFrame());
-  base::ListValue unpacked_args;
+  base::Value::List unpacked_args;
   base::Value::Dict options;
   options.Set("failQuietly", true);
-  unpacked_args.Append(base::Value(std::move(options)));
+  unpacked_args.Append(std::move(options));
   current_ids = registry()->enabled_extensions().GetIDs();
   EXPECT_FALSE(RunFunction(function, unpacked_args));
   EXPECT_EQ(manifest_errors::kManifestUnreadable, function->GetError());
@@ -1129,8 +1127,8 @@
 
   auto function =
       base::MakeRefCounted<api::DeveloperPrivateRequestFileSourceFunction>();
-  base::ListValue file_source_args;
-  file_source_args.Append(base::Value(properties.ToValue()));
+  base::Value::List file_source_args;
+  file_source_args.Append(properties.ToValue());
   EXPECT_TRUE(RunFunction(function, file_source_args)) << function->GetError();
 
   const base::Value& response_value = (*function->GetResultList())[0];
@@ -1155,7 +1153,7 @@
   {
     auto function =
         base::MakeRefCounted<api::DeveloperPrivateGetExtensionsInfoFunction>();
-    EXPECT_TRUE(RunFunction(function, base::ListValue()))
+    EXPECT_TRUE(RunFunction(function, base::Value::List()))
         << function->GetError();
     const base::Value::List* results = function->GetResultList();
     ASSERT_EQ(1u, results->size());
@@ -1172,7 +1170,7 @@
   {
     auto function =
         base::MakeRefCounted<api::DeveloperPrivateGetItemsInfoFunction>();
-    base::ListValue args;
+    base::Value::List args;
     args.Append(false);
     args.Append(false);
     EXPECT_TRUE(RunFunction(function, args)) << function->GetError();
@@ -1206,16 +1204,15 @@
   // Start by removing all errors for the extension of a given type (manifest).
   std::string type_string = api::developer_private::ToString(
       api::developer_private::ERROR_TYPE_MANIFEST);
-  std::unique_ptr<base::ListValue> args =
-      ListBuilder()
-          .Append(DictionaryBuilder()
-                      .Set("extensionId", extension->id())
-                      .Set("type", type_string)
-                      .Build())
-          .Build();
+  base::Value::List args = ListBuilder()
+                               .Append(DictionaryBuilder()
+                                           .Set("extensionId", extension->id())
+                                           .Set("type", type_string)
+                                           .Build())
+                               .BuildList();
   auto function = base::MakeRefCounted<
       api::DeveloperPrivateDeleteExtensionErrorsFunction>();
-  EXPECT_TRUE(RunFunction(function, *args)) << function->GetError();
+  EXPECT_TRUE(RunFunction(function, args)) << function->GetError();
   // Two errors should remain.
   const ErrorList& error_list =
       error_console->GetErrorsForExtension(extension->id());
@@ -1229,10 +1226,10 @@
                       .Set("extensionId", extension->id())
                       .Set("errorIds", ListBuilder().Append(error_id).Build())
                       .Build())
-          .Build();
+          .BuildList();
   function = base::MakeRefCounted<
       api::DeveloperPrivateDeleteExtensionErrorsFunction>();
-  EXPECT_TRUE(RunFunction(function, *args)) << function->GetError();
+  EXPECT_TRUE(RunFunction(function, args)) << function->GetError();
   // And then there was one.
   EXPECT_EQ(1u, error_console->GetErrorsForExtension(extension->id()).size());
 
@@ -1241,10 +1238,10 @@
       ListBuilder()
           .Append(
               DictionaryBuilder().Set("extensionId", extension->id()).Build())
-          .Build();
+          .BuildList();
   function = base::MakeRefCounted<
       api::DeveloperPrivateDeleteExtensionErrorsFunction>();
-  EXPECT_TRUE(RunFunction(function, *args)) << function->GetError();
+  EXPECT_TRUE(RunFunction(function, args)) << function->GetError();
   // No more errors!
   EXPECT_TRUE(error_console->GetErrorsForExtension(extension->id()).empty());
 }
@@ -1256,11 +1253,10 @@
   const Extension* extension = InstallCRX(extension_path, INSTALL_NEW);
 
   // Attempt to repair the good extension, expect failure.
-  std::unique_ptr<base::ListValue> args =
-      ListBuilder().Append(extension->id()).Build();
+  base::Value::List args = ListBuilder().Append(extension->id()).BuildList();
   auto function =
       base::MakeRefCounted<api::DeveloperPrivateRepairExtensionFunction>();
-  EXPECT_FALSE(RunFunction(function, *args));
+  EXPECT_FALSE(RunFunction(function, args));
   EXPECT_EQ("Cannot repair a healthy extension.", function->GetError());
 }
 
@@ -1287,20 +1283,19 @@
   }
 
   // Attempt to repair the good extension, expect failure.
-  std::unique_ptr<base::ListValue> args =
-      ListBuilder().Append(extension_id).Build();
+  base::Value::List args = ListBuilder().Append(extension_id).BuildList();
   auto function =
       base::MakeRefCounted<api::DeveloperPrivateRepairExtensionFunction>();
-  EXPECT_FALSE(RunFunction(function, *args));
+  EXPECT_FALSE(RunFunction(function, args));
   EXPECT_EQ("Cannot repair a healthy extension.", function->GetError());
 
   // Corrupt the extension , still expect repair failure because this is a
   // policy extension.
   service()->DisableExtension(extension_id, disable_reason::DISABLE_CORRUPTED);
-  args = ListBuilder().Append(extension_id).Build();
+  args = ListBuilder().Append(extension_id).BuildList();
   function =
       base::MakeRefCounted<api::DeveloperPrivateRepairExtensionFunction>();
-  EXPECT_FALSE(RunFunction(function, *args));
+  EXPECT_FALSE(RunFunction(function, args));
   EXPECT_EQ("Cannot repair a policy-installed extension.",
             function->GetError());
 }
@@ -1964,7 +1959,7 @@
   auto function =
       base::MakeRefCounted<api::DeveloperPrivateGetUserSiteSettingsFunction>();
 
-  base::ListValue args;
+  base::Value::List args;
   EXPECT_TRUE(RunFunction(function, args)) << function->GetError();
   ASSERT_TRUE(function->GetResultList());
   ASSERT_EQ(1u, function->GetResultList()->size());
@@ -2090,7 +2085,8 @@
 
   auto function = base::MakeRefCounted<
       api::DeveloperPrivateGetUserAndExtensionSitesByEtldFunction>();
-  EXPECT_TRUE(RunFunction(function, base::ListValue())) << function->GetError();
+  EXPECT_TRUE(RunFunction(function, base::Value::List()))
+      << function->GetError();
   const base::Value::List* results = function->GetResultList();
   ASSERT_EQ(1u, results->size());
 
@@ -2146,7 +2142,8 @@
 
   auto function = base::MakeRefCounted<
       api::DeveloperPrivateGetUserAndExtensionSitesByEtldFunction>();
-  EXPECT_TRUE(RunFunction(function, base::ListValue())) << function->GetError();
+  EXPECT_TRUE(RunFunction(function, base::Value::List()))
+      << function->GetError();
   const base::Value::List* results = function->GetResultList();
   ASSERT_EQ(1u, results->size());
 
@@ -2223,7 +2220,8 @@
 
   auto function = base::MakeRefCounted<
       api::DeveloperPrivateGetUserAndExtensionSitesByEtldFunction>();
-  EXPECT_TRUE(RunFunction(function, base::ListValue())) << function->GetError();
+  EXPECT_TRUE(RunFunction(function, base::Value::List()))
+      << function->GetError();
   const base::Value::List* results = function->GetResultList();
   ASSERT_EQ(1u, results->size());
 
@@ -2277,7 +2275,7 @@
   auto get_user_and_extension_sites = [this](const std::string& expected_json) {
     auto function = base::MakeRefCounted<
         api::DeveloperPrivateGetUserAndExtensionSitesByEtldFunction>();
-    EXPECT_TRUE(RunFunction(function, base::ListValue()))
+    EXPECT_TRUE(RunFunction(function, base::Value::List()))
         << function->GetError();
     const base::Value::List* results = function->GetResultList();
     ASSERT_EQ(1u, results->size());
diff --git a/chrome/browser/extensions/api/language_settings_private/language_settings_private_api.cc b/chrome/browser/extensions/api/language_settings_private/language_settings_private_api.cc
index 5bbd575..f092e5a 100644
--- a/chrome/browser/extensions/api/language_settings_private/language_settings_private_api.cc
+++ b/chrome/browser/extensions/api/language_settings_private/language_settings_private_api.cc
@@ -19,6 +19,7 @@
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/browser_process.h"
@@ -555,8 +556,7 @@
   SpellcheckCustomDictionary* dictionary = service->GetCustomDictionary();
 
   if (dictionary->IsLoaded())
-    return RespondNow(
-        WithArguments(base::Value::FromUniquePtrValue(GetSpellcheckWords())));
+    return RespondNow(WithArguments(GetSpellcheckWords()));
 
   dictionary->AddObserver(this);
   AddRef();  // Balanced in OnCustomDictionaryLoaded().
@@ -568,7 +568,7 @@
   SpellcheckService* service =
       SpellcheckServiceFactory::GetForContext(browser_context());
   service->GetCustomDictionary()->RemoveObserver(this);
-  Respond(WithArguments(base::Value::FromUniquePtrValue(GetSpellcheckWords())));
+  Respond(WithArguments(GetSpellcheckWords()));
   Release();
 }
 
@@ -580,7 +580,7 @@
                   "OnCustomDictionaryLoaded()";
 }
 
-std::unique_ptr<base::ListValue>
+base::Value::List
 LanguageSettingsPrivateGetSpellcheckWordsFunction::GetSpellcheckWords() const {
   SpellcheckService* service =
       SpellcheckServiceFactory::GetForContext(browser_context());
@@ -588,10 +588,11 @@
   DCHECK(dictionary->IsLoaded());
 
   // TODO(michaelpg): Sort using app locale.
-  std::unique_ptr<base::ListValue> word_list(new base::ListValue());
+  base::Value::List word_list;
   const std::set<std::string>& words = dictionary->GetWords();
+  word_list.reserve(words.size());
   for (const std::string& word : words)
-    word_list->Append(word);
+    word_list.Append(word);
   return word_list;
 }
 
diff --git a/chrome/browser/extensions/api/language_settings_private/language_settings_private_api.h b/chrome/browser/extensions/api/language_settings_private/language_settings_private_api.h
index dc629f22..a657cf9 100644
--- a/chrome/browser/extensions/api/language_settings_private/language_settings_private_api.h
+++ b/chrome/browser/extensions/api/language_settings_private/language_settings_private_api.h
@@ -252,7 +252,7 @@
       const SpellcheckCustomDictionary::Change& dictionary_change) override;
 
   // Returns the list of words from the loaded custom dictionary.
-  std::unique_ptr<base::ListValue> GetSpellcheckWords() const;
+  base::Value::List GetSpellcheckWords() const;
 };
 
 // Implements the languageSettingsPrivate.addSpellcheckWord method.
diff --git a/chrome/browser/feed/android/BUILD.gn b/chrome/browser/feed/android/BUILD.gn
index 05ca142..3931f2f5 100644
--- a/chrome/browser/feed/android/BUILD.gn
+++ b/chrome/browser/feed/android/BUILD.gn
@@ -178,12 +178,18 @@
     "java/res/drawable-xhdpi/follow_accelerator_shadow.9.png",
     "java/res/drawable-xxhdpi/follow_accelerator_shadow.9.png",
     "java/res/drawable/back_to_top_arrow.xml",
+    "java/res/drawable/creator_content_unavailable_error_illustration.xml",
+    "java/res/drawable/creator_general_error_illustration.xml",
+    "java/res/drawable/creator_offline_error_illustration.xml",
     "java/res/drawable/crow_icon.xml",
     "java/res/drawable/header_title_section_tab_background.xml",
     "java/res/drawable/header_title_tab_selected_background.xml",
     "java/res/drawable/rounded_corners.xml",
     "java/res/drawable/web_feed_post_follow_illustration.xml",
     "java/res/layout/back_to_top_bubble.xml",
+    "java/res/layout/creator_content_unavailable_error.xml",
+    "java/res/layout/creator_general_error.xml",
+    "java/res/layout/creator_offline_error.xml",
     "java/res/layout/feed_management_activity.xml",
     "java/res/layout/feed_management_list_item.xml",
     "java/res/layout/feed_options_panel.xml",
diff --git a/chrome/browser/feed/android/java/res/drawable/creator_content_unavailable_error_illustration.xml b/chrome/browser/feed/android/java/res/drawable/creator_content_unavailable_error_illustration.xml
new file mode 100644
index 0000000..5005961
--- /dev/null
+++ b/chrome/browser/feed/android/java/res/drawable/creator_content_unavailable_error_illustration.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2022 The Chromium Authors
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="150dp"
+    android:height="150dp"
+    android:viewportWidth="150"
+    android:viewportHeight="150">
+  <path
+      android:pathData="M86,37L132,37A2,2 0,0 1,134 39L134,53A2,2 0,0 1,132 55L86,55A2,2 0,0 1,84 53L84,39A2,2 0,0 1,86 37z"
+      android:fillColor="#34A853"/>
+  <path
+      android:pathData="M27,63L73,63A2,2 0,0 1,75 65L75,79A2,2 0,0 1,73 81L27,81A2,2 0,0 1,25 79L25,65A2,2 0,0 1,27 63z"
+      android:fillColor="#FBBC04"/>
+  <path
+      android:pathData="M86,37L132,37A2,2 0,0 1,134 39L134,53A2,2 0,0 1,132 55L86,55A2,2 0,0 1,84 53L84,39A2,2 0,0 1,86 37z"
+      android:fillColor="#34A853"/>
+  <path
+      android:pathData="M71,100L117,100A2,2 0,0 1,119 102L119,116A2,2 0,0 1,117 118L71,118A2,2 0,0 1,69 116L69,102A2,2 0,0 1,71 100z"
+      android:fillColor="#4285F4"/>
+  <path
+      android:pathData="M51,12L99,12A5,5 0,0 1,104 17L104,133A5,5 0,0 1,99 138L51,138A5,5 0,0 1,46 133L46,17A5,5 0,0 1,51 12z"
+      android:strokeWidth="2"
+      android:strokeColor="#D9D9D9"/>
+  <path
+      android:pathData="M103,37v18H86a2,2 0,0 1,-2 -2V39a2,2 0,0 1,2 -2h17zM103,100v18H71a2,2 0,0 1,-2 -2v-14a2,2 0,0 1,2 -2h32zM47,81V63h26a2,2 0,0 1,2 2v14a2,2 0,0 1,-2 2H47z"
+      android:fillColor="#E8EAED"
+      android:fillType="evenOdd"/>
+</vector>
diff --git a/chrome/browser/feed/android/java/res/drawable/creator_general_error_illustration.xml b/chrome/browser/feed/android/java/res/drawable/creator_general_error_illustration.xml
new file mode 100644
index 0000000..ba47353
--- /dev/null
+++ b/chrome/browser/feed/android/java/res/drawable/creator_general_error_illustration.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2022 The Chromium Authors
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="150dp"
+    android:height="150dp"
+    android:viewportWidth="150"
+    android:viewportHeight="150">
+  <path
+      android:pathData="M51,12L99,12A5,5 0,0 1,104 17L104,133A5,5 0,0 1,99 138L51,138A5,5 0,0 1,46 133L46,17A5,5 0,0 1,51 12z"
+      android:strokeWidth="2"
+      android:fillColor="#00000000"
+      android:strokeColor="#D9D9D9"/>
+  <path
+      android:pathData="M15.82,121.91L57.68,102.83A2,2 126.49,0 1,60.33 103.82L66.13,116.56A2,2 0,0 1,65.14 119.21L23.29,138.29A2,2 89.84,0 1,20.64 137.3L14.83,124.56A2,2 61.71,0 1,15.82 121.91z"
+      android:fillColor="#FBBC04"/>
+  <path
+      android:pathData="M63,121L109,121A2,2 0,0 1,111 123L111,137A2,2 0,0 1,109 139L63,139A2,2 0,0 1,61 137L61,123A2,2 0,0 1,63 121z"
+      android:fillColor="#34A853"/>
+  <path
+      android:pathData="M106.84,93.38L140.22,125.02A2,2 93.46,0 1,140.29 127.85L130.66,138.01A2,2 117.88,0 1,127.84 138.09L94.45,106.44A2,2 88.56,0 1,94.38 103.61L104.01,93.45A2,2 47.29,0 1,106.84 93.38z"
+      android:fillColor="#4285F4"/>
+</vector>
diff --git a/chrome/browser/feed/android/java/res/drawable/creator_offline_error_illustration.xml b/chrome/browser/feed/android/java/res/drawable/creator_offline_error_illustration.xml
new file mode 100644
index 0000000..81cc83e
--- /dev/null
+++ b/chrome/browser/feed/android/java/res/drawable/creator_offline_error_illustration.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2022 The Chromium Authors
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="150dp"
+    android:height="150dp"
+    android:viewportWidth="150"
+    android:viewportHeight="150">
+  <path
+      android:pathData="M61.72,47c0,11.05 -8.92,20 -19.92,20 -11,0 -19.92,-8.95 -19.92,-20s8.92,-20 19.92,-20c11.01,0 19.92,8.95 19.92,20z"
+      android:fillColor="#E8EAED"/>
+  <path
+      android:pathData="M37.57,53.67C37.57,61.03 31.62,67 24.28,67 16.95,67 11,61.03 11,53.67c0,-7.36 5.95,-13.33 13.28,-13.33 7.34,0 13.28,5.97 13.28,13.33zM75,57.3C75,62.66 70.68,67 65.34,67c-5.34,0 -9.66,-4.34 -9.66,-9.7 0,-5.36 4.32,-9.7 9.66,-9.7 5.34,0 9.66,4.34 9.66,9.7z"
+      android:fillColor="#E8EAED"/>
+  <path
+      android:pathData="M24.28,59.73H65.34V67H24.28v-7.27zM118.72,91c0,11.05 -8.92,20 -19.92,20 -11.01,0 -19.92,-8.95 -19.92,-20s8.92,-20 19.92,-20c11,0 19.92,8.95 19.92,20z"
+      android:fillColor="#E8EAED"/>
+  <path
+      android:pathData="M94.57,97.67c0,7.36 -5.95,13.33 -13.28,13.33C73.95,111 68,105.03 68,97.67c0,-7.36 5.95,-13.33 13.28,-13.33 7.34,0 13.28,5.97 13.28,13.33zM132,101.3c0,5.36 -4.32,9.7 -9.66,9.7 -5.34,0 -9.66,-4.34 -9.66,-9.7 0,-5.36 4.32,-9.7 9.66,-9.7 5.34,0 9.66,4.34 9.66,9.7z"
+      android:fillColor="#E8EAED"/>
+  <path
+      android:pathData="M81.28,103.73h41.06V111H81.28v-7.27z"
+      android:fillColor="#E8EAED"/>
+  <path
+      android:pathData="M51,12L99,12A5,5 0,0 1,104 17L104,133A5,5 0,0 1,99 138L51,138A5,5 0,0 1,46 133L46,17A5,5 0,0 1,51 12z"
+      android:strokeWidth="2"
+      android:fillColor="#00000000"
+      android:strokeColor="#D9D9D9"/>
+  <path
+      android:pathData="M52,93L98,93A2,2 0,0 1,100 95L100,109A2,2 0,0 1,98 111L52,111A2,2 0,0 1,50 109L50,95A2,2 0,0 1,52 93z"
+      android:fillColor="#4285F4"/>
+  <path
+      android:pathData="M52,71L98,71A2,2 0,0 1,100 73L100,87A2,2 0,0 1,98 89L52,89A2,2 0,0 1,50 87L50,73A2,2 0,0 1,52 71z"
+      android:fillColor="#34A853"/>
+  <path
+      android:pathData="M52,49L98,49A2,2 0,0 1,100 51L100,65A2,2 0,0 1,98 67L52,67A2,2 0,0 1,50 65L50,51A2,2 0,0 1,52 49z"
+      android:fillColor="#FBBC04"/>
+  <path
+      android:pathData="M52,27L98,27A2,2 0,0 1,100 29L100,43A2,2 0,0 1,98 45L52,45A2,2 0,0 1,50 43L50,29A2,2 0,0 1,52 27z"
+      android:fillColor="#4285F4"/>
+  <group>
+    <clip-path
+        android:pathData="M61.72,47c0,11.05 -8.92,20 -19.92,20 -11,0 -19.92,-8.95 -19.92,-20s8.92,-20 19.92,-20c11.01,0 19.92,8.95 19.92,20zM37.57,53.67C37.57,61.03 31.62,67 24.28,67 16.95,67 11,61.03 11,53.67c0,-7.36 5.95,-13.33 13.28,-13.33 7.34,0 13.28,5.97 13.28,13.33zM75,57.3C75,62.66 70.68,67 65.34,67c-5.34,0 -9.66,-4.34 -9.66,-9.7 0,-5.36 4.32,-9.7 9.66,-9.7 5.34,0 9.66,4.34 9.66,9.7zM24.28,59.73H65.34V67H24.28v-7.27zM118.72,91c0,11.05 -8.92,20 -19.92,20 -11.01,0 -19.92,-8.95 -19.92,-20s8.92,-20 19.92,-20c11,0 19.92,8.95 19.92,20zM94.57,97.67c0,7.36 -5.95,13.33 -13.28,13.33C73.95,111 68,105.03 68,97.67c0,-7.36 5.95,-13.33 13.28,-13.33 7.34,0 13.28,5.97 13.28,13.33zM132,101.3c0,5.36 -4.32,9.7 -9.66,9.7 -5.34,0 -9.66,-4.34 -9.66,-9.7 0,-5.36 4.32,-9.7 9.66,-9.7 5.34,0 9.66,4.34 9.66,9.7zM81.28,103.73h41.06V111H81.28v-7.27z"/>
+    <path
+        android:pathData="M51,13L99,13A4,4 0,0 1,103 17L103,133A4,4 0,0 1,99 137L51,137A4,4 0,0 1,47 133L47,17A4,4 0,0 1,51 13z"
+        android:fillColor="#fff"/>
+  </group>
+</vector>
+
diff --git a/chrome/browser/feed/android/java/res/layout/creator_content_unavailable_error.xml b/chrome/browser/feed/android/java/res/layout/creator_content_unavailable_error.xml
new file mode 100644
index 0000000..ee3c5270
--- /dev/null
+++ b/chrome/browser/feed/android/java/res/layout/creator_content_unavailable_error.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2022 The Chromium Authors
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_marginStart="16dp"
+    android:layout_marginEnd="16dp"
+    android:gravity="center" >
+  <ImageView
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      tools:ignore="ContentDescription"
+      android:src="@drawable/creator_content_unavailable_error_illustration" />
+  <TextView
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:padding="16dp"
+      android:textAppearance="@style/TextAppearance.TextLarge.Primary"
+      android:gravity="center"
+      android:text="@string/cormorant_creator_content_unavailable_error_title" />
+  <TextView
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:paddingLeft="16dp"
+      android:paddingRight="16dp"
+      android:textAppearance="@style/TextAppearance.TextSmall.Secondary"
+      android:gravity="center"
+      android:text="@string/cormorant_creator_content_unavailable_error_description" />
+</LinearLayout>
diff --git a/chrome/browser/feed/android/java/res/layout/creator_general_error.xml b/chrome/browser/feed/android/java/res/layout/creator_general_error.xml
new file mode 100644
index 0000000..2322cb7
--- /dev/null
+++ b/chrome/browser/feed/android/java/res/layout/creator_general_error.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2022 The Chromium Authors
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_marginStart="16dp"
+    android:layout_marginEnd="16dp"
+    android:gravity="center" >
+  <ImageView
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      tools:ignore="ContentDescription"
+      android:src="@drawable/creator_general_error_illustration" />
+  <TextView
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:padding="16dp"
+      android:textAppearance="@style/TextAppearance.TextLarge.Primary"
+      android:gravity="center"
+      android:text="@string/cormorant_creator_general_error_title" />
+  <TextView
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:paddingLeft="16dp"
+      android:paddingRight="16dp"
+      android:textAppearance="@style/TextAppearance.TextSmall.Secondary"
+      android:gravity="center"
+      android:text="@string/cormorant_creator_general_error_description" />
+</LinearLayout>
diff --git a/chrome/browser/feed/android/java/res/layout/creator_offline_error.xml b/chrome/browser/feed/android/java/res/layout/creator_offline_error.xml
new file mode 100644
index 0000000..569b8cbf
--- /dev/null
+++ b/chrome/browser/feed/android/java/res/layout/creator_offline_error.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2022 The Chromium Authors
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_marginStart="16dp"
+    android:layout_marginEnd="16dp"
+    android:gravity="center" >
+  <ImageView
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      tools:ignore="ContentDescription"
+      android:src="@drawable/creator_offline_error_illustration" />
+  <TextView
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:padding="16dp"
+      android:textAppearance="@style/TextAppearance.TextLarge.Primary"
+      android:gravity="center"
+      android:text="@string/cormorant_creator_offline_error_title" />
+  <TextView
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:paddingLeft="16dp"
+      android:paddingRight="16dp"
+      android:textAppearance="@style/TextAppearance.TextSmall.Secondary"
+      android:gravity="center"
+      android:text="@string/cormorant_creator_offline_error_description" />
+</LinearLayout>
diff --git a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedStream.java b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedStream.java
index a23f0a6..baf20126 100644
--- a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedStream.java
+++ b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedStream.java
@@ -10,8 +10,10 @@
 import android.os.SystemClock;
 import android.util.DisplayMetrics;
 import android.util.TypedValue;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
 import android.view.ViewParent;
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.FrameLayout;
@@ -1137,6 +1139,31 @@
             return new NtpListContentManager.NativeViewContent(
                     getLateralPaddingsPx(), sliceId, R.layout.following_empty_state);
         }
+        if (mStreamKind == StreamKind.SINGLE_WEB_FEED) {
+            View creatorErrorCard;
+            if (slice.getZeroStateSlice().getType()
+                    == FeedUiProto.ZeroStateSlice.Type.CANT_REFRESH) {
+                creatorErrorCard = LayoutInflater.from(mActivity).inflate(
+                        R.layout.creator_offline_error, mRecyclerView, false);
+            } else if (slice.getZeroStateSlice().getType()
+                    == FeedUiProto.ZeroStateSlice.Type.NO_CARDS_AVAILABLE) {
+                creatorErrorCard = LayoutInflater.from(mActivity).inflate(
+                        R.layout.creator_content_unavailable_error, mRecyclerView, false);
+            } else {
+                creatorErrorCard = LayoutInflater.from(mActivity).inflate(
+                        R.layout.creator_general_error, mRecyclerView, false);
+            }
+             // TODO(crbug/1385903): Replace display height dependency with setting the
+             // RecyclerView height to match_parent.
+            DisplayMetrics displayMetrics = new DisplayMetrics();
+            mActivity.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
+            MarginLayoutParams marginParams =
+                    (MarginLayoutParams) creatorErrorCard.getLayoutParams();
+            marginParams.setMargins(0, displayMetrics.heightPixels / 4, 0, 0);
+            creatorErrorCard.setLayoutParams(marginParams);
+            return new NtpListContentManager.NativeViewContent(
+                    getLateralPaddingsPx(), sliceId, creatorErrorCard);
+        }
         if (slice.getZeroStateSlice().getType() == FeedUiProto.ZeroStateSlice.Type.CANT_REFRESH) {
             return new NtpListContentManager.NativeViewContent(
                     getLateralPaddingsPx(), sliceId, R.layout.no_connection);
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 5477247..e9c9a630 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -2108,19 +2108,9 @@
     "expiry_milestone": 106
   },
   {
-    "name": "enable-discover-feed-preview",
-    "owners": [ "tinazwang", "sczs" ],
-    "expiry_milestone": 101
-  },
-  {
-    "name": "enable-discover-feed-static-resource-serving",
-    "owners": [ "adamta", "sczs" ],
-    "expiry_milestone": 106
-  },
-  {
     "name": "enable-discover-feed-top-sync-promo",
-    "owners": [ "mrefaat", "sczs"],
-    "expiry_milestone": 106
+    "owners": [ "adamta", "mrefaat", "sczs"],
+    "expiry_milestone": 118
   },
   {
     "name": "enable-discover-multi-column",
@@ -2278,7 +2268,7 @@
   {
     "name": "enable-feed-ablation",
     "owners": [ "adamta", "sczs" ],
-    "expiry_milestone": 106
+    "expiry_milestone": 118
   },
   {
     "name": "enable-feed-position-on-ntp",
@@ -2538,7 +2528,7 @@
   {
     "name": "enable-lens-image-format-optimizations",
     "owners": ["mercerd@google.com", "stanfield@google.com", "lens-chrome@google.com"],
-    "expiry_milestone": 110
+    "expiry_milestone": 112
   },
   {
     "name": "enable-lens-in-home-screen-widget",
@@ -2563,12 +2553,12 @@
   {
     "name": "enable-lens-instruction-chip-improvements",
     "owners": [ "schechter@google.com", "juanmojica@google.com" ],
-    "expiry_milestone": 110
+    "expiry_milestone": 112
   },
   {
     "name": "enable-lens-region-search-static-page",
     "owners": ["juanmojica@google.com", "stanfield@google.com", "lens-chrome@google.com"],
-    "expiry_milestone": 110
+    "expiry_milestone": 112
   },
   {
     "name": "enable-lens-standalone",
@@ -4713,11 +4703,6 @@
     "expiry_milestone": 120
   },
   {
-    "name": "new-content-suggestions-feed",
-    "owners": [ "adamta@google.com", "sczs", "gogerald", "bling-flags@google.com" ],
-    "expiry_milestone": 93
-  },
-  {
     "name": "new-mobile-identity-consistency-fre",
     "owners": [ "jlebel", "chrome-signin-team" ],
     "expiry_milestone": 112
@@ -6244,7 +6229,7 @@
   {
     "name" : "side-panel-journeys",
     "owners": [ "mfacey", "chrome-journeys@google.com", "chrome-desktop-ui-sea@google.com"],
-    "expiry_milestone" : 110
+    "expiry_milestone" : 115
   },
   {
     "name" : "side-panel-journeys-queryless",
diff --git a/chrome/browser/lacros/desk_template_client_lacros.cc b/chrome/browser/lacros/desk_template_client_lacros.cc
index 16efcb4..2749a15 100644
--- a/chrome/browser/lacros/desk_template_client_lacros.cc
+++ b/chrome/browser/lacros/desk_template_client_lacros.cc
@@ -84,10 +84,10 @@
   }
 }
 
-void DeskTemplateClientLacros::GetTabStripModelUrls(
+void DeskTemplateClientLacros::GetBrowserInformation(
     uint32_t serial,
     const std::string& window_unique_id,
-    GetTabStripModelUrlsCallback callback) {
+    GetBrowserInformationCallback callback) {
   Browser* browser = nullptr;
   for (auto* b : *BrowserList::GetInstance()) {
     if (views::DesktopWindowTreeHostLacros::From(
diff --git a/chrome/browser/lacros/desk_template_client_lacros.h b/chrome/browser/lacros/desk_template_client_lacros.h
index 9820744a..cb06508 100644
--- a/chrome/browser/lacros/desk_template_client_lacros.h
+++ b/chrome/browser/lacros/desk_template_client_lacros.h
@@ -25,9 +25,9 @@
       const gfx::Rect& bounds,
       const ui::mojom::WindowShowState show_state,
       crosapi::mojom::DeskTemplateStatePtr additional_state) override;
-  void GetTabStripModelUrls(uint32_t serial,
-                            const std::string& window_unique_id,
-                            GetTabStripModelUrlsCallback callback) override;
+  void GetBrowserInformation(uint32_t serial,
+                             const std::string& window_unique_id,
+                             GetBrowserInformationCallback callback) override;
   void GetFaviconImage(const GURL& url,
                        GetFaviconImageCallback callback) override;
 
diff --git a/chrome/browser/metrics/power/battery_discharge_reporter.cc b/chrome/browser/metrics/power/battery_discharge_reporter.cc
index 61bd158..3c6a40f 100644
--- a/chrome/browser/metrics/power/battery_discharge_reporter.cc
+++ b/chrome/browser/metrics/power/battery_discharge_reporter.cc
@@ -82,8 +82,8 @@
   // suffix.
   const std::vector<const char*> long_interval_suffixes{
       "", long_interval_scenario_params.histogram_suffix};
-  ReportBatteryHistograms(sampling_event_delta, battery_discharge,
-                          is_initial_interval_, long_interval_suffixes);
+  ReportAlignedBatteryHistograms(sampling_event_delta, battery_discharge,
+                                 is_initial_interval_, long_interval_suffixes);
   is_initial_interval_ = false;
 }
 
diff --git a/chrome/browser/metrics/power/battery_discharge_reporter_unittest.cc b/chrome/browser/metrics/power/battery_discharge_reporter_unittest.cc
index 5c22859..91c226ca 100644
--- a/chrome/browser/metrics/power/battery_discharge_reporter_unittest.cc
+++ b/chrome/browser/metrics/power/battery_discharge_reporter_unittest.cc
@@ -104,33 +104,6 @@
       const BatteryDischargeReporterTest& rhs) = delete;
   ~BatteryDischargeReporterTest() override = default;
 
-  // Tests that the right BatteryDischargeMode histogram sample is emitted given
-  // the battery states before and after an interval.
-  void TestBatteryDischargeMode(
-      const absl::optional<base::BatteryLevelProvider::BatteryState>&
-          previous_battery_state,
-      const absl::optional<base::BatteryLevelProvider::BatteryState>&
-          new_battery_state,
-      BatteryDischargeMode expected_mode) {
-    TestUsageScenarioDataStoreImpl usage_scenario_data_store;
-
-    base::BatteryStateSampler battery_state_sampler(
-        std::make_unique<NoopSamplingEventSource>(),
-        std::make_unique<NoopBatteryLevelProvider>());
-    BatteryDischargeReporter battery_discharge_reporter(
-        &battery_state_sampler, &usage_scenario_data_store);
-
-    battery_discharge_reporter.OnBatteryStateSampled(previous_battery_state);
-    task_environment_.FastForwardBy(base::Minutes(1));
-    battery_discharge_reporter.OnBatteryStateSampled(new_battery_state);
-
-    const std::vector<const char*> suffixes(
-        {"", ".Initial", ".ZeroWindow", ".ZeroWindow.Initial"});
-    ExpectHistogramSamples(&histogram_tester_, suffixes,
-                           {{kBatteryDischargeModeHistogramName,
-                             static_cast<int>(expected_mode)}});
-  }
-
  protected:
   content::BrowserTaskEnvironment task_environment_{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
@@ -280,145 +253,3 @@
   histogram_tester_.ExpectTotalCount(kBatteryDischargeRateRelativeHistogramName,
                                      1);
 }
-
-TEST_F(BatteryDischargeReporterTest, RetrievalError) {
-  TestBatteryDischargeMode(absl::nullopt, absl::nullopt,
-                           BatteryDischargeMode::kRetrievalError);
-}
-
-TEST_F(BatteryDischargeReporterTest, StateChanged_Battery) {
-  TestBatteryDischargeMode(
-      base::BatteryLevelProvider::BatteryState{
-          .battery_count = 0,
-      },
-      base::BatteryLevelProvider::BatteryState{
-          .battery_count = 1,
-      },
-      BatteryDischargeMode::kStateChanged);
-}
-
-TEST_F(BatteryDischargeReporterTest, StateChanged_PluggedIn) {
-  TestBatteryDischargeMode(
-      base::BatteryLevelProvider::BatteryState{
-          .battery_count = 1,
-          .is_external_power_connected = true,
-      },
-      base::BatteryLevelProvider::BatteryState{
-          .battery_count = 1,
-          .is_external_power_connected = false,
-      },
-      BatteryDischargeMode::kStateChanged);
-}
-
-TEST_F(BatteryDischargeReporterTest, NoBattery) {
-  TestBatteryDischargeMode(
-      base::BatteryLevelProvider::BatteryState{
-          .battery_count = 0,
-      },
-      base::BatteryLevelProvider::BatteryState{
-          .battery_count = 0,
-      },
-      BatteryDischargeMode::kNoBattery);
-}
-
-TEST_F(BatteryDischargeReporterTest, PluggedIn) {
-  TestBatteryDischargeMode(
-      base::BatteryLevelProvider::BatteryState{
-          .battery_count = 1,
-          .is_external_power_connected = true,
-      },
-      base::BatteryLevelProvider::BatteryState{
-          .battery_count = 1,
-          .is_external_power_connected = true,
-      },
-      BatteryDischargeMode::kPluggedIn);
-}
-
-TEST_F(BatteryDischargeReporterTest, MultipleBatteries) {
-  TestBatteryDischargeMode(
-      base::BatteryLevelProvider::BatteryState{
-          .battery_count = 2,
-          .is_external_power_connected = false,
-      },
-      base::BatteryLevelProvider::BatteryState{
-          .battery_count = 2,
-          .is_external_power_connected = false,
-      },
-      BatteryDischargeMode::kMultipleBatteries);
-}
-
-TEST_F(BatteryDischargeReporterTest, InsufficientResolution) {
-  TestBatteryDischargeMode(
-      base::BatteryLevelProvider::BatteryState{
-          .battery_count = 1,
-          .is_external_power_connected = false,
-          .charge_unit =
-              base::BatteryLevelProvider::BatteryLevelUnit::kRelative,
-      },
-      base::BatteryLevelProvider::BatteryState{
-          .battery_count = 1,
-          .is_external_power_connected = false,
-          .charge_unit =
-              base::BatteryLevelProvider::BatteryLevelUnit::kRelative,
-      },
-      BatteryDischargeMode::kInsufficientResolution);
-}
-
-#if BUILDFLAG(IS_MAC)
-TEST_F(BatteryDischargeReporterTest, MacFullyCharged) {
-  TestBatteryDischargeMode(
-      base::BatteryLevelProvider::BatteryState{
-          .battery_count = 1,
-          .is_external_power_connected = false,
-          .current_capacity = 100,
-          .full_charged_capacity = 100,
-          .charge_unit = base::BatteryLevelProvider::BatteryLevelUnit::kMWh,
-      },
-      base::BatteryLevelProvider::BatteryState{
-          .battery_count = 1,
-          .is_external_power_connected = false,
-          .current_capacity = 99,
-          .full_charged_capacity = 100,
-          .charge_unit = base::BatteryLevelProvider::BatteryLevelUnit::kMWh,
-      },
-      BatteryDischargeMode::kMacFullyCharged);
-}
-#endif  // BUILDFLAG(IS_MAC)
-
-TEST_F(BatteryDischargeReporterTest, FullChargedCapacityIsZero) {
-  TestBatteryDischargeMode(
-      base::BatteryLevelProvider::BatteryState{
-          .battery_count = 1,
-          .is_external_power_connected = false,
-          .current_capacity = 10,
-          .full_charged_capacity = 0,
-          .charge_unit = base::BatteryLevelProvider::BatteryLevelUnit::kMWh,
-      },
-      base::BatteryLevelProvider::BatteryState{
-          .battery_count = 1,
-          .is_external_power_connected = false,
-          .current_capacity = 10,
-          .full_charged_capacity = 0,
-          .charge_unit = base::BatteryLevelProvider::BatteryLevelUnit::kMWh,
-      },
-      BatteryDischargeMode::kFullChargedCapacityIsZero);
-}
-
-TEST_F(BatteryDischargeReporterTest, BatteryLevelIncreased) {
-  TestBatteryDischargeMode(
-      base::BatteryLevelProvider::BatteryState{
-          .battery_count = 1,
-          .is_external_power_connected = false,
-          .current_capacity = 40,
-          .full_charged_capacity = 100,
-          .charge_unit = base::BatteryLevelProvider::BatteryLevelUnit::kMWh,
-      },
-      base::BatteryLevelProvider::BatteryState{
-          .battery_count = 1,
-          .is_external_power_connected = false,
-          .current_capacity = 50,
-          .full_charged_capacity = 100,
-          .charge_unit = base::BatteryLevelProvider::BatteryLevelUnit::kMWh,
-      },
-      BatteryDischargeMode::kBatteryLevelIncreased);
-}
diff --git a/chrome/browser/metrics/power/power_metrics.cc b/chrome/browser/metrics/power/power_metrics.cc
index ea7d256e..54c8cfa 100644
--- a/chrome/browser/metrics/power/power_metrics.cc
+++ b/chrome/browser/metrics/power/power_metrics.cc
@@ -16,11 +16,16 @@
 
 namespace {
 
-constexpr const char* kBatteryDischargeRateMilliwattsHistogramName =
-    "Power.BatteryDischargeRateMilliwatts5";
-constexpr const char* kBatteryDischargeRateRelativeHistogramName =
-    "Power.BatteryDischargeRateRelative5";
+constexpr const char* kBatteryDischargeRateHistogramName =
+    "Power.BatteryDischargeRate2";
 constexpr const char* kBatteryDischargeModeHistogramName =
+    "Power.BatteryDischargeMode2";
+
+constexpr const char* kAlignedBatteryDischargeRateMilliwattsHistogramName =
+    "Power.BatteryDischargeRateMilliwatts5";
+constexpr const char* kAlignedBatteryDischargeRateRelativeHistogramName =
+    "Power.BatteryDischargeRateRelative5";
+constexpr const char* kAlignedBatteryDischargeModeHistogramName =
     "Power.BatteryDischargeMode5";
 
 #if BUILDFLAG(IS_MAC)
@@ -51,21 +56,6 @@
 constexpr int kMaxGPUProportion = 1;
 #endif  // BUILDFLAG(IS_MAC)
 
-// Returns the current capacity of |battery_state| in milliwatt-hours.
-uint64_t GetBatteryCapacityinMWh(
-    const base::BatteryLevelProvider::BatteryState& battery_state) {
-  if (battery_state.charge_unit ==
-      base::BatteryLevelProvider::BatteryLevelUnit::kMWh) {
-    return battery_state.current_capacity.value();
-  }
-
-  DCHECK_EQ(battery_state.charge_unit.value(),
-            base::BatteryLevelProvider::BatteryLevelUnit::kMAh);
-  DCHECK(battery_state.voltage_mv.has_value());
-  return battery_state.current_capacity.value() *
-         battery_state.voltage_mv.value() / 1000;
-}
-
 }  // namespace
 
 void ReportAggregatedProcessMetricsHistograms(
@@ -78,22 +68,22 @@
   }
 }
 
+// Returns the discharge rate in milliwatts.
 int64_t CalculateDischargeRateMilliwatts(
     const absl::optional<base::BatteryLevelProvider::BatteryState>&
         previous_battery_state,
     const absl::optional<base::BatteryLevelProvider::BatteryState>&
         new_battery_state,
     base::TimeDelta interval_duration) {
-  DCHECK(previous_battery_state);
-  DCHECK(new_battery_state);
-  DCHECK_EQ(previous_battery_state->charge_unit.value(),
-            new_battery_state->charge_unit.value());
-
+  DCHECK(previous_battery_state &&
+         previous_battery_state->charge_unit ==
+             base::BatteryLevelProvider::BatteryLevelUnit::kMWh);
+  DCHECK(new_battery_state &&
+         previous_battery_state->charge_unit ==
+             base::BatteryLevelProvider::BatteryLevelUnit::kMWh);
   const uint64_t previous_capacity =
-      GetBatteryCapacityinMWh(previous_battery_state.value());
-  const uint64_t new_capacity =
-      GetBatteryCapacityinMWh(new_battery_state.value());
-
+      previous_battery_state->current_capacity.value();
+  const uint64_t new_capacity = new_battery_state->current_capacity.value();
   // The capacity is in mWh. Divide by hours to get mW. Note that there is no
   // InHoursF() method.
   const double interval_duration_in_hours =
@@ -102,6 +92,8 @@
   return (previous_capacity - new_capacity) / interval_duration_in_hours;
 }
 
+// Returns the discharge rate in one hundredth of a percent of full capacity per
+// minute.
 int64_t CalculateDischargeRateRelative(
     const absl::optional<base::BatteryLevelProvider::BatteryState>&
         previous_battery_state,
@@ -185,9 +177,8 @@
   const auto discharge_rate_mw = CalculateDischargeRateMilliwatts(
       previous_battery_state, new_battery_state, interval_duration);
 
-  if (discharge_rate_relative < 0 || discharge_rate_mw < 0) {
+  if (discharge_rate_relative < 0 || discharge_rate_mw < 0)
     return {BatteryDischargeMode::kBatteryLevelIncreased, absl::nullopt};
-  }
   return {BatteryDischargeMode::kDischarging, discharge_rate_mw,
           discharge_rate_relative};
 }
@@ -195,6 +186,24 @@
 void ReportBatteryHistograms(
     base::TimeDelta interval_duration,
     BatteryDischarge battery_discharge,
+    const std::vector<const char*>& scenario_suffixes) {
+  for (const char* scenario_suffix : scenario_suffixes) {
+    base::UmaHistogramEnumeration(
+        base::StrCat({kBatteryDischargeModeHistogramName, scenario_suffix}),
+        battery_discharge.mode);
+
+    if (battery_discharge.mode == BatteryDischargeMode::kDischarging) {
+      DCHECK(battery_discharge.rate_relative.has_value());
+      base::UmaHistogramCounts1000(
+          base::StrCat({kBatteryDischargeRateHistogramName, scenario_suffix}),
+          *battery_discharge.rate_relative);
+    }
+  }
+}
+
+void ReportAlignedBatteryHistograms(
+    base::TimeDelta interval_duration,
+    BatteryDischarge battery_discharge,
     bool is_initial_interval,
     const std::vector<const char*>& scenario_suffixes) {
   const char* interval_type_suffixes[] = {
@@ -203,19 +212,19 @@
   for (const char* scenario_suffix : scenario_suffixes) {
     for (const char* interval_type_suffix : interval_type_suffixes) {
       base::UmaHistogramEnumeration(
-          base::StrCat({kBatteryDischargeModeHistogramName, scenario_suffix,
-                        interval_type_suffix}),
+          base::StrCat({kAlignedBatteryDischargeModeHistogramName,
+                        scenario_suffix, interval_type_suffix}),
           battery_discharge.mode);
 
       if (battery_discharge.mode == BatteryDischargeMode::kDischarging) {
         DCHECK(battery_discharge.rate_milliwatts.has_value());
         base::UmaHistogramCounts100000(
-            base::StrCat({kBatteryDischargeRateMilliwattsHistogramName,
+            base::StrCat({kAlignedBatteryDischargeRateMilliwattsHistogramName,
                           scenario_suffix, interval_type_suffix}),
             *battery_discharge.rate_milliwatts);
         DCHECK(battery_discharge.rate_relative.has_value());
         base::UmaHistogramCounts1000(
-            base::StrCat({kBatteryDischargeRateRelativeHistogramName,
+            base::StrCat({kAlignedBatteryDischargeRateRelativeHistogramName,
                           scenario_suffix, interval_type_suffix}),
             *battery_discharge.rate_relative);
       }
diff --git a/chrome/browser/metrics/power/power_metrics.h b/chrome/browser/metrics/power/power_metrics.h
index 2cd7fc2..00cf194 100644
--- a/chrome/browser/metrics/power/power_metrics.h
+++ b/chrome/browser/metrics/power/power_metrics.h
@@ -48,23 +48,6 @@
   absl::optional<int64_t> rate_relative;
 };
 
-// Returns the discharge rate in milliwatts.
-int64_t CalculateDischargeRateMilliwatts(
-    const absl::optional<base::BatteryLevelProvider::BatteryState>&
-        previous_battery_state,
-    const absl::optional<base::BatteryLevelProvider::BatteryState>&
-        new_battery_state,
-    base::TimeDelta interval_duration);
-
-// Returns the discharge rate in one hundredth of a percent of full capacity per
-// minute.
-int64_t CalculateDischargeRateRelative(
-    const absl::optional<base::BatteryLevelProvider::BatteryState>&
-        previous_battery_state,
-    const absl::optional<base::BatteryLevelProvider::BatteryState>&
-        new_battery_state,
-    base::TimeDelta interval_duration);
-
 // Computes and returns the battery discharge mode and rate during the interval.
 // If the discharge rate isn't valid, the returned rate is nullopt and the
 // reason is indicated per BatteryDischargeMode.
@@ -78,9 +61,17 @@
 // Report battery metrics to histograms with |scenario_suffixes|.
 void ReportBatteryHistograms(base::TimeDelta interval_duration,
                              BatteryDischarge battery_discharge,
-                             bool is_initial_interval,
                              const std::vector<const char*>& scenario_suffixes);
 
+// Report battery metrics to histograms with |scenario_suffixes|. The difference
+// with the above version is that when possible, the intervals are aligned with
+// battery discharge notifications from the OS (MacOS only for now).
+void ReportAlignedBatteryHistograms(
+    base::TimeDelta interval_duration,
+    BatteryDischarge battery_discharge,
+    bool is_initial_interval,
+    const std::vector<const char*>& scenario_suffixes);
+
 #if BUILDFLAG(IS_MAC)
 void ReportShortIntervalHistograms(
     const char* scenario_suffix,
diff --git a/chrome/browser/metrics/power/power_metrics_reporter.cc b/chrome/browser/metrics/power/power_metrics_reporter.cc
index b495992..b3e430d 100644
--- a/chrome/browser/metrics/power/power_metrics_reporter.cc
+++ b/chrome/browser/metrics/power/power_metrics_reporter.cc
@@ -32,6 +32,12 @@
 constexpr const char* kBatterySamplingDelayHistogramName =
     "Power.BatterySamplingDelay";
 
+bool IsWithinTolerance(base::TimeDelta value,
+                       base::TimeDelta expected,
+                       base::TimeDelta tolerance) {
+  return (value - expected).magnitude() < tolerance;
+}
+
 // Calculates the UKM bucket |value| falls in and returns it. This uses an
 // exponential bucketing approach with an exponent base of 1.3, resulting in
 // 17 buckets for an interval of 120 seconds.
@@ -342,6 +348,22 @@
   // Report UKMs.
   ReportBatteryUKMs(long_interval_data, aggregated_process_metrics,
                     interval_duration, battery_discharge);
+
+  // Ratio by which the time elapsed can deviate from
+  // |kLongPowerMetricsIntervalDuration| without invalidating this sample.
+  // TODO(pmonette): Change to DCHECK after ensuring this never triggers.
+  CHECK_GE(interval_duration, kLongPowerMetricsIntervalDuration);
+  constexpr double kTolerableTimeElapsedRatio = 0.10;
+  if (battery_discharge.mode == BatteryDischargeMode::kDischarging &&
+      !IsWithinTolerance(
+          interval_duration, kLongPowerMetricsIntervalDuration,
+          kLongPowerMetricsIntervalDuration * kTolerableTimeElapsedRatio)) {
+    battery_discharge.mode = BatteryDischargeMode::kInvalidInterval;
+  }
+
+  ReportBatteryHistograms(
+      interval_duration, battery_discharge,
+      {"", GetLongIntervalScenario(long_interval_data).histogram_suffix});
 }
 
 void PowerMetricsReporter::ReportBatteryUKMs(
diff --git a/chrome/browser/metrics/power/power_metrics_reporter_unittest.cc b/chrome/browser/metrics/power/power_metrics_reporter_unittest.cc
index 709a7a36..b239e87 100644
--- a/chrome/browser/metrics/power/power_metrics_reporter_unittest.cc
+++ b/chrome/browser/metrics/power/power_metrics_reporter_unittest.cc
@@ -29,6 +29,14 @@
 
 namespace {
 
+constexpr const char* kBatteryDischargeRateHistogramName =
+    "Power.BatteryDischargeRate2";
+constexpr const char* kBatteryDischargeModeHistogramName =
+    "Power.BatteryDischargeMode2";
+
+constexpr double kTolerableTimeElapsedRatio = 0.10;
+constexpr double kTolerablePositiveDrift = 1 + kTolerableTimeElapsedRatio;
+
 base::BatteryLevelProvider::BatteryState MakeBatteryDischargingState(
     int battery_percent) {
   return base::BatteryLevelProvider::BatteryState{
@@ -319,6 +327,43 @@
 }
 #endif
 
+TEST_F(PowerMetricsReporterUnitTest, BatteryDischargeCaptureIsTooLate) {
+  ProcessMonitor::Metrics aggregated_process_metrics = {};
+  process_monitor_.SetMetricsToReturn(aggregated_process_metrics);
+
+  // Pretend that the battery has dropped by 2%.
+  battery_states_.push(MakeBatteryDischargingState(48));
+
+  const base::TimeDelta kTooLate =
+      kLongPowerMetricsIntervalDuration * kTolerablePositiveDrift +
+      base::Microseconds(1);
+  process_monitor_.ForceSampleAllProcessesIn(power_metrics_reporter_.get(),
+                                             &task_environment_, kTooLate);
+
+  histogram_tester_.ExpectTotalCount(kBatteryDischargeRateHistogramName, 0);
+  histogram_tester_.ExpectUniqueSample(kBatteryDischargeModeHistogramName,
+                                       BatteryDischargeMode::kInvalidInterval,
+                                       1);
+}
+
+TEST_F(PowerMetricsReporterUnitTest, BatteryDischargeCaptureIsLate) {
+  ProcessMonitor::Metrics aggregated_process_metrics = {};
+  process_monitor_.SetMetricsToReturn(aggregated_process_metrics);
+
+  // Pretend that the battery has dropped by 2%.
+  battery_states_.push(MakeBatteryDischargingState(48));
+
+  const base::TimeDelta kLate =
+      kLongPowerMetricsIntervalDuration * kTolerablePositiveDrift -
+      base::Microseconds(1);
+  process_monitor_.ForceSampleAllProcessesIn(power_metrics_reporter_.get(),
+                                             &task_environment_, kLate);
+
+  histogram_tester_.ExpectTotalCount(kBatteryDischargeRateHistogramName, 1);
+  histogram_tester_.ExpectUniqueSample(kBatteryDischargeModeHistogramName,
+                                       BatteryDischargeMode::kDischarging, 1);
+}
+
 TEST_F(PowerMetricsReporterUnitTest, UKMs) {
   int fake_value = 42;
 
@@ -445,6 +490,11 @@
           fake_interval_data.longest_visible_origin_duration));
   test_ukm_recorder_.ExpectEntryMetric(
       entries[0], UkmEntry::kDeviceSleptDuringIntervalName, false);
+
+  histogram_tester_.ExpectUniqueSample(kBatteryDischargeRateHistogramName, 2500,
+                                       1);
+  histogram_tester_.ExpectUniqueSample(kBatteryDischargeModeHistogramName,
+                                       BatteryDischargeMode::kDischarging, 1);
 }
 
 TEST_F(PowerMetricsReporterUnitTest, UKMsBrowserShuttingDown) {
@@ -515,6 +565,10 @@
   test_ukm_recorder_.ExpectEntryMetric(
       entries[0], UkmEntry::kBatteryDischargeModeName,
       static_cast<int64_t>(BatteryDischargeMode::kPluggedIn));
+
+  histogram_tester_.ExpectTotalCount(kBatteryDischargeRateHistogramName, 0);
+  histogram_tester_.ExpectUniqueSample(kBatteryDischargeModeHistogramName,
+                                       BatteryDischargeMode::kPluggedIn, 1);
 }
 
 TEST_F(PowerMetricsReporterUnitTest, UKMsBatteryStateChanges) {
@@ -545,6 +599,10 @@
   test_ukm_recorder_.ExpectEntryMetric(
       entries[0], UkmEntry::kBatteryDischargeModeName,
       static_cast<int64_t>(BatteryDischargeMode::kStateChanged));
+
+  histogram_tester_.ExpectTotalCount(kBatteryDischargeRateHistogramName, 0);
+  histogram_tester_.ExpectUniqueSample(kBatteryDischargeModeHistogramName,
+                                       BatteryDischargeMode::kStateChanged, 1);
 }
 
 TEST_F(PowerMetricsReporterUnitTest, UKMsBatteryStateUnavailable) {
@@ -568,6 +626,11 @@
   test_ukm_recorder_.ExpectEntryMetric(
       entries[0], UkmEntry::kBatteryDischargeModeName,
       static_cast<int64_t>(BatteryDischargeMode::kRetrievalError));
+
+  histogram_tester_.ExpectTotalCount(kBatteryDischargeRateHistogramName, 0);
+  histogram_tester_.ExpectUniqueSample(kBatteryDischargeModeHistogramName,
+                                       BatteryDischargeMode::kRetrievalError,
+                                       1);
 }
 
 TEST_F(PowerMetricsReporterUnitTest, UKMsNoBattery) {
@@ -601,6 +664,10 @@
   test_ukm_recorder_.ExpectEntryMetric(
       entries[0], UkmEntry::kBatteryDischargeModeName,
       static_cast<int64_t>(BatteryDischargeMode::kNoBattery));
+
+  histogram_tester_.ExpectTotalCount(kBatteryDischargeRateHistogramName, 0);
+  histogram_tester_.ExpectUniqueSample(kBatteryDischargeModeHistogramName,
+                                       BatteryDischargeMode::kNoBattery, 1);
 }
 
 #if BUILDFLAG(IS_MAC)
@@ -628,6 +695,11 @@
   test_ukm_recorder_.ExpectEntryMetric(
       entries[0], UkmEntry::kBatteryDischargeModeName,
       static_cast<int64_t>(BatteryDischargeMode::kMacFullyCharged));
+
+  histogram_tester_.ExpectTotalCount(kBatteryDischargeRateHistogramName, 0);
+  histogram_tester_.ExpectUniqueSample(kBatteryDischargeModeHistogramName,
+                                       BatteryDischargeMode::kMacFullyCharged,
+                                       1);
 }
 #endif  // BUILDFLAG(IS_MAC)
 
@@ -653,6 +725,11 @@
   test_ukm_recorder_.ExpectEntryMetric(
       entries[0], UkmEntry::kBatteryDischargeModeName,
       static_cast<int64_t>(BatteryDischargeMode::kBatteryLevelIncreased));
+
+  histogram_tester_.ExpectTotalCount(kBatteryDischargeRateHistogramName, 0);
+  histogram_tester_.ExpectUniqueSample(
+      kBatteryDischargeModeHistogramName,
+      BatteryDischargeMode::kBatteryLevelIncreased, 1);
 }
 
 TEST_F(PowerMetricsReporterUnitTest, UKMsNoTab) {
diff --git a/chrome/browser/metrics/power/power_metrics_unittest.cc b/chrome/browser/metrics/power/power_metrics_unittest.cc
index 15873e47..d6b9074 100644
--- a/chrome/browser/metrics/power/power_metrics_unittest.cc
+++ b/chrome/browser/metrics/power/power_metrics_unittest.cc
@@ -180,50 +180,3 @@
       "PerformanceMonitor.ResourceCoalition.EnergyImpact.Foo", 0);
 }
 #endif  // BUILDFLAG(IS_MAC)
-
-TEST(PowerMetricsTest, CalculateDischargeRateMilliwatts_mWh) {
-  int64_t discharge_rate = CalculateDischargeRateMilliwatts(
-      base::BatteryLevelProvider::BatteryState{
-          .battery_count = 1,
-          .is_external_power_connected = false,
-          .current_capacity = 100,
-          .full_charged_capacity = 10000,
-          .charge_unit = base::BatteryLevelProvider::BatteryLevelUnit::kMWh,
-      },
-      base::BatteryLevelProvider::BatteryState{
-          .battery_count = 1,
-          .is_external_power_connected = false,
-          .current_capacity = 90,
-          .full_charged_capacity = 10000,
-          .charge_unit = base::BatteryLevelProvider::BatteryLevelUnit::kMWh,
-      },
-      base::Minutes(1));
-
-  // 10 mWh discharge in 1 minute translates to 600 mWh in 1 hour.
-  EXPECT_EQ(discharge_rate, 600);
-}
-
-TEST(PowerMetricsTest, CalculateDischargeRateMilliwatts_mAh) {
-  int64_t discharge_rate = CalculateDischargeRateMilliwatts(
-      base::BatteryLevelProvider::BatteryState{
-          .battery_count = 1,
-          .is_external_power_connected = false,
-          .current_capacity = 100,
-          .full_charged_capacity = 10000,
-          .voltage_mv = 12000,
-          .charge_unit = base::BatteryLevelProvider::BatteryLevelUnit::kMAh,
-      },
-      base::BatteryLevelProvider::BatteryState{
-          .battery_count = 1,
-          .is_external_power_connected = false,
-          .current_capacity = 90,
-          .full_charged_capacity = 10000,
-          .voltage_mv = 12000,
-          .charge_unit = base::BatteryLevelProvider::BatteryLevelUnit::kMAh,
-      },
-      base::Minutes(1));
-
-  // 10 mAh discharge in 1 minute translates to 600 mWh in 1 hour. That value is
-  // then multiplied by the voltage (12v) to get 7200 milliwatts.
-  EXPECT_EQ(discharge_rate, 7200);
-}
diff --git a/chrome/browser/obsolete_system/obsolete_system_win.cc b/chrome/browser/obsolete_system/obsolete_system_win.cc
index b97bf74..a599a80 100644
--- a/chrome/browser/obsolete_system/obsolete_system_win.cc
+++ b/chrome/browser/obsolete_system/obsolete_system_win.cc
@@ -14,8 +14,16 @@
 
 namespace {
 
+// Obsolete-system checks get the system version from kernel32.dll's version, to
+// avoid getting an incorrect version reported by App Compatibility mode. This
+// prevents obsolete-system warnings from appearing when Chrome is run in
+// compatibility mode on modern versions of Windows.
+base::win::Version GetRealOSVersion() {
+  return base::win::OSInfo::GetInstance()->Kernel32Version();
+}
+
 bool IsObsoleteOsVersion() {
-  return base::win::GetVersion() < base::win::Version::WIN10;
+  return GetRealOSVersion() < base::win::Version::WIN10;
 }
 
 }  // namespace
@@ -27,7 +35,7 @@
 
 // static
 std::u16string ObsoleteSystem::LocalizedObsoleteString() {
-  const auto version = base::win::GetVersion();
+  const auto version = GetRealOSVersion();
   if (version == base::win::Version::WIN7)
     return l10n_util::GetStringUTF16(IDS_WIN_7_OBSOLETE);
   if (version == base::win::Version::WIN8)
@@ -44,7 +52,7 @@
 
 // static
 const char* ObsoleteSystem::GetLinkURL() {
-  const auto version = base::win::GetVersion();
+  const auto version = GetRealOSVersion();
   if (version < base::win::Version::WIN7)
     return chrome::kWindowsXPVistaDeprecationURL;
   return chrome::kWindows78DeprecationURL;
diff --git a/chrome/browser/page_load_metrics/integration_tests/layout_instability_browsertest.cc b/chrome/browser/page_load_metrics/integration_tests/layout_instability_browsertest.cc
index d1cbff39..f83a0c86 100644
--- a/chrome/browser/page_load_metrics/integration_tests/layout_instability_browsertest.cc
+++ b/chrome/browser/page_load_metrics/integration_tests/layout_instability_browsertest.cc
@@ -197,3 +197,86 @@
       "PageLoad.LayoutInstability.CumulativeShiftScore",
       page_load_metrics::LayoutShiftUmaValue(0.03));
 }
+
+IN_PROC_BROWSER_TEST_F(LayoutInstabilityTest,
+                       CumulativeLayoutShift_OneSecondGap) {
+  auto waiter = std::make_unique<page_load_metrics::PageLoadMetricsTestWaiter>(
+      web_contents());
+  waiter->AddPageExpectation(
+      page_load_metrics::PageLoadMetricsTestWaiter::TimingField::kLayoutShift);
+  Start();
+  StartTracing({"loading", TRACE_DISABLED_BY_DEFAULT("layout_shift.debug")});
+  Load("/layout-instability/simple-block-movement.html");
+
+  // Wait for the first layout shift.
+  waiter->Wait();
+
+  // Have the program sleep for 1 second to ensure the one second gap
+  base::PlatformThread::Sleep(base::Milliseconds(1000));
+
+  // Simulate the layout shift and this layout shift should be in the
+  // new window session because it has been 1 second since last
+  // layout shift. The first layout shift in simple-block-movement moves
+  // the shifter to 160px and this layout shift moves the shifter to
+  // 500px, so the second layout shift has 340px distance.
+  const auto& result = ExecJs(web_contents(),
+    "("
+      "async () => {"
+        "document.querySelector('#shifter').style = \"top: 500px\";"
+        "await watcher.promise;"
+      "}"
+    ")()"
+  );
+
+  // Extract the startTime and score list from ScoreWatcher.
+  base::Value entry_records =
+      EvalJs(web_contents(), "watcher.get_entry_record()").ExtractList();
+  const auto& entry_records_list = entry_records.GetList();
+
+  // Verify that the entry_records_list has exactly 2 records.
+  EXPECT_EQ(2ul, entry_records_list.size());
+
+  // Extract the startTime and score from each records.
+  optional<double> record_startTime_one =
+      entry_records_list[0].GetDict().FindDouble("startTime");
+  optional<double> record_score_one =
+      entry_records_list[0].GetDict().FindDouble("score");
+  optional<double> record_startTime_two =
+      entry_records_list[1].GetDict().FindDouble("startTime");
+  optional<double> record_score_two =
+      entry_records_list[1].GetDict().FindDouble("score");
+
+  // Verify that the optional<double> has value.
+  ASSERT_TRUE(record_startTime_one);
+  ASSERT_TRUE(record_score_one);
+  ASSERT_TRUE(record_startTime_two);
+  ASSERT_TRUE(record_score_two);
+
+  // Verify that layout shift two happened at least 1 second after
+  // layout shift one, and it has bigger score than layout shift one.
+  EXPECT_GT(*record_startTime_two, *record_startTime_one + 1000);
+  EXPECT_GT(*record_score_two, *record_score_one);
+
+  // TODO(crbug.com/1385897): We have issue with test_waiter while there are multiple
+  // layout shifts. Should replace Sleep() with waiter->Wait() after
+  // fixing the test_waiter for layout shifts.
+  base::PlatformThread::Sleep(base::Milliseconds(1000));
+
+  // Finish session.
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
+
+  // Check UKM with CLS Normalization value, and it should be the same as the
+  // second layout shift score.
+  ExpectUKMPageLoadMetric(
+      PageLoad::
+          kLayoutInstability_MaxCumulativeShiftScore_SessionWindow_Gap1000ms_Max5000msName,
+      page_load_metrics::LayoutShiftUkmValue(*record_score_two));
+
+  // Check UMA with the second layout shift score.
+  auto samples = histogram_tester().GetAllSamples(
+      "PageLoad.LayoutInstability.CumulativeShiftScore");
+  EXPECT_EQ(1ul, samples.size());
+  EXPECT_EQ(
+      samples[0],
+      Bucket(page_load_metrics::LayoutShiftUmaValue(*record_score_two), 1));
+}
diff --git a/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer.cc
index d80d232..0168d18 100644
--- a/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer.cc
@@ -656,11 +656,6 @@
 
   // FCP is reported in OnFirstContentfulPaintInPage.
 
-  if (WasStartedInForegroundOptionalEventInForeground(
-          timing.paint_timing->first_meaningful_paint, GetDelegate())) {
-    builder.SetExperimental_PaintTiming_NavigationToFirstMeaningfulPaint(
-        timing.paint_timing->first_meaningful_paint.value().InMilliseconds());
-  }
   const page_load_metrics::ContentfulPaintTimingInfo&
       main_frame_largest_contentful_paint =
           GetDelegate()
diff --git a/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer_unittest.cc b/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer_unittest.cc
index 9e87300..4862434 100644
--- a/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer_unittest.cc
+++ b/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer_unittest.cc
@@ -313,8 +313,7 @@
 TEST_F(UkmPageLoadMetricsObserverTest, Basic) {
   // PageLoadTiming with all recorded metrics other than FMP. This allows us to
   // verify both that all metrics are logged, and that we don't log metrics that
-  // aren't present in the PageLoadTiming struct. Logging of FMP is verified in
-  // the FirstMeaningfulPaint test below.
+  // aren't present in the PageLoadTiming struct.
   page_load_metrics::mojom::PageLoadTiming timing;
   page_load_metrics::InitPageLoadTimingForTest(&timing);
   timing.navigation_start = base::Time::FromDoubleT(1);
@@ -371,10 +370,6 @@
         PageLoad::kDocumentTiming_NavigationToLoadEventFiredName, 500);
     tester()->test_ukm_recorder().ExpectEntryMetric(
         kv.second.get(), PageLoad::kNet_HttpResponseCodeName, 200);
-    EXPECT_FALSE(tester()->test_ukm_recorder().EntryHasMetric(
-        kv.second.get(),
-        PageLoad::
-            kExperimental_PaintTiming_NavigationToFirstMeaningfulPaintName));
     EXPECT_TRUE(tester()->test_ukm_recorder().EntryHasMetric(
         kv.second.get(), PageLoad::kPageTiming_ForegroundDurationName));
     EXPECT_FALSE(tester()->test_ukm_recorder().EntryHasMetric(
@@ -443,38 +438,6 @@
   }
 }
 
-TEST_F(UkmPageLoadMetricsObserverTest, FirstMeaningfulPaint) {
-  page_load_metrics::mojom::PageLoadTiming timing;
-  page_load_metrics::InitPageLoadTimingForTest(&timing);
-  timing.navigation_start = base::Time::FromDoubleT(1);
-  timing.parse_timing->parse_start = base::Milliseconds(10);
-  timing.paint_timing->first_meaningful_paint = base::Milliseconds(600);
-  PopulateRequiredTimingFields(&timing);
-
-  NavigateAndCommit(GURL(kTestUrl1));
-  tester()->SimulateTimingUpdate(timing);
-
-  // Simulate closing the tab.
-  DeleteContents();
-
-  std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries =
-      tester()->test_ukm_recorder().GetMergedEntriesByName(
-          PageLoad::kEntryName);
-  EXPECT_EQ(1ul, merged_entries.size());
-
-  for (const auto& kv : merged_entries) {
-    tester()->test_ukm_recorder().ExpectEntrySourceHasUrl(kv.second.get(),
-                                                          GURL(kTestUrl1));
-    tester()->test_ukm_recorder().ExpectEntryMetric(
-        kv.second.get(),
-        PageLoad::
-            kExperimental_PaintTiming_NavigationToFirstMeaningfulPaintName,
-        600);
-    EXPECT_TRUE(tester()->test_ukm_recorder().EntryHasMetric(
-        kv.second.get(), PageLoad::kPageTiming_ForegroundDurationName));
-  }
-}
-
 TEST_F(UkmPageLoadMetricsObserverTest, LargestImagePaint) {
   page_load_metrics::mojom::PageLoadTiming timing;
   page_load_metrics::InitPageLoadTimingForTest(&timing);
@@ -1576,10 +1539,6 @@
       page_load_metrics::END_NEW_NAVIGATION);
   tester()->test_ukm_recorder().ExpectEntryMetric(
       entry1, PageLoad::kPaintTiming_NavigationToFirstContentfulPaintName, 200);
-  EXPECT_FALSE(tester()->test_ukm_recorder().EntryHasMetric(
-      entry1,
-      PageLoad::
-          kExperimental_PaintTiming_NavigationToFirstMeaningfulPaintName));
   EXPECT_TRUE(tester()->test_ukm_recorder().EntryHasMetric(
       entry1, PageLoad::kPageTiming_ForegroundDurationName));
 
@@ -1592,10 +1551,6 @@
       entry2, PageLoad::kParseTiming_NavigationToParseStartName));
   EXPECT_FALSE(tester()->test_ukm_recorder().EntryHasMetric(
       entry2, PageLoad::kPaintTiming_NavigationToFirstContentfulPaintName));
-  EXPECT_FALSE(tester()->test_ukm_recorder().EntryHasMetric(
-      entry2,
-      PageLoad::
-          kExperimental_PaintTiming_NavigationToFirstMeaningfulPaintName));
   EXPECT_TRUE(tester()->test_ukm_recorder().EntryHasMetric(
       entry2, PageLoad::kPageTiming_ForegroundDurationName));
 }
diff --git a/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc b/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc
index af0b1153..a7ae47d 100644
--- a/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc
+++ b/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc
@@ -1522,53 +1522,6 @@
       page_load_metrics::internal::INVALID_ORDER_PARSE_START_FIRST_PAINT, 1);
 }
 
-// TODO(crbug.com/1009885): Flaky on Linux MSan builds.
-#if defined(MEMORY_SANITIZER) && (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS))
-#define MAYBE_FirstMeaningfulPaintRecorded DISABLED_FirstMeaningfulPaintRecorded
-#else
-#define MAYBE_FirstMeaningfulPaintRecorded FirstMeaningfulPaintRecorded
-#endif
-IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
-                       MAYBE_FirstMeaningfulPaintRecorded) {
-  ASSERT_TRUE(embedded_test_server()->Start());
-
-  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
-  waiter->AddPageExpectation(TimingField::kFirstMeaningfulPaint);
-  ASSERT_TRUE(ui_test_utils::NavigateToURL(
-      browser(), embedded_test_server()->GetURL("/title1.html")));
-  waiter->Wait();
-
-  histogram_tester_->ExpectUniqueSample(
-      internal::kHistogramFirstMeaningfulPaintStatus,
-      internal::FIRST_MEANINGFUL_PAINT_RECORDED, 1);
-  histogram_tester_->ExpectTotalCount(internal::kHistogramFirstMeaningfulPaint,
-                                      1);
-}
-
-IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
-                       FirstMeaningfulPaintNotRecorded) {
-  ASSERT_TRUE(embedded_test_server()->Start());
-
-  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
-  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
-
-  ASSERT_TRUE(ui_test_utils::NavigateToURL(
-      browser(), embedded_test_server()->GetURL(
-                     "/page_load_metrics/page_with_active_connections.html")));
-  waiter->Wait();
-
-  // Navigate away before a FMP is reported.
-  NavigateToUntrackedUrl();
-
-  histogram_tester_->ExpectTotalCount(internal::kHistogramFirstContentfulPaint,
-                                      1);
-  histogram_tester_->ExpectUniqueSample(
-      internal::kHistogramFirstMeaningfulPaintStatus,
-      internal::FIRST_MEANINGFUL_PAINT_DID_NOT_REACH_NETWORK_STABLE, 1);
-  histogram_tester_->ExpectTotalCount(internal::kHistogramFirstMeaningfulPaint,
-                                      0);
-}
-
 IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, PayloadSize) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
@@ -2425,9 +2378,6 @@
     histogram_tester_->ExpectTotalCount(
         internal::kHistogramSessionRestoreForegroundTabFirstContentfulPaint,
         expected_total_count);
-    histogram_tester_->ExpectTotalCount(
-        internal::kHistogramSessionRestoreForegroundTabFirstMeaningfulPaint,
-        expected_total_count);
   }
 };
 
@@ -2447,7 +2397,6 @@
     auto waiter = std::make_unique<PageLoadMetricsTestWaiter>(contents);
     waiter->AddPageExpectation(TimingField::kFirstPaint);
     waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
-    waiter->AddPageExpectation(TimingField::kFirstMeaningfulPaint);
     waiters_[contents] = std::move(waiter);
   }
 
@@ -2558,7 +2507,7 @@
 
   auto waiter = std::make_unique<PageLoadMetricsTestWaiter>(
       new_browser->tab_strip_model()->GetActiveWebContents());
-  waiter->AddPageExpectation(TimingField::kFirstMeaningfulPaint);
+  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
   ASSERT_TRUE(ui_test_utils::NavigateToURL(new_browser, GetTestURL()));
   waiter->Wait();
 
@@ -2584,7 +2533,7 @@
   // Load a new page after session restore.
   auto waiter = std::make_unique<PageLoadMetricsTestWaiter>(
       new_browser->tab_strip_model()->GetActiveWebContents());
-  waiter->AddPageExpectation(TimingField::kFirstMeaningfulPaint);
+  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
   ASSERT_TRUE(ui_test_utils::NavigateToURL(new_browser, GetTestURL()));
   waiter->Wait();
 
diff --git a/chrome/browser/payments/android/payment_app_service_bridge_unittest.cc b/chrome/browser/payments/android/payment_app_service_bridge_unittest.cc
index b528b56..979ca92 100644
--- a/chrome/browser/payments/android/payment_app_service_bridge_unittest.cc
+++ b/chrome/browser/payments/android/payment_app_service_bridge_unittest.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <memory>
 #include <vector>
 
 #include "base/bind.h"
@@ -10,6 +11,7 @@
 #include "base/memory/weak_ptr.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/payments/content/android/payment_app_service_bridge.h"
+#include "components/payments/content/payment_app_service.h"
 #include "components/payments/content/payment_manifest_web_data_service.h"
 #include "components/payments/content/payment_request_spec.h"
 #include "components/payments/core/const_csp_checker.h"
@@ -102,10 +104,11 @@
   MockCallback mock_callback;
   base::WeakPtr<PaymentAppServiceBridge> bridge =
       PaymentAppServiceBridge::Create(
-          /*number_of_factories=*/3, web_contents_->GetPrimaryMainFrame(),
-          top_origin_, spec.AsWeakPtr(), /*twa_package_name=*/GetParam(),
-          web_data_service_, /*is_off_the_record=*/false,
-          const_csp_checker.GetWeakPtr(),
+          std::make_unique<PaymentAppService>(
+              web_contents_->GetBrowserContext()),
+          web_contents_->GetPrimaryMainFrame(), top_origin_, spec.AsWeakPtr(),
+          /*twa_package_name=*/GetParam(), web_data_service_,
+          /*is_off_the_record=*/false, const_csp_checker.GetWeakPtr(),
           base::BindRepeating(&MockCallback::NotifyCanMakePaymentCalculated,
                               base::Unretained(&mock_callback)),
           base::BindRepeating(&MockCallback::NotifyPaymentAppCreated,
@@ -116,7 +119,7 @@
                          base::Unretained(&mock_callback)),
           base::BindRepeating(&MockCallback::SetCanMakePaymentEvenWithoutApps,
                               base::Unretained(&mock_callback)))
-          ->GetWeakPtr();
+          ->GetWeakPtrForTest();
 
   EXPECT_TRUE(bridge->SkipCreatingNativePaymentApps());
   EXPECT_EQ(web_contents_, bridge->GetWebContents());
diff --git a/chrome/browser/policy/messaging_layer/util/reporting_server_connector.cc b/chrome/browser/policy/messaging_layer/util/reporting_server_connector.cc
index 33f624d..f98685d1 100644
--- a/chrome/browser/policy/messaging_layer/util/reporting_server_connector.cc
+++ b/chrome/browser/policy/messaging_layer/util/reporting_server_connector.cc
@@ -8,9 +8,12 @@
 
 #include "base/bind.h"
 #include "base/callback.h"
+#include "base/json/json_writer.h"
 #include "base/logging.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/singleton.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/time/time.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/browser_process.h"
@@ -46,6 +49,68 @@
 
 namespace reporting {
 
+// Manages reporting payload size via UMA.
+class PayloadSizeUmaReporter {
+ public:
+  PayloadSizeUmaReporter() = default;
+  PayloadSizeUmaReporter(const PayloadSizeUmaReporter&) = delete;
+  PayloadSizeUmaReporter& operator=(const PayloadSizeUmaReporter&) = delete;
+  PayloadSizeUmaReporter(PayloadSizeUmaReporter&&) = default;
+  PayloadSizeUmaReporter& operator=(PayloadSizeUmaReporter&&) = default;
+
+  // Whether payload size should be reported now.
+  bool ShouldReport() const {
+    return base::Time::Now() >= last_reported_time_ + min_report_timedelta_;
+  }
+
+  // Report to UMA.
+  void Report() {
+    DCHECK_GE(request_payload_size_, 0);
+    DCHECK_GE(response_payload_size_, 0);
+
+    last_reported_time_ = base::Time::Now();
+    base::UmaHistogramCounts1M("Browser.ERP.RequestPayloadSize",
+                               request_payload_size_);
+    base::UmaHistogramCounts1M("Browser.ERP.ResponsePayloadSize",
+                               response_payload_size_);
+  }
+
+  // Update request payload size.
+  void UpdateRequestPayloadSize(const base::Value::Dict& request_payload) {
+    request_payload_size_ = GetPayloadSize(request_payload);
+  }
+
+  // Update response payload size.
+  void UpdateResponsePayloadSize(const base::Value::Dict& response_payload) {
+    response_payload_size_ = GetPayloadSize(response_payload);
+  }
+
+ private:
+  // Gets the size of payload as a JSON string.
+  static int GetPayloadSize(const base::Value::Dict& payload) {
+    std::string payload_json;
+    base::JSONWriter::Write(payload, &payload_json);
+    return static_cast<int>(payload_json.size());
+  }
+
+  // Minimum amount of time between two reports.
+  static constexpr base::TimeDelta min_report_timedelta_ = base::Hours(1);
+
+  // Last time UMA report was done. This is accessed from |Report| and
+  // |ShouldReport|, both of which of all instances of this class should only be
+  // called in the same sequence.
+  static base::Time last_reported_time_;
+
+  // Request payload size. Negative means not set yet.
+  int request_payload_size_ = -1;
+
+  // Response payload size. Negative means not set yet.
+  int response_payload_size_ = -1;
+};
+
+// static
+base::Time PayloadSizeUmaReporter::last_reported_time_{base::Time::UnixEpoch()};
+
 ReportingServerConnector::ReportingServerConnector() = default;
 
 ReportingServerConnector::~ReportingServerConnector() {
@@ -112,20 +177,35 @@
     return;
   }
 
+  // UMA for payload size
+  PayloadSizeUmaReporter payload_size_uma_reporter;
+  payload_size_uma_reporter.UpdateRequestPayloadSize(merging_payload);
+
   // Forward the `UploadEncryptedReport` to the cloud policy client.
   connector->client_->UploadEncryptedReport(
       std::move(merging_payload), std::move(context),
       base::BindOnce(
           [](ResponseCallback callback,
+             PayloadSizeUmaReporter payload_size_uma_reporter,
              absl::optional<base::Value::Dict> result) {
             if (!result.has_value()) {
               std::move(callback).Run(
                   Status(error::DATA_LOSS, "Failed to upload"));
               return;
             }
+
+            // Let UMA report the request and response payload sizes.
+            if (payload_size_uma_reporter.ShouldReport()) {
+              // Request payload has already been computed at the time of
+              // request.
+              payload_size_uma_reporter.UpdateResponsePayloadSize(
+                  result.value());
+              payload_size_uma_reporter.Report();
+            }
+
             std::move(callback).Run(std::move(result.value()));
           },
-          std::move(callback)));
+          std::move(callback), std::move(payload_size_uma_reporter)));
 }
 
 Status ReportingServerConnector::EnsureUsableCore() {
diff --git a/chrome/browser/policy/status_provider/status_provider_util.cc b/chrome/browser/policy/status_provider/status_provider_util.cc
index 58eaa74a..034d09a 100644
--- a/chrome/browser/policy/status_provider/status_provider_util.cc
+++ b/chrome/browser/policy/status_provider/status_provider_util.cc
@@ -5,6 +5,8 @@
 #include "chrome/browser/policy/status_provider/status_provider_util.h"
 
 #include "base/values.h"
+#include "chrome/browser/enterprise/identifiers/profile_id_service_factory.h"
+#include "components/enterprise/browser/identifiers/profile_id_service.h"
 #include "components/policy/core/browser/webui/policy_status_provider.h"
 #include "google_apis/gaia/gaia_auth_util.h"
 #if BUILDFLAG(IS_CHROMEOS_ASH)
@@ -56,6 +58,18 @@
     user_status.Set(policy::kDomainKey, gaia::ExtractDomainName(*username));
 }
 
+void SetProfileId(base::Value::Dict* dict, Profile* profile) {
+  CHECK(profile);
+  auto* profile_id_service =
+      enterprise::ProfileIdServiceFactory::GetForProfile(profile);
+  if (!profile_id_service)
+    return;
+
+  auto profile_id = profile_id_service->GetProfileId();
+  if (profile_id)
+    dict->Set("profileId", profile_id.value());
+}
+
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 void GetOffHoursStatus(base::Value::Dict* dict) {
   policy::off_hours::DeviceOffHoursController* off_hours_controller =
diff --git a/chrome/browser/policy/status_provider/status_provider_util.h b/chrome/browser/policy/status_provider/status_provider_util.h
index 259fe27..e10ca3b 100644
--- a/chrome/browser/policy/status_provider/status_provider_util.h
+++ b/chrome/browser/policy/status_provider/status_provider_util.h
@@ -21,6 +21,10 @@
 // domain of username.
 void SetDomainInUserStatus(base::Value::Dict& user_status);
 
+// Adds a new entry to |dict| with the enterprise profile identifier of the
+// current |profile|.
+void SetProfileId(base::Value::Dict* dict, Profile* profile);
+
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 void GetOffHoursStatus(base::Value::Dict* dict);
 
diff --git a/chrome/browser/policy/status_provider/user_cloud_policy_status_provider.cc b/chrome/browser/policy/status_provider/user_cloud_policy_status_provider.cc
index 92b27a2..19ed145 100644
--- a/chrome/browser/policy/status_provider/user_cloud_policy_status_provider.cc
+++ b/chrome/browser/policy/status_provider/user_cloud_policy_status_provider.cc
@@ -27,5 +27,6 @@
   GetUserAffiliationStatus(&dict, profile_);
   dict.Set(policy::kPolicyDescriptionKey, kUserPolicyStatusDescription);
   SetDomainInUserStatus(dict);
+  SetProfileId(&dict, profile_);
   return dict;
 }
diff --git a/chrome/browser/policy/status_provider/user_cloud_policy_status_provider_chromeos.cc b/chrome/browser/policy/status_provider/user_cloud_policy_status_provider_chromeos.cc
index ed6a852b..10adfd5 100644
--- a/chrome/browser/policy/status_provider/user_cloud_policy_status_provider_chromeos.cc
+++ b/chrome/browser/policy/status_provider/user_cloud_policy_status_provider_chromeos.cc
@@ -29,5 +29,6 @@
   GetUserManager(&dict, profile_);
   dict.Set(policy::kPolicyDescriptionKey, kUserPolicyStatusDescription);
   SetDomainInUserStatus(dict);
+  SetProfileId(&dict, profile_);
   return dict;
 }
diff --git a/chrome/browser/policy/status_provider/user_policy_status_provider_lacros.cc b/chrome/browser/policy/status_provider/user_policy_status_provider_lacros.cc
index f9e927e..ef2381e 100644
--- a/chrome/browser/policy/status_provider/user_policy_status_provider_lacros.cc
+++ b/chrome/browser/policy/status_provider/user_policy_status_provider_lacros.cc
@@ -56,5 +56,6 @@
                policy::CloudPolicyValidatorBase::Status::VALIDATION_OK));
   dict.Set(policy::kPolicyDescriptionKey, kUserPolicyStatusDescription);
   SetDomainInUserStatus(dict);
+  SetProfileId(&dict, profile_);
   return dict;
 }
diff --git a/chrome/browser/resources/bluetooth_internals/adapter_broker.js b/chrome/browser/resources/bluetooth_internals/adapter_broker.js
index 4953301..1da2ab4a 100644
--- a/chrome/browser/resources/bluetooth_internals/adapter_broker.js
+++ b/chrome/browser/resources/bluetooth_internals/adapter_broker.js
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {NativeEventTarget as EventTarget} from 'chrome://resources/js/cr/event_target.js';
-
 import {AdapterInfo, AdapterObserverInterface, AdapterObserverReceiver, AdapterRemote, ConnectResult, DiscoverySessionRemote} from './adapter.mojom-webui.js';
 import {BluetoothInternalsHandler, BluetoothInternalsHandlerRemote} from './bluetooth_internals.mojom-webui.js';
 import {Device, DeviceInfo, DeviceRemote} from './device.mojom-webui.js';
diff --git a/chrome/browser/resources/bluetooth_internals/page.js b/chrome/browser/resources/bluetooth_internals/page.js
index aece636..1bc1add 100644
--- a/chrome/browser/resources/bluetooth_internals/page.js
+++ b/chrome/browser/resources/bluetooth_internals/page.js
@@ -2,7 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {NativeEventTarget as EventTarget} from 'chrome://resources/js/cr/event_target.js';
 import {getRequiredElement} from 'chrome://resources/js/util.js';
 
 /**
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/dictation_test.js b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/dictation_test.js
index 4df2572..1dbbabb8 100644
--- a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/dictation_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/dictation_test.js
@@ -296,3 +296,23 @@
       assertEquals(
           0, this.mockAccessibilityPrivate.getSpokenFeedbackSilencedCount());
     });
+
+AX_TEST_F(
+    'DictationE2ETest', 'SurroundingInfoResetsAfterToggleOff',
+    async function() {
+      assertEquals(null, this.getInputController().surroundingInfo_);
+      this.toggleDictationOn();
+      const value = 'This is a test';
+      this.sendFinalSpeechResult(value);
+      // A surroundingTextChanged event is fired whenever the editable value
+      // or the caret index is changed.
+      this.mockInputIme.callOnSurroundingTextChanged({
+        anchor: value.length,
+        focus: value.length,
+        offset: 0,
+        text: value,
+      });
+      assertNotNullNorUndefined(this.getInputController().surroundingInfo_);
+      this.toggleDictationOff();
+      assertEquals(null, this.getInputController().surroundingInfo_);
+    });
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/dictation_test_base.js b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/dictation_test_base.js
index cda418e..331a926 100644
--- a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/dictation_test_base.js
+++ b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/dictation_test_base.js
@@ -263,6 +263,11 @@
     return accessibilityCommon.dictation_.speechParser_.pumpkinParseStrategy_;
   }
 
+  /** @return {InputController} */
+  getInputController() {
+    return accessibilityCommon.dictation_.inputController_;
+  }
+
   // Speech recognition methods.
 
   /** @param {string} transcript */
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/editing_util.js b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/editing_util.js
index 1fb1d617..469112d 100644
--- a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/editing_util.js
+++ b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/editing_util.js
@@ -274,6 +274,10 @@
     }
 
     const leftOfCaret = value.substring(0, caretIndex).trim();
+    if (!leftOfCaret) {
+      return EditingUtil.capitalize_(commitText);
+    }
+
     return EditingUtil.ENDS_WITH_END_OF_SENTENCE_REGEX_.test(leftOfCaret) ?
         EditingUtil.capitalize_(commitText) :
         EditingUtil.lowercase_(commitText);
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/editing_util_test.js b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/editing_util_test.js
index 8ace446..e83a143 100644
--- a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/editing_util_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/editing_util_test.js
@@ -427,6 +427,12 @@
   caretIndex = value.length;
   commitText = 'world';
   assertEquals(' world', f());
+
+  // The below pattern is observed in an empty gmail compose box.
+  value = '\n';
+  caretIndex = value.length;
+  commitText = 'Hello';
+  assertEquals('Hello', f());
 });
 
 AX_TEST_F('DictationEditingUtilTest', 'SmartCapitalization', function() {
@@ -475,6 +481,12 @@
   caretIndex = value.length;
   commitText = 'world';
   assertEquals('world', f());
+
+  // The below pattern is observed in an empty gmail compose box.
+  value = '\n';
+  caretIndex = value.length;
+  commitText = 'hello';
+  assertEquals('Hello', f());
 });
 
 AX_TEST_F('DictationEditingUtilTest', 'NavNextSentJa', function() {
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/input_controller.js b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/input_controller.js
index 7332a5e..a158089 100644
--- a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/input_controller.js
+++ b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/input_controller.js
@@ -131,6 +131,7 @@
     this.activeImeContextId_ = InputController.NO_ACTIVE_IME_CONTEXT_ID_;
     chrome.inputMethodPrivate.setCurrentInputMethod(this.previousImeEngineId_);
     this.previousImeEngineId_ = '';
+    this.surroundingInfo_ = null;
   }
 
   /**
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
index d9f3891..147bab14f 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
@@ -114,7 +114,6 @@
   "background/smart_sticky_mode.js",
   "background/tts_background.js",
   "background/user_action_monitor.js",
-  "common/abstract_earcons.js",
   "common/abstract_tts.js",
   "common/background_bridge.js",
   "common/braille/braille_command_data.js",
@@ -128,6 +127,7 @@
   "common/command_store.js",
   "common/composite_tts.js",
   "common/custom_automation_event.js",
+  "common/earcon_interface.js",
   "common/event_source_type.js",
   "common/extension_bridge.js",
   "common/gesture_command_data.js",
@@ -212,12 +212,12 @@
   dest_dir = chromevox_out_dir
   sources = [
     "background/background.html",
-    "background/earcons/control.wav",
-    "background/earcons/selection.wav",
-    "background/earcons/selection_reverse.wav",
-    "background/earcons/skim.wav",
-    "background/earcons/small_room_2.wav",
-    "background/earcons/static.wav",
+    "earcons/control.wav",
+    "earcons/selection.wav",
+    "earcons/selection_reverse.wav",
+    "earcons/skim.wav",
+    "earcons/small_room_2.wav",
+    "earcons/static.wav",
     "images/chromevox.svg",
     "images/close-36.png",
     "images/close-hover-36.png",
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js
index 345722c..1cf4137 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js
@@ -7,9 +7,9 @@
 import {constants} from '../../common/constants.js';
 import {CursorRange} from '../../common/cursors/range.js';
 import {InstanceChecker} from '../../common/instance_checker.js';
-import {AbstractEarcons} from '../common/abstract_earcons.js';
 import {NavBraille} from '../common/braille/nav_braille.js';
 import {CompositeTts} from '../common/composite_tts.js';
+import {EarconInterface} from '../common/earcon_interface.js';
 import {ExtensionBridge} from '../common/extension_bridge.js';
 import {LocaleOutputHelper} from '../common/locale_output_helper.js';
 import {Msgs} from '../common/msgs.js';
@@ -64,7 +64,7 @@
     /** @private {CursorRange} */
     this.currentRange_ = null;
 
-    /** @private {!AbstractEarcons} */
+    /** @private {!EarconInterface} */
     this.earcons_ = new Earcons();
 
     /** @private {boolean} */
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/chromevox.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/chromevox.js
index cfa11dbc..10d06713 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/chromevox.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/chromevox.js
@@ -6,14 +6,14 @@
  * @fileoverview Defines a global object that holds references to the three
  * different output engines.
  */
-import {AbstractEarcons} from '../common/abstract_earcons.js';
 import {BrailleInterface} from '../common/braille/braille_interface.js';
+import {EarconInterface} from '../common/earcon_interface.js';
 import {TtsInterface} from '../common/tts_interface.js';
 
 export const ChromeVox = {
   /** @type {BrailleInterface} */
   braille: null,
-  /** @type {AbstractEarcons} */
+  /** @type {EarconInterface} */
   earcons: null,
   /** @type {TtsInterface} */
   tts: null,
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js
index 14c63229..e08ab7c 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js
@@ -13,13 +13,13 @@
 import {EventGenerator} from '../../common/event_generator.js';
 import {KeyCode} from '../../common/key_code.js';
 import {RectUtil} from '../../common/rect_util.js';
-import {Earcon} from '../common/abstract_earcons.js';
 import {AbstractTts} from '../common/abstract_tts.js';
 import {NavBraille} from '../common/braille/nav_braille.js';
 import {BridgeConstants} from '../common/bridge_constants.js';
 import {BridgeHelper} from '../common/bridge_helper.js';
 import {Command, CommandStore} from '../common/command_store.js';
 import {ChromeVoxEvent, CustomAutomationEvent} from '../common/custom_automation_event.js';
+import {Earcon} from '../common/earcon_interface.js';
 import {EventSourceType} from '../common/event_source_type.js';
 import {GestureGranularity} from '../common/gesture_command_data.js';
 import {ChromeVoxKbHandler} from '../common/keyboard_handler.js';
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcon_engine.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcon_engine.js
index 0e940c9..8486b77 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcon_engine.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcon_engine.js
@@ -8,92 +8,89 @@
  * rest of the code.
  */
 
-import {Earcon} from '../common/abstract_earcons.js';
+import {Earcon} from '../common/earcon_interface.js';
 
-/**
- * EarconEngine generates ChromeVox's earcons using the web audio API.
- */
+/** EarconEngine generates ChromeVox's earcons using the web audio API. */
 export class EarconEngine {
   constructor() {
     // Public control parameters. All of these are meant to be adjustable.
 
-    /** @type {number} The output volume, as an amplification factor. */
+    /** @public {number} The output volume, as an amplification factor. */
     this.outputVolume = 1.0;
 
-    /** @type {number} The base relative pitch adjustment, in half-steps. */
+    /** @public {number} The base relative pitch adjustment, in half-steps. */
     this.basePitch = -4;
 
-    /** @type {number} The click volume, as an amplification factor. */
+    /** @public {number} The click volume, as an amplification factor. */
     this.clickVolume = 0.4;
 
     /**
-     * @type {number} The volume of the static sound, as an
+     * @public {number} The volume of the static sound, as an
      * amplification factor.
      */
     this.staticVolume = 0.2;
 
-    /** @type {number} The base delay for repeated sounds, in seconds. */
+    /** @public {number} The base delay for repeated sounds, in seconds. */
     this.baseDelay = 0.045;
 
-    /** @type {number} The base stereo panning, from -1 to 1. */
+    /** @public {number} The base stereo panning, from -1 to 1. */
     this.basePan = EarconEngine.CENTER_PAN_;
 
-    /** @type {number} The base reverb level as an amplification factor. */
+    /** @public {number} The base reverb level as an amplification factor. */
     this.baseReverb = 0.4;
 
     /**
-     * @type {string} The choice of the reverb impulse response to use.
+     * @public {string} The choice of the reverb impulse response to use.
      * Must be one of the strings from EarconEngine.REVERBS.
      */
     this.reverbSound = 'small_room_2';
 
-    /** @type {number} The base pitch for the 'wrap' sound in half-steps. */
+    /** @public {number} The base pitch for the 'wrap' sound in half-steps. */
     this.wrapPitch = 0;
 
-    /** @type {number} The base pitch for the 'alert' sound in half-steps. */
+    /** @public {number} The base pitch for the 'alert' sound in half-steps. */
     this.alertPitch = 0;
 
-    /** @type {string} The choice of base sound for most controls. */
+    /** @public {string} The choice of base sound for most controls. */
     this.controlSound = 'control';
 
     /**
-     * @type {number} The delay between sounds in the on/off sweep effect,
+     * @public {number} The delay between sounds in the on/off sweep effect,
      * in seconds.
      */
     this.sweepDelay = 0.045;
 
     /**
-     * @type {number} The delay between echos in the on/off sweep, in seconds.
+     * @public {number} The delay between echos in the on/off sweep, in seconds.
      */
     this.sweepEchoDelay = 0.15;
 
-    /** @type {number} The number of echos in the on/off sweep. */
+    /** @public {number} The number of echos in the on/off sweep. */
     this.sweepEchoCount = 3;
 
-    /** @type {number} The pitch offset of the on/off sweep, in half-steps. */
+    /** @public {number} The pitch offset of the on/off sweep, in half-steps. */
     this.sweepPitch = -7;
 
     /**
-     * @type {number} The final gain of the progress sound, as an
+     * @public {number} The final gain of the progress sound, as an
      * amplification factor.
      */
     this.progressFinalGain = 0.05;
 
-    /** @type {number} The multiplicative decay rate of the progress ticks. */
+    /** @public {number} The multiplicative decay rate of the progress ticks. */
     this.progressGain_Decay = 0.7;
 
     // Private variables.
 
-    /** @type {AudioContext} @private The audio context. */
+    /** @private {AudioContext} The audio context. */
     this.context_ = new AudioContext();
 
-    /** @type {?ConvolverNode} @private The reverb node, lazily initialized. */
+    /** @private {?ConvolverNode} The reverb node, lazily initialized. */
     this.reverbConvolver_ = null;
 
     /**
-     * @type {Object<string, AudioBuffer>} A map between the name of an
+     * @private {Object<string, AudioBuffer>} A map between the name of an
      *     audio data file and its loaded AudioBuffer.
-     * @private
      */
     this.buffers_ = {};
 
@@ -101,21 +98,17 @@
      * The source audio nodes for queued tick / tocks for progress.
      * Kept around so they can be canceled.
      *
-     * @type {Array<Array<AudioNode>>}
-     * @private
+     * @private {Array<Array<AudioNode>>}
      */
     this.progressSources_ = [];
 
-    /** @type {number} The current gain for progress sounds. @private */
+    /** @private {number} The current gain for progress sounds. */
     this.progressGain_ = 1.0;
 
-    /** @type {?number} The current time for progress sounds. @private */
+    /** @private {?number} The current time for progress sounds. */
     this.progressTime_ = this.context_.currentTime;
 
-    /**
-     * @type {?number} The setInterval ID for progress sounds.
-     * @private
-     */
+    /** @private {?number} The setInterval ID for progress sounds. */
     this.progressIntervalID_ = null;
 
     /** @private {boolean} */
@@ -387,32 +380,24 @@
     return source;
   }
 
-  /**
-   * Play the static sound.
-   */
+  /** Play the static sound. */
   onStatic() {
     this.play('static', {gain: this.staticVolume});
   }
 
-  /**
-   * Play the link sound.
-   */
+  /** Play the link sound. */
   onLink() {
     this.play('static', {gain: this.clickVolume});
     this.play(this.controlSound, {pitch: 12});
   }
 
-  /**
-   * Play the button sound.
-   */
+  /** Play the button sound. */
   onButton() {
     this.play('static', {gain: this.clickVolume});
     this.play(this.controlSound);
   }
 
-  /**
-   * Play the text field sound.
-   */
+  /** Play the text field sound. */
   onTextField() {
     this.play('static', {gain: this.clickVolume});
     this.play(
@@ -422,9 +407,7 @@
         this.controlSound, {pitch: 4, time: this.baseDelay * 1.5, gain: 0.5});
   }
 
-  /**
-   * Play the pop up button sound.
-   */
+  /** Play the pop up button sound. */
   onPopUpButton() {
     this.play('static', {gain: this.clickVolume});
 
@@ -435,43 +418,33 @@
         this.controlSound, {time: this.baseDelay * 4.5, gain: 0.2, pitch: 12});
   }
 
-  /**
-   * Play the check on sound.
-   */
+  /** Play the check on sound. */
   onCheckOn() {
     this.play('static', {gain: this.clickVolume});
     this.play(this.controlSound, {pitch: -5});
     this.play(this.controlSound, {pitch: 7, time: this.baseDelay * 2});
   }
 
-  /**
-   * Play the check off sound.
-   */
+  /** Play the check off sound. */
   onCheckOff() {
     this.play('static', {gain: this.clickVolume});
     this.play(this.controlSound, {pitch: 7});
     this.play(this.controlSound, {pitch: -5, time: this.baseDelay * 2});
   }
 
-  /**
-   * Play the smart sticky mode on sound.
-   */
+  /** Play the smart sticky mode on sound. */
   onSmartStickyModeOn() {
     this.play('static', {gain: this.clickVolume * 0.5});
     this.play(this.controlSound, {pitch: 7});
   }
 
-  /**
-   * Play the smart sticky mode off sound.
-   */
+  /** Play the smart sticky mode off sound. */
   onSmartStickyModeOff() {
     this.play('static', {gain: this.clickVolume * 0.5});
     this.play(this.controlSound, {pitch: -5});
   }
 
-  /**
-   * Play the select control sound.
-   */
+  /** Play the select control sound. */
   onSelect() {
     this.play('static', {gain: this.clickVolume});
     this.play(this.controlSound);
@@ -479,9 +452,7 @@
     this.play(this.controlSound, {time: this.baseDelay * 2});
   }
 
-  /**
-   * Play the slider sound.
-   */
+  /** Play the slider sound. */
   onSlider() {
     this.play('static', {gain: this.clickVolume});
     this.play(this.controlSound);
@@ -494,23 +465,17 @@
         this.controlSound, {time: this.baseDelay * 4, gain: 0.0625, pitch: 8});
   }
 
-  /**
-   * Play the skim sound.
-   */
+  /** Play the skim sound. */
   onSkim() {
     this.play('skim');
   }
 
-  /**
-   * Play the selection sound.
-   */
+  /** Play the selection sound. */
   onSelection() {
     this.play('selection');
   }
 
-  /**
-   * Play the selection reverse sound.
-   */
+  /** Play the selection reverse sound. */
   onSelectionReverse() {
     this.play('selection_reverse');
   }
@@ -685,23 +650,17 @@
     }
   }
 
-  /**
-   * Play the "ChromeVox On" sound.
-   */
+  /** Play the "ChromeVox On" sound. */
   onChromeVoxOn() {
     this.onChromeVoxSweep(false);
   }
 
-  /**
-   * Play the "ChromeVox Off" sound.
-   */
+  /** Play the "ChromeVox Off" sound. */
   onChromeVoxOff() {
     this.onChromeVoxSweep(true);
   }
 
-  /**
-   * Play an alert sound.
-   */
+  /** Play an alert sound. */
   onAlert() {
     const freq1 = 220 * Math.pow(EarconEngine.HALF_STEP, this.alertPitch - 2);
     const freq2 = 220 * Math.pow(EarconEngine.HALF_STEP, this.alertPitch - 3);
@@ -727,9 +686,7 @@
     this.currentTrackedEarcon_ = undefined;
   }
 
-  /**
-   * Play a wrap sound.
-   */
+  /** Play a wrap sound. */
   onWrap() {
     this.play('static', {gain: this.clickVolume * 0.3});
     const freq1 = 220 * Math.pow(EarconEngine.HALF_STEP, this.wrapPitch - 8);
@@ -816,9 +773,7 @@
         setInterval(this.generateProgressTickTocks_.bind(this), 1000);
   }
 
-  /**
-   * Stop playing any tick / tock progress sounds.
-   */
+  /** Stop playing any tick / tock progress sounds. */
   cancelProgress() {
     if (this.persistProgressTicks_) {
       return;
@@ -879,47 +834,27 @@
     this.basePan = x;
   }
 
-  /**
-   * Resets panning to default (centered).
-   */
+  /** Resets panning to default (centered). */
   resetPan() {
     this.basePan = EarconEngine.CENTER_PAN_;
   }
 }
 
-/**
- * @type {Array<string>} The list of sound data files to load.
- * @const
- */
+/* @const {Array<string>} The list of sound data files to load. */
 EarconEngine.SOUNDS =
     ['control', 'selection', 'selection_reverse', 'skim', 'static'];
 
-/**
- * @type {Array<string>} The list of reverb data files to load.
- * @const
- */
+/** @const {Array<string>} The list of reverb data files to load. */
 EarconEngine.REVERBS = ['small_room_2'];
 
-/**
- * @type {number} The scale factor for one half-step.
- * @const
- */
+/** @const {number} The scale factor for one half-step. */
 EarconEngine.HALF_STEP = Math.pow(2.0, 1.0 / 12.0);
 
-/**
- * @type {string} The base url for earcon sound resources.
- * @const
- */
-EarconEngine.BASE_URL =
-    chrome.extension.getURL('chromevox/background/earcons/');
+/** @const {string} The base url for earcon sound resources. */
+EarconEngine.BASE_URL = chrome.extension.getURL('chromevox/earcons/');
 
-/**
- * The maximum value to pass to PannerNode.setPosition.
- */
+/** The maximum value to pass to PannerNode.setPosition. */
 EarconEngine.MAX_PAN_ABS_X_POSITION = 4;
 
-/**
- * Default (centered) pan position.
- * @const {number}
- */
+/** @const {number} Default (centered) pan position. */
 EarconEngine.CENTER_PAN_ = 0;
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons.js
index 49dd7829..487d2ca 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons.js
@@ -7,14 +7,14 @@
  * auditory cues.
  */
 
-import {AbstractEarcons, Earcon} from '../common/abstract_earcons.js';
+import {Earcon, EarconInterface} from '../common/earcon_interface.js';
 import {LogType} from '../common/log_types.js';
 
 import {ChromeVoxState} from './chromevox_state.js';
 import {EarconEngine} from './earcon_engine.js';
 import {LogStore} from './logging/log_store.js';
 
-export class Earcons extends AbstractEarcons {
+export class Earcons extends EarconInterface {
   constructor() {
     super();
 
@@ -38,6 +38,16 @@
     }
   }
 
+  /** @override */
+  get enabled() {
+    return localStorage['earcons'] === 'true';
+  }
+
+  /** @override */
+  set enabled(value) {
+    localStorage['earcons'] = value;
+  }
+
   /**
    * @return {string} The human-readable name of the earcon set.
    */
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_types.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_types.js
index 2d474a70..2e2e284 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_types.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_types.js
@@ -6,7 +6,7 @@
  * @fileoverview Definitions of all types related to output.
  */
 
-import {Earcon} from '../../common/abstract_earcons.js';
+import {Earcon} from '../../common/earcon_interface.js';
 import {Spannable} from '../../common/spannable.js';
 import {ChromeVox} from '../chromevox.js';
 
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/page_load_sound_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/page_load_sound_handler.js
index 2fcb21f..1e6b45cf 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/page_load_sound_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/page_load_sound_handler.js
@@ -7,8 +7,8 @@
  */
 import {AutomationUtil} from '../../common/automation_util.js';
 import {constants} from '../../common/constants.js';
-import {Earcon} from '../common/abstract_earcons.js';
 import {ChromeVoxEvent} from '../common/custom_automation_event.js';
+import {Earcon} from '../common/earcon_interface.js';
 
 import {BaseAutomationHandler} from './base_automation_handler.js';
 import {ChromeVox} from './chromevox.js';
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/panel/panel_background.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/panel/panel_background.js
index 19a29d9..9be8949 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/panel/panel_background.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/panel/panel_background.js
@@ -8,9 +8,9 @@
  */
 import {constants} from '../../../common/constants.js';
 import {CursorRange} from '../../../common/cursors/range.js';
-import {Earcon} from '../../common/abstract_earcons.js';
 import {BridgeConstants} from '../../common/bridge_constants.js';
 import {BridgeHelper} from '../../common/bridge_helper.js';
+import {Earcon} from '../../common/earcon_interface.js';
 import {PanelBridge} from '../../common/panel_bridge.js';
 import {ALL_PANEL_MENU_NODE_DATA} from '../../common/panel_menu_data.js';
 import {QueueMode} from '../../common/tts_types.js';
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/pointer_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/pointer_handler.js
index ce6c252e..43bcd82 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/pointer_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/pointer_handler.js
@@ -8,8 +8,8 @@
  */
 import {AutomationPredicate} from '../../common/automation_predicate.js';
 import {EventGenerator} from '../../common/event_generator.js';
-import {Earcon} from '../common/abstract_earcons.js';
 import {CustomAutomationEvent} from '../common/custom_automation_event.js';
+import {Earcon} from '../common/earcon_interface.js';
 import {QueueMode} from '../common/tts_types.js';
 
 import {BaseAutomationHandler} from './base_automation_handler.js';
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/smart_sticky_mode.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/smart_sticky_mode.js
index 5172eba..fb9f94b 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/smart_sticky_mode.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/smart_sticky_mode.js
@@ -9,7 +9,7 @@
  */
 import {AutomationUtil} from '../../common/automation_util.js';
 import {CursorRange} from '../../common/cursors/range.js';
-import {Earcon} from '../common/abstract_earcons.js';
+import {Earcon} from '../common/earcon_interface.js';
 
 import {ChromeVox} from './chromevox.js';
 import {ChromeVoxState, ChromeVoxStateObserver} from './chromevox_state.js';
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/common/abstract_earcons.js b/chrome/browser/resources/chromeos/accessibility/chromevox/common/earcon_interface.js
similarity index 89%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/common/abstract_earcons.js
rename to chrome/browser/resources/chromeos/accessibility/chromevox/common/earcon_interface.js
index c2d2864..3bbc0bd 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/common/abstract_earcons.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/common/earcon_interface.js
@@ -4,15 +4,9 @@
 
 /**
  * @fileoverview Base class for implementing earcons.
- *
- * When adding earcons, please add them to getEarconName and getEarconId.
- *
  */
 
-/**
- * Earcon names.
- * @enum {string}
- */
+/** @enum {string} Earcon names. */
 export const Earcon = {
   ALERT_MODAL: 'alert_modal',
   ALERT_NONMODAL: 'alert_nonmodal',
@@ -71,7 +65,7 @@
 };
 
 
-export class AbstractEarcons {
+export class EarconInterface {
   /**
    * Plays the specified earcon sound.
    * @param {Earcon} earcon An earcon identifier.
@@ -90,23 +84,17 @@
    * Whether or not earcons are available.
    * @return {boolean} True if earcons are available.
    */
-  earconsAvailable() {
-    return true;
-  }
+  earconsAvailable() {}
 
   /**
    * Whether or not earcons are enabled.
    * @return {boolean} True if earcons are enabled.
    */
-  get enabled() {
-    return localStorage['earcons'] === 'true';
-  }
+  get enabled() {}
 
   /**
    * Set whether or not earcons are enabled.
    * @param {boolean} value True turns on earcons, false turns off earcons.
    */
-  set enabled(value) {
-    localStorage['earcons'] = value;
-  }
+  set enabled(value) {}
 }
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/alert_modal.ogg b/chrome/browser/resources/chromeos/accessibility/chromevox/earcons/alert_modal.ogg
similarity index 100%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/alert_modal.ogg
rename to chrome/browser/resources/chromeos/accessibility/chromevox/earcons/alert_modal.ogg
Binary files differ
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/alert_nonmodal.ogg b/chrome/browser/resources/chromeos/accessibility/chromevox/earcons/alert_nonmodal.ogg
similarity index 100%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/alert_nonmodal.ogg
rename to chrome/browser/resources/chromeos/accessibility/chromevox/earcons/alert_nonmodal.ogg
Binary files differ
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/button.ogg b/chrome/browser/resources/chromeos/accessibility/chromevox/earcons/button.ogg
similarity index 100%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/button.ogg
rename to chrome/browser/resources/chromeos/accessibility/chromevox/earcons/button.ogg
Binary files differ
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/check_off.ogg b/chrome/browser/resources/chromeos/accessibility/chromevox/earcons/check_off.ogg
similarity index 100%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/check_off.ogg
rename to chrome/browser/resources/chromeos/accessibility/chromevox/earcons/check_off.ogg
Binary files differ
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/check_on.ogg b/chrome/browser/resources/chromeos/accessibility/chromevox/earcons/check_on.ogg
similarity index 100%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/check_on.ogg
rename to chrome/browser/resources/chromeos/accessibility/chromevox/earcons/check_on.ogg
Binary files differ
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/control.wav b/chrome/browser/resources/chromeos/accessibility/chromevox/earcons/control.wav
similarity index 100%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/control.wav
rename to chrome/browser/resources/chromeos/accessibility/chromevox/earcons/control.wav
Binary files differ
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/editable_text.ogg b/chrome/browser/resources/chromeos/accessibility/chromevox/earcons/editable_text.ogg
similarity index 100%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/editable_text.ogg
rename to chrome/browser/resources/chromeos/accessibility/chromevox/earcons/editable_text.ogg
Binary files differ
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/ellipsis.ogg b/chrome/browser/resources/chromeos/accessibility/chromevox/earcons/ellipsis.ogg
similarity index 100%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/ellipsis.ogg
rename to chrome/browser/resources/chromeos/accessibility/chromevox/earcons/ellipsis.ogg
Binary files differ
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/invalid_keypress.ogg b/chrome/browser/resources/chromeos/accessibility/chromevox/earcons/invalid_keypress.ogg
similarity index 100%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/invalid_keypress.ogg
rename to chrome/browser/resources/chromeos/accessibility/chromevox/earcons/invalid_keypress.ogg
Binary files differ
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/link.ogg b/chrome/browser/resources/chromeos/accessibility/chromevox/earcons/link.ogg
similarity index 100%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/link.ogg
rename to chrome/browser/resources/chromeos/accessibility/chromevox/earcons/link.ogg
Binary files differ
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/list_item.ogg b/chrome/browser/resources/chromeos/accessibility/chromevox/earcons/list_item.ogg
similarity index 100%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/list_item.ogg
rename to chrome/browser/resources/chromeos/accessibility/chromevox/earcons/list_item.ogg
Binary files differ
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/listbox.ogg b/chrome/browser/resources/chromeos/accessibility/chromevox/earcons/listbox.ogg
similarity index 100%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/listbox.ogg
rename to chrome/browser/resources/chromeos/accessibility/chromevox/earcons/listbox.ogg
Binary files differ
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/long_desc.ogg b/chrome/browser/resources/chromeos/accessibility/chromevox/earcons/long_desc.ogg
similarity index 100%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/long_desc.ogg
rename to chrome/browser/resources/chromeos/accessibility/chromevox/earcons/long_desc.ogg
Binary files differ
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/math.ogg b/chrome/browser/resources/chromeos/accessibility/chromevox/earcons/math.ogg
similarity index 100%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/math.ogg
rename to chrome/browser/resources/chromeos/accessibility/chromevox/earcons/math.ogg
Binary files differ
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/object_close.ogg b/chrome/browser/resources/chromeos/accessibility/chromevox/earcons/object_close.ogg
similarity index 100%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/object_close.ogg
rename to chrome/browser/resources/chromeos/accessibility/chromevox/earcons/object_close.ogg
Binary files differ
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/object_enter.ogg b/chrome/browser/resources/chromeos/accessibility/chromevox/earcons/object_enter.ogg
similarity index 100%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/object_enter.ogg
rename to chrome/browser/resources/chromeos/accessibility/chromevox/earcons/object_enter.ogg
Binary files differ
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/object_exit.ogg b/chrome/browser/resources/chromeos/accessibility/chromevox/earcons/object_exit.ogg
similarity index 100%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/object_exit.ogg
rename to chrome/browser/resources/chromeos/accessibility/chromevox/earcons/object_exit.ogg
Binary files differ
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/object_open.ogg b/chrome/browser/resources/chromeos/accessibility/chromevox/earcons/object_open.ogg
similarity index 100%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/object_open.ogg
rename to chrome/browser/resources/chromeos/accessibility/chromevox/earcons/object_open.ogg
Binary files differ
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/object_select.ogg b/chrome/browser/resources/chromeos/accessibility/chromevox/earcons/object_select.ogg
similarity index 100%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/object_select.ogg
rename to chrome/browser/resources/chromeos/accessibility/chromevox/earcons/object_select.ogg
Binary files differ
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/page_finish_loading.ogg b/chrome/browser/resources/chromeos/accessibility/chromevox/earcons/page_finish_loading.ogg
similarity index 100%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/page_finish_loading.ogg
rename to chrome/browser/resources/chromeos/accessibility/chromevox/earcons/page_finish_loading.ogg
Binary files differ
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/page_start_loading.ogg b/chrome/browser/resources/chromeos/accessibility/chromevox/earcons/page_start_loading.ogg
similarity index 100%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/page_start_loading.ogg
rename to chrome/browser/resources/chromeos/accessibility/chromevox/earcons/page_start_loading.ogg
Binary files differ
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/recover_focus.ogg b/chrome/browser/resources/chromeos/accessibility/chromevox/earcons/recover_focus.ogg
similarity index 100%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/recover_focus.ogg
rename to chrome/browser/resources/chromeos/accessibility/chromevox/earcons/recover_focus.ogg
Binary files differ
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/selection.ogg b/chrome/browser/resources/chromeos/accessibility/chromevox/earcons/selection.ogg
similarity index 100%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/selection.ogg
rename to chrome/browser/resources/chromeos/accessibility/chromevox/earcons/selection.ogg
Binary files differ
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/selection.wav b/chrome/browser/resources/chromeos/accessibility/chromevox/earcons/selection.wav
similarity index 100%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/selection.wav
rename to chrome/browser/resources/chromeos/accessibility/chromevox/earcons/selection.wav
Binary files differ
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/selection_reverse.ogg b/chrome/browser/resources/chromeos/accessibility/chromevox/earcons/selection_reverse.ogg
similarity index 100%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/selection_reverse.ogg
rename to chrome/browser/resources/chromeos/accessibility/chromevox/earcons/selection_reverse.ogg
Binary files differ
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/selection_reverse.wav b/chrome/browser/resources/chromeos/accessibility/chromevox/earcons/selection_reverse.wav
similarity index 100%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/selection_reverse.wav
rename to chrome/browser/resources/chromeos/accessibility/chromevox/earcons/selection_reverse.wav
Binary files differ
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/skim.wav b/chrome/browser/resources/chromeos/accessibility/chromevox/earcons/skim.wav
similarity index 100%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/skim.wav
rename to chrome/browser/resources/chromeos/accessibility/chromevox/earcons/skim.wav
Binary files differ
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/skip.ogg b/chrome/browser/resources/chromeos/accessibility/chromevox/earcons/skip.ogg
similarity index 100%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/skip.ogg
rename to chrome/browser/resources/chromeos/accessibility/chromevox/earcons/skip.ogg
Binary files differ
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/small_room_2.wav b/chrome/browser/resources/chromeos/accessibility/chromevox/earcons/small_room_2.wav
similarity index 100%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/small_room_2.wav
rename to chrome/browser/resources/chromeos/accessibility/chromevox/earcons/small_room_2.wav
Binary files differ
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/static.wav b/chrome/browser/resources/chromeos/accessibility/chromevox/earcons/static.wav
similarity index 100%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/static.wav
rename to chrome/browser/resources/chromeos/accessibility/chromevox/earcons/static.wav
Binary files differ
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/wrap.ogg b/chrome/browser/resources/chromeos/accessibility/chromevox/earcons/wrap.ogg
similarity index 100%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/wrap.ogg
rename to chrome/browser/resources/chromeos/accessibility/chromevox/earcons/wrap.ogg
Binary files differ
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/wrap_edge.ogg b/chrome/browser/resources/chromeos/accessibility/chromevox/earcons/wrap_edge.ogg
similarity index 100%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/background/earcons/wrap_edge.ogg
rename to chrome/browser/resources/chromeos/accessibility/chromevox/earcons/wrap_edge.ogg
Binary files differ
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/testing/chromevox_e2e_test_base.js b/chrome/browser/resources/chromeos/accessibility/chromevox/testing/chromevox_e2e_test_base.js
index 70f348e..0f01b19 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/testing/chromevox_e2e_test_base.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/testing/chromevox_e2e_test_base.js
@@ -55,7 +55,7 @@
     await importModule(
         'NavBraille', '/chromevox/common/braille/nav_braille.js');
     await importModule(
-        ['AbstractEarcons', 'Earcon'], '/chromevox/common/abstract_earcons.js');
+        ['Earcon', 'EarconInterface'], '/chromevox/common/earcon_interface.js');
     await importModule('TtsInterface', '/chromevox/common/tts_interface.js');
     await importModule('QueueMode', '/chromevox/common/tts_types.js');
 
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/testing/mock_feedback.js b/chrome/browser/resources/chromeos/accessibility/chromevox/testing/mock_feedback.js
index 0e187a4..f8c64047 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/testing/mock_feedback.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/testing/mock_feedback.js
@@ -129,7 +129,7 @@
 
     const MockEarcons = function() {};
     MockEarcons.prototype = {
-      __proto__: AbstractEarcons.prototype,
+      __proto__: EarconInterface.prototype,
       playEarcon: this.addEarcon_.bind(this),
     };
 
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/testing/mock_feedback_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/testing/mock_feedback_test.js
index 752edd93..d5c17d1 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/testing/mock_feedback_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/testing/mock_feedback_test.js
@@ -33,11 +33,11 @@
     // Alphabetical based on file path.
     await importModule('ChromeVox', '/chromevox/background/chromevox.js');
     await importModule(
-        ['AbstractEarcons', 'Earcon'], '/chromevox/common/abstract_earcons.js');
-    await importModule(
         'BrailleInterface', '/chromevox/common/braille/braille_interface.js');
     await importModule(
         'NavBraille', '/chromevox/common/braille/nav_braille.js');
+    await importModule(
+        ['Earcon', 'EarconInterface'], '/chromevox/common/earcon_interface.js');
     await importModule('Spannable', '/chromevox/common/spannable.js');
     await importModule('QueueMode', '/chromevox/common/tts_types.js');
   }
diff --git a/chrome/browser/resources/chromeos/accessibility/common/testing/mock_input_ime.js b/chrome/browser/resources/chromeos/accessibility/common/testing/mock_input_ime.js
index 4959668..d5dc63a 100644
--- a/chrome/browser/resources/chromeos/accessibility/common/testing/mock_input_ime.js
+++ b/chrome/browser/resources/chromeos/accessibility/common/testing/mock_input_ime.js
@@ -26,6 +26,16 @@
  */
 let MockImeCommitParameters;
 
+/**
+ * @typedef {{
+ * anchor: number,
+ * focus: number,
+ * offset: number,
+ * text: string,
+ * }}
+ */
+let SurroundingInfo;
+
 /*
  * A mock chrome.input.ime API for tests.
  */
@@ -159,6 +169,18 @@
   },
 
   /**
+   * Calls listeners for chrome.input.ime.onSurroundingTextChanged with the
+   * given surroundingInfo object.
+   * @param {!SurroundingInfo} surroundingInfo
+   */
+  callOnSurroundingTextChanged(surroundingInfo) {
+    if (MockInputIme.onSurroundingTextChangedListener_) {
+      MockInputIme.onSurroundingTextChangedListener_(
+          'dictation', surroundingInfo);
+    }
+  },
+
+  /**
    * Gets the most recently set composition parameters.
    * @return {MockImeCompositionParameters}
    */
diff --git a/chrome/browser/resources/chromeos/parent_access/BUILD.gn b/chrome/browser/resources/chromeos/parent_access/BUILD.gn
index 37dd46d..fd2389db 100644
--- a/chrome/browser/resources/chromeos/parent_access/BUILD.gn
+++ b/chrome/browser/resources/chromeos/parent_access/BUILD.gn
@@ -41,6 +41,7 @@
 js_library("parent_access_app") {
   deps = [
     ":parent_access_after",
+    ":parent_access_error",
     ":parent_access_ui",
     ":parent_access_ui_handler",
     "//chrome/browser/ui/webui/ash/parent_access:mojo_bindings_webui_js",
@@ -56,6 +57,12 @@
   ]
 }
 
+js_library("parent_access_error") {
+  deps = [
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+  ]
+}
+
 js_library("parent_access_ui") {
   deps = [
     ":parent_access_controller",
@@ -82,6 +89,7 @@
   js_files = [
     "parent_access_after.js",
     "parent_access_app.js",
+    "parent_access_error.js",
     "parent_access_ui.js",
   ]
 }
diff --git a/chrome/browser/resources/chromeos/parent_access/parent_access_app.html b/chrome/browser/resources/chromeos/parent_access/parent_access_app.html
index 2648970..ac326ab90 100644
--- a/chrome/browser/resources/chromeos/parent_access/parent_access_app.html
+++ b/chrome/browser/resources/chromeos/parent_access/parent_access_app.html
@@ -3,4 +3,6 @@
     </parent-access-ui>
     <parent-access-after id="parent-access-after" slot="view">
     </parent-access-after>
-</cr-view-manager>
\ No newline at end of file
+    <parent-access-error id="parent-access-error" slot="view">
+    </parent-access-error>
+</cr-view-manager>
diff --git a/chrome/browser/resources/chromeos/parent_access/parent_access_app.js b/chrome/browser/resources/chromeos/parent_access/parent_access_app.js
index 9f64031..24a92d2f 100644
--- a/chrome/browser/resources/chromeos/parent_access/parent_access_app.js
+++ b/chrome/browser/resources/chromeos/parent_access/parent_access_app.js
@@ -7,15 +7,20 @@
 // into |window.loadTimeData|.
 import './strings.m.js';
 import './parent_access_after.js';
+import './parent_access_error.js';
 import './parent_access_ui.js';
 import 'chrome://resources/cr_elements/cr_view_manager/cr_view_manager.js';
 
 import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {ParentAccessResult} from './parent_access_ui.mojom-webui.js';
+import {getParentAccessUIHandler} from './parent_access_ui_handler.js';
+
 /** @enum {string} */
 export const Screens = {
   ONLINE_FLOW: 'parent-access-ui',
   AFTER_FLOW: 'parent-access-after',
+  ERROR: 'parent-access-error',
 };
 
 class ParentAccessApp extends PolymerElement {
@@ -50,10 +55,25 @@
       this.shadowRoot.querySelector('parent-access-after').onShowAfterScreen();
     });
 
+    this.addEventListener('show-error', () => {
+      this.onError_();
+    });
+
     // TODO(b/200187536): Show offline screen if device is offline.
     this.currentScreen_ = Screens.ONLINE_FLOW;
     /** @type {CrViewManagerElement} */ (this.$.viewManager)
         .switchView(this.currentScreen_);
   }
+
+  /**
+   * Shows an error screen, which is a terminal state for the flow.
+   * @private
+   */
+  onError_() {
+    this.currentScreen_ = Screens.ERROR;
+    /** @type {CrViewManagerElement} */ (this.$.viewManager)
+        .switchView(this.currentScreen_);
+    getParentAccessUIHandler().onParentAccessDone(ParentAccessResult.kError);
+  }
 }
 customElements.define(ParentAccessApp.is, ParentAccessApp);
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/parent_access/parent_access_error.html b/chrome/browser/resources/chromeos/parent_access/parent_access_error.html
new file mode 100644
index 0000000..61111836
--- /dev/null
+++ b/chrome/browser/resources/chromeos/parent_access/parent_access_error.html
@@ -0,0 +1,37 @@
+<style>
+  #error-screen {
+    background-color: var(--cros-bg-color-elevation-1);
+    box-sizing: border-box;
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+    justify-content: flex-start;
+    padding: 26px 24px 20px;
+  }
+
+  .google-logo {
+    width: 100px;
+  }
+
+  .error-text {
+    align-items: center;
+    display: flex;
+    font-size: 14px;
+    margin-top: 26px;
+  }
+
+  .error-title {
+    font-family: var(--cros-font-family-google-sans);
+    font-size: 24px;
+    font-weight: 500;
+    margin-top: 32px;
+  }
+</style>
+
+<div id="error-screen">
+  <img class="google-logo" src="chrome://theme/IDR_LOGO_GOOGLE_COLOR_90"
+    alt="Google">
+  </img>
+  <div class="error-title"> $i18n{errorTitle} </div>
+  <div class="error-text"> $i18n{errorDescription} </div>
+</div>
diff --git a/chrome/browser/resources/chromeos/parent_access/parent_access_error.js b/chrome/browser/resources/chromeos/parent_access/parent_access_error.js
new file mode 100644
index 0000000..f37d9214
--- /dev/null
+++ b/chrome/browser/resources/chromeos/parent_access/parent_access_error.js
@@ -0,0 +1,16 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+class ParentAccessError extends PolymerElement {
+  static get is() {
+    return 'parent-access-error';
+  }
+
+  static get template() {
+    return html`{__html_template__}`;
+  }
+}
+customElements.define(ParentAccessError.is, ParentAccessError);
diff --git a/chrome/browser/resources/chromeos/parent_access/parent_access_ui.js b/chrome/browser/resources/chromeos/parent_access/parent_access_ui.js
index 02b1975..aa436e29 100644
--- a/chrome/browser/resources/chromeos/parent_access/parent_access_ui.js
+++ b/chrome/browser/resources/chromeos/parent_access/parent_access_ui.js
@@ -99,7 +99,10 @@
         });
     this.configureUi().then(
         () => {/* success */},
-        origin => {/* TODO(b/200187536): show error page. */});
+        (error) => {
+          this.showErrorPage_();
+        },
+    );
   }
 
   async configureUi() {
@@ -117,8 +120,7 @@
 
       const oauthFetchResult = await this.parentAccessUIHandler.getOAuthToken();
       if (oauthFetchResult.status != GetOAuthTokenStatus.kSuccess) {
-        // TODO(b/200187536): show error page.
-        return;
+        throw new Error('OAuth token was not successfully fetched.');
       }
 
       const webview =
@@ -139,13 +141,18 @@
       const url = new URL(this.webviewUrl_);
       webview.src = url.toString();
 
+      webview.addEventListener('loadabort', () => {
+        this.webviewLoading = false;
+        this.showErrorPage_();
+      });
+
       // Set up the controller. It will automatically start the initialization
       // handshake with the hosted content.
       this.server = new ParentAccessController(
           webview, url.toString(), eventOriginFilter);
 
     } catch (e) {
-      // TODO(b/200187536): show error page.
+      this.showErrorPage_();
       return;
     }
 
@@ -173,7 +180,7 @@
       // the server.
       if (!(parentAccessServerMessage instanceof Object)) {
         console.error('Error initializing ParentAccessController');
-        // TODO(b/200187536): show error page
+        this.showErrorPage_();
         break;
       }
 
@@ -187,18 +194,22 @@
           }));
           break;
 
-        case ParentAccessServerMessageType.kError:
-          // TODO(b/200187536): show error page
-          break;
-
         case ParentAccessServerMessageType.kIgnore:
           continue;
 
+        case ParentAccessServerMessageType.kError:
         default:
-          // TODO(b/200187536): show error page
+          this.showErrorPage_();
           break;
       }
     }
   }
+
+  showErrorPage_() {
+    this.dispatchEvent(new CustomEvent('show-error', {
+      bubbles: true,
+      composed: true,
+    }));
+  }
 }
 customElements.define(ParentAccessUi.is, ParentAccessUi);
diff --git a/chrome/browser/resources/new_tab_page/middle_slot_promo.html b/chrome/browser/resources/new_tab_page/middle_slot_promo.html
index d7985e4..08a192d 100644
--- a/chrome/browser/resources/new_tab_page/middle_slot_promo.html
+++ b/chrome/browser/resources/new_tab_page/middle_slot_promo.html
@@ -50,18 +50,13 @@
   }
 
   img {
+    background-color: var(--color-new-tab-page-promo-image-background);
     border-radius: 50%;
     height: 20px;
     pointer-events: none;
     width: 20px;
   }
 
-  @media (prefers-color-scheme: dark) {
-    img {
-      background-color: var(--google-grey-200);
-    }
-  }
-
   #dismissPromoButton {
     --cr-icon-button-icon-size: 14px;
     --cr-icon-button-size: 20px;
diff --git a/chrome/browser/resources/ntp4/context_menu_handler.js b/chrome/browser/resources/ntp4/context_menu_handler.js
index 8678f2f..2f23c00b 100644
--- a/chrome/browser/resources/ntp4/context_menu_handler.js
+++ b/chrome/browser/resources/ntp4/context_menu_handler.js
@@ -9,7 +9,6 @@
 
 // clang-format off
 import {assertInstanceof} from 'chrome://resources/js/assert.js';
-import {NativeEventTarget as EventTarget} from 'chrome://resources/js/cr/event_target.js';
 import {EventTracker} from 'chrome://resources/js/event_tracker.js';
 
 import {dispatchPropertyChange} from './cr_deprecated.js';
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/multidevice_page/BUILD.gn
index c86a2e5..97407330 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/BUILD.gn
@@ -22,7 +22,6 @@
     ":multidevice_permissions_setup_dialog",
     ":multidevice_radio_button",
     ":multidevice_screen_lock_subpage",
-    ":multidevice_smartlock_subpage",
     ":multidevice_subpage",
     ":multidevice_task_continuation_disabled_link",
     ":multidevice_task_continuation_item",
@@ -118,27 +117,6 @@
   ]
 }
 
-js_library("multidevice_smartlock_subpage") {
-  deps = [
-    ":multidevice_constants",
-    ":multidevice_feature_behavior",
-    ":multidevice_metrics_logger",
-    "..:deep_linking_behavior",
-    "..:metrics_recorder",
-    "..:os_route",
-    "..:os_settings_routes",
-    "..:prefs_behavior",
-    "..:route_observer_behavior",
-    "../..:router",
-    "//ash/webui/common/resources:web_ui_listener_behavior",
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-  ]
-  externs_list = [
-    "$externs_path/quick_unlock_private.js",
-    "//ui/webui/resources/cr_elements/cr_radio_button/cr_radio_button_externs.js",
-  ]
-}
-
 js_library("multidevice_subpage") {
   deps = [
     ":multidevice_browser_proxy",
@@ -271,7 +249,6 @@
     "multidevice_radio_button.js",
     "multidevice_screen_lock_subpage.js",
     "multidevice_smartlock_item.js",
-    "multidevice_smartlock_subpage.js",
     "multidevice_subpage.js",
     "multidevice_task_continuation_disabled_link.js",
     "multidevice_task_continuation_item.js",
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.html b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.html
index c745861..bc64513d 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.html
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.html
@@ -166,15 +166,6 @@
       </settings-multidevice-subpage>
     </settings-subpage>
   </template>
-  <template is="dom-if" route-path="/multidevice/features/smartLock"
-      restamp>
-    <settings-subpage page-title="$i18n{easyUnlockSectionTitle}">
-      <settings-multidevice-smartlock-subpage
-          prefs="{{prefs}}"
-          page-content-data="[[pageContentData]]">
-      </settings-multidevice-smartlock-subpage>
-    </settings-subpage>
-  </template>
   <template is="dom-if" if="[[isNearbyShareSupported_]]" restamp>
     <template is="dom-if" route-path="/multidevice/nearbyshare" restamp>
       <settings-subpage page-title="$i18n{nearbyShareTitle}">
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.js
index 6b06fbe..1f371a5 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.js
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_page.js
@@ -15,12 +15,11 @@
 import './multidevice_feature_toggle.js';
 import './multidevice_notification_access_setup_dialog.js';
 import './multidevice_permissions_setup_dialog.js';
-import './multidevice_smartlock_subpage.js';
 import './multidevice_subpage.js';
 
-import {assert, assertNotReached} from 'chrome://resources/js/assert.js';
 import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/ash/common/i18n_behavior.js';
 import {WebUIListenerBehavior, WebUIListenerBehaviorInterface} from 'chrome://resources/ash/common/web_ui_listener_behavior.js';
+import {assert, assertNotReached} from 'chrome://resources/js/assert.js';
 import {beforeNextRender, html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {loadTimeData} from '../../i18n_setup.js';
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_item.html b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_item.html
index 845acbf..61e04807 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_item.html
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_item.html
@@ -4,7 +4,6 @@
   <settings-multidevice-feature-item id="smartLockItem"
       feature="[[MultiDeviceFeature.SMART_LOCK]]"
       page-content-data="[[pageContentData]]"
-      subpage-route="[[getSubpageRoute_()]]"
       is-feature-icon-hidden>
   </settings-multidevice-feature-item>
 </template>
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_item.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_item.js
index 8de043f4..2e99bc3 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_item.js
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_item.js
@@ -139,19 +139,6 @@
         feature, enabled, this.authToken.token);
     recordSettingChange();
   }
-
-  /**
-   * TODO(b/227674947): Delete method when Sign in with Smart Lock is removed.
-   * If Smart Lock Sign in is removed there is no subpage to navigate to, so we
-   * set the subpageRoute to undefined.
-   * @return {undefined | Object}
-   * @private
-   */
-  getSubpageRoute_() {
-    return loadTimeData.getBoolean('isSmartLockSignInRemoved') ?
-        undefined :
-        routes.SMART_LOCK;
-  }
 }
 
 customElements.define(
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_subpage.html b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_subpage.html
deleted file mode 100644
index 56cf92e..0000000
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_subpage.html
+++ /dev/null
@@ -1,50 +0,0 @@
-<style include="settings-shared"></style>
-<div class="settings-box first">
-  <!-- TODO(jhawkins): Remove this status text and move the toggle into
-        the subpage header section. -->
-  <div class="start">
-    <template is="dom-if" if="[[smartLockEnabled_]]" restamp>
-      $i18n{multideviceEnabled}
-    </template>
-    <template is="dom-if" if="[[!smartLockEnabled_]]" restamp>
-      $i18n{multideviceDisabled}
-    </template>
-  </div>
-  <settings-multidevice-feature-toggle
-      feature="[[MultiDeviceFeature.SMART_LOCK]]"
-      page-content-data="[[pageContentData]]"
-      deep-link-focus-id$="[[Setting.kSmartLockOnOff]]">
-  </settings-multidevice-feature-toggle>
-</div>
-<iron-collapse opened="[[smartLockEnabled_]]">
-  <div class="settings-box first line-only">
-      <h2 class="start first">
-        $i18n{multideviceSmartLockOptions}
-      </h2>
-  </div>
-  <div class="list-frame">
-    <cr-radio-group
-        selected="[[smartLockSignInEnabled_]]"
-        selectable-elements="multidevice-radio-button"
-        disabled="[[!smartLockSignInAllowed_]]"
-        on-selected-changed="onSmartLockSignInEnabledChanged_"
-        deep-link-focus-id$="[[Setting.kSmartLockUnlockOrSignIn]]">
-      <multidevice-radio-button
-          name="disabled"
-          class="list-item underbar"
-          label="$i18n{easyUnlockUnlockDeviceOnly}">
-      </multidevice-radio-button>
-      <multidevice-radio-button
-          name="enabled"
-          class="list-item"
-          label="$i18n{easyUnlockUnlockDeviceAndAllowSignin}">
-      </multidevice-radio-button>
-    </cr-radio-group>
-  </div>
-</iron-collapse>
-<template is="dom-if" if="[[showPasswordPromptDialog_]]" restamp>
-  <settings-password-prompt-dialog id="smartLockSignInPasswordPrompt"
-      on-close="onEnableSignInDialogClose_"
-      on-token-obtained="onTokenObtained_">
-  </settings-password-prompt-dialog>
-</template>
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_subpage.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_subpage.js
deleted file mode 100644
index 0f82566..0000000
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_subpage.js
+++ /dev/null
@@ -1,286 +0,0 @@
-// Copyright 2018 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.js';
-import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.js';
-import './multidevice_feature_toggle.js';
-import './multidevice_radio_button.js';
-import '../../settings_shared.css.js';
-
-import {WebUIListenerBehavior, WebUIListenerBehaviorInterface} from 'chrome://resources/ash/common/web_ui_listener_behavior.js';
-import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-
-import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route, Router} from '../../router.js';
-import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
-import {recordSettingChange} from '../metrics_recorder.js';
-import {routes} from '../os_route.js';
-import {OsSettingsRoutes} from '../os_settings_routes.js';
-import {RouteObserverBehavior, RouteObserverBehaviorInterface} from '../route_observer_behavior.js';
-
-import {MultiDeviceBrowserProxy, MultiDeviceBrowserProxyImpl} from './multidevice_browser_proxy.js';
-import {MultiDeviceFeature, MultiDeviceFeatureState, SmartLockSignInEnabledState} from './multidevice_constants.js';
-import {MultiDeviceFeatureBehavior, MultiDeviceFeatureBehaviorInterface} from './multidevice_feature_behavior.js';
-import {recordSmartLockToggleMetric, SmartLockToggleLocation} from './multidevice_metrics_logger.js';
-
-/**
- * @constructor
- * @extends {PolymerElement}
- * @implements {DeepLinkingBehaviorInterface}
- * @implements {MultiDeviceFeatureBehaviorInterface}
- * @implements {RouteObserverBehaviorInterface}
- * @implements {WebUIListenerBehaviorInterface}
- */
-const SettingsMultideviceSmartlockSubpageElementBase = mixinBehaviors(
-    [
-      DeepLinkingBehavior,
-      MultiDeviceFeatureBehavior,
-      RouteObserverBehavior,
-      WebUIListenerBehavior,
-    ],
-    PolymerElement);
-
-/** @polymer */
-class SettingsMultideviceSmartlockSubpageElement extends
-    SettingsMultideviceSmartlockSubpageElementBase {
-  static get is() {
-    return 'settings-multidevice-smartlock-subpage';
-  }
-
-  static get template() {
-    return html`{__html_template__}`;
-  }
-
-  static get properties() {
-    return {
-      /** @type {?OsSettingsRoutes} */
-      routes: {
-        type: Object,
-        value: routes,
-      },
-
-      /**
-       * True if Smart Lock is enabled.
-       * @private
-       */
-      smartLockEnabled_: {
-        type: Boolean,
-        computed: 'computeIsSmartLockEnabled_(pageContentData)',
-      },
-
-      /**
-       * Whether Smart Lock may be used to sign-in the user (as opposed to only
-       * being able to unlock the user's screen).
-       * @private {!SmartLockSignInEnabledState}
-       */
-      smartLockSignInEnabled_: {
-        type: Object,
-        value: SmartLockSignInEnabledState.DISABLED,
-      },
-
-      /**
-       * True if the user is allowed to enable Smart Lock sign-in.
-       * @private
-       */
-      smartLockSignInAllowed_: {
-        type: Boolean,
-        value: true,
-      },
-
-      /** @private */
-      showPasswordPromptDialog_: {
-        type: Boolean,
-        value: false,
-      },
-
-      /**
-       * Authentication token provided by password-prompt-dialog.
-       * @private {!chrome.quickUnlockPrivate.TokenInfo|undefined}
-       */
-      authToken_: {
-        type: Object,
-      },
-
-      /**
-       * Used by DeepLinkingBehavior to focus this page's deep links.
-       * @type {!Set<!Setting>}
-       */
-      supportedSettingIds: {
-        type: Object,
-        value: () => new Set([
-          Setting.kSmartLockOnOff,
-          Setting.kSmartLockUnlockOrSignIn,
-        ]),
-      },
-    };
-  }
-
-  constructor() {
-    super();
-
-    /** @private {!MultiDeviceBrowserProxy} */
-    this.browserProxy_ = MultiDeviceBrowserProxyImpl.getInstance();
-  }
-
-  /** @override */
-  ready() {
-    super.ready();
-
-    this.addEventListener('feature-toggle-clicked', (event) => {
-      this.onFeatureToggleClicked_(
-          /**
-           * @type {!CustomEvent<!{feature: !MultiDeviceFeature, enabled:
-           *  boolean}>}
-           */
-          (event));
-    });
-
-    this.addWebUIListener(
-        'smart-lock-signin-enabled-changed',
-        this.updateSmartLockSignInEnabled_.bind(this));
-
-    this.addWebUIListener(
-        'smart-lock-signin-allowed-changed',
-        this.updateSmartLockSignInAllowed_.bind(this));
-
-    this.browserProxy_.getSmartLockSignInEnabled().then(enabled => {
-      this.updateSmartLockSignInEnabled_(enabled);
-    });
-
-    this.browserProxy_.getSmartLockSignInAllowed().then(allowed => {
-      this.updateSmartLockSignInAllowed_(allowed);
-    });
-  }
-
-  /**
-   * @param {!Route} route
-   * @param {!Route=} oldRoute
-   */
-  currentRouteChanged(route, oldRoute) {
-    // Does not apply to this page.
-    if (route !== routes.SMART_LOCK) {
-      return;
-    }
-
-    this.attemptDeepLink();
-  }
-
-  /**
-   * Returns true if Smart Lock is an enabled feature.
-   * @return {boolean}
-   * @private
-   */
-  computeIsSmartLockEnabled_() {
-    return !!this.pageContentData &&
-        this.getFeatureState(MultiDeviceFeature.SMART_LOCK) ===
-        MultiDeviceFeatureState.ENABLED_BY_USER;
-  }
-
-  /**
-   * Updates the state of the Smart Lock 'sign-in enabled' toggle.
-   * @private
-   */
-  updateSmartLockSignInEnabled_(enabled) {
-    this.smartLockSignInEnabled_ = enabled ?
-        SmartLockSignInEnabledState.ENABLED :
-        SmartLockSignInEnabledState.DISABLED;
-  }
-
-  /**
-   * Updates the Smart Lock 'sign-in enabled' toggle such that disallowing
-   * sign-in disables the toggle.
-   * @private
-   */
-  updateSmartLockSignInAllowed_(allowed) {
-    this.smartLockSignInAllowed_ = allowed;
-  }
-
-  /** @private */
-  openPasswordPromptDialog_() {
-    this.showPasswordPromptDialog_ = true;
-  }
-
-  /**
-   * Sets the Smart Lock 'sign-in enabled' pref based on the value of the
-   * radio group representing the pref.
-   * @private
-   */
-  onSmartLockSignInEnabledChanged_() {
-    const radioGroup = this.shadowRoot.querySelector('cr-radio-group');
-    const enabled = radioGroup.selected === SmartLockSignInEnabledState.ENABLED;
-
-    if (!enabled) {
-      // No authentication check is required to disable.
-      this.browserProxy_.setSmartLockSignInEnabled(false /* enabled */);
-      recordSettingChange();
-      return;
-    }
-
-    // Toggle the enabled state back to disabled, as authentication may not
-    // succeed. The toggle state updates automatically by the pref listener.
-    radioGroup.selected = SmartLockSignInEnabledState.DISABLED;
-    this.openPasswordPromptDialog_();
-  }
-
-  /**
-   * Updates the state of the password dialog controller flag when the UI
-   * element closes.
-   * @private
-   */
-  onEnableSignInDialogClose_() {
-    this.showPasswordPromptDialog_ = false;
-
-    // If |this.authToken_| is set when the dialog has been closed, this means
-    // that the user entered the correct password into the dialog when
-    // attempting to enable SignIn with Smart Lock.
-    if (this.authToken_) {
-      this.browserProxy_.setSmartLockSignInEnabled(
-          true /* enabled */, this.authToken_.token);
-      recordSettingChange();
-    }
-
-    // Always require password entry if re-enabling SignIn with Smart Lock.
-    this.authToken_ = undefined;
-  }
-
-  /**
-   * @param {!CustomEvent<!chrome.quickUnlockPrivate.TokenInfo>} e
-   * @private
-   */
-  onTokenObtained_(e) {
-    this.authToken_ = e.detail;
-  }
-
-  /**
-   * Intercept (but do not stop propagation of) the feature-toggle-clicked event
-   * for the purpose of logging metrics.
-   * @private
-   */
-  onFeatureToggleClicked_(event) {
-    const feature = event.detail.feature;
-    const enabled = event.detail.enabled;
-
-    if (feature !== MultiDeviceFeature.SMART_LOCK) {
-      return;
-    }
-
-    const previousRoute = window.history.state &&
-        Router.getInstance().getRouteForPath(
-            /** @type {string} */ (window.history.state));
-    if (!previousRoute) {
-      return;
-    }
-
-    let toggleLocation = SmartLockToggleLocation.MULTIDEVICE_PAGE;
-    if (previousRoute === routes.LOCK_SCREEN) {
-      toggleLocation = SmartLockToggleLocation.LOCK_SCREEN_SETTINGS;
-    }
-
-    recordSmartLockToggleMetric(toggleLocation, enabled);
-  }
-}
-
-customElements.define(
-    SettingsMultideviceSmartlockSubpageElement.is,
-    SettingsMultideviceSmartlockSubpageElement);
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_subpage.html b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_subpage.html
index 919f3f880..c273e40 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_subpage.html
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_subpage.html
@@ -51,7 +51,6 @@
       <settings-multidevice-feature-item id="smartLockItem"
           feature="[[MultiDeviceFeature.SMART_LOCK]]"
           page-content-data="[[pageContentData]]"
-          subpage-route="[[getSmartLockSubpageRoute_()]]"
           deep-link-focus-id$="[[Setting.kSmartLockOnOff]]">
       </settings-multidevice-feature-item>
     </template>
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_subpage.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_subpage.js
index 72e30d65..ea7087a6 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_subpage.js
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_subpage.js
@@ -246,19 +246,6 @@
   }
 
   /**
-   * TODO(b/227674947): Delete method when Sign in with Smart Lock is removed.
-   * If Smart Lock Sign in is removed there is no subpage to navigate to, so we
-   * set the subpageRoute to undefined.
-   * @return {undefined | Object}
-   * @private
-   */
-  getSmartLockSubpageRoute_() {
-    return loadTimeData.getBoolean('isSmartLockSignInRemoved') ?
-        undefined :
-        routes.SMART_LOCK;
-  }
-
-  /**
    * @return {boolean}
    * @private
    */
diff --git a/chrome/browser/resources/settings/chromeos/os_route.js b/chrome/browser/resources/settings/chromeos/os_route.js
index c737560076..5c5d5bc98 100644
--- a/chrome/browser/resources/settings/chromeos/os_route.js
+++ b/chrome/browser/resources/settings/chromeos/os_route.js
@@ -91,9 +91,6 @@
     r.MULTIDEVICE_FEATURES = createSubpage(
         r.MULTIDEVICE, routesMojomWebui.MULTI_DEVICE_FEATURES_SUBPAGE_PATH,
         Subpage.kMultiDeviceFeatures);
-    r.SMART_LOCK = createSubpage(
-        r.MULTIDEVICE_FEATURES, routesMojomWebui.SMART_LOCK_SUBPAGE_PATH,
-        Subpage.kSmartLock);
     if (loadTimeData.getBoolean('isNearbyShareSupported')) {
       r.NEARBY_SHARE = createSubpage(
           r.MULTIDEVICE, routesMojomWebui.NEARBY_SHARE_SUBPAGE_PATH,
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.gni b/chrome/browser/resources/settings/chromeos/os_settings.gni
index 776f854..b67a76c 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.gni
+++ b/chrome/browser/resources/settings/chromeos/os_settings.gni
@@ -346,7 +346,6 @@
   "chromeos/multidevice_page/multidevice_radio_button.js",
   "chromeos/multidevice_page/multidevice_screen_lock_subpage.js",
   "chromeos/multidevice_page/multidevice_smartlock_item.js",
-  "chromeos/multidevice_page/multidevice_smartlock_subpage.js",
   "chromeos/multidevice_page/multidevice_subpage.js",
   "chromeos/multidevice_page/multidevice_task_continuation_disabled_link.js",
   "chromeos/multidevice_page/multidevice_task_continuation_item.js",
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_icons.html b/chrome/browser/resources/settings/chromeos/os_settings_icons.html
index 63039d8..8dac26e 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_icons.html
+++ b/chrome/browser/resources/settings/chromeos/os_settings_icons.html
@@ -162,6 +162,7 @@
       <g id="bluetooth-disabled" viewBox="0 0 24 24"><path d="M13 5.83l1.88 1.88-1.6 1.6 1.41 1.41 3.02-3.02L12 2h-1v5.03l2 2v-3.2zM5.41 4L4 5.41 10.59 12 5 17.59 6.41 19 11 14.41V22h1l4.29-4.29 2.3 2.29L20 18.59 5.41 4zM13 18.17v-3.76l1.88 1.88L13 18.17z"></path></g>
       <g id="chevron-left" viewBox="0 0 24 24"><path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"></path></g>
       <g id="end-of-life" viewBox="0 0 24 24"><path d="M4.574 2.916H4.55l.01.01.014-.01zM2.5 4.968v.023a.18.18 0 0 1 .01-.013l-.01-.01zm14.585 10.49v-.024l-.01.013.01.01zm.223 1.817l-.933-.95-4.106-4.11L8.026 7.99 3.675 3.635l-.942-.941-.712-.713L1 3.002l1.733 1.733A9.056 9.056 0 0 0 1.05 9.98c0 1.95.628 3.748 1.683 5.22.574.8 1.274 1.501 2.074 2.075a8.918 8.918 0 0 0 5.218 1.684 8.918 8.918 0 0 0 5.218-1.684L16.991 19l1.02-1.021-.703-.704zM15.243 2.684A8.922 8.922 0 0 0 10.025 1a8.922 8.922 0 0 0-5.218 1.684c-.005.003 4.135 4.16 4.135 4.16l1.083-1.814L15.042 11h-1.846l4.11 4.214a8.939 8.939 0 0 0 .011-10.456 9.021 9.021 0 0 0-2.074-2.074zM12 15H7.012v-3.989L4.5 11l2.227-1.876L12 14.6v.4z" fill-rule="evenodd"></path></g>
+      <g id="feedback" viewBox="0 0 24 24"><path d="M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H5.17l-.59.59-.58.58V4h16v12zm-9-4h2v2h-2zm0-6h2v4h-2z"></path></g>
       <g id="fingerprint" viewBox="0 0 24 24"><path d="M17.81 4.47c-.08 0-.16-.02-.23-.06C15.66 3.42 14 3 12.01 3c-1.98 0-3.86.47-5.57 1.41-.24.13-.54.04-.68-.2-.13-.24-.04-.55.2-.68C7.82 2.52 9.86 2 12.01 2c2.13 0 3.99.47 6.03 1.52.25.13.34.43.21.67-.09.18-.26.28-.44.28zM3.5 9.72c-.1 0-.2-.03-.29-.09-.23-.16-.28-.47-.12-.7.99-1.4 2.25-2.5 3.75-3.27C9.98 4.04 14 4.03 17.15 5.65c1.5.77 2.76 1.86 3.75 3.25.16.22.11.54-.12.7-.23.16-.54.11-.7-.12-.9-1.26-2.04-2.25-3.39-2.94-2.87-1.47-6.54-1.47-9.4.01-1.36.7-2.5 1.7-3.4 2.96-.08.14-.23.21-.39.21zm6.25 12.07c-.13 0-.26-.05-.35-.15-.87-.87-1.34-1.43-2.01-2.64-.69-1.23-1.05-2.73-1.05-4.34 0-2.97 2.54-5.39 5.66-5.39s5.66 2.42 5.66 5.39c0 .28-.22.5-.5.5s-.5-.22-.5-.5c0-2.42-2.09-4.39-4.66-4.39-2.57 0-4.66 1.97-4.66 4.39 0 1.44.32 2.77.93 3.85.64 1.15 1.08 1.64 1.85 2.42.19.2.19.51 0 .71-.11.1-.24.15-.37.15zm7.17-1.85c-1.19 0-2.24-.3-3.1-.89-1.49-1.01-2.38-2.65-2.38-4.39 0-.28.22-.5.5-.5s.5.22.5.5c0 1.41.72 2.74 1.94 3.56.71.48 1.54.71 2.54.71.24 0 .64-.03 1.04-.1.27-.05.53.13.58.41.05.27-.13.53-.41.58-.57.11-1.07.12-1.21.12zM14.91 22c-.04 0-.09-.01-.13-.02-1.59-.44-2.63-1.03-3.72-2.1-1.4-1.39-2.17-3.24-2.17-5.22 0-1.62 1.38-2.94 3.08-2.94 1.7 0 3.08 1.32 3.08 2.94 0 1.07.93 1.94 2.08 1.94s2.08-.87 2.08-1.94c0-3.77-3.25-6.83-7.25-6.83-2.84 0-5.44 1.58-6.61 4.03-.39.81-.59 1.76-.59 2.8 0 .78.07 2.01.67 3.61.1.26-.03.55-.29.64-.26.1-.55-.04-.64-.29-.49-1.31-.73-2.61-.73-3.96 0-1.2.23-2.29.68-3.24 1.33-2.79 4.28-4.6 7.51-4.6 4.55 0 8.25 3.51 8.25 7.83 0 1.62-1.38 2.94-3.08 2.94s-3.08-1.32-3.08-2.94c0-1.07-.93-1.94-2.08-1.94s-2.08.87-2.08 1.94c0 1.71.66 3.31 1.87 4.51.95.94 1.86 1.46 3.27 1.85.27.07.42.35.35.61-.05.23-.26.38-.47.38z"></path></g>
       <g id="gamepad" viewBox="0 0 24 24"><path d="M15 7.5V2H9v5.5l3 3 3-3zM7.5 9H2v6h5.5l3-3-3-3zM9 16.5V22h6v-5.5l-3-3-3 3zM16.5 9l-3 3 3 3H22V9h-5.5z"></path></g>
       <g id="headset" viewBox="0 0 24 24"><path d="M12 1c-4.97 0-9 4.03-9 9v7c0 1.66 1.34 3 3 3h3v-8H5v-2c0-3.87 3.13-7 7-7s7 3.13 7 7v2h-4v8h3c1.66 0 3-1.34 3-3v-7c0-4.97-4.03-9-9-9z"></path></g>
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_routes.js b/chrome/browser/resources/settings/chromeos/os_settings_routes.js
index 7800b0b..a4c7913 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_routes.js
+++ b/chrome/browser/resources/settings/chromeos/os_settings_routes.js
@@ -95,7 +95,6 @@
  *   PRIVACY_HUB: !Route,
  *   SEARCH: !Route,
  *   SEARCH_SUBPAGE: !Route,
- *   SMART_LOCK: !Route,
  *   SMART_PRIVACY: !Route,
  *   SMB_SHARES: !Route,
  *   STORAGE: !Route,
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.html b/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.html
index 458b08e..fa6b5c10 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.html
+++ b/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.html
@@ -99,12 +99,22 @@
     max-height: 50vh;
   }
 
+  .search-feedback-icon {
+    margin-inline-end: 6px;
+  }
+
   #noSearchResultsContainer {
     height: 24px;
     margin-inline-start: 64px;
     margin-top: var(--separator-height);
   }
 
+  #reportSearchResult {
+    display: flex;
+    justify-content: flex-end;
+    margin: 0 16px;
+  }
+
   /* The separator covers the top box shadow of the dropdown so that
     * var(--cr-elevation-3) can be used instead of custom values. */
   .separator {
@@ -154,5 +164,20 @@
     <div id="noSearchResultsContainer" hidden="[[searchResultsExist_]]">
       $i18n{searchNoResults}
     </div>
+<if expr="_google_chrome">
+    <template is="dom-if" if="[[showFeedbackButton_]]">
+      <div id="reportSearchResult">
+        <cr-button
+            id="reportSearchResultButton"
+            hidden="[[searchResultsExist_]]"
+            on-click="onSendFeedbackClick_">
+            <iron-icon class="search-feedback-icon"
+                icon="os-settings:feedback">
+            </iron-icon>
+            <span>$i18n{searchFeedbackButton}</span>
+        </cr-button>
+      </div>
+    </template>
+</if>
   </div>
 </iron-dropdown>
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.ts b/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.ts
index ce14fe41..44f4fed3 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.ts
+++ b/chrome/browser/resources/settings/chromeos/os_settings_search_box/os_settings_search_box.ts
@@ -6,10 +6,13 @@
  * @fileoverview 'os-settings-search-box' is the container for the search input
  * and settings search results.
  */
+import 'chrome://resources/cr_elements/cr_button/cr_button.js';
 import 'chrome://resources/cr_elements/cr_toolbar/cr_toolbar_search_field.js';
 import 'chrome://resources/js/focus_row.js';
 import 'chrome://resources/polymer/v3_0/iron-dropdown/iron-dropdown.js';
+import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
 import 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
+import '../../icons.html.js';
 import '../../settings_shared.css.js';
 import './os_search_result_row.js';
 
@@ -17,6 +20,7 @@
 import {CrToolbarSearchFieldElement} from 'chrome://resources/cr_elements/cr_toolbar/cr_toolbar_search_field.js';
 import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {String16} from 'chrome://resources/mojo/mojo/public/mojom/base/string16.mojom-webui.js';
 import {IronListElement} from 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
 import {afterNextRender, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
@@ -26,6 +30,7 @@
 import {Router} from '../../router.js';
 import {castExists} from '../assert_extras.js';
 import {recordSearch} from '../metrics_recorder.js';
+import {AboutPageBrowserProxy, AboutPageBrowserProxyImpl} from '../os_about_page/about_page_browser_proxy.js';
 import {routes} from '../os_route.js';
 import {combinedSearch, getPersonalizationSearchHandler, getSettingsSearchHandler, SearchResult} from '../search/combined_search_handler.js';
 
@@ -165,6 +170,13 @@
         type: Number,
         value: 0,
       },
+
+      showFeedbackButton_: {
+        type: Boolean,
+        value() {
+          return loadTimeData.getBoolean('searchFeedbackEnabled');
+        },
+      },
     };
   }
 
@@ -185,12 +197,14 @@
       null;
   private personalizationSearchResultObserverReceiver_:
       PersonalizationSearchResultsObserverReceiver|null;
+  private aboutPageBrowserProxy_: AboutPageBrowserProxy;
 
   constructor() {
     super();
 
     this.settingsSearchResultObserverReceiver_ = null;
     this.personalizationSearchResultObserverReceiver_ = null;
+    this.aboutPageBrowserProxy_ = AboutPageBrowserProxyImpl.getInstance();
   }
 
   override ready() {
@@ -520,6 +534,12 @@
     this.getSelectedOsSearchResultRow_().scrollIntoViewIfNeeded();
   }
 
+  // <if expr="_google_chrome">
+  private onSendFeedbackClick_() {
+    this.aboutPageBrowserProxy_.openFeedbackDialog();
+  }
+  // </if>
+
   /**
    * Keydown handler to specify how enter-key, arrow-up key, and arrow-down-key
    * interacts with search results in the dropdown. Note that 'Enter' on keyDown
diff --git a/chrome/browser/safe_browsing/safe_browsing_service.cc b/chrome/browser/safe_browsing/safe_browsing_service.cc
index de95f6e..8847322a 100644
--- a/chrome/browser/safe_browsing/safe_browsing_service.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_service.cc
@@ -25,6 +25,7 @@
 #include "build/build_config.h"
 #include "chrome/browser/browser_features.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/download/download_item_warning_data.h"
 #include "chrome/browser/net/system_network_context_manager.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
@@ -61,6 +62,7 @@
 #include "components/safe_browsing/core/browser/referrer_chain_provider.h"
 #include "components/safe_browsing/core/browser/safe_browsing_metrics_collector.h"
 #include "components/safe_browsing/core/common/features.h"
+#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
 #include "components/safe_browsing/core/common/safebrowsing_constants.h"
 #include "components/unified_consent/pref_names.h"
 #include "content/public/browser/browser_context.h"
@@ -107,6 +109,18 @@
     base::UmaHistogramCounts10000("SafeBrowsing.CookieAgeHours", age.InHours());
   }
 }
+
+#if BUILDFLAG(FULL_SAFE_BROWSING)
+void PopulateDownloadWarningActions(download::DownloadItem* download,
+                                    ClientSafeBrowsingReportRequest* report) {
+  for (auto& event :
+       DownloadItemWarningData::GetWarningActionEvents(download)) {
+    report->mutable_download_warning_actions()->Add(
+        DownloadItemWarningData::ConstructCsbrrDownloadWarningAction(event));
+  }
+}
+#endif
+
 }  // namespace
 
 // static
@@ -471,6 +485,10 @@
   std::string token = DownloadProtectionService::GetDownloadPingToken(download);
   if (!token.empty())
     report->set_token(token);
+  if (IsExtendedReportingEnabled(*profile->GetPrefs()) &&
+      base::FeatureList::IsEnabled(kSafeBrowsingCsbrrNewDownloadTrigger)) {
+    PopulateDownloadWarningActions(download, report.get());
+  }
   return ChromePingManagerFactory::GetForBrowserContext(profile)
              ->ReportThreatDetails(std::move(report)) ==
          PingManager::ReportThreatDetailsResult::SUCCESS;
diff --git a/chrome/browser/safe_browsing/safe_browsing_service_unittest.cc b/chrome/browser/safe_browsing/safe_browsing_service_unittest.cc
index 1a10be4..16ac9f49 100644
--- a/chrome/browser/safe_browsing/safe_browsing_service_unittest.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_service_unittest.cc
@@ -7,6 +7,7 @@
 
 #include "base/test/bind.h"
 #include "build/build_config.h"
+#include "chrome/browser/download/download_item_warning_data.h"
 #include "chrome/browser/safe_browsing/chrome_ping_manager_factory.h"
 #include "chrome/browser/safe_browsing/download_protection/download_protection_service.h"
 #include "chrome/test/base/testing_browser_process.h"
@@ -15,24 +16,37 @@
 #include "components/download/public/common/mock_download_item.h"
 #include "components/safe_browsing/content/browser/safe_browsing_service_interface.h"
 #include "components/safe_browsing/core/browser/ping_manager.h"
+#include "components/safe_browsing/core/common/features.h"
 #include "components/safe_browsing/core/common/proto/csd.pb.h"
+#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
 #include "content/public/browser/download_item_utils.h"
-#include "content/public/browser/global_routing_id.h"
 #include "content/public/test/browser_task_environment.h"
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
 #include "services/network/test/test_url_loader_factory.h"
 #include "services/network/test/test_utils.h"
-#include "testing/gtest/include/gtest/gtest.h"
 
 using network::GetUploadData;
 using testing::Return;
 using testing::ReturnRef;
 
+using WarningSurface = DownloadItemWarningData::WarningSurface;
+using WarningAction = DownloadItemWarningData::WarningAction;
+using WarningActionEvent = DownloadItemWarningData::WarningActionEvent;
+using DownloadWarningAction =
+    safe_browsing::ClientSafeBrowsingReportRequest::DownloadWarningAction;
+
 namespace safe_browsing {
 
+namespace {
+const char kTestDownloadUrl[] = "https://example.com";
+}
+
 class SafeBrowsingServiceTest : public testing::Test {
  public:
-  SafeBrowsingServiceTest() = default;
+  SafeBrowsingServiceTest() {
+    feature_list_.InitAndEnableFeature(
+        safe_browsing::kSafeBrowsingCsbrrNewDownloadTrigger);
+  }
 
   void SetUp() override {
     browser_process_ = TestingBrowserProcess::GetGlobal();
@@ -68,61 +82,167 @@
   Profile* profile() { return profile_.get(); }
 
  protected:
+  void SetUpDownload() {
+    content::DownloadItemUtils::AttachInfoForTesting(&download_item_, profile(),
+                                                     /*web_contents=*/nullptr);
+    EXPECT_CALL(download_item_, GetDangerType())
+        .WillRepeatedly(Return(download::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST));
+    EXPECT_CALL(download_item_, GetURL())
+        .WillRepeatedly(ReturnRef(download_url_));
+
+    DownloadProtectionService::SetDownloadProtectionData(
+        &download_item_, "download_token",
+        ClientDownloadResponse::DANGEROUS_HOST,
+        ClientDownloadResponse::TailoredVerdict());
+    DownloadItemWarningData::AddWarningActionEvent(
+        &download_item_, WarningSurface::BUBBLE_MAINPAGE, WarningAction::SHOWN);
+    DownloadItemWarningData::AddWarningActionEvent(
+        &download_item_, WarningSurface::BUBBLE_SUBPAGE, WarningAction::CLOSE);
+    DownloadItemWarningData::AddWarningActionEvent(
+        &download_item_, WarningSurface::DOWNLOADS_PAGE,
+        WarningAction::DISCARD);
+  }
+
+  std::unique_ptr<ClientSafeBrowsingReportRequest> GetActualRequest(
+      const network::ResourceRequest& request) {
+    std::string request_string = GetUploadData(request);
+    auto actual_request = std::make_unique<ClientSafeBrowsingReportRequest>();
+    actual_request->ParseFromString(request_string);
+    return actual_request;
+  }
+
+  void VerifyDownloadReportRequest(
+      ClientSafeBrowsingReportRequest* actual_request) {
+    EXPECT_EQ(actual_request->type(),
+              ClientSafeBrowsingReportRequest::DANGEROUS_DOWNLOAD_OPENED);
+    EXPECT_EQ(actual_request->download_verdict(),
+              ClientDownloadResponse::DANGEROUS_HOST);
+    EXPECT_EQ(actual_request->url(), download_url_.spec());
+    EXPECT_TRUE(actual_request->did_proceed());
+    EXPECT_TRUE(actual_request->show_download_in_folder());
+    EXPECT_EQ(actual_request->token(), "download_token");
+  }
+
+  void VerifyDownloadWarningAction(
+      const ClientSafeBrowsingReportRequest::DownloadWarningAction&
+          warning_action,
+      DownloadWarningAction::Surface surface,
+      DownloadWarningAction::Action action,
+      bool is_terminal_action,
+      int64_t interval_msec) {
+    EXPECT_EQ(warning_action.surface(), surface);
+    EXPECT_EQ(warning_action.action(), action);
+    EXPECT_EQ(warning_action.is_terminal_action(), is_terminal_action);
+    EXPECT_EQ(warning_action.interval_msec(), interval_msec);
+  }
+
   content::BrowserTaskEnvironment task_environment_;
   raw_ptr<TestingBrowserProcess> browser_process_;
   scoped_refptr<SafeBrowsingService> sb_service_;
   std::unique_ptr<TestingProfile> profile_;
+
+  ::testing::NiceMock<download::MockDownloadItem> download_item_;
+  GURL download_url_ = GURL(kTestDownloadUrl);
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
 };
 
 TEST_F(SafeBrowsingServiceTest, SendDownloadReport_Success) {
-  std::unique_ptr<download::MockDownloadItem> download_item =
-      std::make_unique<::testing::NiceMock<download::MockDownloadItem>>();
-  const GURL url("http://example.com/");
-  ClientSafeBrowsingReportRequest::ReportType report_type =
-      ClientSafeBrowsingReportRequest::DANGEROUS_DOWNLOAD_OPENED;
-  ClientDownloadResponse::Verdict download_verdict =
-      ClientDownloadResponse::DANGEROUS_HOST;
-  download::DownloadDangerType danger_type =
-      download::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST;
-  bool did_proceed = true;
-  bool show_download_in_folder = true;
-  std::string token = "download_token";
-
-  content::DownloadItemUtils::AttachInfo(download_item.get(), profile(),
-                                         /*web_contents=*/nullptr,
-                                         content::GlobalRenderFrameHostId());
-  EXPECT_CALL(*download_item, GetDangerType())
-      .WillRepeatedly(Return(danger_type));
-  EXPECT_CALL(*download_item, GetURL()).WillRepeatedly(ReturnRef(url));
-
-  DownloadProtectionService::SetDownloadProtectionData(
-      download_item.get(), token, download_verdict,
-      ClientDownloadResponse::TailoredVerdict());
+  SetUpDownload();
+  SetExtendedReportingPrefForTests(profile_->GetPrefs(), true);
 
   auto* ping_manager =
       ChromePingManagerFactory::GetForBrowserContext(profile());
   network::TestURLLoaderFactory test_url_loader_factory;
   test_url_loader_factory.SetInterceptor(
       base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
-        std::string request_string = GetUploadData(request);
-        auto actual_request =
-            std::make_unique<ClientSafeBrowsingReportRequest>();
-        actual_request->ParseFromString(request_string);
-        EXPECT_EQ(actual_request->type(), report_type);
-        EXPECT_EQ(actual_request->download_verdict(), download_verdict);
-        EXPECT_EQ(actual_request->url(), url.spec());
-        EXPECT_EQ(actual_request->did_proceed(), did_proceed);
-        EXPECT_EQ(actual_request->show_download_in_folder(),
-                  show_download_in_folder);
-        EXPECT_EQ(actual_request->token(), token);
+        std::unique_ptr<ClientSafeBrowsingReportRequest> actual_request =
+            GetActualRequest(request);
+        VerifyDownloadReportRequest(actual_request.get());
+        ASSERT_EQ(actual_request->download_warning_actions().size(), 2);
+        VerifyDownloadWarningAction(
+            actual_request->download_warning_actions().Get(0),
+            DownloadWarningAction::BUBBLE_SUBPAGE, DownloadWarningAction::CLOSE,
+            /*is_terminal_action=*/false, /*interval_msec=*/0);
+        VerifyDownloadWarningAction(
+            actual_request->download_warning_actions().Get(1),
+            DownloadWarningAction::DOWNLOADS_PAGE,
+            DownloadWarningAction::DISCARD,
+            /*is_terminal_action=*/true, /*interval_msec=*/0);
       }));
   ping_manager->SetURLLoaderFactoryForTesting(
       base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
           &test_url_loader_factory));
 
-  bool is_successful = sb_service_->SendDownloadReport(
-      download_item.get(), report_type, did_proceed, show_download_in_folder);
-  EXPECT_TRUE(is_successful);
+  EXPECT_TRUE(sb_service_->SendDownloadReport(
+      &download_item_,
+      ClientSafeBrowsingReportRequest::DANGEROUS_DOWNLOAD_OPENED,
+      /*did_proceed=*/true,
+      /*show_download_in_folder=*/true));
+}
+
+TEST_F(
+    SafeBrowsingServiceTest,
+    SendDownloadReport_NoDownloadWarningActionWhenExtendedReportingDisabled) {
+  SetUpDownload();
+  SetExtendedReportingPrefForTests(profile_->GetPrefs(), false);
+
+  auto* ping_manager =
+      ChromePingManagerFactory::GetForBrowserContext(profile());
+  network::TestURLLoaderFactory test_url_loader_factory;
+  test_url_loader_factory.SetInterceptor(
+      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
+        std::unique_ptr<ClientSafeBrowsingReportRequest> actual_request =
+            GetActualRequest(request);
+        EXPECT_TRUE(actual_request->download_warning_actions().empty());
+      }));
+  ping_manager->SetURLLoaderFactoryForTesting(
+      base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+          &test_url_loader_factory));
+
+  EXPECT_TRUE(sb_service_->SendDownloadReport(
+      &download_item_,
+      ClientSafeBrowsingReportRequest::DANGEROUS_DOWNLOAD_OPENED,
+      /*did_proceed=*/true,
+      /*show_download_in_folder=*/true));
+}
+
+class SafeBrowsingServiceTestWithCsbrrNewTriggerDisabled
+    : public SafeBrowsingServiceTest {
+ public:
+  SafeBrowsingServiceTestWithCsbrrNewTriggerDisabled() {
+    feature_list_.InitAndDisableFeature(
+        safe_browsing::kSafeBrowsingCsbrrNewDownloadTrigger);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+TEST_F(SafeBrowsingServiceTestWithCsbrrNewTriggerDisabled,
+       SendDownloadReport_NoDownloadWarningActionWhenFeatureFlagDisabled) {
+  SetUpDownload();
+  SetExtendedReportingPrefForTests(profile_->GetPrefs(), true);
+
+  auto* ping_manager =
+      ChromePingManagerFactory::GetForBrowserContext(profile());
+  network::TestURLLoaderFactory test_url_loader_factory;
+  test_url_loader_factory.SetInterceptor(
+      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
+        std::unique_ptr<ClientSafeBrowsingReportRequest> actual_request =
+            GetActualRequest(request);
+        EXPECT_TRUE(actual_request->download_warning_actions().empty());
+      }));
+  ping_manager->SetURLLoaderFactoryForTesting(
+      base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+          &test_url_loader_factory));
+
+  EXPECT_TRUE(sb_service_->SendDownloadReport(
+      &download_item_,
+      ClientSafeBrowsingReportRequest::DANGEROUS_DOWNLOAD_OPENED,
+      /*did_proceed=*/true,
+      /*show_download_in_folder=*/true));
 }
 
 }  // namespace safe_browsing
diff --git a/chrome/browser/supervised_user/web_approvals_manager.cc b/chrome/browser/supervised_user/web_approvals_manager.cc
index d7955e2..b29f3b4 100644
--- a/chrome/browser/supervised_user/web_approvals_manager.cc
+++ b/chrome/browser/supervised_user/web_approvals_manager.cc
@@ -86,8 +86,8 @@
       return "Approved";
     case ash::ParentAccessDialog::Result::Status::kDeclined:
       return "Declined";
-    case ash::ParentAccessDialog::Result::Status::kCancelled:
-      return "Cancelled";
+    case ash::ParentAccessDialog::Result::Status::kCanceled:
+      return "Canceled";
     case ash::ParentAccessDialog::Result::Status::kError:
       return "Error";
   }
@@ -106,7 +106,7 @@
       histogram_enum =
           WebApprovalsManager::LocalApprovalResultMetric::kDeclined;
       break;
-    case ash::ParentAccessDialog::Result::Status::kCancelled:
+    case ash::ParentAccessDialog::Result::Status::kCanceled:
       histogram_enum =
           WebApprovalsManager::LocalApprovalResultMetric::kCanceled;
       break;
diff --git a/chrome/browser/supervised_user/web_approvals_manager.h b/chrome/browser/supervised_user/web_approvals_manager.h
index 5238050..ae2990b 100644
--- a/chrome/browser/supervised_user/web_approvals_manager.h
+++ b/chrome/browser/supervised_user/web_approvals_manager.h
@@ -149,7 +149,7 @@
   FRIEND_TEST_ALL_PREFIXES(WebApprovalsManagerTest,
                            LocalWebApprovalDeclinedChromeOSTest);
   FRIEND_TEST_ALL_PREFIXES(WebApprovalsManagerTest,
-                           LocalWebApprovalCancelledChromeOSTest);
+                           LocalWebApprovalCanceledChromeOSTest);
   FRIEND_TEST_ALL_PREFIXES(WebApprovalsManagerTest,
                            LocalWebApprovalErrorChromeOSTest);
 
diff --git a/chrome/browser/supervised_user/web_approvals_manager_unittest.cc b/chrome/browser/supervised_user/web_approvals_manager_unittest.cc
index 65f9daa2..5d2b53d 100644
--- a/chrome/browser/supervised_user/web_approvals_manager_unittest.cc
+++ b/chrome/browser/supervised_user/web_approvals_manager_unittest.cc
@@ -236,7 +236,7 @@
       WebApprovalsManager::GetLocalApprovalDurationMillisecondsHistogram(),
       elapsed_time, 1);
 
-  // Receive a request cancelled by the parent.
+  // Receive a request canceled by the parent.
   // Check that no duration metric is recorded for incomplete requests.
   web_approvals_manager().OnLocalApprovalRequestCompleted(
       &supervisedUserSettingsServiceMock, url, start_time,
@@ -338,7 +338,7 @@
       approval_duration, 1);
 }
 
-TEST_F(WebApprovalsManagerTest, LocalWebApprovalCancelledChromeOSTest) {
+TEST_F(WebApprovalsManagerTest, LocalWebApprovalCanceledChromeOSTest) {
   base::HistogramTester histogram_tester;
   const GURL url("http://www.example.com");
 
@@ -349,7 +349,7 @@
       .Times(0);
 
   auto dialog_result = std::make_unique<ash::ParentAccessDialog::Result>();
-  dialog_result->status = ash::ParentAccessDialog::Result::Status::kCancelled;
+  dialog_result->status = ash::ParentAccessDialog::Result::Status::kCanceled;
 
   // Capture approval start time and forward clock by the fake approval
   // duration.
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index 2acbebb..b6e9522 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -3192,6 +3192,24 @@
       <message name="IDS_CORMORANT_CREATOR_ACTIVITY" desc="Menu item to launch the Cormorant creator activity. This is used only for testing." translateable="false">
         Creator Profile
       </message>
+      <message name="IDS_CORMORANT_CREATOR_OFFLINE_ERROR_TITLE" desc="Creator page error title when the user is offline.">
+        You're offline
+      </message>
+      <message name="IDS_CORMORANT_CREATOR_OFFLINE_ERROR_DESCRIPTION" desc="Creator page error description when the user is offline.">
+        Check your internet connection and try again
+      </message>
+      <message name="IDS_CORMORANT_CREATOR_CONTENT_UNAVAILABLE_ERROR_TITLE" desc="Creator page error title when the creator has no content available.">
+        Nothing to show yet
+      </message>
+      <message name="IDS_CORMORANT_CREATOR_CONTENT_UNAVAILABLE_ERROR_DESCRIPTION" desc="Creator page error description when the creator has no content available.">
+        You'll see content when it becomes available
+      </message>
+      <message name="IDS_CORMORANT_CREATOR_GENERAL_ERROR_TITLE" desc="Creator page general error title when something goes wrong.">
+        Something went wrong
+      </message>
+      <message name="IDS_CORMORANT_CREATOR_GENERAL_ERROR_DESCRIPTION" desc="Creator page general error description when something goes wrong.">
+        Try again later
+      </message>
       <message name="IDS_FEED_MANAGE_ACTIVITY" desc="Menu item to manage my activity from the feed header overflow menu.">
         Activity
       </message>
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_CORMORANT_CREATOR_CONTENT_UNAVAILABLE_ERROR_DESCRIPTION.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_CORMORANT_CREATOR_CONTENT_UNAVAILABLE_ERROR_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..6424c98
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_CORMORANT_CREATOR_CONTENT_UNAVAILABLE_ERROR_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+6f21b6d74d447e1b0a77f7294c01215e79950118
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_CORMORANT_CREATOR_CONTENT_UNAVAILABLE_ERROR_TITLE.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_CORMORANT_CREATOR_CONTENT_UNAVAILABLE_ERROR_TITLE.png.sha1
new file mode 100644
index 0000000..6424c98
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_CORMORANT_CREATOR_CONTENT_UNAVAILABLE_ERROR_TITLE.png.sha1
@@ -0,0 +1 @@
+6f21b6d74d447e1b0a77f7294c01215e79950118
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_CORMORANT_CREATOR_GENERAL_ERROR_DESCRIPTION.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_CORMORANT_CREATOR_GENERAL_ERROR_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..7d415c7
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_CORMORANT_CREATOR_GENERAL_ERROR_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+879c2f16e1633925f6d658a2abf88eefbe0e0136
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_CORMORANT_CREATOR_GENERAL_ERROR_TITLE.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_CORMORANT_CREATOR_GENERAL_ERROR_TITLE.png.sha1
new file mode 100644
index 0000000..7d415c7
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_CORMORANT_CREATOR_GENERAL_ERROR_TITLE.png.sha1
@@ -0,0 +1 @@
+879c2f16e1633925f6d658a2abf88eefbe0e0136
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_CORMORANT_CREATOR_OFFLINE_ERROR_DESCRIPTION.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_CORMORANT_CREATOR_OFFLINE_ERROR_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..5c2257d
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_CORMORANT_CREATOR_OFFLINE_ERROR_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+abca6167318fc6bf61ea1d6f54fdbd514538405c
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_CORMORANT_CREATOR_OFFLINE_ERROR_TITLE.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_CORMORANT_CREATOR_OFFLINE_ERROR_TITLE.png.sha1
new file mode 100644
index 0000000..5c2257d
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_CORMORANT_CREATOR_OFFLINE_ERROR_TITLE.png.sha1
@@ -0,0 +1 @@
+abca6167318fc6bf61ea1d6f54fdbd514538405c
\ No newline at end of file
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
index de21360..5c01d8b 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
@@ -1619,10 +1619,13 @@
 
     private ToolbarSnapshotState generateToolbarSnapshotState() {
         UrlBarData urlBarData = getToolbarDataProvider().getUrlBarData();
+        String displayedUrlText = urlBarData.displayText.toString();
+        CharSequence prefixHint = mLocationBar.getOmniboxVisibleTextPrefixHint();
+        boolean isValidPrefixHint =
+                ToolbarSnapshotState.isValidVisibleTextPrefixHint(displayedUrlText, prefixHint);
         return new ToolbarSnapshotState(getTint().getDefaultColor(),
-                mTabCountProvider.getTabCount(), mButtonData, mVisualState,
-                urlBarData.displayText.toString(),
-                mLocationBar.getOmniboxVisibleTextPrefixHint(),
+                mTabCountProvider.getTabCount(), mButtonData, mVisualState, displayedUrlText,
+                isValidPrefixHint ? prefixHint : null,
                 getToolbarDataProvider().getSecurityIconResource(false),
                 ImageViewCompat.getImageTintList(mHomeButton),
                 getMenuButtonCoordinator().isShowingUpdateBadge(),
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarSnapshotState.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarSnapshotState.java
index 91eef17..7b677a58 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarSnapshotState.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarSnapshotState.java
@@ -82,6 +82,9 @@
         mVisualState = visualState;
         mUrlText = urlText;
         mVisibleTextPrefixHint = visibleTextPrefixHint;
+        if (visibleTextPrefixHint != null) {
+            assert isValidVisibleTextPrefixHint(urlText, visibleTextPrefixHint);
+        }
         mSecurityIcon = securityIcon;
         mColorStateList = colorStateList;
         mIsShowingUpdateBadgeDuringLastCapture = isShowingUpdateBadgeDuringLastCapture;
@@ -128,10 +131,7 @@
     private boolean isVisibleUrlTextSame(ToolbarSnapshotState that) {
         if (mVisibleTextPrefixHint != null
                 && TextUtils.equals(mVisibleTextPrefixHint, that.mVisibleTextPrefixHint)) {
-            if (TextUtils.indexOf(mUrlText, mVisibleTextPrefixHint) >= 0) return true;
-            assert false : "The visible hint, " + mVisibleTextPrefixHint
-                           + ", should always be part of the URL text, "
-                           + mUrlText;
+            return true;
         }
         return TextUtils.equals(mUrlText, that.mUrlText);
     }
@@ -144,4 +144,16 @@
     int getTabCount() {
         return mTabCount;
     }
+
+    /**
+     * Determines the validity of the hint text given the passed in full text.
+     * @param fullText The full text that should start with the hint.
+     * @param hintText The hint text to be checked.
+     * @return Whether the full text starts with the specified hint text.
+     */
+    static boolean isValidVisibleTextPrefixHint(CharSequence fullText, CharSequence hintText) {
+        if (fullText == null || TextUtils.isEmpty(hintText)) return false;
+        if (hintText.length() > fullText.length()) return false;
+        return TextUtils.indexOf(fullText, hintText, 0, hintText.length()) == 0;
+    }
 }
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarSnapshotStateTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarSnapshotStateTest.java
index e6e0df9..91d8e25 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarSnapshotStateTest.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarSnapshotStateTest.java
@@ -118,8 +118,13 @@
     @Test
     public void testSameUrlText_DifferentHintText() {
         ToolbarSnapshotState initialToolbarSnapshotState =
-                new ToolbarSnapshotStateBuilder().setVisibleTextPrefixHint("foo").build();
-        ToolbarSnapshotState otherToolbarSnapshotState = new ToolbarSnapshotStateBuilder().build();
+                new ToolbarSnapshotStateBuilder()
+                        .setVisibleTextPrefixHint(DEFAULT_URL_TEXT.substring(0, 2))
+                        .build();
+        ToolbarSnapshotState otherToolbarSnapshotState =
+                new ToolbarSnapshotStateBuilder()
+                        .setVisibleTextPrefixHint(DEFAULT_URL_TEXT.substring(0, 3))
+                        .build();
         Assert.assertEquals(ToolbarSnapshotDifference.NONE,
                 initialToolbarSnapshotState.getAnyDifference(otherToolbarSnapshotState));
     }
@@ -200,6 +205,24 @@
                 otherToolbarSnapshotState.getAnyDifference(mDefaultToolbarSnapshotState));
     }
 
+    @Test
+    public void testIsValidVisibleTextPrefixHint() {
+        Assert.assertFalse(ToolbarSnapshotState.isValidVisibleTextPrefixHint(null, null));
+        Assert.assertFalse(ToolbarSnapshotState.isValidVisibleTextPrefixHint("foo", null));
+        Assert.assertFalse(ToolbarSnapshotState.isValidVisibleTextPrefixHint(null, "foo"));
+
+        Assert.assertFalse(ToolbarSnapshotState.isValidVisibleTextPrefixHint("", ""));
+        Assert.assertFalse(ToolbarSnapshotState.isValidVisibleTextPrefixHint("foo", ""));
+
+        Assert.assertFalse(ToolbarSnapshotState.isValidVisibleTextPrefixHint("foo", "fooo"));
+        Assert.assertFalse(ToolbarSnapshotState.isValidVisibleTextPrefixHint("foo", "foo/"));
+        Assert.assertFalse(ToolbarSnapshotState.isValidVisibleTextPrefixHint("foo", "o/"));
+        Assert.assertFalse(ToolbarSnapshotState.isValidVisibleTextPrefixHint("foo", "oo"));
+
+        Assert.assertTrue(ToolbarSnapshotState.isValidVisibleTextPrefixHint("foo.com", "foo"));
+        Assert.assertTrue(ToolbarSnapshotState.isValidVisibleTextPrefixHint("foo.com", "foo.com"));
+    }
+
     private class ToolbarSnapshotStateBuilder {
         private @ColorInt int mTint = DEFAULT_TINT;
         private int mTabCount = DEFAULT_TAB_COUNT;
diff --git a/chrome/browser/ui/ash/desks/chrome_desks_templates_delegate.cc b/chrome/browser/ui/ash/desks/chrome_desks_templates_delegate.cc
index 96caf256..25910302 100644
--- a/chrome/browser/ui/ash/desks/chrome_desks_templates_delegate.cc
+++ b/chrome/browser/ui/ash/desks/chrome_desks_templates_delegate.cc
@@ -313,7 +313,7 @@
     const std::string* lacros_window_id =
         window->GetProperty(app_restore::kLacrosWindowId);
     DCHECK(lacros_window_id);
-    const_cast<ChromeDesksTemplatesDelegate*>(this)->GetLacrosChromeUrls(
+    const_cast<ChromeDesksTemplatesDelegate*>(this)->GetLacrosChromeInfo(
         std::move(callback), *lacros_window_id, std::move(app_launch_info));
     return;
   }
@@ -438,7 +438,7 @@
   return name;
 }
 
-void ChromeDesksTemplatesDelegate::OnLacrosChromeUrlsReturned(
+void ChromeDesksTemplatesDelegate::OnLacrosChromeInfoReturned(
     GetAppLaunchDataCallback callback,
     std::unique_ptr<app_restore::AppLaunchInfo> app_launch_info,
     crosapi::mojom::DeskTemplateStatePtr state) {
@@ -447,7 +447,7 @@
   std::move(callback).Run(std::move(app_launch_info));
 }
 
-void ChromeDesksTemplatesDelegate::GetLacrosChromeUrls(
+void ChromeDesksTemplatesDelegate::GetLacrosChromeInfo(
     GetAppLaunchDataCallback callback,
     const std::string& window_unique_id,
     std::unique_ptr<app_restore::AppLaunchInfo> app_launch_info) {
@@ -459,9 +459,9 @@
     return;
   }
 
-  browser_manager->GetTabStripModelUrls(
+  browser_manager->GetBrowserInformation(
       window_unique_id,
-      base::BindOnce(&ChromeDesksTemplatesDelegate::OnLacrosChromeUrlsReturned,
+      base::BindOnce(&ChromeDesksTemplatesDelegate::OnLacrosChromeInfoReturned,
                      weak_factory_.GetWeakPtr(), std::move(callback),
                      std::move(app_launch_info)));
 }
diff --git a/chrome/browser/ui/ash/desks/chrome_desks_templates_delegate.h b/chrome/browser/ui/ash/desks/chrome_desks_templates_delegate.h
index 9bba7d8..bd4d22b4 100644
--- a/chrome/browser/ui/ash/desks/chrome_desks_templates_delegate.h
+++ b/chrome/browser/ui/ash/desks/chrome_desks_templates_delegate.h
@@ -55,15 +55,15 @@
 
  private:
   // Receives the state of the tabstrip from the Lacros window.
-  void OnLacrosChromeUrlsReturned(
+  void OnLacrosChromeInfoReturned(
       GetAppLaunchDataCallback callback,
       std::unique_ptr<app_restore::AppLaunchInfo> app_launch_info,
       crosapi::mojom::DeskTemplateStatePtr state);
 
   // Asynchronously requests the state of the tabstrip from the Lacros window
   // with `window_unique_id`.  The response is handled by
-  // OnLacrosChromeUrlsReturned().
-  void GetLacrosChromeUrls(
+  // OnLacrosChromeInfoReturned().
+  void GetLacrosChromeInfo(
       GetAppLaunchDataCallback callback,
       const std::string& window_unique_id,
       std::unique_ptr<app_restore::AppLaunchInfo> app_launch_info);
diff --git a/chrome/browser/ui/ash/desks/desks_client_browsertest.cc b/chrome/browser/ui/ash/desks/desks_client_browsertest.cc
index b1f1edb..f3c5631f 100644
--- a/chrome/browser/ui/ash/desks/desks_client_browsertest.cc
+++ b/chrome/browser/ui/ash/desks/desks_client_browsertest.cc
@@ -2698,6 +2698,18 @@
   EXPECT_EQ(desk_uuid_, desk_uuid);
 }
 
+// Tests that floating workspace template can be captured with fixed uuid.
+IN_PROC_BROWSER_TEST_F(DesksClientTest, CaptureFloatingWorkspaceTemplateTest) {
+  // Create a new browser and add a few tabs to it.
+  CreateBrowser({GURL(kExampleUrl1), GURL(kExampleUrl2)});
+  std::unique_ptr<ash::DeskTemplate> desk_template =
+      CaptureActiveDeskAndSaveTemplate(
+          ash::DeskTemplateType::kFloatingWorkspace);
+  EXPECT_TRUE(desk_template->uuid().is_valid());
+  EXPECT_EQ(desk_template->uuid(),
+            base::GUID::ParseLowercase(ash::kFloatingWorkspaceTemplateUuid));
+}
+
 class DesksTemplatesClientLacrosTest : public InProcessBrowserTest {
  public:
   DesksTemplatesClientLacrosTest() {
diff --git a/chrome/browser/ui/ash/ime_controller_client_impl_unittest.cc b/chrome/browser/ui/ash/ime_controller_client_impl_unittest.cc
index e21bfa6..11c8daae 100644
--- a/chrome/browser/ui/ash/ime_controller_client_impl_unittest.cc
+++ b/chrome/browser/ui/ash/ime_controller_client_impl_unittest.cc
@@ -72,6 +72,14 @@
       return std::make_unique<std::vector<InputMethodDescriptor>>(
           input_methods_);
     }
+    const InputMethodDescriptor* GetInputMethodFromId(
+        const std::string& input_method_id) const override {
+      for (const InputMethodDescriptor& descriptor : input_methods_) {
+        if (input_method_id == descriptor.id())
+          return &descriptor;
+      }
+      return nullptr;
+    }
     InputMethodDescriptor GetCurrentInputMethod() const override {
       for (const InputMethodDescriptor& descriptor : input_methods_) {
         if (current_ime_id_ == descriptor.id())
diff --git a/chrome/browser/ui/bookmarks/bookmark_drag_drop.cc b/chrome/browser/ui/bookmarks/bookmark_drag_drop.cc
index fb9966ce7f..2f48852 100644
--- a/chrome/browser/ui/bookmarks/bookmark_drag_drop.cc
+++ b/chrome/browser/ui/bookmarks/bookmark_drag_drop.cc
@@ -61,10 +61,13 @@
       for (size_t i = 0; i < dragged_nodes.size(); ++i) {
         if (copy) {
           model->Copy(dragged_nodes[i], parent_node, index);
+          // Increment `index` so that the next copied node ends up after the
+          // one that was just inserted.
+          ++index;
         } else {
           model->Move(dragged_nodes[i], parent_node, index);
+          index = parent_node->GetIndexOf(dragged_nodes[i]).value() + 1;
         }
-        index = parent_node->GetIndexOf(dragged_nodes[i]).value() + 1;
       }
       RecordBookmarkDropped(data, parent_node, is_reorder);
       return copy ? DragOperation::kCopy : DragOperation::kMove;
diff --git a/chrome/browser/ui/bookmarks/bookmark_drag_drop_unittest.cc b/chrome/browser/ui/bookmarks/bookmark_drag_drop_unittest.cc
new file mode 100644
index 0000000..151d80c
--- /dev/null
+++ b/chrome/browser/ui/bookmarks/bookmark_drag_drop_unittest.cc
@@ -0,0 +1,60 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/bookmarks/bookmark_drag_drop.h"
+
+#include <stddef.h>
+
+#include <memory>
+#include <string>
+
+#include "base/memory/raw_ptr.h"
+#include "chrome/browser/bookmarks/bookmark_model_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/bookmarks/browser/bookmark_model.h"
+#include "components/bookmarks/browser/bookmark_node.h"
+#include "components/bookmarks/browser/bookmark_node_data.h"
+#include "components/bookmarks/test/bookmark_test_helpers.h"
+#include "content/public/test/browser_task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class BookmarkDragDropTest : public testing::Test {
+ public:
+  BookmarkDragDropTest() : model_(nullptr) {}
+
+  void SetUp() override {
+    TestingProfile::Builder profile_builder;
+    profile_builder.AddTestingFactory(
+        BookmarkModelFactory::GetInstance(),
+        BookmarkModelFactory::GetDefaultFactory());
+    profile_ = profile_builder.Build();
+    model_ = BookmarkModelFactory::GetForBrowserContext(profile_.get());
+    bookmarks::test::WaitForBookmarkModelToLoad(model_);
+  }
+
+ protected:
+  content::BrowserTaskEnvironment task_environment_;
+  std::unique_ptr<TestingProfile> profile_;
+  raw_ptr<bookmarks::BookmarkModel> model_;
+};
+
+TEST_F(BookmarkDragDropTest, DropBookmarksWithCopyFromSameProfile) {
+  // Adds a url node along with a folder containing another url node.
+  const bookmarks::BookmarkNode* bb_node = model_->bookmark_bar_node();
+  model_->AddURL(bb_node, 0, u"c", GURL("about:blank"));
+  const bookmarks::BookmarkNode* folder =
+      model_->AddFolder(bb_node, 1, u"folder");
+  const bookmarks::BookmarkNode* folder_child_node =
+      model_->AddURL(folder, 0, u"child", GURL("https://foo.com"));
+  bookmarks::BookmarkNodeData bookmark_node_data(folder_child_node);
+  bookmark_node_data.SetOriginatingProfilePath(profile_->GetPath());
+  // Make a copy of `folder_child_node` added to the bookmark bar node.
+  chrome::DropBookmarks(profile_.get(), bookmark_node_data, bb_node, 0, true);
+  ASSERT_EQ(3u, bb_node->children().size());
+  const bookmarks::BookmarkNode* newly_copied_node =
+      bb_node->children()[0].get();
+  EXPECT_EQ(folder_child_node->GetTitle(), newly_copied_node->GetTitle());
+  EXPECT_EQ(folder_child_node->url(), newly_copied_node->url());
+}
diff --git a/chrome/browser/ui/color/chrome_color_id.h b/chrome/browser/ui/color/chrome_color_id.h
index 392f3834..c4506f5 100644
--- a/chrome/browser/ui/color/chrome_color_id.h
+++ b/chrome/browser/ui/color/chrome_color_id.h
@@ -160,6 +160,7 @@
   E_CPONLY(kColorNewTabPageFocusShadow) \
   E_CPONLY(kColorNewTabPageHeader) \
   E_CPONLY(kColorNewTabPagePromoBackground) \
+  E_CPONLY(kColorNewTabPagePromoImageBackground) \
   E_CPONLY(kColorNewTabPageIconButtonBackground) \
   E_CPONLY(kColorNewTabPageIconButtonBackgroundActive) \
   E_CPONLY(kColorNewTabPageLink) \
diff --git a/chrome/browser/ui/color/new_tab_page_color_mixer.cc b/chrome/browser/ui/color/new_tab_page_color_mixer.cc
index 93e3e8e4..ad64a28 100644
--- a/chrome/browser/ui/color/new_tab_page_color_mixer.cc
+++ b/chrome/browser/ui/color/new_tab_page_color_mixer.cc
@@ -151,6 +151,8 @@
   mixer[kColorNewTabPageOnThemeForeground] = themed_foreground_color;
   mixer[kColorNewTabPagePrimaryForeground] = primary_foreground_color;
   mixer[kColorNewTabPagePromoBackground] = element_background_color;
+  mixer[kColorNewTabPagePromoImageBackground] = SelectBasedOnDarkInput(
+      element_background_color, {gfx::kGoogleGrey200}, {SK_ColorWHITE});
   mixer[kColorNewTabPageSecondaryForeground] = SelectBasedOnDarkInput(
       element_background_color,
       ui::PickGoogleColor({gfx::kGoogleGrey700}, element_background_color,
@@ -410,6 +412,8 @@
                                /* 40% opacity */ 0.4 * SK_AlphaOPAQUE);
   mixer[kColorNewTabPagePromoBackground] = {dark_mode ? gfx::kGoogleGrey900
                                                       : SK_ColorWHITE};
+  mixer[kColorNewTabPagePromoImageBackground] = {dark_mode ? gfx::kGoogleGrey200
+                                                           : SK_ColorWHITE};
   mixer[kColorNewTabPageIconButtonBackground] = {
       dark_mode ? SK_ColorWHITE : gfx::kGoogleGrey600};
   mixer[kColorNewTabPageIconButtonBackgroundActive] = {
diff --git a/chrome/browser/ui/ui_features.cc b/chrome/browser/ui/ui_features.cc
index bf0ab714..06093671 100644
--- a/chrome/browser/ui/ui_features.cc
+++ b/chrome/browser/ui/ui_features.cc
@@ -186,6 +186,12 @@
              "SplitTabStrip",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Enables tabs to be frozen when collapsed.
+// https://crbug.com/1110108
+BASE_FEATURE(kTabGroupsCollapseFreezing,
+             "TabGroupsCollapseFreezing",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 // Directly controls the "new" badge (as opposed to old "master switch"; see
 // https://crbug.com/1169907 for master switch deprecation and
 // https://crbug.com/968587 for the feature itself)
diff --git a/chrome/browser/ui/ui_features.h b/chrome/browser/ui/ui_features.h
index c27bf440..46379cd 100644
--- a/chrome/browser/ui/ui_features.h
+++ b/chrome/browser/ui/ui_features.h
@@ -91,6 +91,8 @@
 BASE_DECLARE_FEATURE(kSideSearchAutoTriggering);
 extern const base::FeatureParam<int> kSideSearchAutoTriggeringReturnCount;
 
+BASE_DECLARE_FEATURE(kTabGroupsCollapseFreezing);
+
 BASE_DECLARE_FEATURE(kTabGroupsNewBadgePromo);
 
 BASE_DECLARE_FEATURE(kTabGroupsSave);
diff --git a/chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc b/chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc
index 968f5ba4..120585c 100644
--- a/chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc
+++ b/chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc
@@ -11,6 +11,7 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/download/bubble/download_bubble_controller.h"
 #include "chrome/browser/download/bubble/download_bubble_prefs.h"
+#include "chrome/browser/download/download_item_warning_data.h"
 #include "chrome/browser/download/download_stats.h"
 #include "chrome/browser/download/download_ui_model.h"
 #include "chrome/browser/download/drag_download_item.h"
@@ -653,6 +654,11 @@
 }
 
 void DownloadBubbleRowView::RecordDownloadDisplayed() {
+  if (model_->IsDangerous()) {
+    DownloadItemWarningData::AddWarningActionEvent(
+        model_->GetDownloadItem(), DownloadItemWarningData::BUBBLE_MAINPAGE,
+        DownloadItemWarningData::SHOWN);
+  }
   if (!model_->GetEphemeralWarningUiShownTime().has_value() &&
       model_->IsEphemeralWarning()) {
     model_->SetEphemeralWarningUiShownTime(base::Time::Now());
@@ -697,7 +703,7 @@
           base::BindRepeating(
               &DownloadBubbleUIController::ProcessDownloadButtonPress,
               base::Unretained(bubble_controller_),
-              base::Unretained(model_.get()), command),
+              base::Unretained(model_.get()), command, /*is_main_view=*/true),
           button_string));
   button->SetMaxSize(gfx::Size(0, kDownloadButtonHeight));
   button->SetProperty(views::kMarginsKey, kRowInterElementPadding);
@@ -711,7 +717,7 @@
       views::CreateVectorImageButton(base::BindRepeating(
           &DownloadBubbleUIController::ProcessDownloadButtonPress,
           base::Unretained(bubble_controller_), base::Unretained(model_.get()),
-          command)));
+          command, /*is_main_view=*/true)));
   InstallCircleHighlightPathGenerator(quick_action);
   quick_action->SetBorder(
       views::CreateEmptyBorder(GetLayoutInsets(DOWNLOAD_ICON)));
diff --git a/chrome/browser/ui/views/download/bubble/download_bubble_security_view.cc b/chrome/browser/ui/views/download/bubble/download_bubble_security_view.cc
index 7ee2fec..98d5d69d 100644
--- a/chrome/browser/ui/views/download/bubble/download_bubble_security_view.cc
+++ b/chrome/browser/ui/views/download/bubble/download_bubble_security_view.cc
@@ -8,6 +8,7 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/strings/strcat.h"
 #include "chrome/browser/download/bubble/download_bubble_controller.h"
+#include "chrome/browser/download/download_item_warning_data.h"
 #include "chrome/browser/ui/layout_constants.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
 #include "chrome/browser/ui/views/download/bubble/download_bubble_row_view.h"
@@ -95,6 +96,9 @@
 }
 
 void DownloadBubbleSecurityView::BackButtonPressed() {
+  DownloadItemWarningData::AddWarningActionEvent(
+      download_row_view_->model()->GetDownloadItem(),
+      DownloadItemWarningData::BUBBLE_SUBPAGE, DownloadItemWarningData::BACK);
   navigation_handler_->OpenPrimaryDialog();
   base::UmaHistogramEnumeration(
       kSubpageActionHistogram, DownloadBubbleSubpageAction::kPressedBackButton);
@@ -107,6 +111,9 @@
 }
 
 void DownloadBubbleSecurityView::CloseBubble() {
+  DownloadItemWarningData::AddWarningActionEvent(
+      download_row_view_->model()->GetDownloadItem(),
+      DownloadItemWarningData::BUBBLE_SUBPAGE, DownloadItemWarningData::CLOSE);
   // CloseDialog will delete the object. Do not access any members below.
   navigation_handler_->CloseDialog(
       views::Widget::ClosedReason::kCloseButtonClicked);
@@ -220,8 +227,8 @@
   // happens leading to closure of the bubble, it will be called after primary
   // dialog is opened.
   navigation_handler_->OpenPrimaryDialog();
-  bubble_controller_->ProcessDownloadButtonPress(download_row_view_->model(),
-                                                 command);
+  bubble_controller_->ProcessDownloadButtonPress(
+      download_row_view_->model(), command, /*is_main_view=*/false);
   base::UmaHistogramEnumeration(
       kSubpageActionHistogram,
       is_secondary_button ? DownloadBubbleSubpageAction::kPressedSecondaryButton
diff --git a/chrome/browser/ui/views/tabs/OWNERS b/chrome/browser/ui/views/tabs/OWNERS
index 71a3e174..27087e5 100644
--- a/chrome/browser/ui/views/tabs/OWNERS
+++ b/chrome/browser/ui/views/tabs/OWNERS
@@ -4,6 +4,7 @@
 sky@chromium.org
 tbergquist@chromium.org
 dpenning@chromium.org
+shibalik@chromium.org
 
 per-file tab_search_button*=tluk@chromium.org
 
diff --git a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
index abd41d5..55b9aad 100644
--- a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
+++ b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
@@ -702,15 +702,18 @@
         gfx::Range tabs_in_group = ListTabsInGroup(change.group);
         for (auto i = tabs_in_group.start(); i < tabs_in_group.end(); ++i) {
           tabstrip_->tab_at(i)->SetVisible(!new_visuals->is_collapsed());
-          if (visuals_delta->new_visuals->is_collapsed()) {
-            tabstrip_->tab_at(i)->SetFreezingVoteToken(
-                performance_manager::freezing::EmitFreezingVoteForWebContents(
-                    model_->GetWebContentsAt(i),
-                    performance_manager::freezing::FreezingVoteValue::
-                        kCanFreeze,
-                    "Collapsed Tab Group"));
-          } else {
-            tabstrip_->tab_at(i)->ReleaseFreezingVoteToken();
+          if (base::FeatureList::IsEnabled(
+                  features::kTabGroupsCollapseFreezing)) {
+            if (new_visuals->is_collapsed()) {
+              tabstrip_->tab_at(i)->SetFreezingVoteToken(
+                  performance_manager::freezing::EmitFreezingVoteForWebContents(
+                      model_->GetWebContentsAt(i),
+                      performance_manager::freezing::FreezingVoteValue::
+                          kCanFreeze,
+                      "Collapsed Tab Group"));
+            } else {
+              tabstrip_->tab_at(i)->ReleaseFreezingVoteToken();
+            }
           }
         }
       }
diff --git a/chrome/browser/ui/views/tabs/tab_group_editor_bubble_view_browsertest.cc b/chrome/browser/ui/views/tabs/tab_group_editor_bubble_view_browsertest.cc
index 721c6b6e..5820274 100644
--- a/chrome/browser/ui/views/tabs/tab_group_editor_bubble_view_browsertest.cc
+++ b/chrome/browser/ui/views/tabs/tab_group_editor_bubble_view_browsertest.cc
@@ -179,8 +179,21 @@
       active_browser->tab_strip_model()->group_model()->ListTabGroups().size());
 }
 
-IN_PROC_BROWSER_TEST_F(TabGroupEditorBubbleViewDialogBrowserTest,
-                       CollapsingGroupFreezesAllTabs) {
+class TabGroupEditorBubbleViewDialogBrowserTestWithFreezingEnabled
+    : public TabGroupEditorBubbleViewDialogBrowserTest {
+ public:
+  TabGroupEditorBubbleViewDialogBrowserTestWithFreezingEnabled() {
+    scoped_feature_list_.InitWithFeatures(
+        {features::kTabGroupsCollapseFreezing}, {});
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(
+    TabGroupEditorBubbleViewDialogBrowserTestWithFreezingEnabled,
+    CollapsingGroupFreezesAllTabs) {
   BrowserView* browser_view = static_cast<BrowserView*>(browser()->window());
   InProcessBrowserTest::AddBlankTabAndShow(browser());
   InProcessBrowserTest::AddBlankTabAndShow(browser());
diff --git a/chrome/browser/ui/views/user_education/help_bubble_view_interactive_uitest.cc b/chrome/browser/ui/views/user_education/help_bubble_view_interactive_uitest.cc
index 7d1722cb..8a1163f 100644
--- a/chrome/browser/ui/views/user_education/help_bubble_view_interactive_uitest.cc
+++ b/chrome/browser/ui/views/user_education/help_bubble_view_interactive_uitest.cc
@@ -11,8 +11,8 @@
 #include "chrome/browser/ui/views/toolbar/browser_app_menu_button.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
 #include "chrome/browser/ui/views/user_education/browser_user_education_service.h"
-#include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/interactive_test_utils.h"
+#include "chrome/test/interaction/interactive_browser_test.h"
 #include "components/user_education/common/help_bubble.h"
 #include "components/user_education/common/help_bubble_params.h"
 #include "components/user_education/views/help_bubble_view.h"
@@ -20,6 +20,7 @@
 #include "ui/base/interaction/expect_call_in_scope.h"
 #include "ui/base/interaction/interaction_sequence.h"
 #include "ui/events/base_event_utils.h"
+#include "ui/views/controls/button/label_button.h"
 #include "ui/views/focus/focus_manager.h"
 #include "ui/views/interaction/element_tracker_views.h"
 #include "ui/views/test/widget_test.h"
@@ -29,46 +30,60 @@
 using user_education::HelpBubbleParams;
 using user_education::HelpBubbleView;
 
-class HelpBubbleViewInteractiveTest : public InProcessBrowserTest {
+class HelpBubbleViewInteractiveTest : public InteractiveBrowserTest {
  public:
   HelpBubbleViewInteractiveTest() = default;
   ~HelpBubbleViewInteractiveTest() override = default;
 
  protected:
-  views::TrackedElementViews* GetAnchorElement() {
-    return views::ElementTrackerViews::GetInstance()->GetElementForView(
-        BrowserView::GetBrowserViewForBrowser(browser())
-            ->toolbar()
-            ->app_menu_button());
-  }
-
   HelpBubbleParams GetBubbleParams() {
     HelpBubbleParams params;
     params.body_text = u"To X, do Y";
     params.arrow = HelpBubbleArrow::kTopRight;
     return params;
   }
+
+  // Activates the widget containing the given UI element.
+  auto ActivateWidgetContaining(ElementSpecifier spec) {
+    return AfterShow(spec, base::BindOnce([](ui::TrackedElement* element) {
+                       auto* const widget = AsView(element)->GetWidget();
+                       widget->Activate();
+                       views::test::WidgetActivationWaiter(widget, true).Wait();
+                       ASSERT_TRUE(widget->IsActive());
+                     }));
+  }
 };
 
 IN_PROC_BROWSER_TEST_F(HelpBubbleViewInteractiveTest,
                        WidgetNotActivatedByDefault) {
-  auto params = GetBubbleParams();
-
   auto* const browser_view = BrowserView::GetBrowserViewForBrowser(browser());
-  auto* const focus_manager = browser_view->GetWidget()->GetFocusManager();
-  EXPECT_TRUE(browser_view->GetWidget()->IsActive());
+  HelpBubbleView* help_bubble = nullptr;
 
-  browser_view->FocusToolbar();
-  views::View* const initial_focused_view = focus_manager->GetFocusedView();
-  EXPECT_NE(nullptr, initial_focused_view);
-
-  auto* const bubble = new HelpBubbleView(
-      GetHelpBubbleDelegate(), GetAnchorElement()->view(), std::move(params));
-  views::test::WidgetVisibleWaiter(bubble->GetWidget()).Wait();
-
-  EXPECT_TRUE(browser_view->GetWidget()->IsActive());
-  EXPECT_FALSE(bubble->GetWidget()->IsActive());
-  bubble->Close();
+  RunTestSequence(
+      Check(base::BindLambdaForTesting(
+          [browser_view]() { return browser_view->GetWidget()->IsActive(); })),
+      Do(base::BindLambdaForTesting(
+          [browser_view]() { browser_view->FocusToolbar(); })),
+      CheckResult(base::BindLambdaForTesting([browser_view]() {
+                    return browser_view->GetFocusManager()->GetFocusedView();
+                  }),
+                  testing::Ne(nullptr)),
+      WithElement(kAppMenuButtonElementId,
+                  base::BindLambdaForTesting([&help_bubble,
+                                              this](ui::TrackedElement* el) {
+                    help_bubble = new HelpBubbleView(
+                        GetHelpBubbleDelegate(), AsView(el), GetBubbleParams());
+                  })),
+      WaitForShow(HelpBubbleView::kHelpBubbleElementIdForTesting),
+      FlushEvents(), Check(base::BindLambdaForTesting([&help_bubble]() {
+        return help_bubble->GetWidget()->IsVisible();
+      })),
+      Check(base::BindLambdaForTesting(
+          [browser_view]() { return browser_view->GetWidget()->IsActive(); })),
+      Check(base::BindLambdaForTesting(
+          [&help_bubble]() { return !help_bubble->GetWidget()->IsActive(); })),
+      Do(base::BindLambdaForTesting(
+          [&help_bubble]() { help_bubble->Close(); })));
 }
 
 // This is a regression test to ensure that help bubbles prevent other bubbles
@@ -79,176 +94,79 @@
 // close it.
 IN_PROC_BROWSER_TEST_F(HelpBubbleViewInteractiveTest,
                        BubblePreventsCloseOnLossOfFocus) {
-  UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::CompletedCallback, completed);
-
-  HelpBubbleView* help_bubble_view = nullptr;
-
+  HelpBubbleView* help_bubble = nullptr;
   browser()->tab_strip_model()->AddToNewGroup({0});
-  auto sequence =
-      ui::InteractionSequence::Builder()
-          .SetContext(browser()->window()->GetElementContext())
-          .SetCompletedCallback(completed.Get())
-          .AddStep(
-              ui::InteractionSequence::StepBuilder()
-                  .SetElementID(kTabGroupHeaderElementId)
-                  .SetType(ui::InteractionSequence::StepType::kShown)
-                  .SetStartCallback(base::BindLambdaForTesting(
-                      [](ui::InteractionSequence*,
-                         ui::TrackedElement* element) {
-                        // Show the tab group editor bubble.
-                        auto* const view =
-                            element->AsA<views::TrackedElementViews>()->view();
-                        view->ShowContextMenu(
-                            view->GetLocalBounds().CenterPoint(),
-                            ui::MenuSourceType::MENU_SOURCE_KEYBOARD);
-                      }))
-                  .Build())
-          .AddStep(
-              ui::InteractionSequence::StepBuilder()
-                  .SetElementID(kTabGroupEditorBubbleId)
-                  .SetType(ui::InteractionSequence::StepType::kShown)
-                  .SetMustRemainVisible(true)
-                  .SetStartCallback(base::BindLambdaForTesting(
-                      [&](ui::InteractionSequence*,
-                          ui::TrackedElement* element) {
-                        // Show a help bubble attached to the tab group editor
-                        // bubble.
-                        auto* const anchor_view =
-                            element->AsA<views::TrackedElementViews>()->view();
-                        HelpBubbleParams params;
-                        params.body_text = u"foo";
-                        help_bubble_view =
-                            new HelpBubbleView(GetHelpBubbleDelegate(),
-                                               anchor_view, std::move(params));
-                      }))
-                  .Build())
-          .AddStep(
-              ui::InteractionSequence::StepBuilder()
-                  .SetElementID(HelpBubbleView::kHelpBubbleElementIdForTesting)
-                  .SetType(ui::InteractionSequence::StepType::kShown)
-                  .SetMustRemainVisible(true)
-                  .SetStartCallback(base::BindLambdaForTesting(
-                      [&](ui::InteractionSequence*,
-                          ui::TrackedElement* element) {
-                        // Activate the help bubble. This should not cause the
-                        // editor to close.
-                        auto* const widget =
-                            element->AsA<views::TrackedElementViews>()
-                                ->view()
-                                ->GetWidget();
-                        widget->Activate();
-                        views::test::WidgetActivationWaiter(widget, true)
-                            .Wait();
-                      }))
-                  .Build())
-          .AddStep(ui::InteractionSequence::StepBuilder()
-                       .SetElementID(kTabGroupEditorBubbleId)
-                       .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetMustBeVisibleAtStart(true)
-                       .SetMustRemainVisible(true)
-                       .SetStartCallback(base::BindLambdaForTesting(
-                           [&](ui::InteractionSequence*,
-                               ui::TrackedElement* element) {
-                             // Activate the editor then close the help bubble.
-                             auto* const widget =
-                                 element->AsA<views::TrackedElementViews>()
-                                     ->view()
-                                     ->GetWidget();
-                             widget->Activate();
-                             views::test::WidgetActivationWaiter(widget, true)
-                                 .Wait();
-                             ASSERT_TRUE(widget->IsActive());
-                             help_bubble_view->GetWidget()->Close();
-                           }))
-                       .Build())
-          .AddStep(
-              ui::InteractionSequence::StepBuilder()
-                  // Wait for the help bubble to close.
-                  .SetElementID(HelpBubbleView::kHelpBubbleElementIdForTesting)
-                  .SetType(ui::InteractionSequence::StepType::kHidden)
-                  .Build())
-          .AddStep(
-              ui::InteractionSequence::StepBuilder()
-                  .SetElementID(kTabGroupEditorBubbleId)
-                  .SetType(ui::InteractionSequence::StepType::kShown)
-                  .SetMustBeVisibleAtStart(true)
-                  .SetStartCallback(base::BindLambdaForTesting(
-                      [&](ui::InteractionSequence*,
-                          ui::TrackedElement* element) {
-                        // Now that the help bubble is gone, locate the editor
-                        // again and transfer activation to its primary window
-                        // widget (the browser window)
-                        // - this should close the editor as it is no longer
-                        // pinned by the help bubble.
-                        auto* const widget =
-                            element->AsA<views::TrackedElementViews>()
-                                ->view()
-                                ->GetWidget();
-                        // Delay this in case we're chaining off of the previous
-                        // hidden step; we need the help bubble to fully clean
-                        // up (this wouldn't be an issue in an actual live
-                        // browser because the activation would be due to user
-                        // input and therefore have to be processed via the
-                        // message pump instead of being allowed to execute
-                        // inside the Widget's close logic).
-                        base::ThreadTaskRunnerHandle::Get()->PostTask(
-                            FROM_HERE,
-                            base::BindOnce(
-                                [](views::Widget* widget) {
-                                  widget->GetPrimaryWindowWidget()->Activate();
-                                },
-                                base::Unretained(widget)));
-                      }))
-                  .Build())
-          .AddStep(ui::InteractionSequence::StepBuilder()
-                       // Verify that the editor bubble closes now that it has
-                       // lost focus.
-                       .SetElementID(kTabGroupEditorBubbleId)
-                       .SetType(ui::InteractionSequence::StepType::kHidden)
-                       .Build())
-          .Build();
 
-  EXPECT_CALL_IN_SCOPE(completed, Run, sequence->RunSynchronouslyForTesting());
+  RunTestSequence(
+      AfterShow(kTabGroupHeaderElementId,
+                base::BindLambdaForTesting([](ui::TrackedElement* element) {
+                  // Show the tab group editor bubble.
+                  auto* const view = AsView(element);
+                  view->ShowContextMenu(
+                      view->GetLocalBounds().CenterPoint(),
+                      ui::MenuSourceType::MENU_SOURCE_KEYBOARD);
+                })),
+      AfterShow(kTabGroupEditorBubbleId,
+                base::BindLambdaForTesting([&](ui::TrackedElement* element) {
+                  // Show a help bubble attached to the tab group editor
+                  // bubble.
+                  HelpBubbleParams params;
+                  params.body_text = u"foo";
+                  help_bubble =
+                      new HelpBubbleView(GetHelpBubbleDelegate(),
+                                         AsView(element), std::move(params));
+                })),
+      // Activate the help bubble. This should not cause the editor to close.
+      ActivateWidgetContaining(HelpBubbleView::kHelpBubbleElementIdForTesting),
+      // Re-Activate the dialog.
+      ActivateWidgetContaining(kTabGroupEditorBubbleId),
+      // Close the help bubble.
+      Do(base::BindLambdaForTesting(
+          [&]() { help_bubble->GetWidget()->Close(); })),
+      WaitForHide(HelpBubbleView::kHelpBubbleElementIdForTesting),
+      // Delay this to prevent chaining off of the previous hidden step; we need
+      // the help bubble to fully clean up (this wouldn't be an issue in an
+      // actual live browser because the activation would be due to user input
+      // and therefore have to be processed via the message pump instead of
+      // being allowed to execute inside the Widget's close logic).
+      FlushEvents(),
+      // Now that the help bubble is gone, locate the editor again and transfer
+      // activation to its primary window widget (the browser window) - this
+      // should close the editor as it is no longer pinned by the help bubble.
+      ActivateWidgetContaining(kAppMenuButtonElementId),
+      // Verify that the editor bubble closes now that it has lost focus.
+      WaitForHide(kTabGroupEditorBubbleId));
 }
 
 IN_PROC_BROWSER_TEST_F(HelpBubbleViewInteractiveTest,
                        ElementIdentifierFindsButton) {
-  user_education::HelpBubbleParams params;
-  params.body_text = u"To X, do Y";
-  params.arrow = HelpBubbleArrow::kTopRight;
-
   constexpr char16_t kButton1Text[] = u"button 1";
   constexpr char16_t kButton2Text[] = u"button 2";
 
+  user_education::HelpBubbleParams params = GetBubbleParams();
+
   user_education::HelpBubbleButtonParams button1;
   button1.text = kButton1Text;
   button1.is_default = true;
-  params.buttons.push_back(std::move(button1));
+  params.buttons.emplace_back(std::move(button1));
 
   user_education::HelpBubbleButtonParams button2;
   button2.text = kButton2Text;
   button2.is_default = false;
-  params.buttons.push_back(std::move(button2));
+  params.buttons.emplace_back(std::move(button2));
 
-  auto* const browser_view = BrowserView::GetBrowserViewForBrowser(browser());
-  auto* const bubble = new HelpBubbleView(
-      GetHelpBubbleDelegate(), GetAnchorElement()->view(), std::move(params));
+  HelpBubbleView* help_bubble = nullptr;
 
-  views::LabelButton* default_button = views::AsViewClass<views::LabelButton>(
-      views::ElementTrackerViews::GetInstance()->GetFirstMatchingView(
-          HelpBubbleView::kDefaultButtonIdForTesting,
-          browser_view->GetElementContext()));
-
-  views::LabelButton* non_default_button =
-      views::AsViewClass<views::LabelButton>(
-          views::ElementTrackerViews::GetInstance()->GetFirstMatchingView(
-              HelpBubbleView::kFirstNonDefaultButtonIdForTesting,
-              browser_view->GetElementContext()));
-
-  EXPECT_EQ(bubble->GetDefaultButtonForTesting()->GetText(),
-            default_button->GetText());
-  EXPECT_EQ(bubble->GetNonDefaultButtonForTesting(0)->GetText(),
-            non_default_button->GetText());
-
-  bubble->Close();
+  RunTestSequence(
+      WithElement(kAppMenuButtonElementId,
+                  base::BindLambdaForTesting([&](ui::TrackedElement* el) {
+                    help_bubble = new HelpBubbleView(
+                        GetHelpBubbleDelegate(), AsView(el), std::move(params));
+                  })),
+      CheckViewProperty(HelpBubbleView::kDefaultButtonIdForTesting,
+                        &views::LabelButton::GetText, kButton1Text),
+      CheckViewProperty(HelpBubbleView::kFirstNonDefaultButtonIdForTesting,
+                        &views::LabelButton::GetText, kButton2Text),
+      PressButton(HelpBubbleView::kDefaultButtonIdForTesting),
+      WaitForHide(HelpBubbleView::kHelpBubbleElementIdForTesting));
 }
diff --git a/chrome/browser/ui/webui/ash/parent_access/parent_access_dialog.cc b/chrome/browser/ui/webui/ash/parent_access/parent_access_dialog.cc
index 54b37287..3617f31b 100644
--- a/chrome/browser/ui/webui/ash/parent_access/parent_access_dialog.cc
+++ b/chrome/browser/ui/webui/ash/parent_access/parent_access_dialog.cc
@@ -86,14 +86,16 @@
 
 void ParentAccessDialog::SetCanceled() {
   auto result = std::make_unique<ParentAccessDialog::Result>();
-  result->status = ParentAccessDialog::Result::Status::kCancelled;
+  result->status = ParentAccessDialog::Result::Status::kCanceled;
   CloseWithResult(std::move(result));
 }
 
 void ParentAccessDialog::SetError() {
   auto result = std::make_unique<ParentAccessDialog::Result>();
   result->status = ParentAccessDialog::Result::Status::kError;
-  CloseWithResult(std::move(result));
+  // Don't close dialog on error state, as user will close the dialog manually
+  // after seeing the error.
+  result_ = std::move(result);
 }
 
 parent_access_ui::mojom::ParentAccessParams*
@@ -112,7 +114,7 @@
 ParentAccessDialog::~ParentAccessDialog() {
   std::move(callback_).Run(
       result_ ? std::move(result_)
-              /* default status is kCancelled */
+              /* default status is kCanceled */
               : std::make_unique<ParentAccessDialog::Result>());
 }
 
diff --git a/chrome/browser/ui/webui/ash/parent_access/parent_access_dialog.h b/chrome/browser/ui/webui/ash/parent_access/parent_access_dialog.h
index 8e337e2..05489d23 100644
--- a/chrome/browser/ui/webui/ash/parent_access/parent_access_dialog.h
+++ b/chrome/browser/ui/webui/ash/parent_access/parent_access_dialog.h
@@ -24,12 +24,12 @@
   struct Result {
     // The status of the result.
     enum class Status {
-      kApproved,   // The parent was verified and they approved.
-      kDeclined,   // The request was explicitly declined by the parent.
-      kCancelled,  // The request was cancelled/dismissed by the parent.
-      kError,      // An error occurred while handling the request.
+      kApproved,  // The parent was verified and they approved.
+      kDeclined,  // The request was explicitly declined by the parent.
+      kCanceled,  // The request was canceled/dismissed by the parent.
+      kError,     // An error occurred while handling the request.
     };
-    Status status = Status::kCancelled;
+    Status status = Status::kCanceled;
 
     // The Parent Access Token.  Only set if status is kVerified.
     std::string parent_access_token = "";
diff --git a/chrome/browser/ui/webui/ash/parent_access/parent_access_dialog_browsertest.cc b/chrome/browser/ui/webui/ash/parent_access/parent_access_dialog_browsertest.cc
index 723bc2df..0aae330 100644
--- a/chrome/browser/ui/webui/ash/parent_access/parent_access_dialog_browsertest.cc
+++ b/chrome/browser/ui/webui/ash/parent_access/parent_access_dialog_browsertest.cc
@@ -43,7 +43,7 @@
   ParentAccessDialog::Callback callback = base::BindLambdaForTesting(
       [&](std::unique_ptr<ParentAccessDialog::Result> result) -> void {
         EXPECT_EQ(result->status,
-                  ParentAccessDialog::Result::Status::kCancelled);
+                  ParentAccessDialog::Result::Status::kCanceled);
         run_loop.Quit();
       });
 
@@ -157,7 +157,7 @@
   base::RunLoop run_loop;
 
   ParentAccessDialog::Result expected_result;
-  expected_result.status = ParentAccessDialog::Result::Status::kCancelled;
+  expected_result.status = ParentAccessDialog::Result::Status::kCanceled;
 
   // Create the callback.
   ParentAccessDialog::Callback callback = base::BindLambdaForTesting(
@@ -210,10 +210,12 @@
   // Set the result.
   ParentAccessDialog::GetInstance()->SetError();
 
-  run_loop.Run();
+  // The dialog instance should not be closed in the error state.
+  EXPECT_NE(ParentAccessDialog::GetInstance(), nullptr);
+  // Ensure that the callback is run when the dialog is manually closed.
+  ParentAccessDialog::GetInstance()->Close();
 
-  // The dialog instance should be gone after SetResult() is called.
-  EXPECT_EQ(ParentAccessDialog::GetInstance(), nullptr);
+  run_loop.Run();
 }
 
 // Verify that if dialog is destroyed without a Result,  it reports being
@@ -222,7 +224,7 @@
   base::RunLoop run_loop;
 
   ParentAccessDialog::Result expected_result;
-  expected_result.status = ParentAccessDialog::Result::Status::kCancelled;
+  expected_result.status = ParentAccessDialog::Result::Status::kCanceled;
 
   // Create the callback.
   ParentAccessDialog::Callback callback = base::BindLambdaForTesting(
diff --git a/chrome/browser/ui/webui/ash/parent_access/parent_access_ui.cc b/chrome/browser/ui/webui/ash/parent_access/parent_access_ui.cc
index abaa182..a05fea88 100644
--- a/chrome/browser/ui/webui/ash/parent_access/parent_access_ui.cc
+++ b/chrome/browser/ui/webui/ash/parent_access/parent_access_ui.cc
@@ -78,6 +78,7 @@
   source->AddResourcePath("parent_access_ui_handler.js",
                           IDR_PARENT_ACCESS_UI_HANDLER_JS);
   source->AddResourcePath("parent_access_after.js", IDR_PARENT_ACCESS_AFTER_JS);
+  source->AddResourcePath("parent_access_error.js", IDR_PARENT_ACCESS_ERROR_JS);
   source->AddResourcePath("flows/local_web_approvals_after.js",
                           IDR_LOCAL_WEB_APPROVALS_AFTER_JS);
   source->AddResourcePath("parent_access_ui.mojom-webui.js",
@@ -99,6 +100,8 @@
       {"localWebApprovalsAfterDetails",
        IDS_PARENT_ACCESS_LOCAL_WEB_APPROVALS_AFTER_DETAILS},
       {"webviewLoadingMessage", IDS_PARENT_ACCESS_WEBVIEW_LOADING_MESSAGE},
+      {"errorTitle", IDS_PARENT_ACCESS_ERROR_TITLE},
+      {"errorDescription", IDS_PARENT_ACCESS_ERROR_DESCRIPTION},
   };
   source->AddLocalizedStrings(kLocalizedStrings);
 
diff --git a/chrome/browser/ui/webui/ash/parent_access/parent_access_ui.mojom b/chrome/browser/ui/webui/ash/parent_access/parent_access_ui.mojom
index 120238ab..bdce8b9 100644
--- a/chrome/browser/ui/webui/ash/parent_access/parent_access_ui.mojom
+++ b/chrome/browser/ui/webui/ash/parent_access/parent_access_ui.mojom
@@ -66,7 +66,7 @@
 enum ParentAccessResult {
   kApproved,
   kDeclined,
-  kCancelled,
+  kCanceled,
   kError,
 };
 
diff --git a/chrome/browser/ui/webui/ash/parent_access/parent_access_ui_handler_impl.cc b/chrome/browser/ui/webui/ash/parent_access/parent_access_ui_handler_impl.cc
index 6c3491a3..708e4b4e 100644
--- a/chrome/browser/ui/webui/ash/parent_access/parent_access_ui_handler_impl.cc
+++ b/chrome/browser/ui/webui/ash/parent_access/parent_access_ui_handler_impl.cc
@@ -133,7 +133,7 @@
     case parent_access_ui::mojom::ParentAccessResult::kDeclined:
       delegate_->SetDeclined();
       break;
-    case parent_access_ui::mojom::ParentAccessResult::kCancelled:
+    case parent_access_ui::mojom::ParentAccessResult::kCanceled:
       delegate_->SetCanceled();
       break;
     case parent_access_ui::mojom::ParentAccessResult::kError:
diff --git a/chrome/browser/ui/webui/ash/parent_access/parent_access_ui_handler_impl_unittest.cc b/chrome/browser/ui/webui/ash/parent_access/parent_access_ui_handler_impl_unittest.cc
index 794d383..9f5f33b 100644
--- a/chrome/browser/ui/webui/ash/parent_access/parent_access_ui_handler_impl_unittest.cc
+++ b/chrome/browser/ui/webui/ash/parent_access/parent_access_ui_handler_impl_unittest.cc
@@ -287,7 +287,7 @@
   // Send the declined result status.
   base::RunLoop run_loop;
   parent_access_ui_handler_->OnParentAccessDone(
-      parent_access_ui::mojom::ParentAccessResult::kCancelled,
+      parent_access_ui::mojom::ParentAccessResult::kCanceled,
       base::BindLambdaForTesting([&]() -> void { run_loop.Quit(); }));
 
   run_loop.Run();
diff --git a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
index c1241d5..191bef7 100644
--- a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
+++ b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
@@ -553,7 +553,6 @@
       theme_service_(ThemeServiceFactory::GetForProfile(profile_)),
       ntp_custom_background_service_(
           NtpCustomBackgroundServiceFactory::GetForProfile(profile_)),
-      web_contents_(web_ui->GetWebContents()),
       // We initialize navigation_start_time_ to a reasonable value to account
       // for the unlikely case where the NewTabPageHandler is created before we
       // received the DidStartNavigation event.
@@ -597,22 +596,18 @@
   // background image available as soon as the page loads to prevent a potential
   // white flicker.
 
-  // Load time data is cached across page reloads. Listen for theme changes so
-  // that theme info is up-to-date when reloading.
-  native_theme_observation_.Observe(ui::NativeTheme::GetInstanceForNativeUi());
-  theme_service_observation_.Observe(theme_service_.get());
   ntp_custom_background_service_observation_.Observe(
       ntp_custom_background_service_.get());
 
   // Create and register customize chrome entry on unified side panel
   if (customize_chrome::IsSidePanelEnabled()) {
     auto* customize_chrome_tab_helper =
-        CustomizeChromeTabHelper::FromWebContents(web_contents_);
+        CustomizeChromeTabHelper::FromWebContents(web_contents());
     customize_chrome_tab_helper->CreateAndRegisterEntry();
   }
 
   // Populates the load time data with basic info.
-  OnThemeChanged();
+  OnColorProviderChanged();
   OnCustomBackgroundImageUpdated();
   OnLoad();
 }
@@ -623,7 +618,7 @@
   // Deregister customize chrome entry on unified side panel
   if (customize_chrome::IsSidePanelEnabled()) {
     auto* customize_chrome_tab_helper =
-        CustomizeChromeTabHelper::FromWebContents(web_contents_);
+        CustomizeChromeTabHelper::FromWebContents(web_contents());
     customize_chrome_tab_helper->DeregisterEntry();
   }
 }
@@ -692,7 +687,7 @@
 void NewTabPageUI::BindInterface(
     mojo::PendingReceiver<realbox::mojom::PageHandler> pending_page_handler) {
   realbox_handler_ = std::make_unique<RealboxHandler>(
-      std::move(pending_page_handler), profile_, web_contents_);
+      std::move(pending_page_handler), profile_, web_contents());
 }
 
 void NewTabPageUI::BindInterface(
@@ -737,7 +732,7 @@
 void NewTabPageUI::BindInterface(
     mojo::PendingReceiver<photos::mojom::PhotosHandler> pending_receiver) {
   photos_handler_ = std::make_unique<PhotosHandler>(std::move(pending_receiver),
-                                                    profile_, web_contents_);
+                                                    profile_, web_contents());
 }
 
 void NewTabPageUI::BindInterface(
@@ -757,7 +752,7 @@
     mojo::PendingReceiver<chrome_cart::mojom::CartHandler>
         pending_page_handler) {
   cart_handler_ = std::make_unique<CartHandler>(std::move(pending_page_handler),
-                                                profile_, web_contents_);
+                                                profile_, web_contents());
 }
 
 void NewTabPageUI::CreatePageHandler(
@@ -768,7 +763,7 @@
   page_handler_ = std::make_unique<NewTabPageHandler>(
       std::move(pending_page_handler), std::move(pending_page), profile_,
       ntp_custom_background_service_, theme_service_,
-      LogoServiceFactory::GetForProfile(profile_), web_contents_,
+      LogoServiceFactory::GetForProfile(profile_), web_contents(),
       navigation_start_time_);
 }
 
@@ -778,7 +773,7 @@
     mojo::PendingReceiver<customize_themes::mojom::CustomizeThemesHandler>
         pending_handler) {
   customize_themes_handler_ = std::make_unique<ChromeCustomizeThemesHandler>(
-      std::move(pending_client), std::move(pending_handler), web_contents_,
+      std::move(pending_client), std::move(pending_handler), web_contents(),
       profile_);
 }
 
@@ -802,22 +797,23 @@
   DCHECK(pending_page.is_valid());
   most_visited_page_handler_ = std::make_unique<MostVisitedHandler>(
       std::move(pending_page_handler), std::move(pending_page), profile_,
-      web_contents_, GURL(chrome::kChromeUINewTabPageURL),
+      web_contents(), GURL(chrome::kChromeUINewTabPageURL),
       navigation_start_time_);
   most_visited_page_handler_->EnableCustomLinks(IsCustomLinksEnabled());
   most_visited_page_handler_->SetShortcutsVisible(IsShortcutsVisible());
 }
 
-void NewTabPageUI::OnNativeThemeUpdated(ui::NativeTheme* observed_theme) {
-  OnThemeChanged();
-}
-
-void NewTabPageUI::OnThemeChanged() {
+// OnColorProviderChanged can be called during the destruction process and
+// should not directly access any member variables.
+void NewTabPageUI::OnColorProviderChanged() {
   base::Value::Dict update;
-  const ui::ColorProvider& color_provider = web_contents_->GetColorProvider();
+  if (!web_contents() || !web_ui())
+    return;
+  const ui::ColorProvider& color_provider = web_contents()->GetColorProvider();
   auto background_color = color_provider.GetColor(kColorNewTabPageBackground);
   update.Set("backgroundColor", skia::SkColorToHexString(background_color));
-  content::WebUIDataSource::Update(profile_, chrome::kChromeUINewTabPageHost,
+  content::WebUIDataSource::Update(Profile::FromWebUI(web_ui()),
+                                   chrome::kChromeUINewTabPageHost,
                                    std::move(update));
 }
 
diff --git a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.h b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.h
index 371b26d..da844092 100644
--- a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.h
+++ b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.h
@@ -43,7 +43,6 @@
 
 namespace content {
 class NavigationHandle;
-class WebContents;
 class WebUI;
 }  // namespace content
 
@@ -76,8 +75,6 @@
       public customize_themes::mojom::CustomizeThemesHandlerFactory,
       public most_visited::mojom::MostVisitedPageHandlerFactory,
       public browser_command::mojom::CommandHandlerFactory,
-      public ui::NativeThemeObserver,
-      public ThemeServiceObserver,
       public NtpCustomBackgroundServiceObserver,
       content::WebContentsObserver {
  public:
@@ -192,12 +189,6 @@
       mojo::PendingReceiver<most_visited::mojom::MostVisitedPageHandler>
           pending_page_handler) override;
 
-  // ui::NativeThemeObserver:
-  void OnNativeThemeUpdated(ui::NativeTheme* observed_theme) override;
-
-  // ThemeServiceObserver:
-  void OnThemeChanged() override;
-
   // NtpCustomBackgroundServiceObserver:
   void OnCustomBackgroundImageUpdated() override;
   void OnNtpCustomBackgroundServiceShuttingDown() override;
@@ -205,6 +196,7 @@
   // content::WebContentsObserver:
   void DidStartNavigation(
       content::NavigationHandle* navigation_handle) override;
+  void OnColorProviderChanged() override;
 
   bool IsCustomLinksEnabled() const;
   bool IsShortcutsVisible() const;
@@ -238,14 +230,9 @@
   raw_ptr<Profile> profile_;
   raw_ptr<ThemeService> theme_service_;
   raw_ptr<NtpCustomBackgroundService> ntp_custom_background_service_;
-  base::ScopedObservation<ui::NativeTheme, ui::NativeThemeObserver>
-      native_theme_observation_{this};
-  base::ScopedObservation<ThemeService, ThemeServiceObserver>
-      theme_service_observation_{this};
   base::ScopedObservation<NtpCustomBackgroundService,
                           NtpCustomBackgroundServiceObserver>
       ntp_custom_background_service_observation_{this};
-  raw_ptr<content::WebContents> web_contents_;
   // Time the NTP started loading. Used for logging the WebUI NTP's load
   // performance.
   base::Time navigation_start_time_;
diff --git a/chrome/browser/ui/webui/policy/policy_ui.cc b/chrome/browser/ui/webui/policy/policy_ui.cc
index 4ed5d14..f694726 100644
--- a/chrome/browser/ui/webui/policy/policy_ui.cc
+++ b/chrome/browser/ui/webui/policy/policy_ui.cc
@@ -52,6 +52,7 @@
     {"labelIsOffHoursActive", IDS_POLICY_LABEL_IS_OFFHOURS_ACTIVE},
     {"labelPoliciesPush", IDS_POLICY_LABEL_PUSH_POLICIES},
     {"labelPrecedence", IDS_POLICY_LABEL_PRECEDENCE},
+    {"labelProfileId", IDS_POLICY_LABEL_PROFILE_ID},
     {"labelRefreshInterval", IDS_POLICY_LABEL_REFRESH_INTERVAL},
     {"labelStatus", IDS_POLICY_LABEL_STATUS},
     {"labelTimeSinceLastFetchAttempt",
diff --git a/chrome/browser/ui/webui/settings/ash/main_section.cc b/chrome/browser/ui/webui/settings/ash/main_section.cc
index bf76aadd..17384877 100644
--- a/chrome/browser/ui/webui/settings/ash/main_section.cc
+++ b/chrome/browser/ui/webui/settings/ash/main_section.cc
@@ -55,6 +55,7 @@
       {"searchResults", IDS_SEARCH_RESULTS},
       {"searchResultSelected", IDS_OS_SEARCH_RESULT_ROW_A11Y_RESULT_SELECTED},
       {"clearSearch", IDS_CLEAR_SEARCH},
+      {"searchFeedbackButton", IDS_OS_SETTINGS_SEARCH_FEEDBACK_BUTTON},
   };
   html_source->AddLocalizedStrings(kLocalizedStrings);
 
@@ -169,6 +170,8 @@
 
   // This handler is for chrome://os-settings.
   html_source->AddBoolean("isOSSettings", true);
+  html_source->AddBoolean("searchFeedbackEnabled",
+                          ash::features::IsOsSettingsSearchFeedbackEnabled());
 
   html_source->AddBoolean("isGuest", features::IsGuestModeActive());
   html_source->AddBoolean(
diff --git a/chrome/browser/ui/webui/settings/ash/multidevice_section.cc b/chrome/browser/ui/webui/settings/ash/multidevice_section.cc
index a496eb6..56cad63 100644
--- a/chrome/browser/ui/webui/settings/ash/multidevice_section.cc
+++ b/chrome/browser/ui/webui/settings/ash/multidevice_section.cc
@@ -45,7 +45,6 @@
 using ::chromeos::settings::mojom::kMultiDeviceFeaturesSubpagePath;
 using ::chromeos::settings::mojom::kMultiDeviceSectionPath;
 using ::chromeos::settings::mojom::kNearbyShareSubpagePath;
-using ::chromeos::settings::mojom::kSmartLockSubpagePath;
 using ::chromeos::settings::mojom::Section;
 using ::chromeos::settings::mojom::Setting;
 using ::chromeos::settings::mojom::Subpage;
@@ -83,7 +82,7 @@
         {.subpage = mojom::Subpage::kMultiDeviceFeatures},
         {IDS_OS_SETTINGS_TAG_MULTIDEVICE_ALT1, SearchConcept::kAltTagEnd}},
        {IDS_OS_SETTINGS_TAG_MULTIDEVICE_SMART_LOCK,
-        mojom::kSmartLockSubpagePath,
+        mojom::kMultiDeviceFeaturesSubpagePath,
         mojom::SearchResultIcon::kLock,
         mojom::SearchResultDefaultRank::kMedium,
         mojom::SearchResultType::kSetting,
@@ -91,17 +90,6 @@
   return *tags;
 }
 
-const std::vector<SearchConcept>& GetSmartLockOptionsSearchConcepts() {
-  static const base::NoDestructor<std::vector<SearchConcept>> tags(
-      {{IDS_OS_SETTINGS_TAG_MULTIDEVICE_SMART_LOCK_OPTIONS,
-        mojom::kSmartLockSubpagePath,
-        mojom::SearchResultIcon::kLock,
-        mojom::SearchResultDefaultRank::kMedium,
-        mojom::SearchResultType::kSetting,
-        {.setting = mojom::Setting::kSmartLockUnlockOrSignIn}}});
-  return *tags;
-}
-
 const std::vector<SearchConcept>&
 GetMultiDeviceOptedInPhoneHubSearchConcepts() {
   static const base::NoDestructor<std::vector<SearchConcept>> tags({
@@ -298,17 +286,6 @@
   return *tags;
 }
 
-void AddEasyUnlockStrings(content::WebUIDataSource* html_source) {
-  static constexpr webui::LocalizedString kLocalizedStrings[] = {
-      {"easyUnlockSectionTitle", IDS_SETTINGS_EASY_UNLOCK_SECTION_TITLE},
-      {"easyUnlockUnlockDeviceOnly",
-       IDS_SETTINGS_EASY_UNLOCK_UNLOCK_DEVICE_ONLY},
-      {"easyUnlockUnlockDeviceAndAllowSignin",
-       IDS_SETTINGS_EASY_UNLOCK_UNLOCK_DEVICE_AND_ALLOW_SIGNIN},
-  };
-  html_source->AddLocalizedStrings(kLocalizedStrings);
-}
-
 bool IsOptedIn(HostStatus host_status) {
   return host_status == HostStatus::kHostSetButNotYetVerified ||
          host_status == HostStatus::kHostVerified;
@@ -628,8 +605,6 @@
           IDS_SETTINGS_MULTIDEVICE_PERMISSIONS_SETUP_DIALOG_NOTIFICATION_ACCESS_PROHIBITED_SUMMARY,
           GetHelpUrlWithBoard(phonehub::kPhoneHubLearnMoreLink)));
 
-  AddEasyUnlockStrings(html_source);
-
   // We still need to register strings even if Nearby Share is not supported.
   // For example, the HTML is always built but only displayed if Nearby Share is
   // supported.
@@ -734,20 +709,6 @@
   generator->RegisterNestedAltSetting(mojom::Setting::kInstantTetheringOnOff,
                                       mojom::Subpage::kMultiDeviceFeatures);
 
-  // Smart Lock.
-  generator->RegisterNestedSubpage(
-      IDS_SETTINGS_EASY_UNLOCK_SECTION_TITLE, mojom::Subpage::kSmartLock,
-      mojom::Subpage::kMultiDeviceFeatures, mojom::SearchResultIcon::kLock,
-      mojom::SearchResultDefaultRank::kMedium, mojom::kSmartLockSubpagePath);
-  static constexpr mojom::Setting kSmartLockSettings[] = {
-      mojom::Setting::kSmartLockOnOff,
-      mojom::Setting::kSmartLockUnlockOrSignIn,
-  };
-  RegisterNestedSettingBulk(mojom::Subpage::kSmartLock, kSmartLockSettings,
-                            generator);
-  generator->RegisterNestedAltSetting(mojom::Setting::kSmartLockOnOff,
-                                      mojom::Subpage::kMultiDeviceFeatures);
-
   // Nearby Share, registered regardless of the flag.
   generator->RegisterTopLevelSubpage(
       IDS_SETTINGS_NEARBY_SHARE_TITLE, mojom::Subpage::kNearbyShare,
@@ -784,17 +745,12 @@
     const multidevice_setup::MultiDeviceSetupClient::FeatureStatesMap&
         feature_states_map) {
   SearchTagRegistry::ScopedTagUpdater updater = registry()->StartUpdate();
-  updater.RemoveSearchTags(GetSmartLockOptionsSearchConcepts());
   updater.RemoveSearchTags(GetMultiDeviceOptedInPhoneHubSearchConcepts());
   updater.RemoveSearchTags(
       GetMultiDeviceOptedInPhoneHubCameraRollSearchConcepts());
   updater.RemoveSearchTags(GetMultiDeviceOptedInWifiSyncSearchConcepts());
   updater.RemoveSearchTags(GetMultiDeviceOptedInPhoneHubAppsSearchConcepts());
 
-  if (feature_states_map.at(Feature::kSmartLock) ==
-      FeatureState::kEnabledByUser) {
-    updater.AddSearchTags(GetSmartLockOptionsSearchConcepts());
-  }
   if (IsFeatureSupported(Feature::kPhoneHub)) {
     updater.AddSearchTags(GetMultiDeviceOptedInPhoneHubSearchConcepts());
     if (features::IsPhoneHubCameraRollEnabled() &&
diff --git a/chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom b/chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom
index 67887a8b..d575abf 100644
--- a/chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom
+++ b/chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom
@@ -52,7 +52,8 @@
 
   // MultiDevice section.
   kMultiDeviceFeatures = 200,
-  kSmartLock = 201,
+  // Note: Value 201 was for deprecated SmartLock subpage - see b/227674947.
+  // Do not reuse.
   kNearbyShare = 202,
 
   // People section.
diff --git a/chrome/browser/ui/webui/settings/chromeos/constants/setting.mojom b/chrome/browser/ui/webui/settings/chromeos/constants/setting.mojom
index ffc1d88..d58d4df 100644
--- a/chrome/browser/ui/webui/settings/chromeos/constants/setting.mojom
+++ b/chrome/browser/ui/webui/settings/chromeos/constants/setting.mojom
@@ -54,7 +54,8 @@
   kVerifyMultiDeviceSetup = 201,
   kMultiDeviceOnOff = 202,
   kSmartLockOnOff = 203,
-  kSmartLockUnlockOrSignIn = 204,
+  // Note: Value 204 was for deprecated kSmartLockUnlockOrSignIn -
+  // see b/227674947. Do not reuse.
   kMessagesSetUp = 205,
   kMessagesOnOff = 206,
   kForgetPhone = 207,
diff --git a/chrome/browser/ui/webui/webui_load_timer.cc b/chrome/browser/ui/webui/webui_load_timer.cc
index ac4f9f2..4099daa 100644
--- a/chrome/browser/ui/webui/webui_load_timer.cc
+++ b/chrome/browser/ui/webui/webui_load_timer.cc
@@ -49,7 +49,7 @@
 void WebuiLoadTimer::DOMContentLoaded(
     content::RenderFrameHost* render_frame_host) {
   // See comment in DocumentOnLoadCompletedInPrimaryMainFrame.
-  if (!timer_ || render_frame_host != web_contents()->GetPrimaryMainFrame())
+  if (!timer_ || !render_frame_host->IsInPrimaryMainFrame())
     return;
   CallUmaHistogramTimes(document_initial_load_uma_id_, timer_->Elapsed());
 }
diff --git a/chrome/browser/web_applications/README.md b/chrome/browser/web_applications/README.md
index 0ab304c..bae88b1 100644
--- a/chrome/browser/web_applications/README.md
+++ b/chrome/browser/web_applications/README.md
@@ -1,264 +1,4 @@
 # Web Apps
 
-This directory holds the core of Chromium's web app system. For a quick code
-starting point see [WebAppProvider::Start()](web_app_provider.h), this is the
-entry point where everything web app related begins.
-
-
-## What are web apps?
-
-Simply put web apps are sites that the user installs onto their machine
-mimicking a native app install on their respective operating system.
-
-### User entry points
-
-Sites that meet our install promotion requirements will have an install prompt
-appear in the omnibox on the right.
- - Example site: https://developers.google.com/
-Users can also install any site they like via `Menu > More tools > Create
-shortcut...`.
-
-Users can see all of their web apps on chrome://apps (viewable on non-ChromeOS).
-
-### Developer interface
-
-Sites customise how their installed site integrates at the OS level using a
-[web app manifest](https://www.w3.org/TR/appmanifest/).
-See developer guides for in depth overviews:
- - https://web.dev/progressive-web-apps/
- - https://web.dev/codelab-make-installable/
-
-## Terms & Phrases
-
-### Manifest, or WebManifest
-
-This refers to the the document described by the [appmanifest](https://www.w3.org/TR/appmanifest/) spec, with some extra features described by [manifest-incubations](https://wicg.github.io/manifest-incubations/index.html). This document describes metadata and developer configuration of an installable webapp.
-
-For code representations of the manifest see [the list](docs/manifest_representations.md).
-
-### Manifest Link
-
-A manifest link is something that looks like this in a html document:
-```html
-<link rel="manifest" href="manifest.webmanifest">
-```
-This link ties the manifest to the document, and subsequently used in the spec algorithms defined in [appmanifest](https://www.w3.org/TR/appmanifest/) or [manifest-incubations](https://wicg.github.io/manifest-incubations/index.html) to describe the webapp and determine if it is installable.
-
-### Installable
-
-If a document or page is considered "installable", then the user agent can create some form of installed web app for that page. To be installable, [web_app::CanCreateWebApp](https://source.chromium.org/search?q=web_app::CanCreateWebApp) must return true, where:
-* The user profile must allow webapps to be installed
-* The web contents of the page must not be crashed
-* The last navigation on the web contents must not be an error (like a 404)
-* The url must be `http`, `https`, or `chrome-extension`
-
-This is different than [promotable](#promotable) below, which determines if Chrome will promote installation of the page.
-
-### Promotable
-
-A document is considered "promotable" if it fulfils a set of criteria. This criteria may change to further encourage a better user experience for installable web apps. There are also a few optional checks that depend on the promotability checker. This general criteria as of 2021/04/20:
-* The document contains a manifest link.
-* The linked manifest can be [processed](https://www.w3.org/TR/appmanifest/#processing) according to the spec and is valid.
-* The processed manifest contains the fields:
-  * `name`
-  * `start_url`
-  * `icons` with at least one icon with a valid response that is a parsable image.
-  * `display` field that is not `"browser"`
-* "Serviceworker check": The `start_url` is 'controlled' (can be served by) a [serviceworker](https://developers.google.com/web/ilt/pwa/introduction-to-service-worker). **Optionally turned off**
-* "Engagement check": The user has engaged with, or interacted with, the page or origin a certain amount (currently at least one click and some seconds on the site). **Optionally turned off**
-
-Notes:
-* Per spec, the document origin and the `start_url` origin must match.
-* Per spec, the `start_url` origin does not have to match the `manifest_url` origin
-* The `start_url` could be different than the `document_url`.
-
-### Manifest id
-The `id` specified in manifest represents the identity of the web app. The manifest id is processed following the algorithm described in [appmanifest specification](https://www.w3.org/TR/appmanifest/#id-member) to produce the app's identity. In web app system, the app's [identity](https://www.w3.org/TR/appmanifest/#dfn-identity) is [hashed](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/web_applications/web_app_helpers.cc;l=69;drc=cafa646efbb6f668d3ba20ff482c1f729159ae97) to be stored to [WebApp->app_id()](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/web_applications/web_app.h;l=43;drc=cafa646efbb6f668d3ba20ff482c1f729159ae97;bpv=1;bpt=1).
-
-The app identity is verified in manifest updating process, if an app gets a manifest with [mismatched identity](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/web_applications/commands/manifest_update_data_fetch_command.cc;l=265-276), the update process is aborted.
-### Scope
-
-Scope refers to the prefix that a WebApp controls. All paths at or nested inside of a WebApp's scope is thought of as "controlled" or "in-scope" of that WebApp. This is a simple string prefix match. For example, if `scope` is `/my-app`, then the following will be "in-scope":
-* `/my-app/index.html`
-* `/my-app/sub/dir/hello.html`
-* `/my-app-still-prefixed/index.html` (Note: if the scope was `/my-app/`, then this would not be out-of-scope)
-
-And the following will "out-of-scope":
-* `/my-other-app/index.html`
-* `/index.html`
-
-### Display Mode
-
-The `display` of a webapp determines how the developer would like the app to look like to the user. See the [spec](https://www.w3.org/TR/appmanifest/#display-modes) for how the `display` member is processed in the manifest and what the display modes mean.
-
-### User Display Mode
-
-In addition to the developer-specified [`display`](#display-mode), the user can specify how they want a WebApp to be displayed, with the only option being whether to "open in a window" or not. Internally, this is expressed in the same display mode enumeration type as [`display`](#display-mode), but only the `kStandalone` and `kBrowser` values are used to specify "open in a window" and "do not open in a window", respectively.
-
-#### Effective Display Mode
-The psuedocode to determine the ACTUAL display mode a WebApp is displayed is:
-
-```js
-if (user_display_mode == kStandalone)
-  return developer_specified_display_mode;
-else
-  return kBrowser; // Open in a tab.
-```
-
-#### Open-in-window
-This refers to the user specifying that a WebApp should open in the developer specified display mode.
-
-#### Open-in-browser-tab
-This refers to the user specifying that a WebApp should NOT open in a window, and thus the WebApp, if launched, will just be opened in a browser tab.
-
-### Placeholder app
-There are some webapps which are managed by external sources - for example, the enterprise policy force-install apps, or the system web apps for ChromeOS. These are generally not installed by user interaction, and the WebAppProvider needs to install something for each of these apps.
-
-Sometimes, the installation of these apps can fail because the install url is not reachable (usually a cert or login needs to occur, and the url is redirected). When this happens, the system [can](https://source.chromium.org/search?q=ExternalInstallOptions::install_placeholder) install a "placeholder" app, which is a fake application that, when launched navigates to the install url of the application, given by the external app manager.
-
-When any web contents, either in-(placeholder)-app or in the browser, successfully [navigates](https://source.chromium.org/search?q=WebAppTabHelper::ReinstallPlaceholderAppIfNecessary) to a install url that the placeholder app is installed for, the web app installation is restarted for the true app, and after that installation succeeds the placeholder app is uninstalled.
-
-### Locally Installed
-When signing into a non-ChromeOS device, all web apps are installed but not **locally installed**. This means that OS integration is not triggered (so there are no platform shortcuts created), install icons will still show up for the app websites, and the app icon will appear greyed out on chrome://apps.
-
-For an app to become locally installed, the user must do one of the following:
-* Navigate to `chrome://apps`, find the greyed-out icon of the app, right click on it, and select "Install".
-* Follow any of the normal installation routes to install that app (e.g. visit the app page in the browser and interact with the omnibox install icon)
-
-This was done because on non-ChromeOS devices it was considered a bad user experience to fully install all of the profile's web apps (creating platform shortcuts, etc), as this might not be expected by the user.
-
-## What makes up Chromium's implementation?
-
-The task of turning web sites into "apps" in the user's OS environment has many parts to it. Before going into the parts, here is where they live:
-
-[![](docs/webappprovider_component_ownership.jpg)](https://docs.google.com/drawings/d/1TqUF2Pqh2S5qPGyA6njQWxOgSgKQBPePKPIH_srGeRk/edit?usp=sharing)
-
-* The `WebAppProvider` core system lives on the `Profile` object.
-* The `WebAppUiManagerImpl` also lives on the `Profile` object (to avoid deps issues).
-* The `AppBrowserController` (typically `WebAppBrowserController` for our interests) lives on the `Browser` object.
-* The `WebAppTabHelper` lives on the `WebContents` object.
-
-While most on-disk storage is done in the [`WebAppSyncBridge`](#webappsyncbridge), the system also sometimes uses the `PrefService`. Most of these prefs live on the `Profile` (`profile->GetPrefs()`), but some prefs are in the global browser prefs (`g_browser_process->local_state()`). See the [storage](#storage) section below for more info.
-
-There is a presentation that also goes over the class structure and dependency diagram [here](https://docs.google.com/presentation/d/1bJfUFPMh7J_Avw3J4HBvAN2RWnV4T9LOXr0JSAgQAvg/pub), but it may be out of date.
-
-Here is more info for some (but not all) of the key parts:
-
-### [`WebAppProvider`](web_app_provider.h)
-
-This is a per-profile object housing all the various web app subsystems. This is
-the "main()" of the web app implementation where everything starts.
-
-
-### [`WebApp`](web_app.h)
-
-This is the representation of an installed web app in RAM. Its member fields
-largely reflect all the ways a site can configure their
-[web app manifest](https://www.w3.org/TR/appmanifest/) plus miscellaneous
-internal bookkeeping and user settings.
-
-
-### [`WebAppRegistrar`](web_app_registrar.h)
-
-This is where all the [`WebApp`](web_app.h)s live in memory and what many other
-subsystems query to look up any given web app's fields. Mutations to the
-registry have to go via [`WebAppSyncBridge`](web_app_sync_bridge.h).
-
-Why is it full of `GetAppXYZ()` getters for every field instead of just
-returning a `WebApp` reference? Because web apps used to be backed by
-`Extension`s and in that mode there were no `WebApp`s; instead everything was
-stored on an `Extension`.
-
-### [`WebAppSyncBridge`](web_app_sync_bridge.h)
-
-This is "bridge" between the WebAppProvider system's in-memory representation of web apps and the sync system's database representation (along with sync system functionality like add/remove/modify operations). This integration is a little complex and deserves it's own document, but it basically:
-* Stores all WebApps into a database and updates the database if any fields change.
-* Updates the system when there are changes from the sync system.
-  * Installs new apps, uninstalls apps the user uninstalled elsewhere, updates metadata like user display mode preference, etc.
-* Tells the sync system if there are local changes (installs, uninstalls, etc).
-
-There is also a slide in a presentation [here](https://docs.google.com/presentation/d/e/2PACX-1vQxYZoCyhZ4xHS4pVuBC9YoE0O-QpW2Wj3scl6jtr3TEYheeod5Ch4b7OVEQEj_Hc6PM1RBGzovug3C/pub?start=false&loop=false&delayms=3000&slide=id.g59d9cb05b6_6_5) which illustrates how this system works, but it may be out of date.
-
-Note: This only stores per-web-app data, and that data will be deleted if the web app is uninstalled. To store data that persists after uninstall, or applies to a more general scope than a single web app, then the `PrefService` can be used, either on the `Profile` object (per-profile data, `profile->GetPrefs()`) or on the browser process (`g_browser_process->local_state()`). Example of needing prefs:
-* Storing if an app was previously installed as a preinstalled app in the past.
-* Information is needed during chrome startup before profiles are loaded.
-* A feature needs to store global data - e.g. "When was the last time we showed the in-product-help banner for any webapp?"
-
-### [`WebAppInstallManager`](web_app_install_manager.h)
-
-This is where web apps are created, updated and removed. The install manager
-spawns [`WebAppInstallTask`](web_app_install_task.h)s for each "job".
-
-Installation comes in many different forms from a simple "here's all the
-[info](web_app_install_info.h) necessary please install it" to
-"please install the site currently loaded in this web contents and fetch all the
-manifest data it specifies" with a few inbetweens.
-
-
-### [`ExternallyManagedAppManager`](externally_managed_app_manager.h)
-
-This is for all installs that are not initiated by the user. This includes
-[preinstalled apps](preinstalled_web_app_manager.h),
-[policy installed apps](policy/web_app_policy_manager.h) and
-[system web apps](../ash/system_web_apps/system_web_app_manager.h).
-
-These all specify a set of [install URLs](external_install_options.h)
-which the `ExternallyManagedAppManager` synchronises the set of currently
-installed web apps with.
-
-
-### [`WebAppInstallFinalizer`](web_app_install_finalizer.h)
-
-This is the tail end of the installation process where we write all our web app
-metadata to [disk](web_app_database.h) and deploy OS integrations (like
-[desktop shortcuts](web_app_shortcut.h) and
-[file handlers](web_app_file_handler_manager.h)) using the
-[`OsIntegrationManager`](os_integration_manager.h).
-
-This also manages the uninstallation process.
-
-### [`WebAppUiManager`](../ui/web_applications/web_app_ui_manager_impl.h)
-
-Sometimes we need to query window state from chrome/browser/ui land even though
-our BUILD.gn targets disallow this as it would be a circular dependency. This
-[abstract class](web_app_ui_manager.h) + [impl](web_app_ui_manager_impl.h)
-injects the dependency at link time (see
-[`WebAppUiManager::Create()`](https://source.chromium.org/search?q=WebAppUiManager::Create)'s
-declaration and definition locations).
-
-## Storage
-
-TODO
-
-### [`AppShimRegistry`](app_shim_registry_mac.h)
-
-On Mac OS we sometimes need to reason about the state of installed PWAs in all
-profiles without loading those profiles into memory. For this purpose,
-`AppShimRegistry` stores the needed information in Chrome's "Local State"
-(global preferences). The information stored here includes:
-
- * All profiles a particular web app is installed in.
- * What profiles a particular web app was open in when it was last used.
- * What file and protocol handlers are enabled for a web app in each profile
-   it is installed in.
-
-This information is used when launching a web app (to determine what profile
-or profiles to open the web app in), as well as when updating an App Shim
-(to make sure all file and protocol handlers for the app are accounted for).
-
-## Deep Dives
-
-* [Installation Sources & Pipeline](docs/installation_pipeline.md)
-* [Operating system integration](docs/os_integration.md)
-* TODO: Uninstallation
-* TODO: Manifest Update
-
-## Testing
-
-See [the testing docs](docs/testing.md).
-
-## Debugging
-
-Use [chrome://web-app-internals](chrome://web-app-internals) to inspect internal
-web app state. For Chromium versions prior to M93 use
-[chrome://internals/web-app](chrome://internals/web-app).
+Moved to [/docs/webapps/README.md](/docs/webapps/README.md)
+ 
\ No newline at end of file
diff --git a/chrome/browser/web_applications/commands/sub_app_install_command.cc b/chrome/browser/web_applications/commands/sub_app_install_command.cc
index 479d93e..b0fbef69 100644
--- a/chrome/browser/web_applications/commands/sub_app_install_command.cc
+++ b/chrome/browser/web_applications/commands/sub_app_install_command.cc
@@ -215,7 +215,7 @@
   }
 
   url_loader_->LoadUrl(
-      install_url, shared_web_contents(),
+      install_url, &lock_->shared_web_contents(),
       WebAppUrlLoader::UrlComparison::kIgnoreQueryParamsAndRef,
       base::BindOnce(
           &SubAppInstallCommand::OnWebAppUrlLoadedGetWebAppInstallInfo,
@@ -250,7 +250,7 @@
   }
 
   data_retriever_->GetWebAppInstallInfo(
-      shared_web_contents(),
+      &lock_->shared_web_contents(),
       base::BindOnce(&SubAppInstallCommand::OnGetWebAppInstallInfo,
                      weak_ptr_factory_.GetWeakPtr(), unhashed_app_id));
 }
@@ -275,7 +275,7 @@
   install_info->user_display_mode = UserDisplayMode::kStandalone;
 
   data_retriever_->CheckInstallabilityAndRetrieveManifest(
-      shared_web_contents(), /*bypass_service_worker_check=*/false,
+      &lock_->shared_web_contents(), /*bypass_service_worker_check=*/false,
       base::BindOnce(&SubAppInstallCommand::OnDidPerformInstallableCheck,
                      weak_ptr_factory_.GetWeakPtr(), unhashed_app_id,
                      std::move(install_info)));
@@ -326,7 +326,7 @@
   base::flat_set<GURL> icon_urls = GetValidIconUrlsToDownload(*web_app_info);
 
   data_retriever_->GetIcons(
-      shared_web_contents(), std::move(icon_urls), skip_page_favicons,
+      &lock_->shared_web_contents(), std::move(icon_urls), skip_page_favicons,
       base::BindOnce(&SubAppInstallCommand::OnIconsRetrievedShowDialog,
                      weak_ptr_factory_.GetWeakPtr(), unhashed_app_id,
                      std::move(web_app_info)));
@@ -386,7 +386,7 @@
 
   RecordWebAppInstallationTimestamp(profile_->GetPrefs(), app_id,
                                     webapps::WebappInstallSource::SUB_APP);
-  RecordAppBanner(shared_web_contents(), start_url);
+  RecordAppBanner(&lock_->shared_web_contents(), start_url);
   MaybeFinishInstall(unhashed_app_id,
                      webapps::InstallResultCode::kSuccessNewInstall);
 }
@@ -465,7 +465,7 @@
 }
 
 bool SubAppInstallCommand::IsWebContentsDestroyed() {
-  return !shared_web_contents() || shared_web_contents()->IsBeingDestroyed();
+  return lock_->shared_web_contents().IsBeingDestroyed();
 }
 
 void SubAppInstallCommand::AddResultToDebugData(
diff --git a/chrome/browser/web_applications/commands/web_app_command.cc b/chrome/browser/web_applications/commands/web_app_command.cc
index eca03dbb..89755475 100644
--- a/chrome/browser/web_applications/commands/web_app_command.cc
+++ b/chrome/browser/web_applications/commands/web_app_command.cc
@@ -48,21 +48,6 @@
   return command_manager_;
 }
 
-void WebAppCommand::RequestLock(WebAppCommandManager* command_manager,
-                                WebAppLockManager* lock_manager,
-                                LockAcquiredCallback on_lock_acquired) {
-  lock_manager->AcquireLock(
-      lock_description(),
-      base::BindOnce(std::move(on_lock_acquired),
-                     base::BindOnce(&WebAppCommand::PrepareForStart,
-                                    AsWeakPtr(), command_manager)));
-}
-
-void WebAppCommand::PrepareForStart(WebAppCommandManager* command_manager) {
-  command_manager_ = command_manager;
-  Start();
-}
-
 base::WeakPtr<WebAppCommand> WebAppCommand::AsWeakPtr() {
   return weak_factory_.GetWeakPtr();
 }
diff --git a/chrome/browser/web_applications/commands/web_app_command.h b/chrome/browser/web_applications/commands/web_app_command.h
index 981d72a..f288bf94 100644
--- a/chrome/browser/web_applications/commands/web_app_command.h
+++ b/chrome/browser/web_applications/commands/web_app_command.h
@@ -45,12 +45,13 @@
 // re-use each other easily.
 //
 // Invariants:
-// * Destruction can occur without `Start()` being called. If the system shuts
+// * Destruction can occur without `StartWithLock()` being called. If the system
+// shuts
 //   down and the command was never started, then it will simply be destructed.
 // * `OnShutdown()` and `OnSyncSourceRemoved()` are only called if
 //   the command has been started.
-// * `SignalCompletionAndSelfDestruct()` can ONLY be called if `Start()` has
-//   been called. Otherwise it will CHECK-fail.
+// * `SignalCompletionAndSelfDestruct()` can ONLY be called if `StartWithLock()`
+//    has been called. Otherwise it will CHECK-fail.
 class WebAppCommand {
  public:
   using Id = int;
@@ -82,15 +83,13 @@
 
   // Triggered by the WebAppCommandManager. Request lock and start the command
   // after the lock is acquired.
-  // TODO(https://crbug.com/1375870): remove the implementation here after all
-  // commands migrate to use `WebAppCommandTemplate`.
   virtual void RequestLock(WebAppCommandManager* command_manager,
                            WebAppLockManager* lock_manager,
-                           LockAcquiredCallback on_lock_acquired);
+                           LockAcquiredCallback on_lock_acquired) = 0;
 
   // This is called when the sync system has triggered an uninstall for an app
-  // id that is relevant to this command and this command is running (`Start()
-  // has been called). Relevance is determined by the
+  // id that is relevant to this command and this command is running
+  // (`StartWithLock()` has been called). Relevance is determined by the
   // `WebAppCommandLock::IsAppLocked()` function for this command's lock). The
   // web app should still be in the registry, but it will no longer have the
   // `WebAppManagement::kSync` source and `is_uninstalling()` will return true.
@@ -109,47 +108,23 @@
   //                           command, it can be passed here to ensure it is
   //                           called after this  command is destructed and any
   //                           chained  commands are queued.
-  // Note: This can ONLY be called if `Start()` has been called (`IsStarted()`
-  // is true). Otherwise it will CHECK-fail.
+  // Note: This can ONLY be called if `StartWithLock()` has been called
+  // (`IsStarted()` is true). Otherwise it will CHECK-fail.
   void SignalCompletionAndSelfDestruct(
       CommandResult result,
       base::OnceClosure call_after_destruction);
 
   virtual WebAppCommandManager* command_manager() const;
 
-  // If the `lock()` includes the lock for the kBackgroundWebContents, then this
-  // will be populated when `Start()` is called.
-  // Commands can assume that this WebContents will outlive them.
-  content::WebContents* shared_web_contents() const {
-    return shared_web_contents_;
-  }
-
   SEQUENCE_CHECKER(command_sequence_checker_);
 
  private:
   friend class WebAppCommandManager;
 
-  // Start called by the WebAppCommandManager.
-  void PrepareForStart(WebAppCommandManager* command_manager);
-
-  // Triggered after lock is acquired. Signals that this command can
-  // start its operations. When this command is complete, it should call
-  // `SignalCompletionAndSelfDestruct` to signal it's completion and destruct
-  // itself. Note: It is not guaranteed that the web app this command was
-  // created for is still installed. All state must be re-checked when method
-  // this is called.
-  virtual void Start() = 0;
-
   base::WeakPtr<WebAppCommand> AsWeakPtr();
 
   Id id_;
   raw_ptr<WebAppCommandManager> command_manager_ = nullptr;
-  // Because this is owned by the command manager, it will always outlive this
-  // object. Thus a raw pointer is save.
-  //
-  // TODO(crbug.com/1298696): unit_tests breaks with MTECheckedPtr
-  // enabled. Triage.
-  raw_ptr<content::WebContents, DegradeToNoOpWhenMTE> shared_web_contents_;
 
   base::WeakPtrFactory<WebAppCommand> weak_factory_{this};
 };
@@ -160,6 +135,12 @@
   WebAppCommandTemplate() = default;
   ~WebAppCommandTemplate() override = default;
 
+  // Triggered after lock is acquired. Signals that this command can
+  // start its operations. When this command is complete, it should call
+  // `SignalCompletionAndSelfDestruct` to signal it's completion and destruct
+  // itself. Note: It is not guaranteed that the web app this command was
+  // created for is still installed. All state must be re-checked when this
+  // method is called.
   virtual void StartWithLock(std::unique_ptr<LockType> lock) = 0;
 
  protected:
@@ -168,10 +149,6 @@
   }
 
  private:
-  //  TODO(https://crbug.com/1375870): remove after all commands are migrated to
-  //  use the template.
-  void Start() override {}
-
   void RequestLock(WebAppCommandManager* command_manager,
                    WebAppLockManager* lock_manager,
                    LockAcquiredCallback on_lock_acquired) override;
diff --git a/chrome/browser/web_applications/docs/install_pipeline_2021_04_22.png b/chrome/browser/web_applications/docs/install_pipeline_2021_04_22.png
deleted file mode 100644
index 3f8ebcc..0000000
--- a/chrome/browser/web_applications/docs/install_pipeline_2021_04_22.png
+++ /dev/null
Binary files differ
diff --git a/chrome/browser/web_applications/docs/installation_pipeline.md b/chrome/browser/web_applications/docs/installation_pipeline.md
deleted file mode 100644
index a5ab9608..0000000
--- a/chrome/browser/web_applications/docs/installation_pipeline.md
+++ /dev/null
@@ -1,122 +0,0 @@
-# [Web Apps](../README.md) - Installation
-
-Installing a webapp can come from a variety of channels. This section serves to enumerate them all and show how they fit together in the installation pipeline. 
-
-Generally, installations all go through the following flow:
-1. Method call in WebAppInstallationManager.
-1. A WebAppInstallTask being queued and run, doing a variety of checks and operations.
-1. A call into WebAppInstallFinalizer, which:
-   1. Triggers any OS integration operations required (creating shortcuts, registering file handlers, etc)
-   1. Saves the WebApp to the database.
-
-The ExternallyManagedAppManager adds a few steps before this, and will sometimes (for placeholder apps) skip the WebAppInstallationManager and directly call into the WebAppInstallFinalizer.
-
-To see a detailed flowchart, see the the [flowchart](#flowchart) below.
-
-## Installation Sources
-
-There are a variety of installation sources and expectations tied to those sources.
-
-### Omnibox install icon
-User-initiated installation. To make the omnibox install icon visible, the document must:
-* Be [promotable](../README.md#promotable) and [installable](../README.md#installable).
-* NOT be inside of the scope of an installed WebApp with an [effective display mode](../README.md#effective-display-mode) display mode that isn't `kBrowser`.
-
-Triggers an install view that will show the name & icon to the user to confirm install.
-
-Calls [`WebAppInstallManager::InstallWebAppFromManifest`](https://source.chromium.org/search?q=function:WebAppInstallManager::InstallWebAppFromManifest), providing just the `WebContents` of the installable page.
-
-Fails if, after the user clicks :
-* After clicking on the install icon, the `WebContents` is no longer [promotable](../README.md#promotable), skipping engagement checks.
-* The user rejects the installation dialog.
-
-
-### 3-dot menu option "Install {App_Name}..."
-User-initiated installation. To make the install menu option visible, the document must:
-* Be [promotable](../README.md#promotable) and [installable](../README.md#installable).
-* NOT be inside of the scope of an installed WebApp with an [effective display mode](../README.md#effective-display-mode) display mode that isn't `kBrowser`.
-
-Triggers an install view that will show the name & icon to the user to confirm install.
-
-Calls [`WebAppInstallManager::InstallWebAppFromManifestWithFallback`](https://source.chromium.org/search?q=WebAppInstallManager::InstallWebAppFromManifestWithFallback) with the `WebContents` of the installable page.
-
-Fails if:
-* The user rejects the installation dialog.
-
-Notably, this option does not go through the same exact pathway as the [omnibox install icon](#omnibox-install-icon), as it shares the call-site as the "Create Shortcut" method below. The main functional difference here is that if the site becomes no longer [promotable](../README.md#promotable) in between clicking on the menu option and the install actually happening, it will not fail and instead fall back to a fake manifest and/or fake icons based on the favicon. Practically, this option doesn't show up if the site is [promotable](../README.md#promotable). Should it share installation pathways as the the [omnibox install icon](#omnibox-install-icon)? Probably, yes.
-
-### 3-dot menu option "Create Shortcut..."
-User-initiated installation. This menu option is always available, except for internal chrome urls like chrome://settings.
-
-Prompts the user whether the shortcut should "open in a window". If the user checks this option, then the resulting WebApp will have the [user display](../README.md#user-display-mode) set to `kStandalone` / open-in-a-window.
-
-The document does not need to have a manifest for this install path to work. If no manifest is found, then a fake one is created with `start_url` equal to the document url, `name` equal to the document title, and the icons are generated from the favicon (if present).
-
-Calls [`WebAppInstallManager::InstallWebAppFromManifestWithFallback`](https://source.chromium.org/search?q=WebAppInstallManager::InstallWebAppFromManifestWithFallback) with the `WebContents` of the page.
-
-Fails if:
-* The user rejects the shortcut creation dialog.
-
-### ChromeOS Management API
-Checks [promotability](../README.md#promotable) before installing, skipping engagement and serviceworker checks
-
-Calls [`WebAppInstallManager::InstallWebAppFromManifest`](https://source.chromium.org/search?q=WebAppInstallManager::InstallWebAppFromManifest), providing just the `WebContents` of the installable page.
-
-TODO: Document when this API is called & why.
-
-### Externally Managed Apps
-There are a number of apps that are managed externally. This means that there is an external manager keeps it's own list of web apps that need to be installed for a given external install source.
-
-See the [`web_app::ExternalInstallSource`](https://source.chromium.org/search?q=web_app::ExternalInstallSource) enum to see all types of externally managed apps. Each source type should have an associated "manager" that gives the list of apps to `ExternallyManagedAppProvider::SynchronizeInstalledApps`.
-
-These installations are customizable than user installations, as these external app management surfaces need to specify all of the options up front (e.g. create shortcut on desktop, open in window, run on login, etc). Thus the install function [`WebAppInstallManager::InstallWebAppWithParams`](https://source.chromium.org/search?q=WebAppInstallManager::InstallWebAppWithParams) is called here, with the params generated by [`web_app::ConvertExternalInstallOptionsToParams`](https://source.chromium.org/search?q=web_app::ConvertExternalInstallOptionsToParams).
-
-The general installation flow of an externally managed app is:
-1. A call to [`ExternallyManagedAppProvider::SynchronizeInstalledApps`](https://source.chromium.org/search?q=ExternallyManagedAppProvider::SynchronizeInstalledApps)
-1. Finding all apps that need to be uninstalled and uninstalling them, find all apps that need to be installed and:
-1. Queue an `ExternallyManagedAppInstallTask` to install each app sequentially.
-1. Each task loads the url for the app.
-1. If the url is successfully loaded, then call [`WebAppInstallManager::InstallWebAppWithParams`](https://source.chromium.org/search?q=WebAppInstallManager::InstallWebAppWithParams), and continue installation on the normal pipeline (described above, and [flowchart](#flowchart) below).
-1. If the url fails to fully load (usually a redirect if the user needs to sign in or corp credentials are not installed), and the external app manager specified a [placeholder app was required](https://source.chromium.org/search?q=ExternalInstallOptions::install_placeholder) then:
-   1. Synthesize a web app with `start_url` as the document url, and `name` as the document title
-   1. Install that webapp using [`WebAppInstallManager::InstallWebAppFromInfo`](https://source.chromium.org/search?q=WebAppInstallManager::InstallWebAppFromInfo). This is **not** part of the regular install pipeline, and basically directly saves the webapp into the database without running OS integration.
-
-These placeholder apps are not meant to stay, and to replace them with the intended apps, the following occurs:
-1. The WebAppProvider system listens to every page load.
-1. If a [navigation is successful](https://source.chromium.org/search?q=WebAppTabHelper::ReinstallPlaceholderAppIfNecessary) to a url that the placeholder app is installed for, then
-   1. The installation is started again with a call to [`WebAppInstallManager::InstallWebAppWithParams`](https://source.chromium.org/search?q=WebAppInstallManager::InstallWebAppWithParams).
-   1. If successful, the placeholder app is uninstalled.
-
-
-### Sync
-
-When the sync system receives an WebApp to install, it calls [`WebAppInstallManager::EnqueueInstallAppFromSync`](https://source.chromium.org/search?q=WebAppInstallManager::EnqueueInstallAppFromSync), which starts the normal installation pipeline. One major difference is if the installation fails for any reason (manifest is invalid or fails to load, etc), then a backup installation happens with a call to [`WebAppInstallTask::InstallWebAppFromInfoRetrieveIcons`](https://source.chromium.org/search?q=WebAppInstallTask::InstallWebAppFromInfoRetrieveIcons). This:
-1. Attempts to get icon data for the install url.
-1. Filters and generates missing icons from the favicon, if there was one.
-1. Calls `WebAppInstallFinalizer` to finalize the install.
-  1. If the platform is not ChromeOS, then the app will not become [locally installed](../README.md#locally-installed). This means that OS integration will not be triggered, no platform shortcuts created, etc.
-  1. If the platform is ChromeOS, it will become [locally installed](../README.md#locally-installed), and all OS integrations will be triggered (just like a normal user-initiated install.)
-
-#### Retry on startup
-Sync installs have a few extra complications:
-* They need to be immediately saved to the database & be installed eventually.
-* Many are often queued up during a new profile sign-in, and it's not uncommon for the user to quit before the installation queue finishes.
-
-Due to this, unlike other installs, a special [`WebApp::is_from_sync_and_pending_installation`](https://source.chromium.org/search?q=WebApp::is_from_sync_and_pending_installation) ([protobuf](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/web_applications/proto/web_app.proto;l=110;bpv=1;bpt=1?q=web_app.proto%20is_from_sync_and_pending_installation&ss=chromium)) variable is saved in the database. WebApps with this set to true are treated as not fully installed, and are often left out of app listings. This variable is reset back to `false` when the app is finished installing.
-
-To handle the cases above, on startup when the database is loaded, any WebApp with `is_from_sync_and_pending_installation` of `true` will be re-installed inside of [`WebAppSyncBridge::MaybeInstallAppsFromSyncAndPendingInstallation`](https://source.chromium.org/search?q=WebAppSyncBridge::MaybeInstallAppsFromSyncAndPendingInstallation)
-
-## Installation State Modifications
-
-### Installing locally
-On non-ChromeOS devices, an app can be [not locally installed](../README.md#locally-installed). To become locally installed, the user can follow a normal install method (install icon will show up), or they can interact with the app on `chrome://apps`.
-
-The `chrome://apps` code is unique here, and instead of re-installing the app, in manually sets the locally_installed bit to true in [`AppLauncherHandler::HandleInstallAppLocally`](https://source.chromium.org/search?q=AppLauncherHandler::HandleInstallAppLocally), and triggers OS integration in [`AppLauncherHandler::InstallOsHooks`](https://source.chromium.org/search?q=AppLauncherHandler::HandleInstallAppLocally)
-
-### Creating Shortcuts
-Similarly to above, in `chrome://apps` the user can "Create Shortcuts..." for a web app. This should overwrite any shortcuts already created, and basically triggers OS integration to install shortcuts again in [`AppLauncherHandler::HandleCreateAppShortcut`](https://source.chromium.org/search?q=AppLauncherHandler::HandleCreateAppShortcut)
-
-## Flowchart
-
-For reference, this is a flowchart of the installation pipeline as of 2021/04/20: ![Installation pipeline flowchart](install_pipeline_2021_04_22.png)
-Or see https://app.code2flow.com/0JJ8c5nNY4hL for an interactive version.
\ No newline at end of file
diff --git a/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_browsertest.cc b/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_browsertest.cc
index 11f74d5..51cc4186 100644
--- a/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_browsertest.cc
+++ b/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_browsertest.cc
@@ -320,6 +320,40 @@
   EXPECT_FALSE(found);
 }
 
+IN_PROC_BROWSER_TEST_F(IsolatedWebAppBrowserTest, WasmLoadableFromFile) {
+  web_app::IsolatedWebAppUrlInfo url_info = InstallDevModeProxyIsolatedWebApp(
+      isolated_web_app_dev_server().GetOrigin());
+  content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());
+
+  content::EvalJsResult result = EvalJs(app_frame, R"(
+    (async function() {
+      const response = await fetch('empty.wasm');
+      await WebAssembly.instantiateStreaming(response);
+      return 'loaded';
+    })();
+  )");
+
+  EXPECT_EQ("loaded", result);
+}
+
+IN_PROC_BROWSER_TEST_F(IsolatedWebAppBrowserTest, WasmLoadableFromBytes) {
+  web_app::IsolatedWebAppUrlInfo url_info = InstallDevModeProxyIsolatedWebApp(
+      isolated_web_app_dev_server().GetOrigin());
+  content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());
+
+  content::EvalJsResult result = EvalJs(app_frame, R"(
+    (async function() {
+      // The smallest possible Wasm module. Just the header (0, "A", "S", "M"),
+      // and the version (0x1).
+      const bytes = new Uint8Array([0, 0x61, 0x73, 0x6d, 0x1, 0, 0, 0]);
+      await WebAssembly.instantiate(bytes);
+      return 'loaded';
+    })();
+  )");
+
+  EXPECT_EQ("loaded", result);
+}
+
 class IsolatedWebAppBrowserCookieTest : public IsolatedWebAppBrowserTest {
  public:
   using CookieHeaders = std::vector<std::string>;
diff --git a/chrome/browser/web_applications/web_app_command_manager.cc b/chrome/browser/web_applications/web_app_command_manager.cc
index 70a3adff..74a52b9b 100644
--- a/chrome/browser/web_applications/web_app_command_manager.cc
+++ b/chrome/browser/web_applications/web_app_command_manager.cc
@@ -125,9 +125,11 @@
   DCHECK(command_it != commands_.end());
 #endif
   if (command->lock_description().IncludesSharedWebContents()) {
-    command->shared_web_contents_ = EnsureWebContentsCreated();
+    CHECK(shared_web_contents_);
     url_loader_->PrepareForLoad(
-        command->shared_web_contents(),
+        // web_contents is created by `WebAppLockManager` when lock is granted,
+        // this grabs the same web_contents.
+        shared_web_contents_.get(),
         base::BindOnce(&WebAppCommandManager::OnAboutBlankLoadedForCommandStart,
                        weak_ptr_factory_.GetWeakPtr(), command,
                        std::move(start_command)));
@@ -200,9 +202,9 @@
   // commands. The main complications that can occur are a command calling
   // `CompleteAndDestruct` or `ScheduleCommand` inside of the
   // `OnSyncSourceRemoved` call. Because all commands are
-  // `Start()`ed asynchronously, we will never have to notify any commands that
-  // are newly scheduled. So at most one command needs to be notified per queue,
-  // and that command can be destroyed before we notify it.
+  // `StartWithLock()`ed asynchronously, we will never have to notify any
+  // commands that are newly scheduled. So at most one command needs to be
+  // notified per queue, and that command can be destroyed before we notify it.
   std::vector<base::WeakPtr<WebAppCommand>> commands_to_notify;
   for (const AppId& app_id : app_ids) {
     for (const auto& [id, command] : commands_) {
diff --git a/chrome/browser/web_applications/web_app_command_manager.h b/chrome/browser/web_applications/web_app_command_manager.h
index 954fac4..bce687d 100644
--- a/chrome/browser/web_applications/web_app_command_manager.h
+++ b/chrome/browser/web_applications/web_app_command_manager.h
@@ -35,10 +35,11 @@
 // from the WebAppProvider system. To use, simply call `ScheduleCommand` to
 // schedule the given command or a CallbackCommand with given callback.
 //
-// Commands will be executed (`Start()` will be called) in-order based on
-// command's `WebAppCommandLock`, the `WebAppCommandLock` specifies which apps
-// or particular entities it wants to lock on. The next command will not execute
-// until `SignalCompletionAndSelfDestruct()` was called by the last command.
+// Commands will be executed (`StartWithLock()` will be called) in-order based
+// on command's `WebAppCommandLock`, the `WebAppCommandLock` specifies which
+// apps or particular entities it wants to lock on. The next command will not
+// execute until `SignalCompletionAndSelfDestruct()` was called by the last
+// command.
 class WebAppCommandManager {
  public:
   using PassKey = base::PassKey<WebAppCommandManager>;
@@ -50,7 +51,7 @@
   void Start();
 
   // Enqueues the given command in the queue corresponding to the command's
-  // `queue_id()`. `Start()` will always be called asynchronously.
+  // `lock_description()`. `Start()` will always be called asynchronously.
   void ScheduleCommand(std::unique_ptr<WebAppCommand> command);
 
   // Called on system shutdown. This call is also forwarded to any commands that
@@ -58,9 +59,9 @@
   void Shutdown();
 
   // Called by the sync integration when a list of apps have had their sync
-  // sources removed and `is_uninstalling()` set to true. Any commands that
-  // whose `queue_id()`s match an id in `app_id` who have also been `Start()`ed
-  // will also be notified.
+  // sources removed and `is_uninstalling()` set to true. Any commands
+  // whose `lock_description().app_ids()` match an id in `app_id` who have also
+  // been `StartWithLock()`ed will also be notified.
   void NotifySyncSourceRemoved(const std::vector<AppId>& app_ids);
 
   // Outputs a debug value of the state of the commands system, including
diff --git a/chrome/browser/web_applications/web_app_command_manager_unittest.cc b/chrome/browser/web_applications/web_app_command_manager_unittest.cc
index 5794b8e..8f034d5a 100644
--- a/chrome/browser/web_applications/web_app_command_manager_unittest.cc
+++ b/chrome/browser/web_applications/web_app_command_manager_unittest.cc
@@ -42,9 +42,11 @@
 
 using ::testing::StrictMock;
 
-class MockCommand : public WebAppCommand {
+template <typename LockType>
+class MockCommand : public WebAppCommandTemplate<LockType> {
  public:
-  explicit MockCommand(std::unique_ptr<LockDescription> lock_description)
+  explicit MockCommand(
+      std::unique_ptr<typename LockType::LockDescription> lock_description)
       : lock_description_(std::move(lock_description)) {}
 
   MOCK_METHOD(void, OnDestruction, ());
@@ -55,7 +57,7 @@
     return *lock_description_;
   }
 
-  MOCK_METHOD(void, Start, (), (override));
+  MOCK_METHOD(void, StartWithLock, (std::unique_ptr<LockType>), (override));
   MOCK_METHOD(void, OnSyncSourceRemoved, (), (override));
   MOCK_METHOD(void, OnShutdown, (), (override));
 
@@ -72,10 +74,6 @@
         result, std::move(completion_callback));
   }
 
-  content::WebContents* get_shared_web_contents() const {
-    return WebAppCommand::shared_web_contents();
-  }
-
  private:
   std::unique_ptr<LockDescription> lock_description_;
 
@@ -112,44 +110,37 @@
     WebAppTest::TearDown();
   }
 
-  void CheckCommandsRunInOrder(base::WeakPtr<MockCommand> command1_ptr,
-                               base::WeakPtr<MockCommand> command2_ptr,
-                               bool check_web_contents_in_first = false,
-                               bool check_web_contents_in_second = false) {
+  template <typename LockType1, typename LockType2>
+  void CheckCommandsRunInOrder(
+      base::WeakPtr<MockCommand<LockType1>> command1_ptr,
+      base::WeakPtr<MockCommand<LockType2>> command2_ptr) {
     ASSERT_TRUE(command1_ptr && command2_ptr);
     EXPECT_FALSE(command1_ptr->IsStarted() || command2_ptr->IsStarted());
 
-    content::WebContents* shared_contents = nullptr;
-
     testing::StrictMock<base::MockCallback<base::OnceClosure>> mock_closure;
     {
       base::RunLoop loop;
       testing::InSequence in_sequence;
-      EXPECT_CALL(*command1_ptr, Start()).Times(1).WillOnce([&]() {
-        shared_contents = command1_ptr->get_shared_web_contents();
-        if (check_web_contents_in_first)
-          EXPECT_TRUE(shared_contents);
-        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
-            FROM_HERE, base::BindLambdaForTesting([&]() {
-              command1_ptr->CallSignalCompletionAndSelfDestruct(
-                  CommandResult::kSuccess, mock_closure.Get());
-            }));
-      });
+      EXPECT_CALL(*command1_ptr, StartWithLock(testing::_))
+          .Times(1)
+          .WillOnce([&]() {
+            base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+                FROM_HERE, base::BindLambdaForTesting([&]() {
+                  command1_ptr->CallSignalCompletionAndSelfDestruct(
+                      CommandResult::kSuccess, mock_closure.Get());
+                }));
+          });
 
       EXPECT_CALL(*command1_ptr, OnDestruction()).Times(1);
       EXPECT_CALL(mock_closure, Run()).Times(1);
 
-      EXPECT_CALL(*command2_ptr, Start()).Times(1).WillOnce([&]() {
-        EXPECT_FALSE(command1_ptr);
-        auto* second_web_contents = command2_ptr->get_shared_web_contents();
-        if (check_web_contents_in_second) {
-          EXPECT_TRUE(second_web_contents);
-          if (check_web_contents_in_first && check_web_contents_in_second)
-            EXPECT_EQ(shared_contents, second_web_contents);
-        }
-        command2_ptr->CallSignalCompletionAndSelfDestruct(
-            CommandResult::kSuccess, mock_closure.Get());
-      });
+      EXPECT_CALL(*command2_ptr, StartWithLock(testing::_))
+          .Times(1)
+          .WillOnce([&]() {
+            EXPECT_FALSE(command1_ptr);
+            command2_ptr->CallSignalCompletionAndSelfDestruct(
+                CommandResult::kSuccess, mock_closure.Get());
+          });
       EXPECT_CALL(*command2_ptr, OnDestruction()).Times(1);
       EXPECT_CALL(mock_closure, Run()).Times(1).WillOnce([&]() {
         loop.Quit();
@@ -160,35 +151,23 @@
     EXPECT_FALSE(command2_ptr);
   }
 
-  void CheckCommandsRunInParallel(base::WeakPtr<MockCommand> command1_ptr,
-                                  base::WeakPtr<MockCommand> command2_ptr,
-                                  bool check_web_contents_in_first = false,
-                                  bool check_web_contents_in_second = false) {
+  template <typename LockType1, typename LockType2>
+  void CheckCommandsRunInParallel(
+      base::WeakPtr<MockCommand<LockType1>> command1_ptr,
+      base::WeakPtr<MockCommand<LockType2>> command2_ptr) {
     testing::StrictMock<base::MockCallback<base::OnceClosure>> mock_closure;
     ASSERT_TRUE(command1_ptr && command2_ptr);
     EXPECT_FALSE(command1_ptr->IsStarted() || command2_ptr->IsStarted());
 
-    content::WebContents* shared_contents = nullptr;
-
     {
       base::RunLoop loop;
       testing::InSequence in_sequence;
 
-      EXPECT_CALL(*command1_ptr, Start()).WillOnce([&]() {
-        shared_contents = command1_ptr->get_shared_web_contents();
-        if (check_web_contents_in_first)
-          EXPECT_TRUE(shared_contents);
-      });
+      EXPECT_CALL(*command1_ptr, StartWithLock(testing::_)).Times(1);
 
       // Only signal completion of command1 after command2 is started to test
       // that starting of command2 is not blocked by command1.
-      EXPECT_CALL(*command2_ptr, Start()).WillOnce([&]() {
-        auto* second_web_contents = command2_ptr->get_shared_web_contents();
-        if (check_web_contents_in_second) {
-          EXPECT_TRUE(second_web_contents);
-          if (check_web_contents_in_first && check_web_contents_in_second)
-            EXPECT_EQ(shared_contents, second_web_contents);
-        }
+      EXPECT_CALL(*command2_ptr, StartWithLock(testing::_)).WillOnce([&]() {
         command2_ptr->CallSignalCompletionAndSelfDestruct(
             CommandResult::kSuccess, mock_closure.Get());
         command1_ptr->CallSignalCompletionAndSelfDestruct(
@@ -214,9 +193,11 @@
 TEST_F(WebAppCommandManagerTest, SimpleCommand) {
   // Simple test of a command enqueued, starting, and completing.
   testing::StrictMock<base::MockCallback<base::OnceClosure>> mock_closure;
-  auto mock_command = std::make_unique<::testing::StrictMock<MockCommand>>(
-      std::make_unique<FullSystemLockDescription>());
-  base::WeakPtr<MockCommand> command_ptr = mock_command->AsWeakPtr();
+  auto mock_command =
+      std::make_unique<::testing::StrictMock<MockCommand<FullSystemLock>>>(
+          std::make_unique<FullSystemLockDescription>());
+  base::WeakPtr<MockCommand<FullSystemLock>> command_ptr =
+      mock_command->AsWeakPtr();
 
   manager().ScheduleCommand(std::move(mock_command));
   ASSERT_TRUE(command_ptr);
@@ -224,7 +205,9 @@
   {
     base::RunLoop loop;
     testing::InSequence in_sequence;
-    EXPECT_CALL(*command_ptr, Start()).WillOnce([&]() { loop.Quit(); });
+    EXPECT_CALL(*command_ptr, StartWithLock(testing::_)).WillOnce([&]() {
+      loop.Quit();
+    });
     loop.Run();
     EXPECT_CALL(*command_ptr, OnDestruction()).Times(1);
     EXPECT_CALL(mock_closure, Run()).Times(1);
@@ -238,19 +221,22 @@
   // Test to make sure the command can complete & destroy itself in the Start
   // method.
   testing::StrictMock<base::MockCallback<base::OnceClosure>> mock_closure;
-  auto command = std::make_unique<::testing::StrictMock<MockCommand>>(
-      std::make_unique<FullSystemLockDescription>());
-  base::WeakPtr<MockCommand> command_ptr = command->AsWeakPtr();
+  auto command =
+      std::make_unique<::testing::StrictMock<MockCommand<FullSystemLock>>>(
+          std::make_unique<FullSystemLockDescription>());
+  base::WeakPtr<MockCommand<FullSystemLock>> command_ptr = command->AsWeakPtr();
 
   manager().ScheduleCommand(std::move(command));
   {
     base::RunLoop loop;
     testing::InSequence in_sequence;
-    EXPECT_CALL(*command_ptr, Start()).Times(1).WillOnce([&]() {
-      ASSERT_TRUE(command_ptr);
-      command_ptr->CallSignalCompletionAndSelfDestruct(CommandResult::kSuccess,
-                                                       mock_closure.Get());
-    });
+    EXPECT_CALL(*command_ptr, StartWithLock(testing::_))
+        .Times(1)
+        .WillOnce([&]() {
+          ASSERT_TRUE(command_ptr);
+          command_ptr->CallSignalCompletionAndSelfDestruct(
+              CommandResult::kSuccess, mock_closure.Get());
+        });
     EXPECT_CALL(*command_ptr, OnDestruction()).Times(1);
     EXPECT_CALL(mock_closure, Run()).Times(1).WillOnce([&]() { loop.Quit(); });
     loop.Run();
@@ -258,14 +244,14 @@
 }
 
 TEST_F(WebAppCommandManagerTest, TwoQueues) {
-  auto command1 = std::make_unique<StrictMock<MockCommand>>(
+  auto command1 = std::make_unique<StrictMock<MockCommand<AppLock>>>(
       std::make_unique<AppLockDescription, base::flat_set<AppId>>(
           {kTestAppId}));
-  auto command2 = std::make_unique<StrictMock<MockCommand>>(
+  auto command2 = std::make_unique<StrictMock<MockCommand<AppLock>>>(
       std::make_unique<AppLockDescription, base::flat_set<AppId>>(
           {kTestAppId2}));
-  base::WeakPtr<MockCommand> command1_ptr = command1->AsWeakPtr();
-  base::WeakPtr<MockCommand> command2_ptr = command2->AsWeakPtr();
+  base::WeakPtr<MockCommand<AppLock>> command1_ptr = command1->AsWeakPtr();
+  base::WeakPtr<MockCommand<AppLock>> command2_ptr = command2->AsWeakPtr();
 
   manager().ScheduleCommand(std::move(command1));
   manager().ScheduleCommand(std::move(command2));
@@ -273,62 +259,64 @@
 }
 
 TEST_F(WebAppCommandManagerTest, MixedQueueTypes) {
-  auto command1 = std::make_unique<StrictMock<MockCommand>>(
+  auto command1 = std::make_unique<StrictMock<MockCommand<FullSystemLock>>>(
       std::make_unique<FullSystemLockDescription>());
-  auto command2 = std::make_unique<StrictMock<MockCommand>>(
+  auto command2 = std::make_unique<StrictMock<MockCommand<AppLock>>>(
       std::make_unique<AppLockDescription, base::flat_set<AppId>>(
           {kTestAppId}));
-  base::WeakPtr<MockCommand> command1_ptr = command1->AsWeakPtr();
-  base::WeakPtr<MockCommand> command2_ptr = command2->AsWeakPtr();
+  base::WeakPtr<MockCommand<FullSystemLock>> command1_ptr =
+      command1->AsWeakPtr();
+  base::WeakPtr<MockCommand<AppLock>> command2_ptr = command2->AsWeakPtr();
 
   manager().ScheduleCommand(std::move(command1));
   manager().ScheduleCommand(std::move(command2));
   // Global command blocks app command.
   CheckCommandsRunInOrder(command1_ptr, command2_ptr);
 
-  command1 = std::make_unique<StrictMock<MockCommand>>(
+  auto command3 = std::make_unique<StrictMock<MockCommand<FullSystemLock>>>(
       std::make_unique<FullSystemLockDescription>());
-  command2 = std::make_unique<StrictMock<MockCommand>>(
-      std::make_unique<SharedWebContentsLockDescription>());
-  command1_ptr = command1->AsWeakPtr();
-  command2_ptr = command2->AsWeakPtr();
+  auto command4 =
+      std::make_unique<StrictMock<MockCommand<SharedWebContentsLock>>>(
+          std::make_unique<SharedWebContentsLockDescription>());
+  base::WeakPtr<MockCommand<FullSystemLock>> command3_ptr =
+      command3->AsWeakPtr();
+  base::WeakPtr<MockCommand<SharedWebContentsLock>> command4_ptr =
+      command4->AsWeakPtr();
 
   // One about:blank load per web contents lock.
   url_loader()->AddPrepareForLoadResults({WebAppUrlLoader::Result::kUrlLoaded});
-  manager().ScheduleCommand(std::move(command1));
-  manager().ScheduleCommand(std::move(command2));
+  manager().ScheduleCommand(std::move(command3));
+  manager().ScheduleCommand(std::move(command4));
   // Global command blocks web contents command.
-  CheckCommandsRunInOrder(command1_ptr, command2_ptr,
-                          /*check_web_contents_in_first=*/false,
-                          /*check_web_contents_in_second=*/true);
+  CheckCommandsRunInOrder(command3_ptr, command4_ptr);
 
   url_loader()->AddPrepareForLoadResults({WebAppUrlLoader::Result::kUrlLoaded});
-  command1 = std::make_unique<StrictMock<MockCommand>>(
+  auto command5 = std::make_unique<StrictMock<MockCommand<AppLock>>>(
       std::make_unique<AppLockDescription, base::flat_set<AppId>>(
           {kTestAppId}));
-  command2 = std::make_unique<StrictMock<MockCommand>>(
-      std::make_unique<SharedWebContentsLockDescription>());
-  command1_ptr = command1->AsWeakPtr();
-  command2_ptr = command2->AsWeakPtr();
+  auto command6 =
+      std::make_unique<StrictMock<MockCommand<SharedWebContentsLock>>>(
+          std::make_unique<SharedWebContentsLockDescription>());
+  base::WeakPtr<MockCommand<AppLock>> command5_ptr = command5->AsWeakPtr();
+  base::WeakPtr<MockCommand<SharedWebContentsLock>> command6_ptr =
+      command6->AsWeakPtr();
 
-  manager().ScheduleCommand(std::move(command1));
-  manager().ScheduleCommand(std::move(command2));
+  manager().ScheduleCommand(std::move(command5));
+  manager().ScheduleCommand(std::move(command6));
   // App command and web contents command queue are independent.
-  CheckCommandsRunInParallel(command1_ptr, command2_ptr,
-                             /*check_web_contents_in_first=*/false,
-                             /*check_web_contents_in_second=*/true);
+  CheckCommandsRunInParallel(command5_ptr, command6_ptr);
 }
 
 TEST_F(WebAppCommandManagerTest, SingleAppQueue) {
-  auto command1 = std::make_unique<StrictMock<MockCommand>>(
+  auto command1 = std::make_unique<StrictMock<MockCommand<AppLock>>>(
       std::make_unique<AppLockDescription, base::flat_set<AppId>>(
           {kTestAppId}));
-  base::WeakPtr<MockCommand> command1_ptr = command1->AsWeakPtr();
+  base::WeakPtr<MockCommand<AppLock>> command1_ptr = command1->AsWeakPtr();
 
-  auto command2 = std::make_unique<StrictMock<MockCommand>>(
+  auto command2 = std::make_unique<StrictMock<MockCommand<AppLock>>>(
       std::make_unique<AppLockDescription, base::flat_set<AppId>>(
           {kTestAppId}));
-  base::WeakPtr<MockCommand> command2_ptr = command2->AsWeakPtr();
+  base::WeakPtr<MockCommand<AppLock>> command2_ptr = command2->AsWeakPtr();
 
   manager().ScheduleCommand(std::move(command1));
   manager().ScheduleCommand(std::move(command2));
@@ -336,13 +324,15 @@
 }
 
 TEST_F(WebAppCommandManagerTest, GlobalQueue) {
-  auto command1 = std::make_unique<StrictMock<MockCommand>>(
+  auto command1 = std::make_unique<StrictMock<MockCommand<FullSystemLock>>>(
       std::make_unique<FullSystemLockDescription>());
-  base::WeakPtr<MockCommand> command1_ptr = command1->AsWeakPtr();
+  base::WeakPtr<MockCommand<FullSystemLock>> command1_ptr =
+      command1->AsWeakPtr();
 
-  auto command2 = std::make_unique<StrictMock<MockCommand>>(
+  auto command2 = std::make_unique<StrictMock<MockCommand<FullSystemLock>>>(
       std::make_unique<FullSystemLockDescription>());
-  base::WeakPtr<MockCommand> command2_ptr = command2->AsWeakPtr();
+  base::WeakPtr<MockCommand<FullSystemLock>> command2_ptr =
+      command2->AsWeakPtr();
 
   manager().ScheduleCommand(std::move(command1));
   manager().ScheduleCommand(std::move(command2));
@@ -350,13 +340,17 @@
 }
 
 TEST_F(WebAppCommandManagerTest, BackgroundWebContentsQueue) {
-  auto command1 = std::make_unique<StrictMock<MockCommand>>(
-      std::make_unique<SharedWebContentsLockDescription>());
-  base::WeakPtr<MockCommand> command1_ptr = command1->AsWeakPtr();
+  auto command1 =
+      std::make_unique<StrictMock<MockCommand<SharedWebContentsLock>>>(
+          std::make_unique<SharedWebContentsLockDescription>());
+  base::WeakPtr<MockCommand<SharedWebContentsLock>> command1_ptr =
+      command1->AsWeakPtr();
 
-  auto command2 = std::make_unique<StrictMock<MockCommand>>(
-      std::make_unique<SharedWebContentsLockDescription>());
-  base::WeakPtr<MockCommand> command2_ptr = command2->AsWeakPtr();
+  auto command2 =
+      std::make_unique<StrictMock<MockCommand<SharedWebContentsLock>>>(
+          std::make_unique<SharedWebContentsLockDescription>());
+  base::WeakPtr<MockCommand<SharedWebContentsLock>> command2_ptr =
+      command2->AsWeakPtr();
 
   url_loader()->AddPrepareForLoadResults({WebAppUrlLoader::Result::kUrlLoaded,
                                           WebAppUrlLoader::Result::kUrlLoaded});
@@ -366,9 +360,9 @@
 }
 
 TEST_F(WebAppCommandManagerTest, ShutdownPreStartCommand) {
-  auto command = std::make_unique<StrictMock<MockCommand>>(
+  auto command = std::make_unique<StrictMock<MockCommand<FullSystemLock>>>(
       std::make_unique<FullSystemLockDescription>());
-  base::WeakPtr<MockCommand> command_ptr = command->AsWeakPtr();
+  base::WeakPtr<MockCommand<FullSystemLock>> command_ptr = command->AsWeakPtr();
   manager().ScheduleCommand(std::move(command));
   EXPECT_CALL(*command_ptr, OnDestruction()).Times(1);
   manager().Shutdown();
@@ -376,16 +370,19 @@
 
 TEST_F(WebAppCommandManagerTest, ShutdownStartedCommand) {
   testing::StrictMock<base::MockCallback<base::OnceClosure>> mock_closure;
-  auto mock_command = std::make_unique<StrictMock<MockCommand>>(
+  auto mock_command = std::make_unique<StrictMock<MockCommand<FullSystemLock>>>(
       std::make_unique<FullSystemLockDescription>());
-  base::WeakPtr<MockCommand> command_ptr = mock_command->AsWeakPtr();
+  base::WeakPtr<MockCommand<FullSystemLock>> command_ptr =
+      mock_command->AsWeakPtr();
 
   manager().ScheduleCommand(std::move(mock_command));
   ASSERT_TRUE(command_ptr);
   EXPECT_FALSE(command_ptr->IsStarted());
   {
     base::RunLoop loop;
-    EXPECT_CALL(*command_ptr, Start()).WillOnce([&]() { loop.Quit(); });
+    EXPECT_CALL(*command_ptr, StartWithLock(testing::_)).WillOnce([&]() {
+      loop.Quit();
+    });
     loop.Run();
   }
   {
@@ -398,19 +395,23 @@
 }
 
 TEST_F(WebAppCommandManagerTest, ShutdownQueuedCommand) {
-  auto command1 = std::make_unique<StrictMock<MockCommand>>(
+  auto command1 = std::make_unique<StrictMock<MockCommand<FullSystemLock>>>(
       std::make_unique<FullSystemLockDescription>());
-  base::WeakPtr<MockCommand> command1_ptr = command1->AsWeakPtr();
+  base::WeakPtr<MockCommand<FullSystemLock>> command1_ptr =
+      command1->AsWeakPtr();
 
-  auto command2 = std::make_unique<StrictMock<MockCommand>>(
+  auto command2 = std::make_unique<StrictMock<MockCommand<FullSystemLock>>>(
       std::make_unique<FullSystemLockDescription>());
-  base::WeakPtr<MockCommand> command2_ptr = command2->AsWeakPtr();
+  base::WeakPtr<MockCommand<FullSystemLock>> command2_ptr =
+      command2->AsWeakPtr();
 
   manager().ScheduleCommand(std::move(command1));
   manager().ScheduleCommand(std::move(command2));
   {
     base::RunLoop loop;
-    EXPECT_CALL(*command1_ptr, Start()).WillOnce([&]() { loop.Quit(); });
+    EXPECT_CALL(*command1_ptr, StartWithLock(testing::_)).WillOnce([&]() {
+      loop.Quit();
+    });
     loop.Run();
   }
   EXPECT_CALL(*command1_ptr, OnShutdown()).Times(1);
@@ -420,13 +421,15 @@
 
 TEST_F(WebAppCommandManagerTest, OnShutdownCallsCompleteAndDestruct) {
   testing::StrictMock<base::MockCallback<base::OnceClosure>> mock_closure;
-  auto command = std::make_unique<StrictMock<MockCommand>>(
+  auto command = std::make_unique<StrictMock<MockCommand<FullSystemLock>>>(
       std::make_unique<FullSystemLockDescription>());
-  base::WeakPtr<MockCommand> command_ptr = command->AsWeakPtr();
+  base::WeakPtr<MockCommand<FullSystemLock>> command_ptr = command->AsWeakPtr();
   manager().ScheduleCommand(std::move(command));
   {
     base::RunLoop loop;
-    EXPECT_CALL(*command_ptr, Start()).WillOnce([&]() { loop.Quit(); });
+    EXPECT_CALL(*command_ptr, StartWithLock(testing::_)).WillOnce([&]() {
+      loop.Quit();
+    });
     loop.Run();
   }
   {
@@ -444,14 +447,16 @@
 
 TEST_F(WebAppCommandManagerTest, NotifySyncCallsCompleteAndDestruct) {
   testing::StrictMock<base::MockCallback<base::OnceClosure>> mock_closure;
-  auto command = std::make_unique<StrictMock<MockCommand>>(
+  auto command = std::make_unique<StrictMock<MockCommand<AppLock>>>(
       std::make_unique<AppLockDescription, base::flat_set<AppId>>(
           {kTestAppId}));
-  base::WeakPtr<MockCommand> command_ptr = command->AsWeakPtr();
+  base::WeakPtr<MockCommand<AppLock>> command_ptr = command->AsWeakPtr();
   manager().ScheduleCommand(std::move(command));
   {
     base::RunLoop loop;
-    EXPECT_CALL(*command_ptr, Start()).WillOnce([&]() { loop.Quit(); });
+    EXPECT_CALL(*command_ptr, StartWithLock(testing::_)).WillOnce([&]() {
+      loop.Quit();
+    });
     loop.Run();
   }
   {
@@ -488,17 +493,19 @@
 }
 
 TEST_F(WebAppCommandManagerTest, AppWithSharedWebContents) {
-  auto command1 = std::make_unique<MockCommand>(
+  auto command1 = std::make_unique<MockCommand<SharedWebContentsWithAppLock>>(
       std::make_unique<SharedWebContentsWithAppLockDescription,
                        base::flat_set<AppId>>({kTestAppId}));
-  auto command2 = std::make_unique<MockCommand>(
+  auto command2 = std::make_unique<MockCommand<AppLock>>(
       std::make_unique<AppLockDescription, base::flat_set<AppId>>(
           {kTestAppId}));
-  auto command3 = std::make_unique<MockCommand>(
+  auto command3 = std::make_unique<MockCommand<SharedWebContentsLock>>(
       std::make_unique<SharedWebContentsLockDescription>());
-  base::WeakPtr<MockCommand> command1_ptr = command1->AsWeakPtr();
-  base::WeakPtr<MockCommand> command2_ptr = command2->AsWeakPtr();
-  base::WeakPtr<MockCommand> command3_ptr = command3->AsWeakPtr();
+  base::WeakPtr<MockCommand<SharedWebContentsWithAppLock>> command1_ptr =
+      command1->AsWeakPtr();
+  base::WeakPtr<MockCommand<AppLock>> command2_ptr = command2->AsWeakPtr();
+  base::WeakPtr<MockCommand<SharedWebContentsLock>> command3_ptr =
+      command3->AsWeakPtr();
 
   testing::StrictMock<base::MockCallback<base::OnceClosure>> mock_closure;
 
@@ -511,15 +518,17 @@
   {
     base::RunLoop loop;
     testing::InSequence in_sequence;
-    EXPECT_CALL(*command1_ptr, Start()).Times(1).WillOnce([&]() {
-      base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
-          FROM_HERE, base::BindLambdaForTesting([&]() {
-            command1_ptr->CallSignalCompletionAndSelfDestruct(
-                CommandResult::kSuccess, mock_closure.Get());
-          }));
-    });
-    EXPECT_CALL(*command2_ptr, Start()).Times(0);
-    EXPECT_CALL(*command3_ptr, Start()).Times(0);
+    EXPECT_CALL(*command1_ptr, StartWithLock(testing::_))
+        .Times(1)
+        .WillOnce([&]() {
+          base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+              FROM_HERE, base::BindLambdaForTesting([&]() {
+                command1_ptr->CallSignalCompletionAndSelfDestruct(
+                    CommandResult::kSuccess, mock_closure.Get());
+              }));
+        });
+    EXPECT_CALL(*command2_ptr, StartWithLock(testing::_)).Times(0);
+    EXPECT_CALL(*command3_ptr, StartWithLock(testing::_)).Times(0);
     EXPECT_CALL(*command1_ptr, OnDestruction()).Times(1);
     EXPECT_CALL(mock_closure, Run())
         .WillOnce(base::test::RunClosure(loop.QuitClosure()));
@@ -527,14 +536,18 @@
   }
   EXPECT_FALSE(command1_ptr);
   {
-    EXPECT_CALL(*command2_ptr, Start()).Times(1).WillOnce([&]() {
-      command2_ptr->CallSignalCompletionAndSelfDestruct(CommandResult::kSuccess,
-                                                        mock_closure.Get());
-    });
-    EXPECT_CALL(*command3_ptr, Start()).Times(1).WillOnce([&]() {
-      command3_ptr->CallSignalCompletionAndSelfDestruct(CommandResult::kSuccess,
-                                                        mock_closure.Get());
-    });
+    EXPECT_CALL(*command2_ptr, StartWithLock(testing::_))
+        .Times(1)
+        .WillOnce([&]() {
+          command2_ptr->CallSignalCompletionAndSelfDestruct(
+              CommandResult::kSuccess, mock_closure.Get());
+        });
+    EXPECT_CALL(*command3_ptr, StartWithLock(testing::_))
+        .Times(1)
+        .WillOnce([&]() {
+          command3_ptr->CallSignalCompletionAndSelfDestruct(
+              CommandResult::kSuccess, mock_closure.Get());
+        });
     base::RunLoop loop;
     base::RepeatingClosure barrier =
         base::BarrierClosure(2, loop.QuitClosure());
diff --git a/chrome/browser/web_applications/web_app_command_scheduler.cc b/chrome/browser/web_applications/web_app_command_scheduler.cc
index 012e705..659c1a1 100644
--- a/chrome/browser/web_applications/web_app_command_scheduler.cc
+++ b/chrome/browser/web_applications/web_app_command_scheduler.cc
@@ -63,16 +63,6 @@
   if (is_in_shutdown_)
     return;
 
-  if (!provider_->is_registry_ready()) {
-    provider_->on_registry_ready().Post(
-        FROM_HERE,
-        base::BindOnce(&WebAppCommandScheduler::FetchManifestAndInstall,
-                       weak_ptr_factory_.GetWeakPtr(),
-                       std::move(install_surface), std::move(contents),
-                       bypass_service_worker_check, std::move(dialog_callback),
-                       std::move(callback), use_fallback));
-    return;
-  }
   provider_->command_manager().ScheduleCommand(
       std::make_unique<FetchManifestAndInstallCommand>(
           std::move(install_surface), std::move(contents),
@@ -89,16 +79,6 @@
   if (is_in_shutdown_)
     return;
 
-  if (!provider_->is_registry_ready()) {
-    provider_->on_registry_ready().Post(
-        FROM_HERE,
-        base::BindOnce(&WebAppCommandScheduler::InstallFromInfo,
-                       weak_ptr_factory_.GetWeakPtr(), std::move(install_info),
-                       overwrite_existing_manifest_fields,
-                       std::move(install_surface),
-                       std::move(install_callback)));
-    return;
-  }
   provider_->command_manager().ScheduleCommand(
       std::make_unique<InstallFromInfoCommand>(
           std::move(install_info), overwrite_existing_manifest_fields,
@@ -114,16 +94,6 @@
   if (is_in_shutdown_)
     return;
 
-  if (!provider_->is_registry_ready()) {
-    provider_->on_registry_ready().Post(
-        FROM_HERE,
-        base::BindOnce(&WebAppCommandScheduler::InstallFromInfoWithParams,
-                       weak_ptr_factory_.GetWeakPtr(), std::move(install_info),
-                       overwrite_existing_manifest_fields,
-                       std::move(install_surface), std::move(install_callback),
-                       install_params));
-    return;
-  }
   provider_->command_manager().ScheduleCommand(
       std::make_unique<InstallFromInfoCommand>(
           std::move(install_info), overwrite_existing_manifest_fields,
@@ -139,15 +109,6 @@
   if (is_in_shutdown_)
     return;
 
-  if (!provider_->is_registry_ready()) {
-    provider_->on_registry_ready().Post(
-        FROM_HERE,
-        base::BindOnce(&WebAppCommandScheduler::InstallExternallyManagedApp,
-                       weak_ptr_factory_.GetWeakPtr(), external_install_options,
-                       std::move(callback), contents,
-                       std::move(data_retriever)));
-    return;
-  }
   provider_->command_manager().ScheduleCommand(
       std::make_unique<ExternallyManagedInstallCommand>(
           external_install_options, std::move(callback), contents,
@@ -161,15 +122,6 @@
   if (is_in_shutdown_)
     return;
 
-  if (!provider_->is_registry_ready()) {
-    provider_->on_registry_ready().Post(
-        FROM_HERE,
-        base::BindOnce(&WebAppCommandScheduler::PersistFileHandlersUserChoice,
-                       weak_ptr_factory_.GetWeakPtr(), app_id, allowed,
-                       std::move(callback)));
-    return;
-  }
-
   provider_->command_manager().ScheduleCommand(
       UpdateFileHandlerCommand::CreateForPersistUserChoice(
           app_id, allowed, std::move(callback)));
@@ -181,15 +133,6 @@
   if (is_in_shutdown_)
     return;
 
-  if (!provider_->is_registry_ready()) {
-    provider_->on_registry_ready().Post(
-        FROM_HERE,
-        base::BindOnce(&WebAppCommandScheduler::UpdateFileHandlerOsIntegration,
-                       weak_ptr_factory_.GetWeakPtr(), app_id,
-                       std::move(callback)));
-    return;
-  }
-
   provider_->command_manager().ScheduleCommand(
       UpdateFileHandlerCommand::CreateForUpdate(app_id, std::move(callback)));
 }
@@ -202,15 +145,6 @@
   if (is_in_shutdown_)
     return;
 
-  if (!provider_->is_registry_ready()) {
-    provider_->on_registry_ready().Post(
-        FROM_HERE,
-        base::BindOnce(&WebAppCommandScheduler::ScheduleManifestUpdateDataFetch,
-                       weak_ptr_factory_.GetWeakPtr(), url, app_id, contents,
-                       std::move(callback)));
-    return;
-  }
-
   provider_->command_manager().ScheduleCommand(
       std::make_unique<ManifestUpdateDataFetchCommand>(
           url, app_id, contents, std::move(callback),
@@ -228,17 +162,6 @@
   if (is_in_shutdown_)
     return;
 
-  if (!provider_->is_registry_ready()) {
-    provider_->on_registry_ready().Post(
-        FROM_HERE,
-        base::BindOnce(&WebAppCommandScheduler::ScheduleManifestUpdateFinalize,
-                       weak_ptr_factory_.GetWeakPtr(), url, app_id,
-                       std::move(install_info), app_identity_update_allowed,
-                       std::move(keep_alive), std::move(profile_keep_alive),
-                       std::move(callback)));
-    return;
-  }
-
   provider_->command_manager().ScheduleCommand(
       std::make_unique<ManifestUpdateFinalizeCommand>(
           url, app_id, std::move(install_info), app_identity_update_allowed,
@@ -253,16 +176,6 @@
   if (is_in_shutdown_)
     return;
 
-  if (!provider_->is_registry_ready()) {
-    provider_->on_registry_ready().Post(
-        FROM_HERE,
-        base::BindOnce(
-            &WebAppCommandScheduler::FetchInstallabilityForChromeManagement,
-            weak_ptr_factory_.GetWeakPtr(), url, web_contents,
-            std::move(callback)));
-    return;
-  }
-
   provider_->command_manager().ScheduleCommand(
       std::make_unique<web_app::FetchInstallabilityForChromeManagement>(
           url, web_contents, std::make_unique<web_app::WebAppUrlLoader>(),
@@ -277,15 +190,6 @@
   if (is_in_shutdown_)
     return;
 
-  if (!provider_->is_registry_ready()) {
-    provider_->on_registry_ready().Post(
-        FROM_HERE,
-        base::BindOnce(&WebAppCommandScheduler::InstallIsolatedWebApp,
-                       weak_ptr_factory_.GetWeakPtr(), url_info, isolation_data,
-                       std::move(callback)));
-    return;
-  }
-
   provider_->command_manager().ScheduleCommand(
       std::make_unique<InstallIsolatedWebAppCommand>(
           url_info, isolation_data, CreateIsolatedWebAppWebContents(*profile_),
@@ -298,14 +202,6 @@
   if (is_in_shutdown_)
     return;
 
-  if (!provider_->is_registry_ready()) {
-    provider_->on_registry_ready().Post(
-        FROM_HERE, base::BindOnce(&WebAppCommandScheduler::SetRunOnOsLoginMode,
-                                  weak_ptr_factory_.GetWeakPtr(), app_id,
-                                  std::move(login_mode), std::move(callback)));
-    return;
-  }
-
   provider_->command_manager().ScheduleCommand(
       RunOnOsLoginCommand::CreateForSetLoginMode(app_id, std::move(login_mode),
                                                  std::move(callback)));
@@ -316,14 +212,6 @@
   if (is_in_shutdown_)
     return;
 
-  if (!provider_->is_registry_ready()) {
-    provider_->on_registry_ready().Post(
-        FROM_HERE, base::BindOnce(&WebAppCommandScheduler::SyncRunOnOsLoginMode,
-                                  weak_ptr_factory_.GetWeakPtr(), app_id,
-                                  std::move(callback)));
-    return;
-  }
-
   provider_->command_manager().ScheduleCommand(
       RunOnOsLoginCommand::CreateForSyncLoginMode(app_id, std::move(callback)));
 }
@@ -335,16 +223,6 @@
   if (is_in_shutdown_)
     return;
 
-  if (!provider_->is_registry_ready()) {
-    provider_->on_registry_ready().Post(
-        FROM_HERE,
-        base::BindOnce(
-            &WebAppCommandScheduler::ScheduleCallbackWithLock<LockType>,
-            weak_ptr_factory_.GetWeakPtr(), std::move(lock_description),
-            std::move(callback)));
-    return;
-  }
-
   provider_->command_manager().ScheduleCommand(
       std::make_unique<CallbackCommand<LockType>>(std::move(lock_description),
                                                   std::move(callback)));
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 65bad9e..7676172 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1668686205-1e30188baf158447cd583d1267554668d5879801.profdata
+chrome-linux-main-1668707896-feee6a0f31179142e5e9ad02536ca3decbac14a8.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 9077140d..50a7a91 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1668686205-641aaf02f1e4b397854cff3b7172b4f527f10d49.profdata
+chrome-mac-arm-main-1668707896-9936320b265257e55703a04d19394c1abb150e8a.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 3ceb7c0..fcddae4c 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1668686205-37e3cf4dcaeb8a19ae96ce1d7fb53858388dbe87.profdata
+chrome-mac-main-1668707896-4b121f80e782118ba3871df21ee31a2b387b662d.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index f822658..b5573d4c 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1668675513-9b48f91eb82a48e591af6c04319dbef7b65e94e6.profdata
+chrome-win32-main-1668697173-0207b40b1d529c5464b54f6774df41b5e0365470.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 77faceb8..0f6246d 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1668686205-267f0d844bd5ca130f55ff4225213ff7a6ad0450.profdata
+chrome-win64-main-1668707896-8a0a24799b42d3ec888f4e0edaa3661dbfe1e546.profdata
diff --git a/chrome/common/extensions/api/input_method_private.json b/chrome/common/extensions/api/input_method_private.json
index 6b2a70b..f1b6baa3 100644
--- a/chrome/common/extensions/api/input_method_private.json
+++ b/chrome/common/extensions/api/input_method_private.json
@@ -356,6 +356,17 @@
           }
         ]
       }, {
+        "name": "openOptionsPage",
+        "type": "function",
+        "description": "Opens the options page for the input method extension. If the input method does not have options, this function will do nothing.",
+        "parameters": [
+          {
+            "name": "inputMethodId",
+            "type": "string",
+            "description": "ID of the input method to open options for."
+          }
+        ]
+      }, {
         "name": "getCompositionBounds",
         "type": "function",
         "description": "Gets the composition bounds",
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 37d7fb6..1aaa0a3 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -5334,6 +5334,7 @@
     "../browser/download/deferred_client_wrapper_unittest.cc",
     "../browser/download/download_history_unittest.cc",
     "../browser/download/download_item_model_unittest.cc",
+    "../browser/download/download_item_warning_data_unittest.cc",
     "../browser/download/download_offline_content_provider_unittest.cc",
     "../browser/download/download_prefs_unittest.cc",
     "../browser/download/download_query_unittest.cc",
@@ -6827,6 +6828,7 @@
       "../browser/ui/autofill_assistant/password_change/apc_utils_unittest.cc",
       "../browser/ui/autofill_assistant/password_change/assistant_onboarding_controller_impl_unittest.cc",
       "../browser/ui/bookmarks/bookmark_context_menu_controller_unittest.cc",
+      "../browser/ui/bookmarks/bookmark_drag_drop_unittest.cc",
       "../browser/ui/bookmarks/bookmark_editor_unittest.cc",
       "../browser/ui/bookmarks/bookmark_ui_utils_desktop_unittest.cc",
       "../browser/ui/bookmarks/bookmark_unittest.cc",
diff --git a/chrome/test/data/web_apps/simple_isolated_app/empty.wasm b/chrome/test/data/web_apps/simple_isolated_app/empty.wasm
new file mode 100644
index 0000000..d8fc92d0
--- /dev/null
+++ b/chrome/test/data/web_apps/simple_isolated_app/empty.wasm
Binary files differ
diff --git a/chrome/test/data/web_apps/simple_isolated_app/empty.wasm.mock-http-headers b/chrome/test/data/web_apps/simple_isolated_app/empty.wasm.mock-http-headers
new file mode 100644
index 0000000..29223b0
--- /dev/null
+++ b/chrome/test/data/web_apps/simple_isolated_app/empty.wasm.mock-http-headers
@@ -0,0 +1,2 @@
+HTTP/1.1 200 OK
+Content-Type: application/wasm
diff --git a/chrome/test/data/webui/chromeos/diagnostics/diagnostics_browsertest.js b/chrome/test/data/webui/chromeos/diagnostics/diagnostics_browsertest.js
index ecb0b6e..ee852cbc 100644
--- a/chrome/test/data/webui/chromeos/diagnostics/diagnostics_browsertest.js
+++ b/chrome/test/data/webui/chromeos/diagnostics/diagnostics_browsertest.js
@@ -64,7 +64,7 @@
   ['InputCard', 'input_card_test.js', 'Input'],
   ['InputList', 'input_list_test.js', 'Input'],
   ['IpConfigInfoDrawer', 'ip_config_info_drawer_test.js'],
-  ['KeyboardTester', 'keyboard_tester_test.js', 'Input', 'DISABLED_All'],
+  ['KeyboardTester', 'keyboard_tester_test.js', 'Input'],
   ['MemoryCard', 'memory_card_test.js'],
   ['NetworkCard', 'network_card_test.js', undefined, 'DISABLED_All'],
   ['NetworkInfo', 'network_info_test.js'],
diff --git a/chrome/test/data/webui/chromeos/diagnostics/keyboard_tester_test.js b/chrome/test/data/webui/chromeos/diagnostics/keyboard_tester_test.js
index c7e1b19..c6259609 100644
--- a/chrome/test/data/webui/chromeos/diagnostics/keyboard_tester_test.js
+++ b/chrome/test/data/webui/chromeos/diagnostics/keyboard_tester_test.js
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'chrome://diagnostics/keyboard_tester.js';
+import 'chrome://diagnostics/strings.m.js';
 import 'chrome://resources/mojo/mojo/public/js/mojo_bindings_lite.js';
 
 import {ConnectionType, KeyEvent, KeyEventType, MechanicalLayout, NumberPadPresence, PhysicalLayout, TopRightKey} from 'chrome://diagnostics/input_data_provider.mojom-webui.js';
@@ -12,6 +14,7 @@
 
 import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chromeos/chai_assert.js';
 import {MockController} from '../mock_controller.m.js';
+import {isVisible} from '../test_util.js';
 
 suite('keyboardTesterTestSuite', function() {
   /** @type {?KeyboardTesterElement} */
@@ -35,6 +38,15 @@
     document.body.appendChild(keyboardTesterElement);
   });
 
+  /**
+   * @param {boolean} isLoggedIn
+   * @return {!Promise}
+   */
+  function setLoggedInState(isLoggedIn) {
+    keyboardTesterElement.isLoggedIn = isLoggedIn;
+    return flushTasks();
+  }
+
   test('topRightKeyCorrections', async () => {
     keyboardTesterElement.keyboard = Object.assign({}, fakeKeyboard, {
       topRightKey: TopRightKey.kPower,
@@ -181,7 +193,7 @@
     keyboardTesterElement.keyboard = fakeKeyboard;
     await flushTasks();
 
-    keyboardTesterElement.$.dialog.showModal();
+    keyboardTesterElement.show();
     await flushTasks();
     assertTrue(keyboardTesterElement.isOpen());
 
@@ -193,4 +205,22 @@
     await keyDownEvent;
     assertFalse(keyboardTesterElement.isOpen());
   });
+
+  test('helpLinkIsHiddenWhenNotLoggedIn', async () => {
+    keyboardTesterElement.keyboard = fakeKeyboard;
+    await setLoggedInState(/** isLoggedIn */ false);
+
+    keyboardTesterElement.show();
+    await flushTasks();
+    assertTrue(keyboardTesterElement.isOpen());
+    const helpLink = keyboardTesterElement.shadowRoot.querySelector('#help');
+    assertTrue(!!helpLink);
+    assertFalse(isVisible(helpLink));
+
+    keyboardTesterElement.close();
+    await setLoggedInState(/** isLoggedIn */ true);
+    keyboardTesterElement.show();
+    await flushTasks();
+    assertTrue(isVisible(helpLink));
+  });
 });
diff --git a/chrome/test/data/webui/chromeos/parent_access/parent_access_app_test.js b/chrome/test/data/webui/chromeos/parent_access/parent_access_app_test.js
index 4adef56..41013d92 100644
--- a/chrome/test/data/webui/chromeos/parent_access/parent_access_app_test.js
+++ b/chrome/test/data/webui/chromeos/parent_access/parent_access_app_test.js
@@ -7,6 +7,7 @@
 import 'chrome://parent-access/strings.m.js';
 
 import {Screens} from 'chrome://parent-access/parent_access_app.js';
+import {GetOAuthTokenStatus} from 'chrome://parent-access/parent_access_ui.mojom-webui.js';
 import {setParentAccessUIHandlerForTest} from 'chrome://parent-access/parent_access_ui_handler.js';
 import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
 
@@ -22,6 +23,7 @@
 parent_access_app_tests.TestNames = {
   TestShowWebApprovalsAfterFlow:
       'Tests that the web approvals after flow is shown',
+  TestShowErrorScreenOnOAuthFailure: 'Tests that the error screen is shown',
 };
 
 suite(parent_access_app_tests.suiteName, function() {
@@ -32,9 +34,10 @@
   test(
       parent_access_app_tests.TestNames.TestShowWebApprovalsAfterFlow,
       async () => {
-        // Set up the ParentAccessParams for the web approvals flow.
+        // Set up the TestParentAccessUIHandler
         const handler = new TestParentAccessUIHandler();
         handler.setParentAccessParams(buildWebApprovalsParams());
+        handler.setOAuthTokenStatus('token', GetOAuthTokenStatus.kSuccess);
         setParentAccessUIHandlerForTest(handler);
 
         // Create app element.
@@ -56,4 +59,22 @@
             'local-web-approvals-after');
         assertNotEquals(null, webApprovalsAfter);
       });
+
+  test(
+      parent_access_app_tests.TestNames.TestShowErrorScreenOnOAuthFailure,
+      async () => {
+        // Set up the TestParentAccessUIHandler
+        const handler = new TestParentAccessUIHandler();
+        handler.setParentAccessParams(buildWebApprovalsParams());
+        handler.setOAuthTokenStatus('token', GetOAuthTokenStatus.kError);
+        setParentAccessUIHandlerForTest(handler);
+
+        // Create app element.
+        const parentAccessApp = document.createElement('parent-access-app');
+        document.body.appendChild(parentAccessApp);
+        await flushTasks();
+
+        // Verify error screen is showing.
+        assertEquals(parentAccessApp.currentScreen_, Screens.ERROR);
+      });
 });
diff --git a/chrome/test/data/webui/chromeos/parent_access/parent_access_browsertest.js b/chrome/test/data/webui/chromeos/parent_access/parent_access_browsertest.js
index 5c454cc..cf62f53 100644
--- a/chrome/test/data/webui/chromeos/parent_access/parent_access_browsertest.js
+++ b/chrome/test/data/webui/chromeos/parent_access/parent_access_browsertest.js
@@ -48,6 +48,11 @@
       parent_access_app_tests.TestNames.TestShowWebApprovalsAfterFlow);
 });
 
+TEST_F('ParentAccessAppTest', 'TestShowErrorScreenOnOAuthFailure', function() {
+  this.runMochaTest(
+      parent_access_app_tests.TestNames.TestShowErrorScreenOnOAuthFailure);
+});
+
 var ParentAccessControllerTest = class extends testing.Test {
   /** @override */
   get browsePreload() {
diff --git a/chrome/test/data/webui/chromeos/parent_access/test_parent_access_ui_handler.js b/chrome/test/data/webui/chromeos/parent_access/test_parent_access_ui_handler.js
index eb385d1d..bafc92f 100644
--- a/chrome/test/data/webui/chromeos/parent_access/test_parent_access_ui_handler.js
+++ b/chrome/test/data/webui/chromeos/parent_access/test_parent_access_ui_handler.js
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {ParentAccessParams} from 'chrome://parent-access/parent_access_ui.mojom-webui.js';
+import {GetOAuthTokenStatus, ParentAccessParams} from 'chrome://parent-access/parent_access_ui.mojom-webui.js';
 import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
 
 /** @implements {ParentAccessUIHandlerInterface} */
@@ -16,15 +16,23 @@
       'onParentAccessDone',
     ]);
 
-    /**
-     * @private {?ParentAccessParams}}
-     */
+    /** @private {?ParentAccessParams} */
     this.params_ = null;
+
+    /** @private {?string} */
+    this.oAuthToken_ = null;
+
+    /** @private {?GetOAuthTokenStatus} */
+    this.oAuthTokenStatus_ = null;
   }
 
   /** @override */
   getOAuthToken() {
     this.methodCalled('getOAuthToken');
+    return Promise.resolve({
+      oauthToken: this.oAuthToken_,
+      status: this.oAuthTokenStatus_,
+    });
   }
 
   /** @override */
@@ -55,4 +63,13 @@
   setParentAccessParams(params) {
     this.params_ = params;
   }
+
+  /**
+   * @param {string} token
+   * @param {!GetOAuthTokenStatus} status
+   */
+  setOAuthTokenStatus(token, status) {
+    this.oAuthToken_ = token;
+    this.oAuthTokenStatus_ = status;
+  }
 }
diff --git a/chrome/test/data/webui/inline_login/inline_login_test_util.ts b/chrome/test/data/webui/inline_login/inline_login_test_util.ts
index 122c62cdb..eebf124 100644
--- a/chrome/test/data/webui/inline_login/inline_login_test_util.ts
+++ b/chrome/test/data/webui/inline_login/inline_login_test_util.ts
@@ -8,7 +8,6 @@
 
 import {InlineLoginBrowserProxy} from 'chrome://chrome-signin/inline_login_browser_proxy.js';
 import {AuthCompletedCredentials, AuthMode, AuthParams} from 'chrome://chrome-signin/gaia_auth_host/authenticator.js';
-import {NativeEventTarget as EventTarget} from 'chrome://resources/js/cr/event_target.js';
 
 import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
 
diff --git a/chrome/test/data/webui/js/BUILD.gn b/chrome/test/data/webui/js/BUILD.gn
index 0b7b53b..12a0953 100644
--- a/chrome/test/data/webui/js/BUILD.gn
+++ b/chrome/test/data/webui/js/BUILD.gn
@@ -23,8 +23,6 @@
     "promise_resolver_test.ts",
     "static_types_test.ts",
     "util_test.ts",
-
-    "cr/event_target_test.ts",
   ]
 
   definitions = [ "//tools/typescript/definitions/chrome_send.d.ts" ]
diff --git a/chrome/test/data/webui/js/cr/event_target_test.ts b/chrome/test/data/webui/js/cr/event_target_test.ts
deleted file mode 100644
index 0454360..0000000
--- a/chrome/test/data/webui/js/cr/event_target_test.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-// Copyright 2020 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// clang-format off
-import {NativeEventTarget as EventTarget} from 'chrome://resources/js/cr/event_target.js';
-import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
-
-// clang-format on
-
-suite('EventTargetModuleTest', () => {
-  test('FucntionListener', () => {
-    let fi = 0;
-    function f(_e: Event) {
-      fi++;
-    }
-
-    let gi = 0;
-    function g(_e: Event) {
-      gi++;
-    }
-
-    const et = new EventTarget();
-    et.addEventListener('f', f);
-    et.addEventListener('g', g);
-
-    // Adding again should not cause it to be called twice
-    et.addEventListener('f', f);
-    et.dispatchEvent(new Event('f'));
-    assertEquals(1, fi, 'Should have been called once');
-    assertEquals(0, gi);
-
-    et.removeEventListener('f', f);
-    et.dispatchEvent(new Event('f'));
-    assertEquals(1, fi, 'Should not have been called again');
-
-    et.dispatchEvent(new Event('g'));
-    assertEquals(1, gi, 'Should have been called once');
-  });
-
-  test('HandleEvent', () => {
-    let fi = 0;
-    const f = /** @type {!EventListener} */ ({
-      handleEvent: function(_e: Event) {
-        fi++;
-      },
-    });
-
-    let gi = 0;
-    const g = /** @type {!EventListener} */ ({
-      handleEvent: function(_e: Event) {
-        gi++;
-      },
-    });
-
-    const et = new EventTarget();
-    et.addEventListener('f', f);
-    et.addEventListener('g', g);
-
-    // Adding again should not cause it to be called twice
-    et.addEventListener('f', f);
-    et.dispatchEvent(new Event('f'));
-    assertEquals(1, fi, 'Should have been called once');
-    assertEquals(0, gi);
-
-    et.removeEventListener('f', f);
-    et.dispatchEvent(new Event('f'));
-    assertEquals(1, fi, 'Should not have been called again');
-
-    et.dispatchEvent(new Event('g'));
-    assertEquals(1, gi, 'Should have been called once');
-  });
-
-  test('PreventDefault', () => {
-    let i = 0;
-    function prevent(e: Event) {
-      i++;
-      e.preventDefault();
-    }
-
-    let j = 0;
-    function pass(_e: Event) {
-      j++;
-    }
-
-    const et = new EventTarget();
-    et.addEventListener('test', pass);
-
-    assertTrue(et.dispatchEvent(new Event('test', {cancelable: true})));
-    assertEquals(1, j);
-
-    et.addEventListener('test', prevent);
-
-    assertFalse(et.dispatchEvent(new Event('test', {cancelable: true})));
-    assertEquals(2, j);
-    assertEquals(1, i);
-  });
-});
diff --git a/chrome/test/data/webui/js/webui_resource_module_async_browsertest.js b/chrome/test/data/webui/js/webui_resource_module_async_browsertest.js
index 68bd00af..f60ad62 100644
--- a/chrome/test/data/webui/js/webui_resource_module_async_browsertest.js
+++ b/chrome/test/data/webui/js/webui_resource_module_async_browsertest.js
@@ -141,17 +141,6 @@
   mocha.run();
 });
 
-var EventTargetModuleTest = class extends WebUIResourceModuleAsyncTest {
-  /** @override */
-  get browsePreload() {
-    return 'chrome://webui-test/test_loader.html?module=js/cr/event_target_test.js';
-  }
-};
-
-TEST_F('EventTargetModuleTest', 'All', function() {
-  mocha.run();
-});
-
 var MockTimerTest = class extends WebUIResourceModuleAsyncTest {
   /** @override */
   get browsePreload() {
diff --git a/chrome/test/data/webui/new_tab_page/app_test.ts b/chrome/test/data/webui/new_tab_page/app_test.ts
index e5ef6642..b0706f4 100644
--- a/chrome/test/data/webui/new_tab_page/app_test.ts
+++ b/chrome/test/data/webui/new_tab_page/app_test.ts
@@ -8,8 +8,8 @@
 import {$$, AppElement, BackgroundManager, BrowserCommandProxy, CustomizeDialogPage, NewTabPageProxy, NtpElement, VoiceAction, WindowProxy} from 'chrome://new-tab-page/new_tab_page.js';
 import {PageCallbackRouter, PageHandlerRemote, PageRemote} from 'chrome://new-tab-page/new_tab_page.mojom-webui.js';
 import {Command, CommandHandlerRemote} from 'chrome://resources/js/browser_command/browser_command.mojom-webui.js';
-import {isMac} from 'chrome://resources/js/platform.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
+import {isMac} from 'chrome://resources/js/platform.js';
 import {PromiseResolver} from 'chrome://resources/js/promise_resolver.js';
 import {assertDeepEquals, assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
@@ -21,12 +21,12 @@
 
 suite('NewTabPageAppTest', () => {
   let app: AppElement;
-  let windowProxy: TestBrowserProxy;
-  let handler: TestBrowserProxy;
+  let windowProxy: TestBrowserProxy<WindowProxy>;
+  let handler: TestBrowserProxy<PageHandlerRemote>;
   let callbackRouterRemote: PageRemote;
   let metrics: MetricsTracker;
-  let moduleRegistry: TestBrowserProxy;
-  let backgroundManager: TestBrowserProxy;
+  let moduleRegistry: TestBrowserProxy<ModuleRegistry>;
+  let backgroundManager: TestBrowserProxy<BackgroundManager>;
   let moduleResolver: PromiseResolver<Module[]>;
 
   const url: URL = new URL(location.href);
diff --git a/chrome/test/data/webui/new_tab_page/customize_backgrounds_test.ts b/chrome/test/data/webui/new_tab_page/customize_backgrounds_test.ts
index 052befa..1f3fe21 100644
--- a/chrome/test/data/webui/new_tab_page/customize_backgrounds_test.ts
+++ b/chrome/test/data/webui/new_tab_page/customize_backgrounds_test.ts
@@ -22,8 +22,8 @@
 }
 
 suite('NewTabPageCustomizeBackgroundsTest', () => {
-  let windowProxy: TestBrowserProxy;
-  let handler: TestBrowserProxy;
+  let windowProxy: TestBrowserProxy<WindowProxy>;
+  let handler: TestBrowserProxy<PageHandlerRemote>;
 
   async function createCustomizeBackgrounds():
       Promise<CustomizeBackgroundsElement> {
diff --git a/chrome/test/data/webui/new_tab_page/customize_dialog_test.ts b/chrome/test/data/webui/new_tab_page/customize_dialog_test.ts
index cde940d9..9c92f06 100644
--- a/chrome/test/data/webui/new_tab_page/customize_dialog_test.ts
+++ b/chrome/test/data/webui/new_tab_page/customize_dialog_test.ts
@@ -16,7 +16,7 @@
 
 suite('NewTabPageCustomizeDialogTest', () => {
   let customizeDialog: CustomizeDialogElement;
-  let handler: TestBrowserProxy;
+  let handler: TestBrowserProxy<PageHandlerRemote>;
 
   setup(() => {
     document.body.innerHTML = window.trustedTypes!.emptyHTML;
diff --git a/chrome/test/data/webui/new_tab_page/customize_modules_test.ts b/chrome/test/data/webui/new_tab_page/customize_modules_test.ts
index c3591a5..8f64ae3 100644
--- a/chrome/test/data/webui/new_tab_page/customize_modules_test.ts
+++ b/chrome/test/data/webui/new_tab_page/customize_modules_test.ts
@@ -18,11 +18,11 @@
 import {assertNotStyle, assertStyle, installMock} from './test_support.js';
 
 suite('NewTabPageCustomizeModulesTest', () => {
-  let handler: TestBrowserProxy;
+  let handler: TestBrowserProxy<PageHandlerRemote>;
   let callbackRouterRemote: PageRemote;
-  let moduleRegistry: TestBrowserProxy;
+  let moduleRegistry: TestBrowserProxy<ModuleRegistry>;
   let metrics: MetricsTracker;
-  let cartHandler: TestBrowserProxy;
+  let cartHandler: TestBrowserProxy<CartHandlerRemote>;
 
   async function createCustomizeModules(
       allDisabled: boolean,
diff --git a/chrome/test/data/webui/new_tab_page/customize_shortcuts_test.ts b/chrome/test/data/webui/new_tab_page/customize_shortcuts_test.ts
index 89ee349c..60bee65b 100644
--- a/chrome/test/data/webui/new_tab_page/customize_shortcuts_test.ts
+++ b/chrome/test/data/webui/new_tab_page/customize_shortcuts_test.ts
@@ -15,7 +15,7 @@
 
 suite('NewTabPageCustomizeShortcutsTest', () => {
   let customizeShortcuts: CustomizeShortcutsElement;
-  let handler: TestBrowserProxy;
+  let handler: TestBrowserProxy<PageHandlerRemote>;
 
   setup(() => {
     document.body.innerHTML = window.trustedTypes!.emptyHTML;
diff --git a/chrome/test/data/webui/new_tab_page/doodle_share_dialog_test.ts b/chrome/test/data/webui/new_tab_page/doodle_share_dialog_test.ts
index 1fad19ce..4c158730 100644
--- a/chrome/test/data/webui/new_tab_page/doodle_share_dialog_test.ts
+++ b/chrome/test/data/webui/new_tab_page/doodle_share_dialog_test.ts
@@ -12,7 +12,7 @@
 
 suite('NewTabPageDoodleShareDialogTest', () => {
   let doodleShareDialog: DoodleShareDialogElement;
-  let windowProxy: TestBrowserProxy;
+  let windowProxy: TestBrowserProxy<WindowProxy>;
 
   setup(() => {
     document.body.innerHTML = window.trustedTypes!.emptyHTML;
diff --git a/chrome/test/data/webui/new_tab_page/lens_upload_dialog_test.ts b/chrome/test/data/webui/new_tab_page/lens_upload_dialog_test.ts
index bfadf0a..03c9a1f 100644
--- a/chrome/test/data/webui/new_tab_page/lens_upload_dialog_test.ts
+++ b/chrome/test/data/webui/new_tab_page/lens_upload_dialog_test.ts
@@ -18,7 +18,7 @@
   let uploadDialog: LensUploadDialogElement;
   let wrapperElement: HTMLDivElement;
   let outsideClickTarget: HTMLDivElement;
-  let windowProxy: TestBrowserProxy;
+  let windowProxy: TestBrowserProxy<WindowProxy>;
   let metrics: MetricsTracker;
 
   let submitUrlCalled = false;
diff --git a/chrome/test/data/webui/new_tab_page/logo_test.ts b/chrome/test/data/webui/new_tab_page/logo_test.ts
index 38169890..826d958a 100644
--- a/chrome/test/data/webui/new_tab_page/logo_test.ts
+++ b/chrome/test/data/webui/new_tab_page/logo_test.ts
@@ -80,8 +80,8 @@
 }
 
 suite('NewTabPageLogoTest', () => {
-  let windowProxy: TestBrowserProxy;
-  let handler: TestBrowserProxy;
+  let windowProxy: TestBrowserProxy<WindowProxy>;
+  let handler: TestBrowserProxy<PageHandlerRemote>;
 
   async function createLogo(doodle: Doodle|null = null): Promise<LogoElement> {
     handler.setResultFor('getDoodle', Promise.resolve({
diff --git a/chrome/test/data/webui/new_tab_page/middle_slot_promo_test.ts b/chrome/test/data/webui/new_tab_page/middle_slot_promo_test.ts
index 18ef372..a261368f 100644
--- a/chrome/test/data/webui/new_tab_page/middle_slot_promo_test.ts
+++ b/chrome/test/data/webui/new_tab_page/middle_slot_promo_test.ts
@@ -19,8 +19,8 @@
 import {installMock} from './test_support.js';
 
 suite('NewTabPageMiddleSlotPromoTest', () => {
-  let newTabPageHandler: TestBrowserProxy;
-  let promoBrowserCommandHandler: TestBrowserProxy;
+  let newTabPageHandler: TestBrowserProxy<PageHandlerRemote>;
+  let promoBrowserCommandHandler: TestBrowserProxy<CommandHandlerRemote>;
   let callbackRouterRemote: PageRemote;
   let metrics: MetricsTracker;
 
diff --git a/chrome/test/data/webui/new_tab_page/modules/cart/module_test.ts b/chrome/test/data/webui/new_tab_page/modules/cart/module_test.ts
index 352bf94..83aa1dd 100644
--- a/chrome/test/data/webui/new_tab_page/modules/cart/module_test.ts
+++ b/chrome/test/data/webui/new_tab_page/modules/cart/module_test.ts
@@ -19,7 +19,7 @@
 import {clickAcceptButton, clickCloseButton, clickRejectButton, nextStep} from './discount_consent_card_test_utils.js';
 
 suite('NewTabPageModulesChromeCartModuleTest', () => {
-  let handler: TestBrowserProxy;
+  let handler: TestBrowserProxy<CartHandlerRemote>;
   let metrics: MetricsTracker;
 
   setup(() => {
@@ -978,52 +978,52 @@
     checkVisibleRange(moduleElement, 0, 1);
   }
 
-    function checkScrollButtonVisibility(
-        moduleElement: ChromeCartModuleElement, isLeftVisible: boolean,
-        isRightVisible: boolean) {
-      assertEquals(
-          isLeftVisible,
-          isVisible(
-              moduleElement.shadowRoot!.querySelector('#leftScrollShadow')));
-      assertEquals(
-          isLeftVisible,
-          isVisible(
-              moduleElement.shadowRoot!.querySelector('#leftScrollButton')));
-      assertEquals(
-          isRightVisible,
-          isVisible(
-              moduleElement.shadowRoot!.querySelector('#rightScrollShadow')));
-      assertEquals(
-          isRightVisible,
-          isVisible(
-              moduleElement.shadowRoot!.querySelector('#rightScrollButton')));
-    }
+  function checkScrollButtonVisibility(
+      moduleElement: ChromeCartModuleElement, isLeftVisible: boolean,
+      isRightVisible: boolean) {
+    assertEquals(
+        isLeftVisible,
+        isVisible(
+            moduleElement.shadowRoot!.querySelector('#leftScrollShadow')));
+    assertEquals(
+        isLeftVisible,
+        isVisible(
+            moduleElement.shadowRoot!.querySelector('#leftScrollButton')));
+    assertEquals(
+        isRightVisible,
+        isVisible(
+            moduleElement.shadowRoot!.querySelector('#rightScrollShadow')));
+    assertEquals(
+        isRightVisible,
+        isVisible(
+            moduleElement.shadowRoot!.querySelector('#rightScrollButton')));
+  }
 
-    function checkVisibleRange(
-        moduleElement: ChromeCartModuleElement, startIndex: number,
-        endIndex: number) {
-      const carts =
-          moduleElement.$.cartCarousel.querySelectorAll('.cart-container');
-      assertTrue(startIndex >= 0);
-      assertTrue(endIndex < carts.length);
-      for (let i = 0; i < carts.length; i++) {
-        if (i >= startIndex && i <= endIndex) {
-          assertTrue(getVisibilityForIndex(moduleElement, i));
-        } else {
-          assertFalse(getVisibilityForIndex(moduleElement, i));
-        }
+  function checkVisibleRange(
+      moduleElement: ChromeCartModuleElement, startIndex: number,
+      endIndex: number) {
+    const carts =
+        moduleElement.$.cartCarousel.querySelectorAll('.cart-container');
+    assertTrue(startIndex >= 0);
+    assertTrue(endIndex < carts.length);
+    for (let i = 0; i < carts.length; i++) {
+      if (i >= startIndex && i <= endIndex) {
+        assertTrue(getVisibilityForIndex(moduleElement, i));
+      } else {
+        assertFalse(getVisibilityForIndex(moduleElement, i));
       }
     }
+  }
 
-    function getVisibilityForIndex(
-        moduleElement: ChromeCartModuleElement, index: number) {
-      const cartCarousel = moduleElement.$.cartCarousel;
-      const cart =
-          cartCarousel.querySelectorAll<HTMLElement>('.cart-container')[index]!;
-      return (cart.offsetLeft > cartCarousel.scrollLeft) &&
-          (cartCarousel.scrollLeft + cartCarousel.clientWidth) >
-          (cart.offsetLeft + cart.offsetWidth);
-    }
+  function getVisibilityForIndex(
+      moduleElement: ChromeCartModuleElement, index: number) {
+    const cartCarousel = moduleElement.$.cartCarousel;
+    const cart =
+        cartCarousel.querySelectorAll<HTMLElement>('.cart-container')[index]!;
+    return (cart.offsetLeft > cartCarousel.scrollLeft) &&
+        (cartCarousel.scrollLeft + cartCarousel.clientWidth) >
+        (cart.offsetLeft + cart.offsetWidth);
+  }
 
   suite('rule-based discount', () => {
     suiteSetup(() => {
diff --git a/chrome/test/data/webui/new_tab_page/modules/cart_v2/module_test.ts b/chrome/test/data/webui/new_tab_page/modules/cart_v2/module_test.ts
index f4dd0dc..cb6e2a9 100644
--- a/chrome/test/data/webui/new_tab_page/modules/cart_v2/module_test.ts
+++ b/chrome/test/data/webui/new_tab_page/modules/cart_v2/module_test.ts
@@ -17,7 +17,7 @@
 import {assertNotStyle, installMock} from '../../test_support.js';
 
 suite('NewTabPageModulesChromeCartModuleTest', () => {
-  let handler: TestBrowserProxy;
+  let handler: TestBrowserProxy<CartHandlerRemote>;
   let metrics: MetricsTracker;
 
   setup(() => {
diff --git a/chrome/test/data/webui/new_tab_page/modules/drive/module_test.ts b/chrome/test/data/webui/new_tab_page/modules/drive/module_test.ts
index 220be6b..78eeb65 100644
--- a/chrome/test/data/webui/new_tab_page/modules/drive/module_test.ts
+++ b/chrome/test/data/webui/new_tab_page/modules/drive/module_test.ts
@@ -14,7 +14,7 @@
 import {installMock} from '../../test_support.js';
 
 suite('NewTabPageModulesDriveModuleTest', () => {
-  let handler: TestBrowserProxy;
+  let handler: TestBrowserProxy<DriveHandlerRemote>;
 
   setup(() => {
     document.body.innerHTML = window.trustedTypes!.emptyHTML;
diff --git a/chrome/test/data/webui/new_tab_page/modules/drive_v2/module_test.ts b/chrome/test/data/webui/new_tab_page/modules/drive_v2/module_test.ts
index 22231d0..f037d55 100644
--- a/chrome/test/data/webui/new_tab_page/modules/drive_v2/module_test.ts
+++ b/chrome/test/data/webui/new_tab_page/modules/drive_v2/module_test.ts
@@ -14,7 +14,7 @@
 import {installMock} from '../../test_support.js';
 
 suite('NewTabPageModulesDriveModuleTest', () => {
-  let handler: TestBrowserProxy;
+  let handler: TestBrowserProxy<DriveHandlerRemote>;
 
   setup(() => {
     document.body.innerHTML = window.trustedTypes!.emptyHTML;
diff --git a/chrome/test/data/webui/new_tab_page/modules/dummy_v2/module_test.ts b/chrome/test/data/webui/new_tab_page/modules/dummy_v2/module_test.ts
index a24de8bd..c560f672 100644
--- a/chrome/test/data/webui/new_tab_page/modules/dummy_v2/module_test.ts
+++ b/chrome/test/data/webui/new_tab_page/modules/dummy_v2/module_test.ts
@@ -14,7 +14,7 @@
 import {installMock} from '../../test_support.js';
 
 suite('NewTabPageModulesDummyModuleTest', () => {
-  let handler: TestBrowserProxy;
+  let handler: TestBrowserProxy<FooHandlerRemote>;
 
   setup(() => {
     document.body.innerHTML = window.trustedTypes!.emptyHTML;
diff --git a/chrome/test/data/webui/new_tab_page/modules/feed/module_test.ts b/chrome/test/data/webui/new_tab_page/modules/feed/module_test.ts
index 84135ec..9596d77 100644
--- a/chrome/test/data/webui/new_tab_page/modules/feed/module_test.ts
+++ b/chrome/test/data/webui/new_tab_page/modules/feed/module_test.ts
@@ -14,7 +14,7 @@
 import {installMock} from '../../test_support.js';
 
 suite('NewTabPageModulesFeedModuleTest', () => {
-  let handler: TestBrowserProxy;
+  let handler: TestBrowserProxy<FeedHandlerRemote>;
 
   setup(() => {
     document.body.innerHTML = window.trustedTypes!.emptyHTML;
diff --git a/chrome/test/data/webui/new_tab_page/modules/module_descriptor_test.ts b/chrome/test/data/webui/new_tab_page/modules/module_descriptor_test.ts
index b7b38cd..bbd91580 100644
--- a/chrome/test/data/webui/new_tab_page/modules/module_descriptor_test.ts
+++ b/chrome/test/data/webui/new_tab_page/modules/module_descriptor_test.ts
@@ -14,7 +14,7 @@
 import {createElement, initNullModule, installMock} from '../test_support.js';
 
 suite('NewTabPageModulesModuleDescriptorTest', () => {
-  let windowProxy: TestBrowserProxy;
+  let windowProxy: TestBrowserProxy<WindowProxy>;
   let metrics: MetricsTracker;
 
   setup(() => {
diff --git a/chrome/test/data/webui/new_tab_page/modules/module_registry_test.ts b/chrome/test/data/webui/new_tab_page/modules/module_registry_test.ts
index e6e96e7b..4a9b69c 100644
--- a/chrome/test/data/webui/new_tab_page/modules/module_registry_test.ts
+++ b/chrome/test/data/webui/new_tab_page/modules/module_registry_test.ts
@@ -17,8 +17,8 @@
 import {createElement, initNullModule, installMock} from '../test_support.js';
 
 suite('NewTabPageModulesModuleRegistryTest', () => {
-  let windowProxy: TestBrowserProxy;
-  let handler: TestBrowserProxy;
+  let windowProxy: TestBrowserProxy<WindowProxy>;
+  let handler: TestBrowserProxy<PageHandlerRemote>;
   let callbackRouterRemote: PageRemote;
   let metrics: MetricsTracker;
 
diff --git a/chrome/test/data/webui/new_tab_page/modules/module_wrapper_test.ts b/chrome/test/data/webui/new_tab_page/modules/module_wrapper_test.ts
index 751c6d78..c9b709e 100644
--- a/chrome/test/data/webui/new_tab_page/modules/module_wrapper_test.ts
+++ b/chrome/test/data/webui/new_tab_page/modules/module_wrapper_test.ts
@@ -17,7 +17,7 @@
 suite('NewTabPageModulesModuleWrapperTest', () => {
   let moduleWrapper: ModuleWrapperElement;
   let metrics: MetricsTracker;
-  let windowProxy: TestBrowserProxy;
+  let windowProxy: TestBrowserProxy<WindowProxy>;
 
   setup(() => {
     document.body.innerHTML = window.trustedTypes!.emptyHTML;
diff --git a/chrome/test/data/webui/new_tab_page/modules/modules_test.ts b/chrome/test/data/webui/new_tab_page/modules/modules_test.ts
index 0e2a0d67..c04d227 100644
--- a/chrome/test/data/webui/new_tab_page/modules/modules_test.ts
+++ b/chrome/test/data/webui/new_tab_page/modules/modules_test.ts
@@ -15,10 +15,10 @@
 import {assertNotStyle, assertStyle, capture, createElement, initNullModule, installMock, render} from '../test_support.js';
 
 suite('NewTabPageModulesModulesTest', () => {
-  let handler: TestBrowserProxy;
+  let handler: TestBrowserProxy<PageHandlerRemote>;
   let callbackRouterRemote: PageRemote;
   let metrics: MetricsTracker;
-  let moduleRegistry: TestBrowserProxy;
+  let moduleRegistry: TestBrowserProxy<ModuleRegistry>;
 
   setup(async () => {
     document.body.innerHTML = window.trustedTypes!.emptyHTML;
diff --git a/chrome/test/data/webui/new_tab_page/modules/photos/module_test.ts b/chrome/test/data/webui/new_tab_page/modules/photos/module_test.ts
index 51eed55..442d012 100644
--- a/chrome/test/data/webui/new_tab_page/modules/photos/module_test.ts
+++ b/chrome/test/data/webui/new_tab_page/modules/photos/module_test.ts
@@ -16,7 +16,7 @@
 import {installMock} from '../../test_support.js';
 
 suite('NewTabPageModulesPhotosModuleTest', () => {
-  let handler: TestBrowserProxy;
+  let handler: TestBrowserProxy<PhotosHandlerRemote>;
   let metrics: MetricsTracker;
 
   setup(() => {
diff --git a/chrome/test/data/webui/new_tab_page/modules/recipes/module_test.ts b/chrome/test/data/webui/new_tab_page/modules/recipes/module_test.ts
index 1b204a9b..e2f024a3 100644
--- a/chrome/test/data/webui/new_tab_page/modules/recipes/module_test.ts
+++ b/chrome/test/data/webui/new_tab_page/modules/recipes/module_test.ts
@@ -16,7 +16,7 @@
 import {installMock} from '../../test_support.js';
 
 suite('NewTabPageModulesRecipesTest', () => {
-  let handler: TestBrowserProxy;
+  let handler: TestBrowserProxy<RecipesHandlerRemote>;
 
   setup(() => {
     document.body.innerHTML = window.trustedTypes!.emptyHTML;
diff --git a/chrome/test/data/webui/new_tab_page/modules/recipes_v2/module_test.ts b/chrome/test/data/webui/new_tab_page/modules/recipes_v2/module_test.ts
index 49346525..7236ce6 100644
--- a/chrome/test/data/webui/new_tab_page/modules/recipes_v2/module_test.ts
+++ b/chrome/test/data/webui/new_tab_page/modules/recipes_v2/module_test.ts
@@ -4,7 +4,7 @@
 
 import 'chrome://webui-test/mojo_webui_test_support.js';
 
-import {RecipesModuleElementV2, RecipesHandlerProxy, recipeTasksV2Descriptor} from 'chrome://new-tab-page/lazy_load.js';
+import {RecipesHandlerProxy, RecipesModuleElementV2, recipeTasksV2Descriptor} from 'chrome://new-tab-page/lazy_load.js';
 import {CrAutoImgElement} from 'chrome://new-tab-page/new_tab_page.js';
 import {RecipesHandlerRemote} from 'chrome://new-tab-page/recipes.mojom-webui.js';
 import {assertEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
@@ -13,7 +13,7 @@
 import {installMock} from '../../test_support.js';
 
 suite('NewTabPageModulesRecipesV2ModuleTest', () => {
-  let handler: TestBrowserProxy;
+  let handler: TestBrowserProxy<RecipesHandlerRemote>;
 
   setup(() => {
     document.body.innerHTML = window.trustedTypes!.emptyHTML;
diff --git a/chrome/test/data/webui/new_tab_page/test_support.ts b/chrome/test/data/webui/new_tab_page/test_support.ts
index bb0e874d..ad4afbe0 100644
--- a/chrome/test/data/webui/new_tab_page/test_support.ts
+++ b/chrome/test/data/webui/new_tab_page/test_support.ts
@@ -42,35 +42,15 @@
 }
 
 type Constructor<T> = new (...args: any[]) => T;
-
-export function createMock<T extends object>(clazz: Constructor<T>):
-    {mock: T, callTracker: TestBrowserProxy} {
-  const callTracker = new TestBrowserProxy(
-      Object.getOwnPropertyNames(clazz.prototype)
-          .filter(methodName => methodName !== 'constructor'));
-  const handler = {
-    get: function(_target: T, prop: string) {
-      if (clazz.prototype[prop] instanceof Function) {
-        return (...args: any[]) => callTracker.methodCalled(prop, ...args);
-      }
-      if (Object.getOwnPropertyDescriptor(clazz.prototype, prop)!.get) {
-        return callTracker.methodCalled(prop);
-      }
-      return undefined;
-    },
-  };
-  return {mock: new Proxy<T>({} as unknown as T, handler), callTracker};
-}
-
 type Installer<T> = (instance: T) => void;
 
 export function installMock<T extends object>(
-    clazz: Constructor<T>, installer?: Installer<T>): TestBrowserProxy {
+    clazz: Constructor<T>, installer?: Installer<T>): TestBrowserProxy<T> {
   installer = installer ||
       (clazz as unknown as {setInstance: Installer<T>}).setInstance;
-  const {mock, callTracker} = createMock(clazz);
+  const mock = TestBrowserProxy.fromClass(clazz);
   installer!(mock);
-  return callTracker;
+  return mock;
 }
 
 export function createBackgroundImage(url: string): BackgroundImage {
diff --git a/chrome/test/data/webui/new_tab_page/voice_search_overlay_test.ts b/chrome/test/data/webui/new_tab_page/voice_search_overlay_test.ts
index 83b0bb1..c19b8c43 100644
--- a/chrome/test/data/webui/new_tab_page/voice_search_overlay_test.ts
+++ b/chrome/test/data/webui/new_tab_page/voice_search_overlay_test.ts
@@ -73,7 +73,7 @@
 
 suite('NewTabPageVoiceSearchOverlayTest', () => {
   let voiceSearchOverlay: VoiceSearchOverlayElement;
-  let windowProxy: TestBrowserProxy;
+  let windowProxy: TestBrowserProxy<WindowProxy>;
   let metrics: MetricsTracker;
 
   setup(async () => {
diff --git a/chrome/test/data/webui/settings/chromeos/BUILD.gn b/chrome/test/data/webui/settings/chromeos/BUILD.gn
index 6252f12b..943c163 100644
--- a/chrome/test/data/webui/settings/chromeos/BUILD.gn
+++ b/chrome/test/data/webui/settings/chromeos/BUILD.gn
@@ -105,7 +105,6 @@
   "multidevice_page_tests.js",
   "multidevice_permissions_setup_dialog_tests.js",
   "multidevice_smartlock_item_test.js",
-  "multidevice_smartlock_subpage_test.js",
   "multidevice_subpage_tests.js",
   "multidevice_task_continuation_disabled_link_tests.js",
   "multidevice_task_continuation_item_tests.js",
diff --git a/chrome/test/data/webui/settings/chromeos/multidevice_smartlock_item_test.js b/chrome/test/data/webui/settings/chromeos/multidevice_smartlock_item_test.js
index 4bfb93d0..a6c5a63 100644
--- a/chrome/test/data/webui/settings/chromeos/multidevice_smartlock_item_test.js
+++ b/chrome/test/data/webui/settings/chromeos/multidevice_smartlock_item_test.js
@@ -193,18 +193,6 @@
         assertFalse(!!featureItem);
       });
 
-  // TODO(b/227674947): Delete this test case when Sign in with Smart Lock is
-  // removed.
-  test('clicking item with verified host opens subpage', function() {
-    initializeElement();
-    const featureItem =
-        smartLockItem.shadowRoot.querySelector('#smartLockItem');
-    assertTrue(!!featureItem);
-    expectRouteOnClick(
-        featureItem.shadowRoot.querySelector('#linkWrapper'),
-        routes.SMART_LOCK);
-  });
-
   test('feature toggle click event handled', function() {
     initializeElement();
     simulateFeatureStateChangeRequest(false).then(function() {
diff --git a/chrome/test/data/webui/settings/chromeos/multidevice_smartlock_subpage_test.js b/chrome/test/data/webui/settings/chromeos/multidevice_smartlock_subpage_test.js
deleted file mode 100644
index 39d38527..0000000
--- a/chrome/test/data/webui/settings/chromeos/multidevice_smartlock_subpage_test.js
+++ /dev/null
@@ -1,395 +0,0 @@
-// Copyright 2018 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import {MultiDeviceBrowserProxyImpl, MultiDeviceFeatureState, Router, routes, SmartLockSignInEnabledState} from 'chrome://os-settings/chromeos/os_settings.js';
-import {assert} from 'chrome://resources/js/assert.js';
-import {webUIListenerCallback} from 'chrome://resources/js/cr.m.js';
-import {getDeepActiveElement} from 'chrome://resources/js/util.js';
-import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import {waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
-import {eventToPromise} from 'chrome://webui-test/test_util.js';
-
-import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
-
-import {TestMultideviceBrowserProxy} from './test_multidevice_browser_proxy.js';
-
-suite('Multidevice', function() {
-  let smartLockSubPage = null;
-  let browserProxy = null;
-
-  function createSmartLockSubPage() {
-    const smartLockSubPage =
-        document.createElement('settings-multidevice-smartlock-subpage');
-    document.body.appendChild(smartLockSubPage);
-    flush();
-
-    return smartLockSubPage;
-  }
-
-  function setSuiteState(newState) {
-    smartLockSubPage.pageContentData = Object.assign(
-        {}, smartLockSubPage.pageContentData, {betterTogetherState: newState});
-  }
-
-  function setSmartLockFeatureState(newState) {
-    smartLockSubPage.pageContentData = Object.assign(
-        {}, smartLockSubPage.pageContentData, {smartLockState: newState});
-  }
-
-  function getSmartLockFeatureToggle() {
-    const smartLockFeatureToggle = smartLockSubPage.shadowRoot.querySelector(
-        'settings-multidevice-feature-toggle');
-    assertTrue(!!smartLockFeatureToggle);
-    return smartLockFeatureToggle;
-  }
-
-  function getSmartLockFeatureToggleControl() {
-    const smartLockFeatureToggle = getSmartLockFeatureToggle();
-    const toggleControl =
-        smartLockFeatureToggle.shadowRoot.querySelector('#toggle');
-    assertTrue(!!toggleControl);
-    return toggleControl;
-  }
-
-  function getScreenLockOptionsContent() {
-    const optionsContent =
-        smartLockSubPage.shadowRoot.querySelector('iron-collapse');
-    assertTrue(!!optionsContent);
-    return optionsContent;
-  }
-
-  function getSmartLockSignInRadio() {
-    const smartLockSignInRadio =
-        smartLockSubPage.shadowRoot.querySelector('cr-radio-group');
-    assertTrue(!!smartLockSignInRadio);
-    return smartLockSignInRadio;
-  }
-
-  setup(function() {
-    PolymerTest.clearBody();
-
-    browserProxy = new TestMultideviceBrowserProxy();
-    MultiDeviceBrowserProxyImpl.setInstanceForTesting(browserProxy);
-
-    // Each test must create a settings-multidevice-smartlock-subpage element.
-    smartLockSubPage = null;
-  });
-
-  teardown(function() {
-    assertTrue(!!smartLockSubPage);
-    smartLockSubPage.remove();
-
-    browserProxy.reset();
-    Router.getInstance().resetRouteForTesting();
-  });
-
-  test('Smart Lock enabled', function() {
-    smartLockSubPage = createSmartLockSubPage();
-    setSmartLockFeatureState(MultiDeviceFeatureState.ENABLED_BY_USER);
-
-    // Feature toggle is checked.
-    const toggleControl = getSmartLockFeatureToggleControl();
-    assertTrue(toggleControl.checked);
-
-    // Screen lock options are visible.
-    const optionsContent = getScreenLockOptionsContent();
-    assertTrue(optionsContent.opened);
-  });
-
-  test('Smart Lock disabled by user', function() {
-    smartLockSubPage = createSmartLockSubPage();
-    setSmartLockFeatureState(MultiDeviceFeatureState.DISABLED_BY_USER);
-
-    // Feature toggle is not checked.
-    const toggleControl = getSmartLockFeatureToggleControl();
-    assertFalse(toggleControl.checked);
-
-    // Screen lock options are not visible.
-    const optionsContent = getScreenLockOptionsContent();
-    assertFalse(optionsContent.opened);
-  });
-
-  test('Smart Lock prohibited by policy', function() {
-    smartLockSubPage = createSmartLockSubPage();
-    setSmartLockFeatureState(MultiDeviceFeatureState.PROHIBITED_BY_POLICY);
-
-    // Feature toggle is not checked.
-    const toggleControl = getSmartLockFeatureToggleControl();
-    assertFalse(toggleControl.checked);
-
-    // Screen lock options are not visible.
-    const optionsContent = getScreenLockOptionsContent();
-    assertFalse(optionsContent.opened);
-  });
-
-  test('Smart Lock enable feature toggle', function() {
-    smartLockSubPage = createSmartLockSubPage();
-    setSuiteState(MultiDeviceFeatureState.ENABLED_BY_USER);
-    setSmartLockFeatureState(MultiDeviceFeatureState.DISABLED_BY_USER);
-
-    // Feature toggle is not checked.
-    const toggleControl = getSmartLockFeatureToggleControl();
-    assertFalse(toggleControl.checked);
-
-    // In the case of Smart Lock, the multidevice-feature-toggle depends on the
-    // top-level settings-multidevice-page handling the authentication process
-    // and feature toggling.
-    //
-    // This code simulates the authentication and toggling by directly toggling
-    // the feature to enabled when the feature toggle is clicked.
-    const featureToggle = getSmartLockFeatureToggle();
-    const whenFeatureClicked =
-        eventToPromise('feature-toggle-clicked', featureToggle).then(() => {
-          setSmartLockFeatureState(MultiDeviceFeatureState.ENABLED_BY_USER);
-        });
-
-    // Toggle the feature to enabled.
-    toggleControl.click();
-
-    // Verify the feature control is toggled to enabled after the user clicks on
-    // the feature toggle.
-    return whenFeatureClicked.then(() => {
-      assertTrue(toggleControl.checked);
-    });
-  });
-
-  test('Smart Lock enable feature toggle without authentication', function() {
-    smartLockSubPage = createSmartLockSubPage();
-    setSuiteState(MultiDeviceFeatureState.ENABLED_BY_USER);
-    setSmartLockFeatureState(MultiDeviceFeatureState.DISABLED_BY_USER);
-
-    // Feature toggle is not checked.
-    const toggleControl = getSmartLockFeatureToggleControl();
-    assertFalse(toggleControl.checked);
-
-    // In the case of Smart Lock, the multidevice-feature-toggle depends on the
-    // top-level settings-multidevice-page handling the authentication process
-    // and feature toggling.
-    //
-    // This code simulates the user cancelling authentication by ignoring the
-    // toggle being clicked.
-    const featureToggle = getSmartLockFeatureToggle();
-    const whenFeatureClicked =
-        eventToPromise('feature-toggle-clicked', featureToggle)
-            .then(
-                () => {
-                    // Do not enable the feature (simulating the user cancelling
-                    // auth).
-                });
-
-    // Toggle the feature to enabled.
-    toggleControl.click();
-
-    // Verify the feature control is not toggled to enabled after the user
-    // cancels authenticating with the password dialog.
-    return whenFeatureClicked.then(() => {
-      assertFalse(toggleControl.checked);
-    });
-  });
-
-  test('Deep link to smart lock on/off', async () => {
-    smartLockSubPage = createSmartLockSubPage();
-    setSuiteState(MultiDeviceFeatureState.ENABLED_BY_USER);
-    setSmartLockFeatureState(MultiDeviceFeatureState.DISABLED_BY_USER);
-
-    const params = new URLSearchParams();
-    params.append('settingId', '203');
-    Router.getInstance().navigateTo(routes.SMART_LOCK, params);
-
-    flush();
-
-    const deepLinkElement = getSmartLockFeatureToggleControl();
-    await waitAfterNextRender(deepLinkElement);
-    assertEquals(
-        deepLinkElement, getDeepActiveElement(),
-        'Smart lock on/off toggle should be focused for settingId=203.');
-  });
-
-  test('Smart Lock signin disabled by default', function() {
-    smartLockSubPage = createSmartLockSubPage();
-
-    const smartLockSignInRadio = getSmartLockSignInRadio();
-    assertEquals(
-        SmartLockSignInEnabledState.DISABLED, smartLockSignInRadio.selected);
-  });
-
-  test('Smart Lock signin enabled', function() {
-    const whenSignInEnabledSet =
-        browserProxy.whenCalled('getSmartLockSignInEnabled');
-
-    smartLockSubPage = createSmartLockSubPage();
-
-    const smartLockSignInRadio = getSmartLockSignInRadio();
-    assertEquals(
-        SmartLockSignInEnabledState.DISABLED, smartLockSignInRadio.selected);
-
-    return whenSignInEnabledSet.then(() => {
-      assertEquals(
-          SmartLockSignInEnabledState.ENABLED, smartLockSignInRadio.selected);
-    });
-  });
-
-  test('Smart Lock signin enabled changed', function() {
-    smartLockSubPage = createSmartLockSubPage();
-
-    const smartLockSignInRadio = getSmartLockSignInRadio();
-    assertEquals(
-        SmartLockSignInEnabledState.DISABLED, smartLockSignInRadio.selected);
-
-    webUIListenerCallback('smart-lock-signin-enabled-changed', true);
-    flush();
-
-    assertEquals(
-        SmartLockSignInEnabledState.ENABLED, smartLockSignInRadio.selected);
-  });
-
-  test('Smart Lock sign in successful authentication', function() {
-    smartLockSubPage = createSmartLockSubPage();
-
-    const smartLockSignInRadio = getSmartLockSignInRadio();
-    assertEquals(
-        SmartLockSignInEnabledState.DISABLED, smartLockSignInRadio.selected);
-
-    // The password dialog is not visible.
-    let passwordDialog = smartLockSubPage.shadowRoot.querySelector(
-        'settings-password-prompt-dialog');
-    assertTrue(!passwordDialog);
-
-    // Click the 'Enable sign in' radio.
-    const enableSmartLockControl = smartLockSubPage.shadowRoot.querySelector(
-        'multidevice-radio-button[name="enabled"]');
-    assertTrue(!!enableSmartLockControl);
-    enableSmartLockControl.click();
-    flush();
-
-    // The password dialog is now visible.
-    passwordDialog = smartLockSubPage.shadowRoot.querySelector(
-        'settings-password-prompt-dialog');
-    assertTrue(!!passwordDialog);
-
-    // Sign in radio is still disabled because the user has not authenticated
-    // using the password dialog.
-    assertEquals(
-        SmartLockSignInEnabledState.DISABLED, smartLockSignInRadio.selected);
-
-    // Simulate the user entering a valid password into the dialog.
-    passwordDialog.authToken = 'validAuthToken';
-    passwordDialog.dispatchEvent(new CustomEvent('close'));
-    flush();
-
-    return browserProxy.whenCalled('getSmartLockSignInEnabled').then(params => {
-      assertEquals(
-          SmartLockSignInEnabledState.ENABLED, smartLockSignInRadio.selected);
-    });
-  });
-
-  test('Smart Lock sign in cancel authentication', function() {
-    smartLockSubPage = createSmartLockSubPage();
-
-    const smartLockSignInRadio = getSmartLockSignInRadio();
-    assertEquals(
-        SmartLockSignInEnabledState.DISABLED, smartLockSignInRadio.selected);
-
-    // The password dialog is not visible.
-    let passwordDialog = smartLockSubPage.shadowRoot.querySelector(
-        'settings-password-prompt-dialog');
-    assertTrue(!passwordDialog);
-
-    // Click the 'Enable sign in' radio.
-    const enableSmartLockControl = smartLockSubPage.shadowRoot.querySelector(
-        'multidevice-radio-button[name="enabled"]');
-    assertTrue(!!enableSmartLockControl);
-    enableSmartLockControl.click();
-    flush();
-
-    // The password dialog is now visible.
-    passwordDialog = smartLockSubPage.shadowRoot.querySelector(
-        'settings-password-prompt-dialog');
-    assertTrue(!!passwordDialog);
-
-    // Sign in radio is still disabled because the user has not authenticated
-    // using the password dialog.
-    assertEquals(
-        SmartLockSignInEnabledState.DISABLED, smartLockSignInRadio.selected);
-
-    // Simulate the user closing the password dialog.
-    passwordDialog.dispatchEvent(new CustomEvent('close'));
-    flush();
-
-    // Verify the password dialog is closed.
-    passwordDialog = smartLockSubPage.shadowRoot.querySelector(
-        'settings-password-prompt-dialog');
-    assert(!passwordDialog);
-
-    // The password dialog is closed and unauthenticated, so sign in is still
-    // disabled.
-    assertEquals(
-        SmartLockSignInEnabledState.DISABLED, smartLockSignInRadio.selected);
-  });
-
-  test('Smart Lock disable sign in does not show password dialog', function() {
-    smartLockSubPage = createSmartLockSubPage();
-
-    // Set sign in as enabled.
-    webUIListenerCallback('smart-lock-signin-enabled-changed', true);
-    flush();
-
-    const smartLockSignInRadio = getSmartLockSignInRadio();
-    assertEquals(
-        SmartLockSignInEnabledState.ENABLED, smartLockSignInRadio.selected);
-
-    // The password dialog is not visible.
-    let passwordDialog = smartLockSubPage.shadowRoot.querySelector(
-        'settings-password-prompt-dialog');
-    assertTrue(!passwordDialog);
-
-    // Click the 'Disable sign in' radio.
-    const disableSmartLockControl = smartLockSubPage.shadowRoot.querySelector(
-        'multidevice-radio-button[name="disabled"]');
-    assertTrue(!!disableSmartLockControl);
-    disableSmartLockControl.click();
-    flush();
-
-    // The password dialog is not shown.
-    passwordDialog = smartLockSubPage.shadowRoot.querySelector(
-        'settings-password-prompt-dialog');
-    assertTrue(!passwordDialog);
-
-    // Sign in radio is now disabled.
-    assertEquals(
-        SmartLockSignInEnabledState.DISABLED, smartLockSignInRadio.selected);
-  });
-
-  test('Smart Lock sign in control enabled by default', function() {
-    smartLockSubPage = createSmartLockSubPage();
-
-    const smartLockSignInRadio = getSmartLockSignInRadio();
-    assertFalse(smartLockSignInRadio.disabled);
-  });
-
-  test('Smart Lock sign in control disabled by policy', function() {
-    browserProxy.smartLockSignInAllowed = false;
-    const whenSignInAllowedSet =
-        browserProxy.whenCalled('getSmartLockSignInAllowed');
-
-    smartLockSubPage = createSmartLockSubPage();
-
-    return whenSignInAllowedSet.then(() => {
-      const smartLockSignInRadio = getSmartLockSignInRadio();
-      assertTrue(smartLockSignInRadio.disabled);
-    });
-  });
-
-  test('Smart Lock sign in control allowed state changes', function() {
-    smartLockSubPage = createSmartLockSubPage();
-
-    const smartLockSignInRadio = getSmartLockSignInRadio();
-    assertFalse(smartLockSignInRadio.disabled);
-
-    webUIListenerCallback('smart-lock-signin-allowed-changed', false);
-    flush();
-
-    assertTrue(smartLockSignInRadio.disabled);
-  });
-});
diff --git a/chrome/test/data/webui/settings/chromeos/multidevice_subpage_tests.js b/chrome/test/data/webui/settings/chromeos/multidevice_subpage_tests.js
index ef8f1f9e..3fa86dc 100644
--- a/chrome/test/data/webui/settings/chromeos/multidevice_subpage_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/multidevice_subpage_tests.js
@@ -257,30 +257,6 @@
       });
 
   test(
-      'SmartLock item routes to subpage with isSmartLockSignInRemoved disabled',
-      function() {
-        multideviceSubpage.remove();
-        loadTimeData.overrideValues({'isSmartLockSignInRemoved': false});
-        browserProxy = new TestMultideviceBrowserProxy();
-        MultiDeviceBrowserProxyImpl.setInstanceForTesting(browserProxy);
-
-        PolymerTest.clearBody();
-        multideviceSubpage =
-            document.createElement('settings-multidevice-subpage');
-        multideviceSubpage.pageContentData = {hostDeviceName: 'Pixel XL'};
-        setMode(MultiDeviceSettingsMode.HOST_SET_VERIFIED);
-        setSupportedFeatures(Object.values(MultiDeviceFeature));
-
-        document.body.appendChild(multideviceSubpage);
-        flush();
-
-        multideviceSubpage.shadowRoot.querySelector('#smartLockItem')
-            .shadowRoot.querySelector('.link-wrapper')
-            .click();
-        assertEquals(Router.getInstance().getCurrentRoute(), routes.SMART_LOCK);
-      });
-
-  test(
       'setting isSmartLockSignInRemoved flag removes SmartLock subpage route',
       function() {
         multideviceSubpage.remove();
diff --git a/chrome/test/data/webui/settings/chromeos/os_settings_search_box_test.js b/chrome/test/data/webui/settings/chromeos/os_settings_search_box_test.js
index 03f024e1..07f987c3 100644
--- a/chrome/test/data/webui/settings/chromeos/os_settings_search_box_test.js
+++ b/chrome/test/data/webui/settings/chromeos/os_settings_search_box_test.js
@@ -2,7 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {OpenWindowProxyImpl, personalizationSearchMojomWebui, Router, routes, routesMojomWebui, searchMojomWebui, searchResultIconMojomWebui, setPersonalizationSearchHandlerForTesting, setSettingsSearchHandlerForTesting, settingMojomWebui, setUserActionRecorderForTesting, userActionRecorderMojomWebui} from 'chrome://os-settings/chromeos/os_settings.js';
+import {AboutPageBrowserProxyImpl, OpenWindowProxyImpl, personalizationSearchMojomWebui, Router, routes, routesMojomWebui, searchMojomWebui, searchResultIconMojomWebui, setPersonalizationSearchHandlerForTesting, setSettingsSearchHandlerForTesting, settingMojomWebui, setUserActionRecorderForTesting, userActionRecorderMojomWebui} from 'chrome://os-settings/chromeos/os_settings.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {assertDeepEquals, assertEquals, assertFalse, assertNotEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
@@ -11,6 +12,7 @@
 import {FakePersonalizationSearchHandler} from './fake_personalization_search_handler.js';
 import {FakeSettingsSearchHandler} from './fake_settings_search_handler.js';
 import {FakeUserActionRecorder} from './fake_user_action_recorder.js';
+import {TestAboutPageBrowserProxyChromeOS} from './test_about_page_browser_proxy_chromeos.js';
 
 /** @fileoverview Runs tests for the OS settings search box. */
 
@@ -199,7 +201,6 @@
   }
 
   setup(function() {
-    setupSearchBox();
     Router.getInstance().navigateTo(routes.BASIC);
 
     openWindowProxy = new TestOpenWindowProxy();
@@ -215,6 +216,7 @@
   });
 
   test('Search availability changed', async () => {
+    setupSearchBox();
     settingsSearchHandler.setFakeResults([fakeSettingsResult('result')]);
     await simulateSearch('test query');
     assertTrue(dropDown.opened);
@@ -269,6 +271,7 @@
   });
 
   test('User action search event', async () => {
+    setupSearchBox();
     settingsSearchHandler.setFakeResults([]);
 
     assertEquals(userActionRecorder.searchCount, 0);
@@ -279,6 +282,7 @@
   test(
       'Clicking magnifying glass shows dropdown and selects all text',
       async () => {
+        setupSearchBox();
         settingsSearchHandler.setFakeResults([fakeSettingsResult('a')]);
         await simulateSearch('query');
         await waitForListUpdate();
@@ -287,13 +291,13 @@
 
         assertFalse(dropDown.opened);
         assertFalse(isTextSelected());
-
         field.$.icon.click();
         assertTrue(isTextSelected());
         assertTrue(dropDown.opened);
       });
 
   test('Dropdown opens correctly when results are fetched', async () => {
+    setupSearchBox();
     // Show no results in dropdown if no results are returned.
     settingsSearchHandler.setFakeResults([]);
     personalizationSearchHandler.setFakeResults([]);
@@ -323,6 +327,7 @@
   });
 
   test('Restore previous existing search results', async () => {
+    setupSearchBox();
     settingsSearchHandler.setFakeResults([fakeSettingsResult('result 1')]);
     personalizationSearchHandler.setFakeResults(
         [fakePersonalizationResult('personalization')]);
@@ -365,6 +370,7 @@
   });
 
   test('Search result rows are selected correctly', async () => {
+    setupSearchBox();
     settingsSearchHandler.setFakeResults([fakeSettingsResult('a')]);
     personalizationSearchHandler.setFakeResults(
         [fakePersonalizationResult('b')]);
@@ -415,6 +421,7 @@
   });
 
   test('Keydown Enter on search box can cause route change', async () => {
+    setupSearchBox();
     settingsSearchHandler.setFakeResults(
         [fakeSettingsResult('WiFi Settings', 'networks?type=WiFi')]);
     await simulateSearch('fake query');
@@ -437,6 +444,7 @@
   test(
       'Keypress Enter on personalization result opens personalization hub',
       async () => {
+        setupSearchBox();
         personalizationSearchHandler.setFakeResults(
             [fakePersonalizationResult('result', 'test')]);
         settingsSearchHandler.setFakeResults([]);
@@ -461,6 +469,7 @@
   test(
       'Clicking on personalization result opens personalization hub',
       async () => {
+        setupSearchBox();
         personalizationSearchHandler.setFakeResults(
             [fakePersonalizationResult('Wallpaper', 'test')]);
         await simulateSearch('fake query 1');
@@ -479,6 +488,7 @@
       });
 
   test('Keypress Enter on row causes route change', async () => {
+    setupSearchBox();
     settingsSearchHandler.setFakeResults(
         [fakeSettingsResult('WiFi Settings', 'networks?type=WiFi')]);
     await simulateSearch('fake query 1');
@@ -500,6 +510,7 @@
   });
 
   test('Route change when result row is clicked', async () => {
+    setupSearchBox();
     settingsSearchHandler.setFakeResults(
         [fakeSettingsResult('WiFi Settings', 'networks?type=WiFi')]);
     await simulateSearch('fake query 2');
@@ -519,6 +530,7 @@
   });
 
   test('Selecting result a second time does not deselect it.', async () => {
+    setupSearchBox();
     settingsSearchHandler.setFakeResults(
         [fakeSettingsResult('WiFi Settings', 'networks?type=WiFi')]);
     await simulateSearch('query');
@@ -540,6 +552,7 @@
   });
 
   test('Test no bolding if not generated from text match', async () => {
+    setupSearchBox();
     settingsSearchHandler.setFakeResults([fakeSettingsResult(
         'Search and Assistant', undefined, undefined,
         /*wasGeneratedFromTextMatch=*/ false)]);
@@ -551,6 +564,7 @@
   });
 
   test('Tokenize and match result text to query text', async () => {
+    setupSearchBox();
     settingsSearchHandler.setFakeResults(
         [fakeSettingsResult('Search and Assistant')]);
     await simulateSearch(`Assistant Search`);
@@ -561,6 +575,7 @@
   });
 
   test('Bold result text to matching query', async () => {
+    setupSearchBox();
     settingsSearchHandler.setFakeResults(
         [fakeSettingsResult('Search and Assistant')]);
     await simulateSearch(`a`);
@@ -571,6 +586,7 @@
   });
 
   test('Bold result including ignored characters', async () => {
+    setupSearchBox();
     settingsSearchHandler.setFakeResults([fakeSettingsResult('Turn on Wi-Fi')]);
     await simulateSearch(`wif`);
     await waitForListUpdate();
@@ -625,6 +641,7 @@
   });
 
   test('Test query longer than result blocks', async () => {
+    setupSearchBox();
     settingsSearchHandler.setFakeResults([fakeSettingsResult('Turn on Wi-Fi')]);
     await simulateSearch(`onwifi`);
     await waitForListUpdate();
@@ -634,6 +651,7 @@
   });
 
   test('Test bolding of accented characters', async () => {
+    setupSearchBox();
     settingsSearchHandler.setFakeResults([fakeSettingsResult('Crème Brûlée')]);
     await simulateSearch(`E U`);
     await waitForListUpdate();
@@ -643,6 +661,7 @@
   });
 
   test('Test no spaces nor characters that have upper/lower case', async () => {
+    setupSearchBox();
     settingsSearchHandler.setFakeResults(
         [fakeSettingsResult('キーボード設定---')]);
     await simulateSearch(`キー設`);
@@ -653,6 +672,7 @@
   });
 
   test('Test blankspace types in result maintained', async () => {
+    setupSearchBox();
     const resultText = 'Turn\xa0on  \xa0Wi-Fi ';
 
     settingsSearchHandler.setFakeResults([fakeSettingsResult(resultText)]);
@@ -664,6 +684,7 @@
   });
 
   test('Test longest common substring for mispellings', async () => {
+    setupSearchBox();
     settingsSearchHandler.setFakeResults([fakeSettingsResult('Linux')]);
     await simulateSearch(`Linuux`);
     await waitForListUpdate();
@@ -703,6 +724,7 @@
   });
 
   test('search results sorted descending', async () => {
+    setupSearchBox();
     personalizationSearchHandler.setFakeResults([
       fakePersonalizationResult(
           'one', /*relativeUrl=*/ '', /*relevanceScore=*/ 0.99),
@@ -747,4 +769,85 @@
         }),
         'search results sorted in expected order');
   });
-});
+
+  suite('SearchFeedback_OfficialBuild', () => {
+    suite('when feature flag is enabled', () => {
+      /** @type {?TestAboutPageBrowserProxyChromeOS} */
+      let browserProxy = null;
+
+      setup(() => {
+        loadTimeData.overrideValues({searchFeedbackEnabled: true});
+
+        browserProxy = new TestAboutPageBrowserProxyChromeOS();
+        AboutPageBrowserProxyImpl.setInstanceForTesting(browserProxy);
+
+        setupSearchBox();
+      });
+
+      teardown(() => {
+        PolymerTest.clearBody();
+      });
+
+      test(
+          'feedback button does not appear when search result exists',
+          async () => {
+            settingsSearchHandler.setFakeResults(['assistant']);
+            await simulateSearch('a');
+            assertTrue(dropDown.opened);
+            assertEquals(1, searchBox.searchResults_.length);
+            assertTrue(noResultsSection.hidden);
+            const feedbackReportResults =
+                searchBox.shadowRoot.querySelector('#reportSearchResultButton');
+            assertTrue(!!feedbackReportResults);
+            assertTrue(feedbackReportResults.hidden);
+          });
+
+      test(
+          'feedback button appears when search result does not exist',
+          async () => {
+            settingsSearchHandler.setFakeResults([]);
+            await simulateSearch('query 1');
+            assertTrue(dropDown.opened);
+            assertEquals(0, searchBox.searchResults_.length);
+            assertFalse(noResultsSection.hidden);
+            // feedback button appears when no search results have been found
+            const feedbackReportResults =
+                searchBox.shadowRoot.querySelector('#reportSearchResultButton');
+            assertTrue(!!feedbackReportResults);
+            assertFalse(feedbackReportResults.hidden);
+          });
+
+      test('clicking the button opens feedback dialog', async () => {
+        settingsSearchHandler.setFakeResults([]);
+        await simulateSearch('query 1');
+        const feedbackReportResults =
+            searchBox.shadowRoot.querySelector('#reportSearchResultButton');
+        feedbackReportResults.click();
+        return browserProxy.whenCalled('openFeedbackDialog');
+      });
+    });
+
+    suite('when feature flag is disabled', () => {
+      setup(() => {
+        loadTimeData.overrideValues({searchFeedbackEnabled: false});
+
+        setupSearchBox();
+      });
+
+      teardown(() => {
+        PolymerTest.clearBody();
+      });
+
+      test('feedback button does not render', async () => {
+        settingsSearchHandler.setFakeResults([]);
+        await simulateSearch('query 1');
+        assertTrue(dropDown.opened);
+        assertEquals(0, searchBox.searchResults_.length);
+        assertFalse(noResultsSection.hidden);
+        const feedbackReportResults =
+            searchBox.shadowRoot.querySelector('#reportSearchResultButton');
+        assertEquals(null, feedbackReportResults);
+      });
+    });
+  });
+});
\ No newline at end of file
diff --git a/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js b/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js
index 5e90e91..c3f72506 100644
--- a/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js
+++ b/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js
@@ -424,7 +424,6 @@
    'multidevice_permissions_setup_dialog_tests.js',
  ],
  ['MultideviceSmartLockItem', 'multidevice_smartlock_item_test.js'],
- ['MultideviceSmartLockSubPage', 'multidevice_smartlock_subpage_test.js'],
  ['MultideviceSubPage', 'multidevice_subpage_tests.js'],
  [
    'MultideviceTaskContinuationItem',
@@ -571,6 +570,17 @@
       mocha.grep('PrivacyHubSubpageTest_OfficialBuild').run();
     });
     GEN('#endif');
+  } else if (testName === 'OsSettingsSearchBox') {
+    TEST_F(className, 'AllBuilds' || 'All', () => {
+      mocha.grep('/^(?!(OSSettingsSearchBox SearchFeedback_OfficialBuild)).*$/')
+          .run();
+    });
+
+    GEN('#if BUILDFLAG(GOOGLE_CHROME_BRANDING)');
+    TEST_F(className, 'OfficialBuild' || 'All', () => {
+      mocha.grep('SearchFeedback_OfficialBuild').run();
+    });
+    GEN('#endif');
   } else {
     TEST_F(className, 'All', () => mocha.run());
   }
diff --git a/chrome/test/data/webui/test_browser_proxy.ts b/chrome/test/data/webui/test_browser_proxy.ts
index ba342f7f..95f164b 100644
--- a/chrome/test/data/webui/test_browser_proxy.ts
+++ b/chrome/test/data/webui/test_browser_proxy.ts
@@ -64,7 +64,7 @@
             .filter(methodName => methodName !== 'constructor') as
         Array<keyof T>;
     const proxy = new TestBrowserProxy<T>(methodNames);
-    proxy.mockMethods_(methodNames);
+    proxy.mockMethods_(methodNames, clazz);
     return proxy as unknown as (T & TestBrowserProxy<T>);
   }
 
@@ -74,10 +74,22 @@
    * |setResultFor(methodName)|, or set a result mapper function that will be
    * invoked when a method is called using |setResultMapperFor(methodName)|.
    */
-  private mockMethods_(methodNames: Array<keyof T>) {
+  private mockMethods_(methodNames: Array<keyof T>, clazz: Constructor<T>) {
     methodNames.forEach(methodName => {
-      (this as unknown as {[key in keyof T]: Function})[methodName] =
-          (...args: any[]) => this.methodCalled(methodName, ...args);
+      const descriptor =
+          Object.getOwnPropertyDescriptor(clazz.prototype, methodName)!;
+      const mockedMethod = (...args: any[]) =>
+          this.methodCalled(methodName, ...args);
+      if (descriptor.get) {
+        descriptor.get = mockedMethod;
+      }
+      if (descriptor.set) {
+        descriptor.set = mockedMethod;
+      }
+      if (descriptor.value && descriptor.value instanceof Function) {
+        descriptor.value = mockedMethod;
+      }
+      Object.defineProperty(this, methodName, descriptor);
     });
   }
 
diff --git a/chrome/test/interaction/README.md b/chrome/test/interaction/README.md
index 06977d2..41471904 100644
--- a/chrome/test/interaction/README.md
+++ b/chrome/test/interaction/README.md
@@ -138,6 +138,9 @@
    - `ExecuteJsAt()` [Browser]
    - `CheckJsResult()` [Browser]
    - `CheckJsResultAt()` [Browser]
+- Utility verbs modify how the test sequence is executed. Currently there is
+  only `FlushEvents()`, which ensures that the next step happens on a fresh
+  message loop rather than being able to chain successive steps.
 
 Example with mouse input:
 ```cpp
diff --git a/chrome/test/interaction/webcontents_interaction_test_util.cc b/chrome/test/interaction/webcontents_interaction_test_util.cc
index 7047bf9..6eae3339e 100644
--- a/chrome/test/interaction/webcontents_interaction_test_util.cc
+++ b/chrome/test/interaction/webcontents_interaction_test_util.cc
@@ -872,6 +872,13 @@
         Observe(nullptr);
       }
     }
+  } else if (change.type() == TabStripModelChange::Type::kReplaced) {
+    auto* const replace = change.GetReplace();
+    if (web_contents() == replace->old_contents) {
+      DiscardCurrentElement();
+      Observe(replace->new_contents);
+      MaybeCreateElement(false);
+    }
   }
 }
 
diff --git a/chrome/test/interaction/webcontents_interaction_test_util_browsertest.cc b/chrome/test/interaction/webcontents_interaction_test_util_browsertest.cc
index ea05c67b..0a64b6ab 100644
--- a/chrome/test/interaction/webcontents_interaction_test_util_browsertest.cc
+++ b/chrome/test/interaction/webcontents_interaction_test_util_browsertest.cc
@@ -10,6 +10,8 @@
 #include "base/test/gtest_util.h"
 #include "base/time/time.h"
 #include "base/timer/elapsed_timer.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/resource_coordinator/tab_manager.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/browser_element_identifiers.h"
@@ -19,6 +21,7 @@
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/interaction/interaction_test_util_browser.h"
+#include "chrome/test/interaction/interactive_browser_test.h"
 #include "chrome/test/interaction/tracked_element_webcontents.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
@@ -32,8 +35,8 @@
 
 namespace {
 
-DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kWebContentsInteractionTestUtilTestId);
-DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kWebContentsInteractionTestUtilTestId2);
+DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kWebContentsElementId);
+DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kWebContentsElementId2);
 DEFINE_LOCAL_CUSTOM_ELEMENT_EVENT_TYPE(kInteractionTestUtilCustomEventType);
 DEFINE_LOCAL_CUSTOM_ELEMENT_EVENT_TYPE(kInteractionTestUtilCustomEventType2);
 
@@ -74,8 +77,7 @@
   // Using this constructor hits all of the rest of the constructors, saving us
   // the hassle of writing three identical tests.
   auto util = WebContentsInteractionTestUtil::ForExistingTabInContext(
-      browser()->window()->GetElementContext(),
-      kWebContentsInteractionTestUtilTestId);
+      browser()->window()->GetElementContext(), kWebContentsElementId);
   auto sequence =
       ui::InteractionSequence::Builder()
           .SetCompletedCallback(completed.Get())
@@ -84,7 +86,7 @@
           .AddStep(
               ui::InteractionSequence::StepBuilder()
                   .SetType(ui::InteractionSequence::StepType::kShown)
-                  .SetElementID(kWebContentsInteractionTestUtilTestId)
+                  .SetElementID(kWebContentsElementId)
                   .SetStartCallback(base::BindLambdaForTesting(
                       [&](ui::InteractionSequence* sequence,
                           ui::TrackedElement* element) {
@@ -105,8 +107,7 @@
   UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
 
   auto util = WebContentsInteractionTestUtil::ForTabWebContents(
-      browser()->tab_strip_model()->GetWebContentsAt(0),
-      kWebContentsInteractionTestUtilTestId);
+      browser()->tab_strip_model()->GetWebContentsAt(0), kWebContentsElementId);
   auto sequence =
       ui::InteractionSequence::Builder()
           .SetCompletedCallback(completed.Get())
@@ -115,7 +116,7 @@
           .AddStep(
               ui::InteractionSequence::StepBuilder()
                   .SetType(ui::InteractionSequence::StepType::kShown)
-                  .SetElementID(kWebContentsInteractionTestUtilTestId)
+                  .SetElementID(kWebContentsElementId)
                   .SetStartCallback(base::BindLambdaForTesting(
                       [&](ui::InteractionSequence* sequence,
                           ui::TrackedElement* element) {
@@ -138,7 +139,7 @@
   const GURL url = embedded_test_server()->GetURL(kEmptyDocumentURL);
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   auto sequence =
       ui::InteractionSequence::Builder()
           .SetCompletedCallback(completed.Get())
@@ -146,7 +147,7 @@
           .SetContext(browser()->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -158,11 +159,11 @@
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kHidden)
                        .SetTransitionOnlyOnEvent(true)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .Build())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .Build())
           .Build();
 
@@ -176,7 +177,7 @@
   const GURL url = embedded_test_server()->GetURL(kEmptyDocumentURL);
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   auto sequence =
       ui::InteractionSequence::Builder()
           .SetCompletedCallback(completed.Get())
@@ -184,7 +185,7 @@
           .SetContext(browser()->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetMustRemainVisible(false)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
@@ -195,7 +196,7 @@
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
                        .SetTransitionOnlyOnEvent(true)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -214,7 +215,7 @@
   const GURL url = embedded_test_server()->GetURL(kEmptyDocumentURL);
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   auto sequence =
       ui::InteractionSequence::Builder()
           .SetCompletedCallback(completed.Get())
@@ -222,7 +223,7 @@
           .SetContext(browser()->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -234,7 +235,7 @@
                        .Build())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kHidden)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetTransitionOnlyOnEvent(true)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
@@ -244,7 +245,7 @@
                        .Build())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -264,7 +265,7 @@
   const GURL url = embedded_test_server()->GetURL(kEmptyDocumentURL);
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   auto sequence =
       ui::InteractionSequence::Builder()
           .SetCompletedCallback(completed.Get())
@@ -272,12 +273,11 @@
           .SetContext(browser()->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
-                             util->set_page_identifier(
-                                 kWebContentsInteractionTestUtilTestId2);
+                             util->set_page_identifier(kWebContentsElementId2);
                              NavigateParams navigate_params(
                                  browser(), url, ui::PAGE_TRANSITION_TYPED);
                              Navigate(&navigate_params);
@@ -285,11 +285,11 @@
                        .Build())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kHidden)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .Build())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId2)
+                       .SetElementID(kWebContentsElementId2)
                        .Build())
           .Build();
 
@@ -306,7 +306,7 @@
   const GURL url2 = embedded_test_server()->GetURL(kDocumentWithTitle2URL);
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
 
   // Load the first page and make sure we wait for the page transition.
   util->LoadPage(url);
@@ -318,7 +318,7 @@
           .SetContext(browser()->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetMustRemainVisible(false)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
@@ -326,44 +326,41 @@
                              EXPECT_EQ(url, util->web_contents()->GetURL());
                              // Load the second page and wait for it to finish
                              // loading.
-                             util->set_page_identifier(
-                                 kWebContentsInteractionTestUtilTestId2);
+                             util->set_page_identifier(kWebContentsElementId2);
                              util->LoadPage(url2);
                            }))
                        .Build())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId2)
+                       .SetElementID(kWebContentsElementId2)
                        .SetMustRemainVisible(false)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
                              EXPECT_EQ(url2, util->web_contents()->GetURL());
                              EXPECT_TRUE(chrome::CanGoBack(browser()));
-                             util->set_page_identifier(
-                                 kWebContentsInteractionTestUtilTestId);
+                             util->set_page_identifier(kWebContentsElementId);
                              chrome::GoBack(browser(),
                                             WindowOpenDisposition::CURRENT_TAB);
                            }))
                        .Build())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetMustRemainVisible(false)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
                              EXPECT_EQ(url, util->web_contents()->GetURL());
                              EXPECT_TRUE(chrome::CanGoForward(browser()));
-                             util->set_page_identifier(
-                                 kWebContentsInteractionTestUtilTestId2);
+                             util->set_page_identifier(kWebContentsElementId2);
                              chrome::GoForward(
                                  browser(), WindowOpenDisposition::CURRENT_TAB);
                            }))
                        .Build())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId2)
+                       .SetElementID(kWebContentsElementId2)
                        .SetMustRemainVisible(false)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
@@ -381,7 +378,7 @@
   UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   auto sequence =
       ui::InteractionSequence::Builder()
           .SetCompletedCallback(completed.Get())
@@ -389,7 +386,7 @@
           .SetContext(browser()->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -406,7 +403,7 @@
   UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   auto sequence =
       ui::InteractionSequence::Builder()
           .SetCompletedCallback(completed.Get())
@@ -414,7 +411,7 @@
           .SetContext(browser()->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -434,7 +431,7 @@
   UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   constexpr char kPromiseScript[] =
       "() => new Promise((resolve) => setTimeout(resolve(123), 300))";
   auto sequence =
@@ -444,7 +441,7 @@
           .SetContext(browser()->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -463,7 +460,7 @@
   UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   auto sequence =
       ui::InteractionSequence::Builder()
           .SetCompletedCallback(completed.Get())
@@ -471,7 +468,7 @@
           .SetContext(browser()->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -487,7 +484,7 @@
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kCustomEvent,
                                 kInteractionTestUtilCustomEventType)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .Build())
           .Build();
 
@@ -500,7 +497,7 @@
   UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   auto sequence =
       ui::InteractionSequence::Builder()
           .SetCompletedCallback(completed.Get())
@@ -508,7 +505,7 @@
           .SetContext(browser()->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -530,7 +527,7 @@
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kCustomEvent,
                                 kInteractionTestUtilCustomEventType)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .Build())
           .Build();
 
@@ -543,7 +540,7 @@
   UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   auto sequence =
       ui::InteractionSequence::Builder()
           .SetCompletedCallback(completed.Get())
@@ -551,7 +548,7 @@
           .SetContext(browser()->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -576,7 +573,7 @@
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kCustomEvent,
                                 kInteractionTestUtilCustomEventType2)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .Build())
           .Build();
 
@@ -590,7 +587,7 @@
 
   constexpr base::TimeDelta kPollTime = base::Milliseconds(50);
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   auto sequence =
       ui::InteractionSequence::Builder()
           .SetCompletedCallback(completed.Get())
@@ -598,7 +595,7 @@
           .SetContext(browser()->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -618,7 +615,7 @@
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kCustomEvent,
                                 kInteractionTestUtilCustomEventType)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .Build())
           .Build();
 
@@ -631,7 +628,7 @@
   UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
 
   // Poll significantly faster than the value in the page is expected to
   // change; this allows us to verify that the value changes after a non-zero
@@ -674,7 +671,7 @@
           .SetContext(browser()->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -686,7 +683,7 @@
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kCustomEvent,
                                 kInteractionTestUtilCustomEventType)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -699,7 +696,7 @@
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kCustomEvent,
                                 kInteractionTestUtilCustomEventType)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -712,7 +709,7 @@
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kCustomEvent,
                                 kInteractionTestUtilCustomEventType)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -725,7 +722,7 @@
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kCustomEvent,
                                 kInteractionTestUtilCustomEventType)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -739,7 +736,7 @@
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kCustomEvent,
                                 kInteractionTestUtilCustomEventType)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -754,7 +751,7 @@
               ui::InteractionSequence::StepBuilder()
                   .SetType(ui::InteractionSequence::StepType::kCustomEvent,
                            kInteractionTestUtilCustomEventType)
-                  .SetElementID(kWebContentsInteractionTestUtilTestId)
+                  .SetElementID(kWebContentsElementId)
                   .SetStartCallback(base::BindLambdaForTesting(
                       [&](ui::InteractionSequence* sequence,
                           ui::TrackedElement* element) { check_elapsed(); }))
@@ -770,7 +767,7 @@
   UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   const GURL url = embedded_test_server()->GetURL(kDocumentWithLinksURL);
   util->LoadPage(url);
   const WebContentsInteractionTestUtil::DeepQuery kQuery = {"a#title1"};
@@ -782,7 +779,7 @@
           .SetContext(browser()->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -800,7 +797,7 @@
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kCustomEvent,
                                 kInteractionTestUtilCustomEventType)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -818,7 +815,7 @@
   UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   const GURL url = embedded_test_server()->GetURL(kDocumentWithLinksURL);
   util->LoadPage(url);
   const WebContentsInteractionTestUtil::DeepQuery kQuery = {"ul#foo"};
@@ -830,7 +827,7 @@
           .SetContext(browser()->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -859,7 +856,7 @@
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kCustomEvent,
                                 kInteractionTestUtilCustomEventType)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -877,7 +874,7 @@
   UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   const GURL url = embedded_test_server()->GetURL(kDocumentWithLinksURL);
   util->LoadPage(url);
   const WebContentsInteractionTestUtil::DeepQuery kQuery = {"ul#foo"};
@@ -889,7 +886,7 @@
           .SetContext(browser()->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -920,7 +917,7 @@
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kCustomEvent,
                                 kInteractionTestUtilCustomEventType2)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .Build())
           .Build();
 
@@ -933,7 +930,7 @@
   UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   const GURL url = embedded_test_server()->GetURL(kDocumentWithLinksURL);
   util->LoadPage(url);
   const WebContentsInteractionTestUtil::DeepQuery kQuery = {"a#title1"};
@@ -946,7 +943,7 @@
           .SetContext(browser()->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -966,7 +963,7 @@
               ui::InteractionSequence::StepBuilder()
                   .SetType(ui::InteractionSequence::StepType::kCustomEvent,
                            kInteractionTestUtilCustomEventType)
-                  .SetElementID(kWebContentsInteractionTestUtilTestId)
+                  .SetElementID(kWebContentsElementId)
                   .SetStartCallback(base::BindLambdaForTesting(
                       [&](ui::InteractionSequence* sequence,
                           ui::TrackedElement* element) {
@@ -986,7 +983,7 @@
   UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   const GURL url = embedded_test_server()->GetURL(kDocumentWithLinksURL);
   util->LoadPage(url);
   const WebContentsInteractionTestUtil::DeepQuery kQuery = {"h1#foo"};
@@ -999,7 +996,7 @@
           .SetContext(browser()->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -1037,7 +1034,7 @@
               ui::InteractionSequence::StepBuilder()
                   .SetType(ui::InteractionSequence::StepType::kCustomEvent,
                            kInteractionTestUtilCustomEventType)
-                  .SetElementID(kWebContentsInteractionTestUtilTestId)
+                  .SetElementID(kWebContentsElementId)
                   .SetStartCallback(base::BindLambdaForTesting(
                       [&](ui::InteractionSequence* sequence,
                           ui::TrackedElement* element) {
@@ -1056,7 +1053,7 @@
   UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   const GURL url = embedded_test_server()->GetURL(kDocumentWithLinksURL);
   util->LoadPage(url);
   const WebContentsInteractionTestUtil::DeepQuery kQuery = {"h1#foo"};
@@ -1069,7 +1066,7 @@
           .SetContext(browser()->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -1104,7 +1101,7 @@
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kCustomEvent,
                                 kInteractionTestUtilCustomEventType2)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .Build())
           .Build();
 
@@ -1117,7 +1114,7 @@
   UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   auto sequence =
       ui::InteractionSequence::Builder()
           .SetCompletedCallback(completed.Get())
@@ -1126,7 +1123,7 @@
           .AddStep(
               ui::InteractionSequence::StepBuilder()
                   .SetType(ui::InteractionSequence::StepType::kShown)
-                  .SetElementID(kWebContentsInteractionTestUtilTestId)
+                  .SetElementID(kWebContentsElementId)
                   .SetStartCallback(base::BindLambdaForTesting(
                       [&](ui::InteractionSequence* sequence,
                           ui::TrackedElement* element) {
@@ -1151,7 +1148,7 @@
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kCustomEvent,
                                 kInteractionTestUtilCustomEventType)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .Build())
           .Build();
 
@@ -1164,7 +1161,7 @@
   UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   const GURL url = embedded_test_server()->GetURL(kDocumentWithLinksURL);
   util->LoadPage(url);
   const WebContentsInteractionTestUtil::DeepQuery kQuery = {"a#title1"};
@@ -1177,7 +1174,7 @@
           .AddStep(
               ui::InteractionSequence::StepBuilder()
                   .SetType(ui::InteractionSequence::StepType::kShown)
-                  .SetElementID(kWebContentsInteractionTestUtilTestId)
+                  .SetElementID(kWebContentsElementId)
                   .SetStartCallback(base::BindLambdaForTesting(
                       [&](ui::InteractionSequence* sequence,
                           ui::TrackedElement* element) {
@@ -1211,7 +1208,7 @@
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kCustomEvent,
                                 kInteractionTestUtilCustomEventType)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .Build())
           .Build();
 
@@ -1226,7 +1223,7 @@
   const GURL url = embedded_test_server()->GetURL(kEmptyDocumentURL);
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   auto sequence =
       ui::InteractionSequence::Builder()
           .SetCompletedCallback(completed.Get())
@@ -1234,7 +1231,7 @@
           .SetContext(browser()->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -1245,11 +1242,11 @@
                        .Build())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kHidden)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .Build())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .Build())
           .Build();
 
@@ -1264,7 +1261,7 @@
   Browser* const other_browser = CreateBrowser(browser()->profile());
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   auto sequence =
       ui::InteractionSequence::Builder()
           .SetCompletedCallback(completed.Get())
@@ -1273,7 +1270,7 @@
           .AddStep(
               ui::InteractionSequence::StepBuilder()
                   .SetType(ui::InteractionSequence::StepType::kShown)
-                  .SetElementID(kWebContentsInteractionTestUtilTestId)
+                  .SetElementID(kWebContentsElementId)
                   .SetStartCallback(base::BindLambdaForTesting(
                       [&](ui::InteractionSequence* sequence,
                           ui::TrackedElement* element) {
@@ -1284,14 +1281,13 @@
                   .Build())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kHidden)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .Build())
           .Build();
 
   EXPECT_CALL_IN_SCOPE(completed, Run, sequence->RunSynchronouslyForTesting());
   EXPECT_THAT(ui::ElementTracker::GetElementTracker()
-                  ->GetAllMatchingElementsInAnyContext(
-                      kWebContentsInteractionTestUtilTestId),
+                  ->GetAllMatchingElementsInAnyContext(kWebContentsElementId),
               testing::IsEmpty());
 }
 
@@ -1301,7 +1297,7 @@
   UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   auto sequence =
       ui::InteractionSequence::Builder()
           .SetCompletedCallback(completed.Get())
@@ -1309,7 +1305,7 @@
           .SetContext(browser()->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -1318,7 +1314,7 @@
                        .Build())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kHidden)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .Build())
           .Build();
 
@@ -1330,7 +1326,7 @@
   const GURL url = embedded_test_server()->GetURL(kEmptyDocumentURL);
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   auto* const model = browser()->tab_strip_model();
   const int count = model->GetTabCount();
   const int index = model->active_index();
@@ -1344,7 +1340,7 @@
   const GURL url = embedded_test_server()->GetURL(kEmptyDocumentURL);
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   auto* const model = browser()->tab_strip_model();
   const int count = model->GetTabCount();
   const int index = model->active_index();
@@ -1361,10 +1357,9 @@
   const GURL url = embedded_test_server()->GetURL(kEmptyDocumentURL);
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   auto util2 = WebContentsInteractionTestUtil::ForNextTabInContext(
-      browser()->window()->GetElementContext(),
-      kWebContentsInteractionTestUtilTestId2);
+      browser()->window()->GetElementContext(), kWebContentsElementId2);
 
   auto sequence =
       ui::InteractionSequence::Builder()
@@ -1373,7 +1368,7 @@
           .SetContext(browser()->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -1382,7 +1377,7 @@
                        .Build())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId2)
+                       .SetElementID(kWebContentsElementId2)
                        .Build())
           .Build();
 
@@ -1399,9 +1394,9 @@
   Browser* const browser2 = CreateBrowser(browser()->profile());
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   auto util2 = WebContentsInteractionTestUtil::ForNextTabInBrowser(
-      browser2, kWebContentsInteractionTestUtilTestId2);
+      browser2, kWebContentsElementId2);
 
   auto sequence =
       ui::InteractionSequence::Builder()
@@ -1410,7 +1405,7 @@
           .SetContext(browser()->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -1436,7 +1431,7 @@
           .SetContext(browser2->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId2)
+                       .SetElementID(kWebContentsElementId2)
                        .Build())
           .Build();
 
@@ -1454,9 +1449,9 @@
   Browser* browser2 = nullptr;
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   auto util2 = WebContentsInteractionTestUtil::ForNextTabInAnyBrowser(
-      kWebContentsInteractionTestUtilTestId2);
+      kWebContentsElementId2);
 
   auto sequence =
       ui::InteractionSequence::Builder()
@@ -1465,7 +1460,7 @@
           .SetContext(browser()->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -1493,7 +1488,7 @@
           .SetContext(browser2->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId2)
+                       .SetElementID(kWebContentsElementId2)
                        .Build())
           .Build();
 
@@ -1509,9 +1504,9 @@
   const GURL url = embedded_test_server()->GetURL(kEmptyDocumentURL);
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   auto util2 = WebContentsInteractionTestUtil::ForNextTabInAnyBrowser(
-      kWebContentsInteractionTestUtilTestId2);
+      kWebContentsElementId2);
 
   auto sequence =
       ui::InteractionSequence::Builder()
@@ -1520,7 +1515,7 @@
           .SetContext(browser()->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -1529,7 +1524,7 @@
                        .Build())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId2)
+                       .SetElementID(kWebContentsElementId2)
                        .Build())
           .Build();
 
@@ -1545,14 +1540,14 @@
   Browser* const other_browser = CreateBrowser(browser()->profile());
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   auto util2 = WebContentsInteractionTestUtil::ForNextTabInAnyBrowser(
-      kWebContentsInteractionTestUtilTestId2);
+      kWebContentsElementId2);
 
   auto get_element2 = [&]() {
-    const auto result = ui::ElementTracker::GetElementTracker()
-                            ->GetAllMatchingElementsInAnyContext(
-                                kWebContentsInteractionTestUtilTestId2);
+    const auto result =
+        ui::ElementTracker::GetElementTracker()
+            ->GetAllMatchingElementsInAnyContext(kWebContentsElementId2);
     return result.empty() ? nullptr : result.front();
   };
 
@@ -1564,7 +1559,7 @@
           .AddStep(
               ui::InteractionSequence::StepBuilder()
                   .SetType(ui::InteractionSequence::StepType::kShown)
-                  .SetElementID(kWebContentsInteractionTestUtilTestId)
+                  .SetElementID(kWebContentsElementId)
                   .SetStartCallback(base::BindLambdaForTesting(
                       [&](ui::InteractionSequence* sequence,
                           ui::TrackedElement* element) {
@@ -1576,7 +1571,7 @@
                   .Build())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kHidden)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .Build())
           .Build();
 
@@ -1594,14 +1589,14 @@
   Browser* const other_browser = CreateBrowser(browser()->profile());
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   auto util2 = WebContentsInteractionTestUtil::ForNextTabInBrowser(
-      other_browser, kWebContentsInteractionTestUtilTestId2);
+      other_browser, kWebContentsElementId2);
 
   auto get_element2 = [&]() {
-    const auto result = ui::ElementTracker::GetElementTracker()
-                            ->GetAllMatchingElementsInAnyContext(
-                                kWebContentsInteractionTestUtilTestId2);
+    const auto result =
+        ui::ElementTracker::GetElementTracker()
+            ->GetAllMatchingElementsInAnyContext(kWebContentsElementId2);
     return result.empty() ? nullptr : result.front();
   };
 
@@ -1613,7 +1608,7 @@
           .AddStep(
               ui::InteractionSequence::StepBuilder()
                   .SetType(ui::InteractionSequence::StepType::kShown)
-                  .SetElementID(kWebContentsInteractionTestUtilTestId)
+                  .SetElementID(kWebContentsElementId)
                   .SetStartCallback(base::BindLambdaForTesting(
                       [&](ui::InteractionSequence* sequence,
                           ui::TrackedElement* element) {
@@ -1625,7 +1620,7 @@
                   .Build())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kHidden)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .Build())
           .Build();
 
@@ -1645,7 +1640,7 @@
       "settings-ui", "settings-main#foo", "div#noSearchResults"};
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   util->LoadPage(GURL("chrome://settings"));
 
   auto sequence =
@@ -1655,7 +1650,7 @@
           .SetContext(browser()->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetMustRemainVisible(false)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
@@ -1680,7 +1675,7 @@
       "settings-ui", "settings-main#main", "div#noSearchResults"};
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   util->LoadPage(GURL("chrome://settings"));
 
   auto sequence =
@@ -1690,7 +1685,7 @@
           .SetContext(browser()->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -1714,7 +1709,7 @@
       "settings-ui", "settings-main#main", "not-exists-element"};
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   util->LoadPage(GURL("chrome://settings"));
 
   auto sequence =
@@ -1724,7 +1719,7 @@
           .SetContext(browser()->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -1757,7 +1752,7 @@
   const WebContentsInteractionTestUtil::DeepQuery kQuery6{"[id='not-present']"};
 
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   const GURL url = embedded_test_server()->GetURL(kDocumentWithLinksURL);
   util->LoadPage(url);
 
@@ -1768,7 +1763,7 @@
           .SetContext(browser()->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -1805,7 +1800,7 @@
 
   const WebContentsInteractionTestUtil::DeepQuery kQuery{"#ref"};
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   const GURL url = embedded_test_server()->GetURL(kDocumentWithLinksURL);
   util->LoadPage(url);
 
@@ -1816,7 +1811,7 @@
           .SetContext(browser()->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -1845,7 +1840,7 @@
 
   const WebContentsInteractionTestUtil::DeepQuery kQuery{"#ref"};
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   const GURL url = embedded_test_server()->GetURL(kDocumentWithLinksURL);
   util->LoadPage(url);
 
@@ -1856,7 +1851,7 @@
           .SetContext(browser()->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -1876,7 +1871,7 @@
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kCustomEvent,
                                 kInteractionTestUtilCustomEventType)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .Build())
           .Build();
 
@@ -1891,7 +1886,7 @@
   const WebContentsInteractionTestUtil::DeepQuery kQuery1{"#ref"};
   const WebContentsInteractionTestUtil::DeepQuery kQuery2{"#ref", "p#pp"};
   auto util = WebContentsInteractionTestUtil::ForExistingTabInBrowser(
-      browser(), kWebContentsInteractionTestUtilTestId);
+      browser(), kWebContentsElementId);
   const GURL url = embedded_test_server()->GetURL(kDocumentWithLinksURL);
   util->LoadPage(url);
 
@@ -1902,7 +1897,7 @@
           .SetContext(browser()->window()->GetElementContext())
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .SetStartCallback(base::BindLambdaForTesting(
                            [&](ui::InteractionSequence* sequence,
                                ui::TrackedElement* element) {
@@ -1923,7 +1918,7 @@
           .AddStep(ui::InteractionSequence::StepBuilder()
                        .SetType(ui::InteractionSequence::StepType::kCustomEvent,
                                 kInteractionTestUtilCustomEventType)
-                       .SetElementID(kWebContentsInteractionTestUtilTestId)
+                       .SetElementID(kWebContentsElementId)
                        .Build())
           .Build();
 
@@ -1978,3 +1973,53 @@
 
   EXPECT_CALL_IN_SCOPE(completed, Run, sequence->RunSynchronouslyForTesting());
 }
+
+class WebContentsInteractionTestUtilInteractiveTest
+    : public InteractiveBrowserTest {
+ public:
+  WebContentsInteractionTestUtilInteractiveTest() = default;
+  ~WebContentsInteractionTestUtilInteractiveTest() override = default;
+
+  void SetUp() override {
+    set_open_about_blank_on_browser_launch(true);
+    ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
+    InteractiveBrowserTest::SetUp();
+  }
+
+  void SetUpOnMainThread() override {
+    InteractiveBrowserTest::SetUpOnMainThread();
+    embedded_test_server()->StartAcceptingConnections();
+  }
+
+  void TearDownOnMainThread() override {
+    EXPECT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
+    InteractiveBrowserTest::TearDownOnMainThread();
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(WebContentsInteractionTestUtilInteractiveTest,
+                       TrackWebContentsAcrossReplace) {
+  const GURL url1 = embedded_test_server()->GetURL(kDocumentWithLinksURL);
+  const GURL url2 = embedded_test_server()->GetURL(kEmptyDocumentURL);
+  InstrumentTab(browser(), kWebContentsElementId);
+  InstrumentNextTab(browser(), kWebContentsElementId2);
+  ASSERT_TRUE(
+      AddTabAtIndex(-1, url2, ui::PageTransition::PAGE_TRANSITION_LINK));
+  RunTestSequence(WaitForWebContentsReady(kWebContentsElementId),
+                  WaitForWebContentsReady(kWebContentsElementId2),
+                  NavigateWebContents(kWebContentsElementId, url1),
+                  SelectTab(kTabStripElementId, 1), FlushEvents(),
+                  // This has to be done on a fresh message loop.
+                  Do(base::BindLambdaForTesting([&]() {
+                    // Discard the first tab. This triggers a replacement.
+                    // Note that because the active tab cannot be discarded,
+                    // this line is guaranteed to discard the tab we want. (But
+                    // if it did not, the following steps would fail.)
+                    g_browser_process->GetTabManager()->DiscardTab(
+                        mojom::LifecycleUnitDiscardReason::EXTERNAL);
+                  })),
+                  WaitForHide(kWebContentsElementId), FlushEvents(),
+                  // This has to be done on a fresh message loop.
+                  SelectTab(kTabStripElementId, 0),
+                  WaitForShow(kWebContentsElementId));
+}
diff --git a/chrome/test/webapps/data/actions.md b/chrome/test/webapps/data/actions.md
index 6495399..bd58191 100644
--- a/chrome/test/webapps/data/actions.md
+++ b/chrome/test/webapps/data/actions.md
@@ -150,8 +150,8 @@
 | check_site_handles_file | Site, FileExtension |  | 118 | Implemented |  |  |
 | check_site_not_handles_file | Site, FileExtension |  | 122 | Implemented |  |  |
 | check_file_handling_dialog | IsShown |  | 119 | Not Implemented |  |  |
-| launch_file | FilesOptions |  | 120 | Not Implemented |  |  |
-| file_handling_dialog | AllowDenyOptions, AskAgainOptions |  | 121 | Not Implemented |  |  |
+| launch_file_expect_dialog | Site, FilesOptions, AllowDenyOptions, AskAgainOptions |  | 120 | Implemented |  |  |
+| launch_file_expect_no_dialog | Site, FilesOptions |  | 121 | Implemented |  |  |
 | check_files_loaded_in_site | Site, FilesOptions |  | 126 | Not Implemented | Check that the appropriate file contents have loaded in in PWA windows. |  |
 | add_file_handling_policy_approval | Site |  | 124 | Not Implemented |  |  |
 | remove_file_handling_policy_approval | Site |  | 125 | Not Implemented |  |  |
diff --git a/chrome/test/webapps/data/critical_user_journeys.md b/chrome/test/webapps/data/critical_user_journeys.md
index 37ea686..d160e5da 100644
--- a/chrome/test/webapps/data/critical_user_journeys.md
+++ b/chrome/test/webapps/data/critical_user_journeys.md
@@ -308,20 +308,20 @@
 | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
 | WMLC | install_or_shortcut(FileHandler) | check_site_handles_file(FileHandler, Foo) | check_site_handles_file(FileHandler, Bar) |
 | # Single open & multiple open behavior |
-| WMLC | install_or_shortcut(MinimalUi) | launch_file(OneFooFile) | check_file_handling_dialog(Shown) |
-| WMLC | install_or_shortcut(MinimalUi) | launch_file(OneFooFile) | file_handling_dialog(Allow, AskAgain) | check_pwa_window_created(MinimalUi, One) | check_files_loaded_in_site(MinimalUi, OneFooFile) |
-| WMLC | install_or_shortcut(MinimalUi) | launch_file(MultipleFooFiles) | check_file_handling_dialog(Shown)
-| WMLC | install_or_shortcut(MinimalUi) | launch_file(MultipleFooFiles) | file_handling_dialog(Allow, AskAgain) | check_pwa_window_created(MinimalUi, One) | check_files_loaded_in_site(MinimalUi, MultipleFooFiles) |
-| WMLC | install_or_shortcut(MinimalUi) | launch_file(OneBarFile) | check_file_handling_dialog(Shown) |
-| WMLC | install_or_shortcut(MinimalUi) | launch_file(OneBarFile) | file_handling_dialog(Allow, AskAgain) | check_pwa_window_created(MinimalUi, One) | check_files_loaded_in_site(MinimalUi, OneBarFile) |
-| WMLC | install_or_shortcut(MinimalUi) | launch_file(MultipleBarFiles) | check_file_handling_dialog(Shown) |
-| WMLC | install_or_shortcut(MinimalUi) | launch_file(MultipleBarFiles) | file_handling_dialog(Allow, AskAgain) | check_pwa_window_created(MinimalUi, Two) | check_files_loaded_in_site(MinimalUi, MultipleBarFiles) |
+| WMLC | install_or_shortcut(FileHandler) | launch_file_expect_dialog(FileHandler, OneFooFile, Allow, AskAgain) |
+| WMLC | install_or_shortcut(FileHandler) | launch_file_expect_dialog(FileHandler, OneFooFile, Allow, AskAgain) | check_pwa_window_created(FileHandler, One) | check_files_loaded_in_site(FileHandler, OneFooFile) |
+| WMLC | install_or_shortcut(FileHandler) | launch_file_expect_dialog(FileHandler, MultipleFooFiles, Allow, AskAgain) |
+| WMLC | install_or_shortcut(FileHandler) | launch_file_expect_dialog(FileHandler, MultipleFooFiles, Allow, AskAgain) | check_pwa_window_created(FileHandler, One) | check_files_loaded_in_site(FileHandler, MultipleFooFiles) |
+| WMLC | install_or_shortcut(FileHandler) | launch_file_expect_dialog(FileHandler, OneBarFile, Allow, AskAgain) |
+| WMLC | install_or_shortcut(FileHandler) | launch_file_expect_dialog(FileHandler, OneBarFile, Allow, AskAgain) | check_pwa_window_created(FileHandler, One) | check_files_loaded_in_site(FileHandler, OneBarFile) |
+| WMLC | install_or_shortcut(FileHandler) | launch_file_expect_dialog(FileHandler, MultipleBarFiles, Allow, AskAgain) |
+| WMLC | install_or_shortcut(FileHandler) | launch_file_expect_dialog(FileHandler, MultipleBarFiles, Allow, AskAgain) | check_pwa_window_created(FileHandler, Two) | check_files_loaded_in_site(FileHandler, MultipleBarFiles) |
 | # Dialog options |
-| WMLC | install_or_shortcut(MinimalUi) | launch_file(OneFooFile) | file_handling_dialog(Allow, Remember) | launch_file(OneFooFile) | check_file_handling_dialog(NotShown) | check_pwa_window_created(MinimalUi, One) |
-| WMLC | install_or_shortcut(MinimalUi) | launch_file(OneFooFile) | file_handling_dialog(Allow, AskAgain) | launch_file(OneFooFile) | check_file_handling_dialog(Shown) |
-| WMLC | install_or_shortcut(MinimalUi) | launch_file(OneFooFile) | file_handling_dialog(Deny, AskAgain) | check_window_not_created | check_site_handles_file(MinimalUi, Foo) | check_site_handles_file(MinimalUi, Bar) |
-| WMLC | install_or_shortcut(MinimalUi) | launch_file(OneFooFile) | file_handling_dialog(Deny, AskAgain) | launch_file(OneFooFile) | check_file_handling_dialog(Shown) |
-| WMLC | install_or_shortcut(MinimalUi) | launch_file(OneFooFile) | file_handling_dialog(Deny, Remember) | check_window_not_created | check_site_not_handles_file(MinimalUi, Foo) | check_site_not_handles_file(MinimalUi, Bar) |
+| WMLC | install_or_shortcut(FileHandler) | launch_file_expect_dialog(FileHandler, OneFooFile, Allow, Remember) | launch_file_expect_no_dialog(FileHandler, OneFooFile) | check_pwa_window_created(FileHandler, One) |
+| WMLC | install_or_shortcut(FileHandler) | launch_file_expect_dialog(FileHandler, OneFooFile, Allow, AskAgain) | launch_file_expect_dialog(FileHandler, OneFooFile, Allow, AskAgain) |
+| WMLC | install_or_shortcut(FileHandler) | launch_file_expect_dialog(FileHandler, OneFooFile, Deny, AskAgain) | check_window_not_created | check_site_handles_file(FileHandler, Foo) | check_site_handles_file(FileHandler, Bar) |
+| WMLC | install_or_shortcut(FileHandler) | launch_file_expect_dialog(FileHandler, OneFooFile, Deny, AskAgain) | launch_file_expect_dialog(FileHandler, OneFooFile, Allow, AskAgain) |
+| WMLC | install_or_shortcut(FileHandler) | launch_file_expect_dialog(FileHandler, OneFooFile, Deny, Remember) | check_window_not_created | check_site_not_handles_file(FileHandler, Foo) | check_site_not_handles_file(FileHandler, Bar) |
 | # Policy approval |
-| WMLC | install_or_shortcut(MinimalUi) | add_file_handling_policy_approval(MinimalUi) | launch_file(OneFooFile) | check_file_handling_dialog(NotShown) | check_pwa_window_created(MinimalUi, One) |
-| WMLC | install_or_shortcut(MinimalUi) | add_file_handling_policy_approval(MinimalUi) | remove_file_handling_policy_approval(MinimalUi) | launch_file(OneFooFile) | check_file_handling_dialog(Shown) |
+| WMLC | install_or_shortcut(FileHandler) | add_file_handling_policy_approval(FileHandler) | launch_file_expect_no_dialog(FileHandler, OneFooFile) | check_pwa_window_created(FileHandler, One) |
+| WMLC | install_or_shortcut(FileHandler) | add_file_handling_policy_approval(FileHandler) | remove_file_handling_policy_approval(FileHandler) | launch_file_expect_dialog(FileHandler, OneFooFile, Allow, AskAgain) |
diff --git a/chrome/updater/net/network_fetcher_linux.cc b/chrome/updater/net/network_fetcher_linux.cc
index a733556d..d3b7b4cb 100644
--- a/chrome/updater/net/network_fetcher_linux.cc
+++ b/chrome/updater/net/network_fetcher_linux.cc
@@ -214,6 +214,14 @@
 
   void OnTransferInfo(curl_off_t total, curl_off_t current);
 
+  // Helper function to find the value of a header or return an empty string.
+  static std::string GetHeaderValue(
+      const base::flat_map<std::string, std::string>& response_headers,
+      const std::string& header) {
+    const std::string lower = base::ToLowerASCII(header);
+    return response_headers.contains(lower) ? response_headers.at(lower) : "";
+  }
+
   // Sequence to perform blocking IO.
   scoped_refptr<base::SequencedTaskRunner> io_sequence_ =
       base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()});
@@ -327,16 +335,11 @@
             << " (CURLcode " << result << ")";
   }
 
-  int x_retry_after = -1;
-  if (response_headers.contains(
-          update_client::NetworkFetcher::kHeaderXRetryAfter)) {
-    if (!base::StringToInt(
-            response_headers.at(
-                update_client::NetworkFetcher::kHeaderXRetryAfter),
-            &x_retry_after)) {
-      x_retry_after = -1;
-    }
-  } else {
+  int x_retry_after;
+  if (!base::StringToInt(
+          GetHeaderValue(response_headers,
+                         update_client::NetworkFetcher::kHeaderXRetryAfter),
+          &x_retry_after)) {
     x_retry_after = -1;
   }
 
@@ -345,14 +348,10 @@
       base::BindOnce(
           std::move(post_request_complete_callback), std::move(response_body),
           CURLE_OK,
-          response_headers.contains(update_client::NetworkFetcher::kHeaderEtag)
-              ? response_headers.at(update_client::NetworkFetcher::kHeaderEtag)
-              : "",
-          response_headers.contains(
-              update_client::NetworkFetcher::kHeaderXCupServerProof)
-              ? response_headers.at(
-                    update_client::NetworkFetcher::kHeaderXCupServerProof)
-              : "",
+          GetHeaderValue(response_headers,
+                         update_client::NetworkFetcher::kHeaderEtag),
+          GetHeaderValue(response_headers,
+                         update_client::NetworkFetcher::kHeaderXCupServerProof),
           x_retry_after));
 
   curl_functions_->slist_free_all(headers);
@@ -494,8 +493,9 @@
     base::TrimWhitespaceASCII(key, base::TRIM_ALL, &key);
     base::TrimWhitespaceASCII(value, base::TRIM_ALL, &value);
 
+    // HTTP headers are case insensitive. For simplicity, always use lower-case.
     if (!key.empty() && !value.empty())
-      headers->insert_or_assign(key, value);
+      headers->insert_or_assign(base::ToLowerASCII(key), value);
   }
   return buf_size.ValueOrDefault(0);
 }
diff --git a/chrome/updater/net/network_unittest.cc b/chrome/updater/net/network_unittest.cc
index 9f1450a..0af9a2ce 100644
--- a/chrome/updater/net/network_unittest.cc
+++ b/chrome/updater/net/network_unittest.cc
@@ -94,9 +94,9 @@
     if (request.method == net::test_server::HttpMethod::METHOD_POST) {
       // Echo the posted data back.
       http_response->set_content(request.content);
-      http_response->AddCustomHeader("X-Retry-After", "67");
-      http_response->AddCustomHeader("ETag", "Wfhw789h");
-      http_response->AddCustomHeader("X-Cup-Server-Proof", "server-proof");
+      http_response->AddCustomHeader("x-retry-after", "67");
+      http_response->AddCustomHeader("etag", "Wfhw789h");
+      http_response->AddCustomHeader("x-cup-server-proof", "server-proof");
     } else if (request.method == net::test_server::HttpMethod::METHOD_GET) {
       http_response->set_content("hello");
       http_response->set_content_type("application/octet-stream");
diff --git a/chrome/updater/test/integration_tests.cc b/chrome/updater/test/integration_tests.cc
index 0edb35c6..929eb18 100644
--- a/chrome/updater/test/integration_tests.cc
+++ b/chrome/updater/test/integration_tests.cc
@@ -96,6 +96,7 @@
                          true,    // enable_thread_id
                          true,    // enable_timestamp
                          false);  // enable_tickcount
+    CleanProcesses();
     EXPECT_TRUE(WaitForUpdaterExit());
     Clean();
     ExpectClean();
diff --git a/chromecast/cast_core/runtime/browser/runtime_application_base.cc b/chromecast/cast_core/runtime/browser/runtime_application_base.cc
index 91697ce5d..350ac0d 100644
--- a/chromecast/cast_core/runtime/browser/runtime_application_base.cc
+++ b/chromecast/cast_core/runtime/browser/runtime_application_base.cc
@@ -226,11 +226,10 @@
   }
   is_application_running_ = false;
 
-  if (delegate_->GetWebContents()) {
-    auto* cast_web_contents =
-        CastWebContents::FromWebContents(delegate_->GetWebContents());
-    DCHECK(cast_web_contents);
-    cast_web_contents->ClosePage();
+  auto* web_contents = delegate_->GetWebContents();
+  if (web_contents) {
+    web_contents->DispatchBeforeUnload(false /* auto_cancel */);
+    web_contents->ClosePage();
 
     // Check if window is still available as page might have been closed before.
     auto* window_controls = delegate_->GetContentWindowControls();
diff --git a/chromeos/ash/components/network/metrics/cellular_network_metrics_logger.cc b/chromeos/ash/components/network/metrics/cellular_network_metrics_logger.cc
index 75b3e70..b5e9eac8 100644
--- a/chromeos/ash/components/network/metrics/cellular_network_metrics_logger.cc
+++ b/chromeos/ash/components/network/metrics/cellular_network_metrics_logger.cc
@@ -28,6 +28,14 @@
 
 CellularNetworkMetricsLogger::~CellularNetworkMetricsLogger() = default;
 
+// static
+void CellularNetworkMetricsLogger::LogCreateCustomApnResult(
+    bool success,
+    chromeos::network_config::mojom::ApnPropertiesPtr apn) {
+  base::UmaHistogramBoolean(kCustomApnCreatedResultHistogram, success);
+  // TODO(b/162365553): Log metrics related to specific properties.
+}
+
 void CellularNetworkMetricsLogger::OnConnectionResult(
     const std::string& guid,
     const absl::optional<std::string>& shill_error) {
diff --git a/chromeos/ash/components/network/metrics/cellular_network_metrics_logger.h b/chromeos/ash/components/network/metrics/cellular_network_metrics_logger.h
index 6921044..a4a400b 100644
--- a/chromeos/ash/components/network/metrics/cellular_network_metrics_logger.h
+++ b/chromeos/ash/components/network/metrics/cellular_network_metrics_logger.h
@@ -9,6 +9,7 @@
 
 #include "base/scoped_observation.h"
 #include "chromeos/ash/components/network/metrics/connection_info_metrics_logger.h"
+#include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h"
 
 namespace ash {
 
@@ -18,6 +19,9 @@
 class COMPONENT_EXPORT(CHROMEOS_NETWORK) CellularNetworkMetricsLogger
     : public ConnectionInfoMetricsLogger::Observer {
  public:
+  static constexpr char kCustomApnCreatedResultHistogram[] =
+      "Network.Ash.Cellular.Apn.CreateCustomApn.Result";
+
   CellularNetworkMetricsLogger(
       NetworkStateHandler* network_state_handler,
       NetworkMetadataStore* network_metadata_store,
@@ -27,6 +31,11 @@
       delete;
   ~CellularNetworkMetricsLogger() override;
 
+  // Logs result from attempting to create a custom APN.
+  static void LogCreateCustomApnResult(
+      bool success,
+      chromeos::network_config::mojom::ApnPropertiesPtr apn);
+
  private:
   // ConnectionInfoMetricsLogger::Observer:
   void OnConnectionResult(
diff --git a/chromeos/components/cdm_factory_daemon/stable_cdm_context_impl.h b/chromeos/components/cdm_factory_daemon/stable_cdm_context_impl.h
index 3fcef02..3ff1893d 100644
--- a/chromeos/components/cdm_factory_daemon/stable_cdm_context_impl.h
+++ b/chromeos/components/cdm_factory_daemon/stable_cdm_context_impl.h
@@ -32,6 +32,8 @@
 
   ~StableCdmContextImpl() override;
 
+  const media::CdmContext* cdm_context() const { return cdm_context_; }
+
   // media::stable::mojom::StableCdmContext:
   void GetHwKeyData(std::unique_ptr<media::DecryptConfig> decrypt_config,
                     const std::vector<uint8_t>& hw_identifier,
diff --git a/chromeos/crosapi/mojom/desk_template.mojom b/chromeos/crosapi/mojom/desk_template.mojom
index e9dbdcc..2c98110 100644
--- a/chromeos/crosapi/mojom/desk_template.mojom
+++ b/chromeos/crosapi/mojom/desk_template.mojom
@@ -72,7 +72,7 @@
 
   // Returns state of the tab strip model in the browser window identified by
   // |window_unique_id|.
-  GetTabStripModelUrls@0(uint32 serial, string window_unique_id) => (
+  GetBrowserInformation@0(uint32 serial, string window_unique_id) => (
     uint32 serial, string window_unique_id, DeskTemplateState? state);
 
   // Gets the image of the favicon associated with `url`. Returns an empty
diff --git a/chromeos/services/network_config/cros_network_config.cc b/chromeos/services/network_config/cros_network_config.cc
index 1e188f8f..bc9e5c2 100644
--- a/chromeos/services/network_config/cros_network_config.cc
+++ b/chromeos/services/network_config/cros_network_config.cc
@@ -25,6 +25,7 @@
 #include "chromeos/ash/components/network/cellular_utils.h"
 #include "chromeos/ash/components/network/device_state.h"
 #include "chromeos/ash/components/network/managed_network_configuration_handler.h"
+#include "chromeos/ash/components/network/metrics/cellular_network_metrics_logger.h"
 #include "chromeos/ash/components/network/network_connection_handler.h"
 #include "chromeos/ash/components/network/network_device_handler.h"
 #include "chromeos/ash/components/network/network_event_log.h"
@@ -3604,6 +3605,8 @@
   if (!network || network->profile_path().empty()) {
     NET_LOG(ERROR) << "CreateCustomApn: Called with unconfigured network: "
                    << network_guid << ".";
+    ash::CellularNetworkMetricsLogger::LogCreateCustomApnResult(
+        /*success=*/false, std::move(apn));
     return;
   }
 
@@ -3632,7 +3635,7 @@
       network_guid, *network,
       UserApnListToOnc(network_guid, std::move(new_apns)),
       base::BindOnce(
-          [](const std::string& guid, bool success,
+          [](const std::string& guid, mojom::ApnPropertiesPtr apn, bool success,
              const std::string& message) {
             if (!success) {
               NET_LOG(ERROR)
@@ -3640,8 +3643,10 @@
                      "list in Shill for network: "
                   << guid << ": [" << message << ']';
             }
+            ash::CellularNetworkMetricsLogger::LogCreateCustomApnResult(
+                success, std::move(apn));
           },
-          network_guid));
+          network_guid, std::move(apn)));
 }
 
 void CrosNetworkConfig::RemoveCustomApn(const std::string& network_guid,
diff --git a/chromeos/services/network_config/cros_network_config_unittest.cc b/chromeos/services/network_config/cros_network_config_unittest.cc
index bd8c6b07..49c48b5a 100644
--- a/chromeos/services/network_config/cros_network_config_unittest.cc
+++ b/chromeos/services/network_config/cros_network_config_unittest.cc
@@ -15,6 +15,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
 #include "base/test/bind.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "chromeos/ash/components/dbus/shill/fake_shill_device_client.h"
@@ -22,6 +23,7 @@
 #include "chromeos/ash/components/network/cellular_metrics_logger.h"
 #include "chromeos/ash/components/network/fake_stub_cellular_networks_provider.h"
 #include "chromeos/ash/components/network/managed_network_configuration_handler.h"
+#include "chromeos/ash/components/network/metrics/cellular_network_metrics_logger.h"
 #include "chromeos/ash/components/network/network_cert_loader.h"
 #include "chromeos/ash/components/network/network_certificate_handler.h"
 #include "chromeos/ash/components/network/network_configuration_handler.h"
@@ -827,23 +829,24 @@
   }
 
   bool UserApnsInNetworkMetadataStoreMatch(
+      const std::string& guid,
       const std::vector<TestApnData*>& expected_apns) {
     if (const base::Value::List* custom_apns =
-            network_metadata_store()->GetCustomApnList(kCellularGuid)) {
+            network_metadata_store()->GetCustomApnList(guid)) {
       return UserApnsMatch(expected_apns, *custom_apns,
                            /*has_state_field=*/true,
                            /*is_password_masked=*/false);
     }
-    return false;
+    return expected_apns.empty();
   }
 
   bool UserApnsInCellularConfigMatch(
+      const std::string& guid,
       const std::vector<TestApnData*>& expected_apns,
       const TestNetworkConfigurationObserver& observer) {
-    const base::Value::Dict* user_settings =
-        observer.GetUserSettings(kCellularGuid);
+    const base::Value::Dict* user_settings = observer.GetUserSettings(guid);
     if (!user_settings) {
-      return false;
+      return expected_apns.empty();
     }
 
     const base::Value::Dict* cellular_settings =
@@ -864,10 +867,11 @@
   }
 
   bool UserApnsInManagedPropertiesMatch(
+      const std::string& guid,
       const std::vector<TestApnData*>& expected_apns) {
-    mojom::ManagedPropertiesPtr props = GetManagedProperties(kCellularGuid);
+    mojom::ManagedPropertiesPtr props = GetManagedProperties(guid);
     if (!props) {
-      return false;
+      return expected_apns.empty();
     }
     if (!props->type_properties->is_cellular()) {
       return false;
@@ -893,6 +897,16 @@
     return true;
   }
 
+  void AssertCreateCustomApnResultBucketCount(size_t num_success,
+                                              size_t num_failure) {
+    histogram_tester_.ExpectBucketCount(
+        ash::CellularNetworkMetricsLogger::kCustomApnCreatedResultHistogram,
+        true, num_success);
+    histogram_tester_.ExpectBucketCount(
+        ash::CellularNetworkMetricsLogger::kCustomApnCreatedResultHistogram,
+        false, num_failure);
+  }
+
   NetworkHandlerTestHelper* helper() { return helper_.get(); }
   CrosNetworkConfigTestObserver* observer() { return observer_.get(); }
   CrosNetworkConfig* cros_network_config() {
@@ -918,6 +932,7 @@
 
  private:
   base::test::SingleThreadTaskEnvironment task_environment_;
+  base::HistogramTester histogram_tester_;
   std::unique_ptr<NetworkHandlerTestHelper> helper_;
   TestingPrefServiceSimple local_state_;
   std::unique_ptr<CrosNetworkConfig> cros_network_config_;
@@ -1695,11 +1710,13 @@
   EXPECT_EQ(1u, network_config_observer.GetOnConfigurationModifiedCallCount());
   {
     std::vector<TestApnData*> expected_apns({&test_apn1});
-    EXPECT_TRUE(UserApnsInNetworkMetadataStoreMatch(expected_apns));
     EXPECT_TRUE(
-        UserApnsInCellularConfigMatch(expected_apns, network_config_observer));
-    EXPECT_TRUE(UserApnsInManagedPropertiesMatch(expected_apns));
+        UserApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
+    EXPECT_TRUE(UserApnsInCellularConfigMatch(kCellularGuid, expected_apns,
+                                              network_config_observer));
+    EXPECT_TRUE(UserApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
   }
+  AssertCreateCustomApnResultBucketCount(/*num_success=*/1, /*num_failure=*/0);
 }
 
 TEST_F(CrosNetworkConfigTest, CreateCustomApn_EmptyList) {
@@ -1713,7 +1730,7 @@
   network_metadata_store()->SetCustomApnList(kCellularGuid,
                                              base::Value::List());
 
-  EXPECT_TRUE(UserApnsInNetworkMetadataStoreMatch({}));
+  EXPECT_TRUE(UserApnsInNetworkMetadataStoreMatch(kCellularGuid, {}));
   EXPECT_EQ(0u, network_config_observer.GetOnConfigurationModifiedCallCount());
 
   // Call the API to create a new user APN
@@ -1733,11 +1750,13 @@
   EXPECT_EQ(1u, network_config_observer.GetOnConfigurationModifiedCallCount());
   {
     std::vector<TestApnData*> expected_apns({&test_apn1});
-    EXPECT_TRUE(UserApnsInNetworkMetadataStoreMatch(expected_apns));
     EXPECT_TRUE(
-        UserApnsInCellularConfigMatch(expected_apns, network_config_observer));
-    EXPECT_TRUE(UserApnsInManagedPropertiesMatch(expected_apns));
+        UserApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
+    EXPECT_TRUE(UserApnsInCellularConfigMatch(kCellularGuid, expected_apns,
+                                              network_config_observer));
+    EXPECT_TRUE(UserApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
   }
+  AssertCreateCustomApnResultBucketCount(/*num_success=*/1, /*num_failure=*/0);
 
   // Call the API to create a second user APN
   TestApnData test_apn2;
@@ -1756,11 +1775,50 @@
   EXPECT_EQ(2u, network_config_observer.GetOnConfigurationModifiedCallCount());
   {
     std::vector<TestApnData*> expected_apns({&test_apn2, &test_apn1});
-    EXPECT_TRUE(UserApnsInNetworkMetadataStoreMatch(expected_apns));
     EXPECT_TRUE(
-        UserApnsInCellularConfigMatch(expected_apns, network_config_observer));
-    EXPECT_TRUE(UserApnsInManagedPropertiesMatch(expected_apns));
+        UserApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
+    EXPECT_TRUE(UserApnsInCellularConfigMatch(kCellularGuid, expected_apns,
+                                              network_config_observer));
+    EXPECT_TRUE(UserApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
   }
+  AssertCreateCustomApnResultBucketCount(/*num_success=*/2, /*num_failure=*/0);
+}
+
+TEST_F(CrosNetworkConfigTest, CreateCustomApn_InvalidGuid) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(ash::features::kApnRevamp);
+
+  // Register an observer to capture values sent to Shill
+  TestNetworkConfigurationObserver network_config_observer(
+      network_configuration_handler());
+
+  const std::string guid = "invalid";
+  const base::Value::List* custom_apns =
+      network_metadata_store()->GetCustomApnList(guid);
+  ASSERT_FALSE(custom_apns);
+
+  TestApnData test_apn1;
+  test_apn1.access_point_name = kCellularTestApn1;
+  test_apn1.name = kCellularTestApnName1;
+  test_apn1.username = kCellularTestApnUsername1;
+  test_apn1.password = kCellularTestApnPassword1;
+  test_apn1.attach = kCellularTestApnAttach1;
+  test_apn1.mojo_apn_types = {mojom::ApnType::kDefault,
+                              mojom::ApnType::kAttach};
+  test_apn1.onc_apn_types = {::onc::cellular_apn::kApnTypeDefault,
+                             ::onc::cellular_apn::kApnTypeAttach};
+  CreateCustomApn(guid, test_apn1.AsMojoApn());
+
+  // Verify that no values were sent to Shill
+  EXPECT_EQ(0u, network_config_observer.GetOnConfigurationModifiedCallCount());
+  {
+    std::vector<TestApnData*> expected_apns;
+    EXPECT_TRUE(UserApnsInNetworkMetadataStoreMatch(guid, expected_apns));
+    EXPECT_TRUE(UserApnsInCellularConfigMatch(guid, expected_apns,
+                                              network_config_observer));
+    EXPECT_TRUE(UserApnsInManagedPropertiesMatch(guid, expected_apns));
+  }
+  AssertCreateCustomApnResultBucketCount(/*num_success=*/0, /*num_failure=*/1);
 }
 
 TEST_F(CrosNetworkConfigTest, RemoveCustomApn) {
@@ -1828,10 +1886,11 @@
             network_config_observer.GetOnConfigurationModifiedCallCount());
   {
     std::vector<TestApnData*> expected_apns({&test_apn1});
-    EXPECT_TRUE(UserApnsInNetworkMetadataStoreMatch(expected_apns));
     EXPECT_TRUE(
-        UserApnsInCellularConfigMatch(expected_apns, network_config_observer));
-    EXPECT_TRUE(UserApnsInManagedPropertiesMatch(expected_apns));
+        UserApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
+    EXPECT_TRUE(UserApnsInCellularConfigMatch(kCellularGuid, expected_apns,
+                                              network_config_observer));
+    EXPECT_TRUE(UserApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
   }
 
   // Try to remove an ID not found in the list, API should do nothing
@@ -1840,10 +1899,11 @@
             network_config_observer.GetOnConfigurationModifiedCallCount());
   {
     std::vector<TestApnData*> expected_apns({&test_apn1});
-    EXPECT_TRUE(UserApnsInNetworkMetadataStoreMatch(expected_apns));
     EXPECT_TRUE(
-        UserApnsInCellularConfigMatch(expected_apns, network_config_observer));
-    EXPECT_TRUE(UserApnsInManagedPropertiesMatch(expected_apns));
+        UserApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
+    EXPECT_TRUE(UserApnsInCellularConfigMatch(kCellularGuid, expected_apns,
+                                              network_config_observer));
+    EXPECT_TRUE(UserApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
   }
 
   // Remove the last test APN
@@ -1859,10 +1919,11 @@
             network_config_observer.GetOnConfigurationModifiedCallCount());
   {
     std::vector<TestApnData*> expected_apns;
-    EXPECT_TRUE(UserApnsInNetworkMetadataStoreMatch(expected_apns));
     EXPECT_TRUE(
-        UserApnsInCellularConfigMatch(expected_apns, network_config_observer));
-    EXPECT_TRUE(UserApnsInManagedPropertiesMatch(expected_apns));
+        UserApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
+    EXPECT_TRUE(UserApnsInCellularConfigMatch(kCellularGuid, expected_apns,
+                                              network_config_observer));
+    EXPECT_TRUE(UserApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
   }
 
   // Try to delete an APN when the custom APN list is empty, it should do
@@ -1872,10 +1933,11 @@
             network_config_observer.GetOnConfigurationModifiedCallCount());
   {
     std::vector<TestApnData*> expected_apns;
-    EXPECT_TRUE(UserApnsInNetworkMetadataStoreMatch(expected_apns));
     EXPECT_TRUE(
-        UserApnsInCellularConfigMatch(expected_apns, network_config_observer));
-    EXPECT_TRUE(UserApnsInManagedPropertiesMatch(expected_apns));
+        UserApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
+    EXPECT_TRUE(UserApnsInCellularConfigMatch(kCellularGuid, expected_apns,
+                                              network_config_observer));
+    EXPECT_TRUE(UserApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
   }
 }
 
diff --git a/components/BUILD.gn b/components/BUILD.gn
index 115fde1..3130a51 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -794,11 +794,6 @@
       "autofill/content/renderer/form_cache_browsertest.cc",
       "autofill/content/renderer/html_based_username_detector_browsertest.cc",
       "autofill/content/renderer/password_form_conversion_utils_browsertest.cc",
-      "autofill_assistant/browser/js_flow_executor_impl_browsertest.cc",
-      "autofill_assistant/browser/script_executor_browsertest.cc",
-      "autofill_assistant/browser/web/batch_element_checker_browsertest.cc",
-      "autofill_assistant/browser/web/semantic_element_finder_browsertest.cc",
-      "autofill_assistant/browser/web/web_controller_browsertest.cc",
       "browsing_data/content/browsing_data_helper_browsertest.h",
       "browsing_data/content/cache_storage_helper_browsertest.cc",
       "browsing_data/content/database_helper_browsertest.cc",
@@ -838,10 +833,6 @@
       "//components/autofill/content/renderer",
       "//components/autofill/content/renderer:test_support",
       "//components/autofill/core/browser",
-      "//components/autofill_assistant/browser",
-      "//components/autofill_assistant/browser:browser_test_support",
-      "//components/autofill_assistant/browser:test_support",
-      "//components/autofill_assistant/content/renderer:browser_tests",
       "//components/browsing_data/content",
       "//components/content_capture/browser:browser_tests",
       "//components/content_settings/core/common",
@@ -910,10 +901,39 @@
 
     if (is_android) {
       sources += [
+        "autofill_assistant/browser/base_browsertest.cc",
+        "autofill_assistant/browser/base_browsertest.h",
+        "autofill_assistant/browser/fake_script_executor_delegate.cc",
+        "autofill_assistant/browser/fake_script_executor_delegate.h",
+        "autofill_assistant/browser/fake_script_executor_ui_delegate.cc",
+        "autofill_assistant/browser/fake_script_executor_ui_delegate.h",
+        "autofill_assistant/browser/js_flow_executor_impl_browsertest.cc",
+        "autofill_assistant/browser/mock_script_executor_delegate.cc",
+        "autofill_assistant/browser/mock_script_executor_delegate.h",
+        "autofill_assistant/browser/script_executor_browsertest.cc",
+        "autofill_assistant/browser/service/mock_service.cc",
+        "autofill_assistant/browser/service/mock_service.h",
+        "autofill_assistant/browser/web/batch_element_checker_browsertest.cc",
+        "autofill_assistant/browser/web/mock_autofill_assistant_agent.cc",
+        "autofill_assistant/browser/web/mock_autofill_assistant_agent.h",
+        "autofill_assistant/browser/web/mock_web_controller.cc",
+        "autofill_assistant/browser/web/mock_web_controller.h",
+        "autofill_assistant/browser/web/semantic_element_finder_browsertest.cc",
+        "autofill_assistant/browser/web/web_controller_browsertest.cc",
         "browser_ui/client_certificate/android/ssl_client_certificate_request_browsertest.cc",
         "test/android/browsertests_apk/components_browser_tests_jni_onload.cc",
       ]
       deps += [
+        "//components/autofill_assistant/browser",
+        "//components/autofill_assistant/browser:proto",
+        "//components/autofill_assistant/browser/devtools:devtools",
+        "//components/autofill_assistant/browser/devtools:gen_devtools_client_api",
+        "//components/autofill_assistant/browser/public:password_change",
+        "//components/autofill_assistant/browser/public:public",
+        "//components/autofill_assistant/content/common",
+        "//components/autofill_assistant/content/common:mojo_interfaces",
+        "//components/autofill_assistant/content/renderer:browser_tests",
+        "//components/autofill_assistant/core/public:public",
         "//components/browser_ui/client_certificate/android",
         "//components/browser_ui/client_certificate/android:java",
         "//components/download/internal/common:internal_java",
diff --git a/components/attribution_reporting/test_utils.h b/components/attribution_reporting/test_utils.h
index 358a674b..43bddada 100644
--- a/components/attribution_reporting/test_utils.h
+++ b/components/attribution_reporting/test_utils.h
@@ -20,10 +20,10 @@
 class FilterData;
 class Filters;
 class SuitableOrigin;
-class TriggerRegistration;
 
 struct EventTriggerData;
 struct SourceRegistration;
+struct TriggerRegistration;
 
 bool operator==(const AggregationKeys&, const AggregationKeys&);
 
diff --git a/components/attribution_reporting/trigger_registration.h b/components/attribution_reporting/trigger_registration.h
index 9a1c764..85e5fac 100644
--- a/components/attribution_reporting/trigger_registration.h
+++ b/components/attribution_reporting/trigger_registration.h
@@ -22,8 +22,7 @@
 
 namespace attribution_reporting {
 
-class COMPONENT_EXPORT(ATTRIBUTION_REPORTING) TriggerRegistration {
- public:
+struct COMPONENT_EXPORT(ATTRIBUTION_REPORTING) TriggerRegistration {
   static base::expected<TriggerRegistration, mojom::TriggerRegistrationError>
   Parse(base::Value::Dict, SuitableOrigin reporting_origin);
 
diff --git a/components/autofill/core/browser/form_data_importer.cc b/components/autofill/core/browser/form_data_importer.cc
index b6829bd..e4d9df63 100644
--- a/components/autofill/core/browser/form_data_importer.cc
+++ b/components/autofill/core/browser/form_data_importer.cc
@@ -277,7 +277,7 @@
   //   at most one import prompt is shown.
   // Reset `imported_credit_card_record_type_` every time we import data from
   // form no matter whether `ImportCreditCard()` is called or not.
-  imported_credit_card_record_type_ = ImportedCreditCardRecordType::NO_CARD;
+  imported_credit_card_record_type_ = ImportedCreditCardRecordType::kNoCard;
   if (payment_methods_autofill_enabled) {
     imported_form_data.credit_card_import_candidate =
         ImportCreditCard(submitted_form);
@@ -727,7 +727,7 @@
 #endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
   // If no card was successfully imported from the form, return.
   if (imported_credit_card_record_type_ ==
-      ImportedCreditCardRecordType::NO_CARD) {
+      ImportedCreditCardRecordType::kNoCard) {
     return false;
   }
   // Do not offer upload save for google domain.
@@ -778,19 +778,19 @@
     // allowing upload, |credit_card_save_manager_| is tasked with deciding if
     // we should fall back to local save or not.
     DCHECK(imported_credit_card_record_type_ ==
-               ImportedCreditCardRecordType::LOCAL_CARD ||
+               ImportedCreditCardRecordType::kLocalCard ||
            imported_credit_card_record_type_ ==
-               ImportedCreditCardRecordType::NEW_CARD);
+               ImportedCreditCardRecordType::kNewCard);
     credit_card_save_manager_->AttemptToOfferCardUploadSave(
         submitted_form, from_dynamic_change_form_, has_non_focusable_field_,
         *credit_card_import_candidate,
         /*uploading_local_card=*/imported_credit_card_record_type_ ==
-            ImportedCreditCardRecordType::LOCAL_CARD);
+            ImportedCreditCardRecordType::kLocalCard);
     return true;
   };
   // If upload save is not allowed, new cards should be saved locally.
   DCHECK(imported_credit_card_record_type_ ==
-         ImportedCreditCardRecordType::NEW_CARD);
+         ImportedCreditCardRecordType::kNewCard);
   if (credit_card_save_manager_->AttemptToOfferCardLocalSave(
           from_dynamic_change_form_, has_non_focusable_field_,
           *credit_card_import_candidate)) {
@@ -833,9 +833,9 @@
   if (fetched_virtual_cards_.contains(candidate.LastFourDigits()))
     return absl::nullopt;
 
-  // Can import one valid card per form. Start by treating it as NEW_CARD, but
+  // Can import one valid card per form. Start by treating it as kNewCard, but
   // overwrite this type if we discover it is already a local or server card.
-  imported_credit_card_record_type_ = ImportedCreditCardRecordType::NEW_CARD;
+  imported_credit_card_record_type_ = ImportedCreditCardRecordType::kNewCard;
 
   // Attempt to merge with an existing local credit card without presenting a
   // prompt.
@@ -847,7 +847,7 @@
     if (maybe_updated_card.UpdateFromImportedCard(candidate, app_locale_)) {
       personal_data_manager_->UpdateCreditCard(maybe_updated_card);
       imported_credit_card_record_type_ =
-          ImportedCreditCardRecordType::LOCAL_CARD;
+          ImportedCreditCardRecordType::kLocalCard;
       if (!maybe_updated_card.nickname().empty()) {
         // The nickname may be shown in the upload save bubble.
         candidate.SetNickname(maybe_updated_card.nickname());
@@ -879,7 +879,7 @@
   if (candidate.expiration_month() == 0 || candidate.expiration_year() == 0)
     return absl::nullopt;
 
-  imported_credit_card_record_type_ = ImportedCreditCardRecordType::SERVER_CARD;
+  imported_credit_card_record_type_ = ImportedCreditCardRecordType::kServerCard;
 
   if (candidate.expiration_month() == server_card->expiration_month() &&
       candidate.expiration_year() == server_card->expiration_year()) {
@@ -1016,7 +1016,7 @@
 
   // We do not want to offer upload save or local card save for server cards.
   if (imported_credit_card_record_type_ ==
-      ImportedCreditCardRecordType::SERVER_CARD) {
+      ImportedCreditCardRecordType::kServerCard) {
     return false;
   }
 
@@ -1025,7 +1025,7 @@
   // local card save as it is already saved as a local card.
   if (!is_credit_card_upload_enabled &&
       imported_credit_card_record_type_ ==
-          ImportedCreditCardRecordType::LOCAL_CARD) {
+          ImportedCreditCardRecordType::kLocalCard) {
     return false;
   }
 
diff --git a/components/autofill/core/browser/form_data_importer.h b/components/autofill/core/browser/form_data_importer.h
index ce5cc70..b34b9d0a 100644
--- a/components/autofill/core/browser/form_data_importer.h
+++ b/components/autofill/core/browser/form_data_importer.h
@@ -36,19 +36,17 @@
 // Owned by `ChromeAutofillClient`.
 class FormDataImporter : public PersonalDataManagerObserver {
  public:
-  // TODO(crbug.com/1356057): Rename below RecordType into kNoCard, kLocalCard.
-  //                          See new naming convention from go/c-style.
   // Record type of the credit card imported from the form, if one exists.
   enum ImportedCreditCardRecordType {
     // No card was successfully imported from the form.
-    NO_CARD,
+    kNoCard,
     // The imported card is already stored locally on the device.
-    LOCAL_CARD,
+    kLocalCard,
     // The imported card is already known to be a server card (either masked or
     // unmasked).
-    SERVER_CARD,
+    kServerCard,
     // The imported card is not currently stored with the browser.
-    NEW_CARD,
+    kNewCard,
   };
 
   // The parameters should outlive the FormDataImporter.
@@ -338,7 +336,7 @@
   // It will be used to determine whether to offer upload save or card
   // migration. Will be passed to `credit_card_save_manager_` for metrics.
   ImportedCreditCardRecordType imported_credit_card_record_type_ =
-      ImportedCreditCardRecordType::NO_CARD;
+      ImportedCreditCardRecordType::kNoCard;
 
   std::string app_locale_;
 
diff --git a/components/autofill/core/browser/form_data_importer_unittest.cc b/components/autofill/core/browser/form_data_importer_unittest.cc
index 3c5cd19..50236863 100644
--- a/components/autofill/core/browser/form_data_importer_unittest.cc
+++ b/components/autofill/core/browser/form_data_importer_unittest.cc
@@ -2800,11 +2800,11 @@
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
   ASSERT_TRUE(imported_data.credit_card_import_candidate);
-  // |imported_credit_card_record_type_| should be LOCAL_CARD because upload was
+  // |imported_credit_card_record_type_| should be kLocalCard because upload was
   // offered and the card is a local card already on the device.
   ASSERT_TRUE(
       form_data_importer().imported_credit_card_record_type_for_testing() ==
-      FormDataImporter::ImportedCreditCardRecordType::LOCAL_CARD);
+      FormDataImporter::ImportedCreditCardRecordType::kLocalCard);
 
   // Second form is filled with a new card so
   // `FormDataImporterTest::imported_credit_card_record_type_` should be reset.
@@ -2821,11 +2821,11 @@
       form_structure2, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
   ASSERT_TRUE(imported_data2.credit_card_import_candidate);
-  // |imported_credit_card_record_type_| should be NEW_CARD because the imported
+  // |imported_credit_card_record_type_| should be kNewCard because the imported
   // card is not already on the device.
   ASSERT_TRUE(
       form_data_importer().imported_credit_card_record_type_for_testing() ==
-      FormDataImporter::ImportedCreditCardRecordType::NEW_CARD);
+      FormDataImporter::ImportedCreditCardRecordType::kNewCard);
 
   // Third form is an address form and set `payment_methods_autofill_enabled` to
   // be false so that the ImportCreditCard won't be called.
@@ -2864,7 +2864,7 @@
   EXPECT_NE(0u, imported_data3.address_profile_import_candidates.size());
   ASSERT_TRUE(
       form_data_importer().imported_credit_card_record_type_for_testing() ==
-      FormDataImporter::ImportedCreditCardRecordType::NO_CARD);
+      FormDataImporter::ImportedCreditCardRecordType::kNoCard);
 }
 
 // Ensures that `FormDataImporterTest::imported_credit_card_record_type_` is set
@@ -2884,11 +2884,11 @@
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
   ASSERT_TRUE(imported_data.credit_card_import_candidate);
-  // |imported_credit_card_record_type_| should be NEW_CARD because the imported
+  // |imported_credit_card_record_type_| should be kNewCard because the imported
   // card is not already on the device.
   ASSERT_TRUE(
       form_data_importer().imported_credit_card_record_type_for_testing() ==
-      FormDataImporter::ImportedCreditCardRecordType::NEW_CARD);
+      FormDataImporter::ImportedCreditCardRecordType::kNewCard);
 }
 
 // Ensures that `imported_credit_card_record_type_` is set correctly.
@@ -2920,11 +2920,11 @@
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
   ASSERT_TRUE(imported_data.credit_card_import_candidate);
-  // |imported_credit_card_record_type_| should be LOCAL_CARD because upload was
+  // |imported_credit_card_record_type_| should be kLocalCard because upload was
   // offered and the card is a local card already on the device.
   ASSERT_TRUE(
       form_data_importer().imported_credit_card_record_type_for_testing() ==
-      FormDataImporter::ImportedCreditCardRecordType::LOCAL_CARD);
+      FormDataImporter::ImportedCreditCardRecordType::kLocalCard);
 }
 
 // Ensures that `FormDataImporterTest::imported_credit_card_record_type_` is set
@@ -2960,7 +2960,7 @@
   // |imported_credit_card_record_type_| should be SERVER_CARD.
   ASSERT_TRUE(
       form_data_importer().imported_credit_card_record_type_for_testing() ==
-      FormDataImporter::ImportedCreditCardRecordType::SERVER_CARD);
+      FormDataImporter::ImportedCreditCardRecordType::kServerCard);
 }
 
 // Ensures that `FormDataImporterTest::imported_credit_card_record_type_` is set
@@ -2996,7 +2996,7 @@
   // |imported_credit_card_record_type_| should be SERVER_CARD.
   ASSERT_TRUE(
       form_data_importer().imported_credit_card_record_type_for_testing() ==
-      FormDataImporter::ImportedCreditCardRecordType::SERVER_CARD);
+      FormDataImporter::ImportedCreditCardRecordType::kServerCard);
 }
 
 // Ensures that `FormDataImporterTest::imported_credit_card_record_type_` is set
@@ -3016,11 +3016,11 @@
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
   ASSERT_FALSE(imported_data.credit_card_import_candidate);
-  // |imported_credit_card_record_type_| should be NO_CARD because no valid card
+  // |imported_credit_card_record_type_| should be kNoCard because no valid card
   // was successfully imported from the form.
   ASSERT_TRUE(
       form_data_importer().imported_credit_card_record_type_for_testing() ==
-      FormDataImporter::ImportedCreditCardRecordType::NO_CARD);
+      FormDataImporter::ImportedCreditCardRecordType::kNoCard);
 }
 
 // Ensures that `FormDataImporterTest::imported_credit_card_record_type_` is set
@@ -3040,11 +3040,11 @@
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
   ASSERT_FALSE(imported_data.credit_card_import_candidate);
-  // |imported_credit_card_record_type_| should be NO_CARD because the card
+  // |imported_credit_card_record_type_| should be kNoCard because the card
   // imported from the form was a virtual card.
   ASSERT_TRUE(
       form_data_importer().imported_credit_card_record_type_for_testing() ==
-      FormDataImporter::ImportedCreditCardRecordType::NO_CARD);
+      FormDataImporter::ImportedCreditCardRecordType::kNoCard);
 }
 
 // Ensures that `FormDataImporterTest::imported_credit_card_record_type_` is set
@@ -3065,11 +3065,11 @@
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
   ASSERT_TRUE(imported_data.credit_card_import_candidate);
-  // |imported_credit_card_record_type_| should be NEW_CARD because card was
+  // |imported_credit_card_record_type_| should be kNewCard because card was
   // successfully imported from the form via the expiration date fix flow.
   ASSERT_TRUE(
       form_data_importer().imported_credit_card_record_type_for_testing() ==
-      FormDataImporter::ImportedCreditCardRecordType::NEW_CARD);
+      FormDataImporter::ImportedCreditCardRecordType::kNewCard);
 }
 
 // Ensures that `FormDataImporterTest::imported_credit_card_record_type_` is set
@@ -3106,11 +3106,11 @@
       form_structure, /*profile_autofill_enabled=*/true,
       /*payment_methods_autofill_enabled=*/true);
   ASSERT_FALSE(imported_data.credit_card_import_candidate);
-  // |imported_credit_card_record_type_| should be NO_CARD because the form
+  // |imported_credit_card_record_type_| should be kNoCard because the form
   // doesn't have credit card section.
   ASSERT_TRUE(
       form_data_importer().imported_credit_card_record_type_for_testing() ==
-      FormDataImporter::ImportedCreditCardRecordType::NO_CARD);
+      FormDataImporter::ImportedCreditCardRecordType::kNoCard);
 }
 
 // ImportFormData tests (both addresses and credit cards).
@@ -4514,11 +4514,11 @@
       ConstructDefaultCreditCardFormStructure();
 
   // `form_data_importer()`'s `imported_credit_card_record_type_` is set to
-  // LOCAL_CARD because we need to make sure we do not return early in the
-  // NEW_CARD case, and LOCAL_CARD with upstream enabled but empty
+  // kLocalCard because we need to make sure we do not return early in the
+  // kNewCard case, and kLocalCard with upstream enabled but empty
   // |imported_credit_card| is the most likely scenario for a crash.
   form_data_importer().set_imported_credit_card_record_type_for_testing(
-      FormDataImporter::ImportedCreditCardRecordType::LOCAL_CARD);
+      FormDataImporter::ImportedCreditCardRecordType::kLocalCard);
 
   // We need a sync service so that
   // LocalCardMigrationManager::ShouldOfferLocalCardMigration() does not crash.
@@ -4547,7 +4547,7 @@
       ConstructDefaultCreditCardFormStructure();
 
   form_data_importer().set_imported_credit_card_record_type_for_testing(
-      FormDataImporter::ImportedCreditCardRecordType::SERVER_CARD);
+      FormDataImporter::ImportedCreditCardRecordType::kServerCard);
   form_data_importer().SetFetchedCardInstrumentId(2222);
 
   // We need a sync service so that
@@ -4591,7 +4591,7 @@
 
   // Should not offer save for local cards if upstream is not enabled.
   form_data_importer().set_imported_credit_card_record_type_for_testing(
-      FormDataImporter::ImportedCreditCardRecordType::LOCAL_CARD);
+      FormDataImporter::ImportedCreditCardRecordType::kLocalCard);
   EXPECT_FALSE(form_data_importer().ShouldOfferUploadCardOrLocalCardSave(
       credit_card_import_candidate,
       /*is_credit_card_upload_enabled=*/false));
@@ -4603,7 +4603,7 @@
 
   // Should not offer save for server cards.
   form_data_importer().set_imported_credit_card_record_type_for_testing(
-      FormDataImporter::ImportedCreditCardRecordType::SERVER_CARD);
+      FormDataImporter::ImportedCreditCardRecordType::kServerCard);
   EXPECT_FALSE(form_data_importer().ShouldOfferUploadCardOrLocalCardSave(
       credit_card_import_candidate,
       /*is_credit_card_upload_enabled=*/true));
@@ -4611,7 +4611,7 @@
   // Should always offer save for new cards; upload save if it is enabled, local
   // save otherwise.
   form_data_importer().set_imported_credit_card_record_type_for_testing(
-      FormDataImporter::ImportedCreditCardRecordType::NEW_CARD);
+      FormDataImporter::ImportedCreditCardRecordType::kNewCard);
   EXPECT_TRUE(form_data_importer().ShouldOfferUploadCardOrLocalCardSave(
       credit_card_import_candidate,
       /*is_credit_card_upload_enabled=*/true));
diff --git a/components/autofill/core/browser/payments/local_card_migration_manager.cc b/components/autofill/core/browser/payments/local_card_migration_manager.cc
index d856699..544909fe 100644
--- a/components/autofill/core/browser/payments/local_card_migration_manager.cc
+++ b/components/autofill/core/browser/payments/local_card_migration_manager.cc
@@ -58,11 +58,11 @@
   imported_credit_card_record_type_ = imported_credit_card_record_type;
   // Must be an existing card. New cards always get Upstream or local save.
   switch (imported_credit_card_record_type_) {
-    case FormDataImporter::ImportedCreditCardRecordType::LOCAL_CARD:
+    case FormDataImporter::ImportedCreditCardRecordType::kLocalCard:
       local_card_migration_origin_ =
           autofill_metrics::LocalCardMigrationOrigin::UseOfLocalCard;
       break;
-    case FormDataImporter::ImportedCreditCardRecordType::SERVER_CARD:
+    case FormDataImporter::ImportedCreditCardRecordType::kServerCard:
       local_card_migration_origin_ =
           autofill_metrics::LocalCardMigrationOrigin::UseOfServerCard;
       break;
@@ -83,11 +83,11 @@
   // Don't show the prompt if max strike count was reached.
   if (GetLocalCardMigrationStrikeDatabase()->ShouldBlockFeature()) {
     switch (imported_credit_card_record_type_) {
-      case FormDataImporter::ImportedCreditCardRecordType::LOCAL_CARD:
+      case FormDataImporter::ImportedCreditCardRecordType::kLocalCard:
         autofill_metrics::LogLocalCardMigrationNotOfferedDueToMaxStrikesMetric(
             AutofillMetrics::SaveTypeMetric::LOCAL);
         break;
-      case FormDataImporter::ImportedCreditCardRecordType::SERVER_CARD:
+      case FormDataImporter::ImportedCreditCardRecordType::kServerCard:
         autofill_metrics::LogLocalCardMigrationNotOfferedDueToMaxStrikesMetric(
             AutofillMetrics::SaveTypeMetric::SERVER);
         break;
@@ -106,14 +106,14 @@
   // was submitted with a server card, offer migration if ANY local cards can be
   // migrated.
   if ((imported_credit_card_record_type_ ==
-           FormDataImporter::ImportedCreditCardRecordType::LOCAL_CARD &&
+           FormDataImporter::ImportedCreditCardRecordType::kLocalCard &&
        migratable_credit_cards_.size() > 1) ||
       (imported_credit_card_record_type_ ==
-           FormDataImporter::ImportedCreditCardRecordType::SERVER_CARD &&
+           FormDataImporter::ImportedCreditCardRecordType::kServerCard &&
        !migratable_credit_cards_.empty())) {
     return true;
   } else if (imported_credit_card_record_type_ ==
-                 FormDataImporter::ImportedCreditCardRecordType::LOCAL_CARD &&
+                 FormDataImporter::ImportedCreditCardRecordType::kLocalCard &&
              migratable_credit_cards_.size() == 1) {
     autofill_metrics::LogLocalCardMigrationDecisionMetric(
         autofill_metrics::LocalCardMigrationDecisionMetric::
@@ -236,7 +236,7 @@
       // unsupported local card.
       if (!supported_card_bin_ranges.empty() &&
           imported_credit_card_record_type_ ==
-              FormDataImporter::ImportedCreditCardRecordType::LOCAL_CARD &&
+              FormDataImporter::ImportedCreditCardRecordType::kLocalCard &&
           imported_credit_card_number_.has_value() &&
           !payments::IsCreditCardNumberSupported(
               imported_credit_card_number_.value(),
diff --git a/components/autofill/core/browser/payments/virtual_card_enrollment_strike_database_unittest.cc b/components/autofill/core/browser/payments/virtual_card_enrollment_strike_database_unittest.cc
index 114454c5..935454f9 100644
--- a/components/autofill/core/browser/payments/virtual_card_enrollment_strike_database_unittest.cc
+++ b/components/autofill/core/browser/payments/virtual_card_enrollment_strike_database_unittest.cc
@@ -10,6 +10,8 @@
 #include "base/test/task_environment.h"
 #include "components/autofill/core/browser/payments/virtual_card_enrollment_strike_database.h"
 #include "components/autofill/core/browser/proto/strike_data.pb.h"
+#include "components/autofill/core/browser/test_autofill_clock.h"
+#include "components/autofill/core/common/autofill_clock.h"
 #include "components/leveldb_proto/public/proto_database_provider.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -53,20 +55,44 @@
 
 TEST_F(VirtualCardEnrollmentStrikeDatabaseTest, AddAndRemoveStrikes) {
   int max_strikes = strike_database_->GetMaxStrikesLimit();
+  DCHECK(strike_database_->GetRequiredDelaySinceLastStrike().has_value());
   std::string test_guid = "00000000-0000-0000-0000-000000000001";
+  TestAutofillClock test_autofill_clock(AutofillClock::Now());
 
   EXPECT_EQ(strike_database_->GetStrikes(test_guid), 0);
+  // Add one strike for the card.
   strike_database_->AddStrike(test_guid);
   EXPECT_EQ(strike_database_->GetStrikes(test_guid), 1);
+
+  // Verify at this moment, even though the strikes have not reached limit,
+  // feature should still be blocked since it is still within the enforced delay
+  // period.
+  EXPECT_TRUE(strike_database_->ShouldBlockFeature(test_guid));
+
+  // Advance time and verify feature should not be blocked.
+  test_autofill_clock.Advance(
+      strike_database_->GetRequiredDelaySinceLastStrike().value());
   EXPECT_FALSE(strike_database_->ShouldBlockFeature(test_guid));
 
+  // Add strikes to reach the limit.
   strike_database_->AddStrikes(max_strikes - 1, test_guid);
   EXPECT_EQ(strike_database_->GetStrikes(test_guid), max_strikes);
   EXPECT_EQ(strike_database_->GetMaxStrikesLimit(), max_strikes);
+
+  // Verify at this moment feature should be blocked.
   EXPECT_TRUE(strike_database_->ShouldBlockFeature(test_guid));
 
+  // Remove one strike.
   strike_database_->RemoveStrike(test_guid);
   EXPECT_EQ(strike_database_->GetStrikes(test_guid), max_strikes - 1);
+
+  // Verify feature should be blocked since it is within the enforced delay
+  // period.
+  EXPECT_TRUE(strike_database_->ShouldBlockFeature(test_guid));
+
+  // Advance time and verify feature should not be blocked.
+  test_autofill_clock.Advance(
+      strike_database_->GetRequiredDelaySinceLastStrike().value());
   EXPECT_FALSE(strike_database_->ShouldBlockFeature(test_guid));
 }
 
diff --git a/components/autofill/core/common/autofill_payments_features.cc b/components/autofill/core/common/autofill_payments_features.cc
index c4e6edb..39dcdc7a 100644
--- a/components/autofill/core/common/autofill_payments_features.cc
+++ b/components/autofill/core/common/autofill_payments_features.cc
@@ -162,7 +162,7 @@
 // added before Chrome attempts to show offer again.
 BASE_FEATURE(kAutofillEnforceDelaysInStrikeDatabase,
              "AutofillEnforceDelaysInStrikeDatabase",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 // When enabled, Autofill will attempt to fill IBAN (International Bank Account
 // Number) fields when data is available.
diff --git a/components/autofill_assistant/browser/BUILD.gn b/components/autofill_assistant/browser/BUILD.gn
index d81d203..4eb837c 100644
--- a/components/autofill_assistant/browser/BUILD.gn
+++ b/components/autofill_assistant/browser/BUILD.gn
@@ -414,7 +414,7 @@
   ]
 }
 
-static_library("test_support") {
+static_library("unit_test_support") {
   testonly = true
   sources = [
     "actions/mock_action_delegate.cc",
@@ -445,8 +445,6 @@
     "mock_execution_delegate.h",
     "mock_personal_data_manager.cc",
     "mock_personal_data_manager.h",
-    "mock_script_executor_delegate.cc",
-    "mock_script_executor_delegate.h",
     "mock_ui_controller_observer.cc",
     "mock_ui_controller_observer.h",
     "mock_user_model.cc",
@@ -457,51 +455,29 @@
     "service/mock_service.h",
     "web/fake_element_store.cc",
     "web/fake_element_store.h",
-    "web/mock_autofill_assistant_agent.cc",
-    "web/mock_autofill_assistant_agent.h",
     "web/mock_web_controller.cc",
     "web/mock_web_controller.h",
   ]
 
-  public_deps = [
+  deps = [
     ":browser",
     ":proto",
-    "devtools:devtools",
-    "devtools:gen_devtools_client_api",
-    "public",
-    "public:password_change",
     "//base",
     "//base/test:test_support",
     "//components/autofill/core/browser:test_support",
-    "//components/autofill_assistant/content/common",
-    "//components/autofill_assistant/content/common:mojo_interfaces",
+    "//components/autofill_assistant/browser/public:password_change",
+    "//components/autofill_assistant/browser/public:public",
     "//components/autofill_assistant/core/public:public",
     "//components/consent_auditor",
     "//components/consent_auditor:test_support",
     "//components/password_manager/core/browser:browser",
     "//components/signin/public/identity_manager:test_support",
     "//components/version_info",
-    "//content/test:test_support",
     "//testing/gmock",
     "//testing/gtest",
   ]
 }
 
-static_library("browser_test_support") {
-  testonly = true
-  sources = [
-    "base_browsertest.cc",
-    "base_browsertest.h",
-  ]
-
-  deps = [
-    "//content/test:browsertest_support",
-    "//net:test_support",
-    "//testing/gtest",
-    "//third_party/blink/public/common:headers",
-  ]
-}
-
 source_set("unit_tests") {
   testonly = true
   sources = [
@@ -651,7 +627,7 @@
     ":browser",
     ":proto",
     ":test_proto",
-    ":test_support",
+    ":unit_test_support",
     "//base",
     "//base/test:test_support",
     "//components/autofill/core/browser:test_support",
@@ -718,7 +694,7 @@
     template = "java_templates/IntentStrings.java.tmpl"
   }
 
-  static_library("android_test_support") {
+  static_library("test_support") {
     testonly = true
     sources = [
       "java_tts_controller.cc",
diff --git a/components/autofill_assistant/browser/public/BUILD.gn b/components/autofill_assistant/browser/public/BUILD.gn
index 3e6ae0a..5c738e2 100644
--- a/components/autofill_assistant/browser/public/BUILD.gn
+++ b/components/autofill_assistant/browser/public/BUILD.gn
@@ -137,6 +137,7 @@
     "//base",
     "//base/test:test_support",
     "//components/autofill/core/browser:test_support",
+    "//components/autofill_assistant/browser/public:unit_test_support",
     "//components/password_manager/core/browser:test_support",
     "//components/password_manager/core/browser/leak_detection:test_support",
     "//components/password_manager/core/common",
diff --git a/components/commerce/core/android/BUILD.gn b/components/commerce/core/android/BUILD.gn
index c9a9621..3f7251e 100644
--- a/components/commerce/core/android/BUILD.gn
+++ b/components/commerce/core/android/BUILD.gn
@@ -12,6 +12,7 @@
   deps = [
     "//base:jni_java",
     "//build/android:build_java",
+    "//third_party/android_deps:guava_android_java",
     "//third_party/androidx:androidx_annotation_annotation_java",
     "//url:gurl_java",
   ]
diff --git a/components/commerce/core/android/java/src/org/chromium/components/commerce/core/ShoppingService.java b/components/commerce/core/android/java/src/org/chromium/components/commerce/core/ShoppingService.java
index f2649e28..89ce899 100644
--- a/components/commerce/core/android/java/src/org/chromium/components/commerce/core/ShoppingService.java
+++ b/components/commerce/core/android/java/src/org/chromium/components/commerce/core/ShoppingService.java
@@ -6,6 +6,8 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.google.common.base.Optional;
+
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.base.annotations.NativeMethods;
@@ -22,16 +24,19 @@
         public final long offerId;
         public final String currencyCode;
         public final long amountMicros;
+        public final Optional<Long> previousAmountMicros;
         public final String countryCode;
 
         public ProductInfo(String title, GURL imageUrl, long productClusterId, long offerId,
-                String currencyCode, long amountMicros, String countryCode) {
+                String currencyCode, long amountMicros, String countryCode,
+                Optional<Long> previousAmountMicros) {
             this.title = title;
             this.imageUrl = imageUrl;
             this.productClusterId = productClusterId;
             this.offerId = offerId;
             this.currencyCode = currencyCode;
             this.amountMicros = amountMicros;
+            this.previousAmountMicros = previousAmountMicros;
             this.countryCode = countryCode;
         }
     }
@@ -158,9 +163,16 @@
 
     @CalledByNative
     private static ProductInfo createProductInfo(String title, GURL imageUrl, long productClusterId,
-            long offerId, String currencyCode, long amountMicros, String countryCode) {
+            long offerId, String currencyCode, long amountMicros, String countryCode,
+            boolean hasPreviousPrice, long previousAmountMicros) {
+        Optional<Long> previousPrice;
+        if (hasPreviousPrice) {
+            previousPrice = Optional.absent();
+        } else {
+            previousPrice = Optional.of(previousAmountMicros);
+        }
         return new ProductInfo(title, imageUrl, productClusterId, offerId, currencyCode,
-                amountMicros, countryCode);
+                amountMicros, countryCode, previousPrice);
     }
 
     @CalledByNative
diff --git a/components/commerce/core/android/shopping_service_android.cc b/components/commerce/core/android/shopping_service_android.cc
index 7417baa..a30d2c82 100644
--- a/components/commerce/core/android/shopping_service_android.cc
+++ b/components/commerce/core/android/shopping_service_android.cc
@@ -59,7 +59,9 @@
         url::GURLAndroid::FromNativeGURL(env, GURL(info->image_url)),
         info->product_cluster_id, info->offer_id,
         ConvertUTF8ToJavaString(env, info->currency_code), info->amount_micros,
-        ConvertUTF8ToJavaString(env, info->country_code));
+        ConvertUTF8ToJavaString(env, info->country_code),
+        info->previous_amount_micros.has_value(),
+        info->previous_amount_micros.value_or(0));
   }
 
   return info_java_object;
@@ -77,7 +79,9 @@
         url::GURLAndroid::FromNativeGURL(env, GURL(info->image_url)),
         info->product_cluster_id, info->offer_id,
         ConvertUTF8ToJavaString(env, info->currency_code), info->amount_micros,
-        ConvertUTF8ToJavaString(env, info->country_code));
+        ConvertUTF8ToJavaString(env, info->country_code),
+        info->previous_amount_micros.has_value(),
+        info->previous_amount_micros.value_or(0));
   }
 
   Java_ShoppingService_runProductInfoCallback(
diff --git a/components/commerce/core/price_tracking_utils.cc b/components/commerce/core/price_tracking_utils.cc
index 889c142c..f4d4be9c2 100644
--- a/components/commerce/core/price_tracking_utils.cc
+++ b/components/commerce/core/price_tracking_utils.cc
@@ -262,6 +262,19 @@
     changed = true;
   }
 
+  if (info.previous_amount_micros.has_value() &&
+      (specifics->previous_price().currency_code() != info.currency_code ||
+       specifics->previous_price().amount_micros() !=
+           info.previous_amount_micros.value())) {
+    // Intentionally use the same currency code for both current and previous
+    // price. Consistency between the values is guaranteed by the shopping
+    // service.
+    specifics->mutable_previous_price()->set_currency_code(info.currency_code);
+    specifics->mutable_previous_price()->set_amount_micros(
+        info.previous_amount_micros.value());
+    changed = true;
+  }
+
   if (specifics->offer_id() != info.offer_id) {
     specifics->set_offer_id(info.offer_id);
     changed = true;
diff --git a/components/commerce/core/price_tracking_utils_unittest.cc b/components/commerce/core/price_tracking_utils_unittest.cc
index 35109268..16a7db3 100644
--- a/components/commerce/core/price_tracking_utils_unittest.cc
+++ b/components/commerce/core/price_tracking_utils_unittest.cc
@@ -250,6 +250,7 @@
   const std::string new_image_url = "https://example.com/product_image.png";
   const std::string new_country_code = "us";
   const long new_price = 500000L;
+  const long old_price = 700000L;
   const std::string new_currency_code = "USD";
   const uint64_t new_offer_id = 10000L;
   const uint64_t cluster_id = 12345L;
@@ -277,6 +278,7 @@
   new_info.country_code = new_country_code;
   new_info.offer_id = new_offer_id;
   new_info.product_cluster_id = cluster_id;  // This shouldn't change.
+  new_info.previous_amount_micros.emplace(old_price);
 
   EXPECT_TRUE(PopulateOrUpdateBookmarkMetaIfNeeded(&meta, new_info));
 
@@ -290,6 +292,7 @@
   EXPECT_EQ(new_currency_code, specifics->current_price().currency_code());
   EXPECT_EQ(new_offer_id, specifics->offer_id());
   EXPECT_EQ(cluster_id, specifics->product_cluster_id());
+  EXPECT_EQ(old_price, specifics->previous_price().amount_micros());
 }
 
 TEST_F(PriceTrackingUtilsTest, PopulateOrUpdateBookmark_NoNewData) {
@@ -336,6 +339,7 @@
   EXPECT_EQ(currency_code, specifics->current_price().currency_code());
   EXPECT_EQ(offer_id, specifics->offer_id());
   EXPECT_EQ(cluster_id, specifics->product_cluster_id());
+  EXPECT_FALSE(specifics->has_previous_price());
 }
 
 TEST_F(PriceTrackingUtilsTest,
diff --git a/components/commerce/core/shopping_service.cc b/components/commerce/core/shopping_service.cc
index eb31849..43bd409 100644
--- a/components/commerce/core/shopping_service.cc
+++ b/components/commerce/core/shopping_service.cc
@@ -432,7 +432,7 @@
   if (!parsed_any.has_value() || !price_data.IsInitialized())
     return nullptr;
 
-  commerce::BuyableProduct buyable_product = price_data.buyable_product();
+  const commerce::BuyableProduct buyable_product = price_data.buyable_product();
 
   std::unique_ptr<ProductInfo> info = std::make_unique<ProductInfo>();
 
@@ -464,6 +464,29 @@
   if (buyable_product.has_country_code())
     info->country_code = buyable_product.country_code();
 
+  // Check to see if there was a price drop associated with this product. Those
+  // prices take priority over what BuyableProduct has.
+  if (price_data.has_product_update()) {
+    const commerce::ProductPriceUpdate price_update =
+        price_data.product_update();
+
+    // Both new and old price should exist and have the same currency code.
+    bool currency_codes_match = price_update.new_price().currency_code() ==
+                                price_update.old_price().currency_code();
+
+    if (price_update.has_new_price() &&
+        info->currency_code == price_update.new_price().currency_code() &&
+        currency_codes_match) {
+      info->amount_micros = price_update.new_price().amount_micros();
+    }
+    if (price_update.has_old_price() &&
+        info->currency_code == price_update.old_price().currency_code() &&
+        currency_codes_match) {
+      info->previous_amount_micros.emplace(
+          price_update.old_price().amount_micros());
+    }
+  }
+
   return info;
 }
 
diff --git a/components/commerce/core/shopping_service.h b/components/commerce/core/shopping_service.h
index f852eda..6517978 100644
--- a/components/commerce/core/shopping_service.h
+++ b/components/commerce/core/shopping_service.h
@@ -111,7 +111,8 @@
   uint64_t product_cluster_id{0};
   uint64_t offer_id{0};
   std::string currency_code;
-  long amount_micros{0};
+  int64_t amount_micros{0};
+  absl::optional<int64_t> previous_amount_micros;
   std::string country_code;
 
  private:
diff --git a/components/commerce/core/shopping_service_test_base.cc b/components/commerce/core/shopping_service_test_base.cc
index ff7b014d..dbea09f 100644
--- a/components/commerce/core/shopping_service_test_base.cc
+++ b/components/commerce/core/shopping_service_test_base.cc
@@ -119,7 +119,9 @@
     const std::string& image_url,
     const uint64_t offer_id,
     const uint64_t product_cluster_id,
-    const std::string& country_code) {
+    const std::string& country_code,
+    const int64_t amount_micros,
+    const std::string& currency_code) {
   OptimizationMetadata meta;
 
   PriceTrackingData price_tracking_data;
@@ -138,6 +140,10 @@
   if (!country_code.empty())
     buyable_product->set_country_code(country_code);
 
+  ProductPrice* price = buyable_product->mutable_current_price();
+  price->set_currency_code(currency_code);
+  price->set_amount_micros(amount_micros);
+
   Any any;
   any.set_type_url(price_tracking_data.GetTypeName());
   price_tracking_data.SerializeToString(any.mutable_value());
@@ -146,6 +152,29 @@
   return meta;
 }
 
+void MockOptGuideDecider::AddPriceUpdateToPriceTrackingResponse(
+    OptimizationMetadata* out_meta,
+    const std::string& currency_code,
+    const int64_t current_price,
+    const int64_t previous_price) {
+  PriceTrackingData price_tracking_data =
+      optimization_guide::ParsedAnyMetadata<PriceTrackingData>(
+          out_meta->any_metadata().value())
+          .value();
+
+  ProductPriceUpdate* price_update =
+      price_tracking_data.mutable_product_update();
+  price_update->mutable_new_price()->set_amount_micros(current_price);
+  price_update->mutable_new_price()->set_currency_code(currency_code);
+  price_update->mutable_old_price()->set_amount_micros(previous_price);
+  price_update->mutable_old_price()->set_currency_code(currency_code);
+
+  Any any;
+  any.set_type_url(price_tracking_data.GetTypeName());
+  price_tracking_data.SerializeToString(any.mutable_value());
+  out_meta->set_any_metadata(any);
+}
+
 OptimizationMetadata MockOptGuideDecider::BuildMerchantTrustResponse(
     const float star_rating,
     const uint32_t count_rating,
diff --git a/components/commerce/core/shopping_service_test_base.h b/components/commerce/core/shopping_service_test_base.h
index cd26a1b..5dcaf2f 100644
--- a/components/commerce/core/shopping_service_test_base.h
+++ b/components/commerce/core/shopping_service_test_base.h
@@ -87,7 +87,14 @@
       const std::string& image_url,
       const uint64_t offer_id,
       const uint64_t product_cluster_id,
-      const std::string& country_code);
+      const std::string& country_code,
+      const int64_t amount_micros = 0,
+      const std::string& currency_code = "USD");
+
+  void AddPriceUpdateToPriceTrackingResponse(OptimizationMetadata* out_meta,
+                                             const std::string& currency_code,
+                                             const int64_t current_price,
+                                             const int64_t previous_price);
 
   OptimizationMetadata BuildMerchantTrustResponse(
       const float star_rating,
diff --git a/components/commerce/core/shopping_service_unittest.cc b/components/commerce/core/shopping_service_unittest.cc
index d584818..70bb836 100644
--- a/components/commerce/core/shopping_service_unittest.cc
+++ b/components/commerce/core/shopping_service_unittest.cc
@@ -39,6 +39,9 @@
 const uint64_t kOfferId = 123;
 const uint64_t kClusterId = 456;
 const char kCountryCode[] = "US";
+const char kCurrencyCode[] = "USD";
+const int64_t kPrice = 1000;
+const int64_t kNewPrice = 500;
 
 const char kMerchantUrl[] = "http://example.com/merchant";
 const float kStarRating = 4.5;
@@ -71,27 +74,80 @@
       {commerce::kShoppingList, commerce::kCommerceAllowServerImages}, {});
 
   OptimizationMetadata meta = opt_guide_->BuildPriceTrackingResponse(
-      kTitle, kImageUrl, kOfferId, kClusterId, kCountryCode);
+      kTitle, kImageUrl, kOfferId, kClusterId, kCountryCode, kPrice,
+      kCurrencyCode);
+  opt_guide_->AddPriceUpdateToPriceTrackingResponse(&meta, kCurrencyCode,
+                                                    kNewPrice, kPrice);
 
   opt_guide_->SetResponse(GURL(kProductUrl), OptimizationType::PRICE_TRACKING,
                           OptimizationGuideDecision::kTrue, meta);
 
   base::RunLoop run_loop;
   shopping_service_->GetProductInfoForUrl(
-      GURL(kProductUrl), base::BindOnce(
-                             [](base::RunLoop* run_loop, const GURL& url,
-                                const absl::optional<ProductInfo>& info) {
-                               ASSERT_EQ(kProductUrl, url.spec());
-                               ASSERT_TRUE(info.has_value());
+      GURL(kProductUrl),
+      base::BindOnce(
+          [](base::RunLoop* run_loop, const GURL& url,
+             const absl::optional<ProductInfo>& info) {
+            ASSERT_EQ(kProductUrl, url.spec());
+            ASSERT_TRUE(info.has_value());
 
-                               ASSERT_EQ(kTitle, info->title);
-                               ASSERT_EQ(kImageUrl, info->image_url);
-                               ASSERT_EQ(kOfferId, info->offer_id);
-                               ASSERT_EQ(kClusterId, info->product_cluster_id);
-                               ASSERT_EQ(kCountryCode, info->country_code);
-                               run_loop->Quit();
-                             },
-                             &run_loop));
+            ASSERT_EQ(kTitle, info->title);
+            ASSERT_EQ(kImageUrl, info->image_url);
+            ASSERT_EQ(kOfferId, info->offer_id);
+            ASSERT_EQ(kClusterId, info->product_cluster_id);
+            ASSERT_EQ(kCountryCode, info->country_code);
+
+            ASSERT_EQ(kCurrencyCode, info->currency_code);
+            ASSERT_EQ(kNewPrice, info->amount_micros);
+            ASSERT_TRUE(info->previous_amount_micros.has_value());
+            ASSERT_EQ(kPrice, info->previous_amount_micros.value());
+
+            run_loop->Quit();
+          },
+          &run_loop));
+  run_loop.Run();
+}
+
+TEST_F(ShoppingServiceTest, TestProductInfoResponse_CurrencyMismatch) {
+  // Ensure a feature that uses product info is enabled. This doesn't
+  // necessarily need to be the shopping list.
+  test_features_.InitWithFeatures(
+      {commerce::kShoppingList, commerce::kCommerceAllowServerImages}, {});
+
+  OptimizationMetadata meta = opt_guide_->BuildPriceTrackingResponse(
+      kTitle, kImageUrl, kOfferId, kClusterId, kCountryCode, kPrice,
+      kCurrencyCode);
+
+  // Add a fake currency code to that doesn't match the original to ensure that
+  // data is not used.
+  opt_guide_->AddPriceUpdateToPriceTrackingResponse(&meta, "ZZ", kNewPrice,
+                                                    kPrice);
+
+  opt_guide_->SetResponse(GURL(kProductUrl), OptimizationType::PRICE_TRACKING,
+                          OptimizationGuideDecision::kTrue, meta);
+
+  base::RunLoop run_loop;
+  shopping_service_->GetProductInfoForUrl(
+      GURL(kProductUrl),
+      base::BindOnce(
+          [](base::RunLoop* run_loop, const GURL& url,
+             const absl::optional<ProductInfo>& info) {
+            ASSERT_EQ(kProductUrl, url.spec());
+            ASSERT_TRUE(info.has_value());
+
+            ASSERT_EQ(kTitle, info->title);
+            ASSERT_EQ(kImageUrl, info->image_url);
+            ASSERT_EQ(kOfferId, info->offer_id);
+            ASSERT_EQ(kClusterId, info->product_cluster_id);
+            ASSERT_EQ(kCountryCode, info->country_code);
+
+            ASSERT_EQ(kCurrencyCode, info->currency_code);
+            ASSERT_EQ(kPrice, info->amount_micros);
+            ASSERT_FALSE(info->previous_amount_micros.has_value());
+
+            run_loop->Quit();
+          },
+          &run_loop));
   run_loop.Run();
 }
 
diff --git a/components/commerce/core/webui/shopping_list_handler.cc b/components/commerce/core/webui/shopping_list_handler.cc
index 86a4559..b03fb35 100644
--- a/components/commerce/core/webui/shopping_list_handler.cc
+++ b/components/commerce/core/webui/shopping_list_handler.cc
@@ -46,19 +46,29 @@
   bookmark_info->info->image_url = GURL(meta->lead_image().url());
 
   const power_bookmarks::ProductPrice price = specifics.current_price();
-  std::string current_code = price.currency_code();
+  std::string currency_code = price.currency_code();
 
   std::unique_ptr<payments::CurrencyFormatter> formatter =
-      std::make_unique<payments::CurrencyFormatter>(current_code, locale);
+      std::make_unique<payments::CurrencyFormatter>(currency_code, locale);
   formatter->SetMaxFractionalDigits(2);
 
   bookmark_info->info->current_price =
       base::UTF16ToUTF8(formatter->Format(base::NumberToString(
           static_cast<float>(price.amount_micros()) / kToMicroCurrency)));
 
-  // TODO(1346620): Hook up previous price. We might need to fetch new
-  //                information when the UI is loaded to handle both
-  //                previous and current price.
+  // Only send the previous price if it is higher than the current price. This
+  // is exclusively used to decide whether to show the price drop chip in the
+  // UI.
+  if (specifics.has_previous_price() &&
+      specifics.previous_price().amount_micros() >
+          specifics.current_price().amount_micros()) {
+    const power_bookmarks::ProductPrice previous_price =
+        specifics.previous_price();
+    bookmark_info->info->previous_price =
+        base::UTF16ToUTF8(formatter->Format(base::NumberToString(
+            static_cast<float>(previous_price.amount_micros()) /
+            kToMicroCurrency)));
+  }
 
   return bookmark_info;
 }
diff --git a/components/commerce/core/webui/shopping_list_handler_unittest.cc b/components/commerce/core/webui/shopping_list_handler_unittest.cc
index bc7540a..b4caadf 100644
--- a/components/commerce/core/webui/shopping_list_handler_unittest.cc
+++ b/components/commerce/core/webui/shopping_list_handler_unittest.cc
@@ -97,6 +97,12 @@
   std::unique_ptr<power_bookmarks::PowerBookmarkMeta> meta =
       power_bookmarks::GetNodePowerBookmarkMeta(bookmark_model_.get(), product);
   meta->mutable_lead_image()->set_url(image_url);
+  meta->mutable_shopping_specifics()
+      ->mutable_previous_price()
+      ->set_amount_micros(4560000);
+  meta->mutable_shopping_specifics()
+      ->mutable_previous_price()
+      ->set_currency_code("usd");
   power_bookmarks::SetNodePowerBookmarkMeta(bookmark_model_.get(), product,
                                             std::move(meta));
 
@@ -109,6 +115,42 @@
 
   EXPECT_EQ(mojo_list[0]->bookmark_id, product->id());
   EXPECT_EQ(mojo_list[0]->info->current_price, "$1.23");
+  EXPECT_EQ(mojo_list[0]->info->previous_price, "$4.56");
+  EXPECT_EQ(mojo_list[0]->info->domain, "example.com");
+  EXPECT_EQ(mojo_list[0]->info->title, "product 1");
+  EXPECT_EQ(mojo_list[0]->info->image_url.spec(), image_url);
+}
+
+// If the new price is greater than the old price, we shouldn't include the
+// |previous_price| field in the mojo data type.
+TEST_F(ShoppingListHandlerTest, ConvertToMojoTypes_PriceIncrease) {
+  const bookmarks::BookmarkNode* product = AddProductBookmark(
+      bookmark_model_.get(), u"product 1", GURL("http://example.com/1"), 123L,
+      true, 1230000, "usd");
+
+  const std::string image_url = "https://example.com/image.png";
+  std::unique_ptr<power_bookmarks::PowerBookmarkMeta> meta =
+      power_bookmarks::GetNodePowerBookmarkMeta(bookmark_model_.get(), product);
+  meta->mutable_lead_image()->set_url(image_url);
+  meta->mutable_shopping_specifics()
+      ->mutable_previous_price()
+      ->set_amount_micros(1000000);
+  meta->mutable_shopping_specifics()
+      ->mutable_previous_price()
+      ->set_currency_code("usd");
+  power_bookmarks::SetNodePowerBookmarkMeta(bookmark_model_.get(), product,
+                                            std::move(meta));
+
+  std::vector<const bookmarks::BookmarkNode*> bookmark_list;
+  bookmark_list.push_back(product);
+
+  std::vector<shopping_list::mojom::BookmarkProductInfoPtr> mojo_list =
+      ShoppingListHandler::BookmarkListToMojoList(*bookmark_model_,
+                                                  bookmark_list, "en-us");
+
+  EXPECT_EQ(mojo_list[0]->bookmark_id, product->id());
+  EXPECT_EQ(mojo_list[0]->info->current_price, "$1.23");
+  EXPECT_TRUE(mojo_list[0]->info->previous_price.empty());
   EXPECT_EQ(mojo_list[0]->info->domain, "example.com");
   EXPECT_EQ(mojo_list[0]->info->title, "product 1");
   EXPECT_EQ(mojo_list[0]->info->image_url.spec(), image_url);
diff --git a/components/crash/content/browser/crash_metrics_reporter_android.cc b/components/crash/content/browser/crash_metrics_reporter_android.cc
index 99ec88df..12cd46d 100644
--- a/components/crash/content/browser/crash_metrics_reporter_android.cc
+++ b/components/crash/content/browser/crash_metrics_reporter_android.cc
@@ -186,7 +186,14 @@
       // the bindings are updated later than visibility on web contents.
       switch (info.binding_state) {
         case base::android::ChildBindingState::UNBOUND:
+          break;
         case base::android::ChildBindingState::WAIVED:
+          if (!intentional_kill && !info.normal_termination) {
+            ReportCrashCount(
+                ProcessedCrashCounts::
+                    kRendererForegroundInvisibleWithWaivedBindingOom,
+                &reported_counts);
+          }
           break;
         case base::android::ChildBindingState::STRONG:
           if (intentional_kill || info.normal_termination) {
diff --git a/components/crash/content/browser/crash_metrics_reporter_android.h b/components/crash/content/browser/crash_metrics_reporter_android.h
index 613812b..999364b 100644
--- a/components/crash/content/browser/crash_metrics_reporter_android.h
+++ b/components/crash/content/browser/crash_metrics_reporter_android.h
@@ -55,7 +55,8 @@
     kRendererForegroundInvisibleWithVisibleBindingOom = 21,
     kRendererForegroundInvisibleWithNotPerceptibleBindingKilled = 22,
     kRendererForegroundInvisibleWithNotPerceptibleBindingOom = 23,
-    kMaxValue = kRendererForegroundInvisibleWithNotPerceptibleBindingOom
+    kRendererForegroundInvisibleWithWaivedBindingOom = 24,
+    kMaxValue = kRendererForegroundInvisibleWithWaivedBindingOom
   };
   using ReportedCrashTypeSet = base::flat_set<ProcessedCrashCounts>;
 
diff --git a/components/exo/surface.cc b/components/exo/surface.cc
index 473d8cc..a69aa6e 100644
--- a/components/exo/surface.cc
+++ b/components/exo/surface.cc
@@ -112,6 +112,54 @@
   }
 }
 
+Transform InvertY(Transform transform) {
+  switch (transform) {
+    case Transform::NORMAL:
+      return Transform::FLIPPED_ROTATE_180;
+    case Transform::ROTATE_90:
+      return Transform::FLIPPED_ROTATE_270;
+    case Transform::ROTATE_180:
+      return Transform::FLIPPED;
+    case Transform::ROTATE_270:
+      return Transform::FLIPPED_ROTATE_90;
+    case Transform::FLIPPED:
+      return Transform::ROTATE_180;
+    case Transform::FLIPPED_ROTATE_90:
+      return Transform::ROTATE_270;
+    case Transform::FLIPPED_ROTATE_180:
+      return Transform::NORMAL;
+    case Transform::FLIPPED_ROTATE_270:
+      return Transform::ROTATE_90;
+  }
+  NOTREACHED();
+}
+
+// Returns a gfx::Transform that can transform a (0,0 1x1) rect to the same
+// rect while rotate/flip the contents about (0.5, 0.5) origin. It's equivalent
+// to rotating/flipping about (0, 0) origin then translating by
+// (0 or 1, 0 or 1). Note that the rotations are counter-clockwise.
+gfx::Transform ToBufferTransformMatrix(Transform transform, bool invert_y) {
+  switch (invert_y ? InvertY(transform) : transform) {
+    case Transform::NORMAL:
+      return gfx::Transform();
+    case Transform::ROTATE_90:
+      return gfx::Transform::Affine(0, -1, 1, 0, 0, 1);
+    case Transform::ROTATE_180:
+      return gfx::Transform::Affine(-1, 0, 0, -1, 1, 1);
+    case Transform::ROTATE_270:
+      return gfx::Transform::Affine(0, 1, -1, 0, 1, 0);
+    case Transform::FLIPPED:
+      return gfx::Transform::Affine(-1, 0, 0, 1, 1, 0);
+    case Transform::FLIPPED_ROTATE_90:
+      return gfx::Transform::Affine(0, 1, 1, 0, 0, 0);
+    case Transform::FLIPPED_ROTATE_180:
+      return gfx::Transform::Affine(1, 0, 0, -1, 0, 1);
+    case Transform::FLIPPED_ROTATE_270:
+      return gfx::Transform::Affine(0, -1, -1, 0, 1, 1);
+  }
+  NOTREACHED();
+}
+
 // Helper function that returns |size| after adjusting for |transform|.
 gfx::Size ToTransformedSize(const gfx::Size& size, Transform transform) {
   switch (transform) {
@@ -1263,43 +1311,12 @@
 }
 
 void Surface::UpdateBufferTransform(bool y_invert) {
-  SkMatrix buffer_matrix;
-  Transform transform = state_.basic_state.buffer_transform;
-  switch (transform) {
-    case Transform::ROTATE_90:
-    case Transform::FLIPPED_ROTATE_90:
-      buffer_matrix.setSinCos(-1, 0, 0.5f, 0.5f);
-      break;
-    case Transform::ROTATE_180:
-    case Transform::FLIPPED_ROTATE_180:
-      buffer_matrix.setSinCos(0, -1, 0.5f, 0.5f);
-      break;
-    case Transform::ROTATE_270:
-    case Transform::FLIPPED_ROTATE_270:
-      buffer_matrix.setSinCos(1, 0, 0.5f, 0.5f);
-      break;
-    default:
-      break;
+  buffer_transform_ =
+      ToBufferTransformMatrix(state_.basic_state.buffer_transform, y_invert);
+  if (state_.basic_state.buffer_scale != 0) {
+    buffer_transform_.PostScale(1.0f / state_.basic_state.buffer_scale,
+                                1.0f / state_.basic_state.buffer_scale);
   }
-  bool x_invert = false;
-  switch (transform) {
-    case Transform::FLIPPED:
-    case Transform::FLIPPED_ROTATE_90:
-    case Transform::FLIPPED_ROTATE_180:
-    case Transform::FLIPPED_ROTATE_270:
-      x_invert = true;
-      break;
-    default:
-      break;
-  }
-  if (x_invert)
-    buffer_matrix.preScale(-1, 1, 0.5f, 0.5f);
-  if (y_invert)
-    buffer_matrix.preScale(1, -1, 0.5f, 0.5f);
-  if (state_.basic_state.buffer_scale != 0)
-    buffer_matrix.postScale(1.0f / state_.basic_state.buffer_scale,
-                            1.0f / state_.basic_state.buffer_scale);
-  buffer_transform_ = gfx::SkMatrixToTransform(buffer_matrix);
 }
 
 // Try to share the |SharedQuadState| (sqs) when a single layer can be
@@ -1396,7 +1413,7 @@
 
   state_.damage.Clear();
 
-  gfx::PointF scale(content_size_.width(), content_size_.height());
+  gfx::Vector2dF scale(content_size_.width(), content_size_.height());
 
   gfx::Vector2dF translate(0.0f, 0.0f);
 
@@ -1404,7 +1421,7 @@
   // use the shared quad clip rect.
   if (get_current_surface_id_) {
     quad_rect = gfx::Rect(embedded_surface_size_);
-    scale = gfx::PointF(1.0f, 1.0f);
+    scale = gfx::Vector2dF(1.0f, 1.0f);
 
     if (!state_.basic_state.crop.IsEmpty()) {
       // In order to crop an AxB rect to CxD we need to scale by A/C, B/D.
@@ -1423,18 +1440,16 @@
 
   // Compute the total transformation from post-transform buffer coordinates to
   // target coordinates.
-  SkMatrix viewport_to_target_matrix;
   // Scale and offset the normalized space to fit the content size rectangle.
-  viewport_to_target_matrix.setScale(scale.x(), scale.y());
-
-  gfx::PointF target = gfx::PointF(origin) + translate;
-  viewport_to_target_matrix.postTranslate(target.x(), target.y());
+  gfx::AxisTransform2d viewport_to_target_transform =
+      gfx::AxisTransform2d::FromScaleAndTranslation(
+          scale, origin.OffsetFromOrigin() + translate);
   // Convert from DPs to pixels.
-  viewport_to_target_matrix.postScale(device_scale_factor, device_scale_factor);
+  viewport_to_target_transform.PostScale(
+      gfx::Vector2dF(device_scale_factor, device_scale_factor));
 
   gfx::Transform quad_to_target_transform(buffer_transform_);
-  quad_to_target_transform.PostConcat(
-      gfx::SkMatrixToTransform(viewport_to_target_matrix));
+  quad_to_target_transform.PostConcat(viewport_to_target_transform);
 
   bool are_contents_opaque =
       !current_resource_has_alpha_ ||
diff --git a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
index 004e8ad..4c6f34c12 100644
--- a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
+++ b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
@@ -471,16 +471,7 @@
         RecordHistogram.recordTimesHistogram(
                 "Android.StrictMode.OverrideUrlLoadingTime", SystemClock.elapsedRealtime() - time);
 
-        if (result.getResultType() != OverrideUrlLoadingResultType.NO_OVERRIDE) {
-            int pageTransitionCore = params.getPageTransition() & PageTransition.CORE_MASK;
-            boolean isFormSubmit = pageTransitionCore == PageTransition.FORM_SUBMIT;
-            boolean isRedirectFromFormSubmit = isFormSubmit && params.isRedirect();
-            if (isRedirectFromFormSubmit) {
-                RecordHistogram.recordBooleanHistogram(
-                        "Android.Intent.LaunchExternalAppFormSubmitHasUserGesture",
-                        params.hasUserGesture());
-            }
-        } else {
+        if (result.getResultType() == OverrideUrlLoadingResultType.NO_OVERRIDE) {
             result = handleFallbackUrl(params, targetIntent, browserFallbackUrl,
                     canLaunchExternalFallbackResult.get());
         }
diff --git a/components/lens/lens_features.cc b/components/lens/lens_features.cc
index 500ed859..1d1bd83 100644
--- a/components/lens/lens_features.cc
+++ b/components/lens/lens_features.cc
@@ -32,7 +32,7 @@
 
 BASE_FEATURE(kEnableRegionSearchOnPdfViewer,
              "LensEnableRegionSearchOnPdfViewer",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 BASE_FEATURE(kLensInstructionChipImprovements,
              "LensInstructionChipImprovements",
diff --git a/components/page_load_metrics/browser/observers/core/uma_page_load_metrics_observer.cc b/components/page_load_metrics/browser/observers/core/uma_page_load_metrics_observer.cc
index c47c37b..fa4ed97f 100644
--- a/components/page_load_metrics/browser/observers/core/uma_page_load_metrics_observer.cc
+++ b/components/page_load_metrics/browser/observers/core/uma_page_load_metrics_observer.cc
@@ -64,13 +64,6 @@
   return LOAD_TYPE_NONE;
 }
 
-void RecordFirstMeaningfulPaintStatus(
-    internal::FirstMeaningfulPaintStatus status) {
-  UMA_HISTOGRAM_ENUMERATION(internal::kHistogramFirstMeaningfulPaintStatus,
-                            status,
-                            internal::FIRST_MEANINGFUL_PAINT_LAST_ENTRY);
-}
-
 std::unique_ptr<base::trace_event::TracedValue> FirstInputDelayTraceData(
     const page_load_metrics::mojom::PageLoadTiming& timing) {
   std::unique_ptr<base::trace_event::TracedValue> data =
@@ -135,8 +128,6 @@
 const char kHistogramFirstContentfulPaintInitiatingProcess[] =
     "PageLoad.Internal.PaintTiming.NavigationToFirstContentfulPaint."
     "InitiatingProcess";
-const char kHistogramFirstMeaningfulPaint[] =
-    "PageLoad.Experimental.PaintTiming.NavigationToFirstMeaningfulPaint";
 const char kHistogramLargestContentfulPaint[] =
     "PageLoad.PaintTiming.NavigationToLargestContentfulPaint2";
 const char kHistogramLargestContentfulPaintContentType[] =
@@ -260,9 +251,6 @@
 const char kHistogramFirstContentfulPaintUserInitiated[] =
     "PageLoad.PaintTiming.NavigationToFirstContentfulPaint.UserInitiated";
 
-const char kHistogramFirstMeaningfulPaintStatus[] =
-    "PageLoad.Experimental.PaintTiming.FirstMeaningfulPaintStatus";
-
 const char kHistogramCachedResourceLoadTimePrefix[] =
     "PageLoad.Experimental.PageTiming.CachedResourceLoadTime.";
 const char kHistogramCommitSentToFirstSubresourceLoadStart[] =
@@ -672,19 +660,6 @@
   }
 }
 
-void UmaPageLoadMetricsObserver::OnFirstMeaningfulPaintInMainFrameDocument(
-    const page_load_metrics::mojom::PageLoadTiming& timing) {
-  if (page_load_metrics::WasStartedInForegroundOptionalEventInForeground(
-          timing.paint_timing->first_meaningful_paint, GetDelegate())) {
-    PAGE_LOAD_HISTOGRAM(internal::kHistogramFirstMeaningfulPaint,
-                        timing.paint_timing->first_meaningful_paint.value());
-    RecordFirstMeaningfulPaintStatus(internal::FIRST_MEANINGFUL_PAINT_RECORDED);
-  } else {
-    RecordFirstMeaningfulPaintStatus(
-        internal::FIRST_MEANINGFUL_PAINT_BACKGROUNDED);
-  }
-}
-
 void UmaPageLoadMetricsObserver::OnFirstInputInPage(
     const page_load_metrics::mojom::PageLoadTiming& timing) {
   if (!page_load_metrics::WasStartedInForegroundOptionalEventInForeground(
@@ -1073,15 +1048,6 @@
         "data", all_frames_largest_contentful_paint.DataAsTraceValue());
   }
 
-  if (main_frame_timing.paint_timing->first_paint &&
-      !main_frame_timing.paint_timing->first_meaningful_paint) {
-    RecordFirstMeaningfulPaintStatus(
-        main_frame_timing.paint_timing->first_contentful_paint
-            ? internal::FIRST_MEANINGFUL_PAINT_DID_NOT_REACH_NETWORK_STABLE
-            : internal::
-                  FIRST_MEANINGFUL_PAINT_DID_NOT_REACH_FIRST_CONTENTFUL_PAINT);
-  }
-
   if (main_frame_timing.interactive_timing->longest_input_timestamp) {
     DCHECK(main_frame_timing.interactive_timing->longest_input_delay);
     UMA_HISTOGRAM_CUSTOM_TIMES(
diff --git a/components/page_load_metrics/browser/observers/core/uma_page_load_metrics_observer.h b/components/page_load_metrics/browser/observers/core/uma_page_load_metrics_observer.h
index 5e9e6cc..6bea647 100644
--- a/components/page_load_metrics/browser/observers/core/uma_page_load_metrics_observer.h
+++ b/components/page_load_metrics/browser/observers/core/uma_page_load_metrics_observer.h
@@ -39,7 +39,6 @@
 extern const char kHistogramDomContentLoaded[];
 extern const char kHistogramLoad[];
 extern const char kHistogramFirstContentfulPaint[];
-extern const char kHistogramFirstMeaningfulPaint[];
 extern const char kHistogramLargestContentfulPaint[];
 extern const char kHistogramLargestContentfulPaintContentType[];
 extern const char kHistogramLargestContentfulPaintMainFrame[];
@@ -67,8 +66,6 @@
 extern const char kHistogramPageTimingForegroundDuration[];
 extern const char kHistogramPageTimingForegroundDurationNoCommit[];
 
-extern const char kHistogramFirstMeaningfulPaintStatus[];
-
 extern const char kHistogramCachedResourceLoadTimePrefix[];
 extern const char kHistogramCommitSentToFirstSubresourceLoadStart[];
 extern const char kHistogramNavigationToFirstSubresourceLoadStart[];
@@ -145,15 +142,6 @@
 extern const char kHistogramMemoryTotal[];
 extern const char kHistogramMemoryUpdateReceived[];
 
-enum FirstMeaningfulPaintStatus {
-  FIRST_MEANINGFUL_PAINT_RECORDED,
-  FIRST_MEANINGFUL_PAINT_BACKGROUNDED,
-  FIRST_MEANINGFUL_PAINT_DID_NOT_REACH_NETWORK_STABLE,
-  FIRST_MEANINGFUL_PAINT_USER_INTERACTION_BEFORE_FMP,
-  FIRST_MEANINGFUL_PAINT_DID_NOT_REACH_FIRST_CONTENTFUL_PAINT,
-  FIRST_MEANINGFUL_PAINT_LAST_ENTRY
-};
-
 // Please keep in sync with PageLoadBackForwardCacheEvent in
 // tools/metrics/histograms/enums.xml. These values should not be renumbered.
 enum class PageLoadBackForwardCacheEvent {
@@ -198,8 +186,6 @@
       const page_load_metrics::mojom::PageLoadTiming& timing) override;
   void OnFirstContentfulPaintInPage(
       const page_load_metrics::mojom::PageLoadTiming& timing) override;
-  void OnFirstMeaningfulPaintInMainFrameDocument(
-      const page_load_metrics::mojom::PageLoadTiming& timing) override;
   void OnFirstInputInPage(
       const page_load_metrics::mojom::PageLoadTiming& timing) override;
   void OnParseStart(
diff --git a/components/page_load_metrics/browser/observers/core/uma_page_load_metrics_observer_unittest.cc b/components/page_load_metrics/browser/observers/core/uma_page_load_metrics_observer_unittest.cc
index ef455801..9034733 100644
--- a/components/page_load_metrics/browser/observers/core/uma_page_load_metrics_observer_unittest.cc
+++ b/components/page_load_metrics/browser/observers/core/uma_page_load_metrics_observer_unittest.cc
@@ -717,25 +717,6 @@
       internal::kHistogramPageLoadCpuTotalUsageForegrounded, 750, 1);
 }
 
-TEST_P(UmaPageLoadMetricsObserverTest, FirstMeaningfulPaint) {
-  page_load_metrics::mojom::PageLoadTiming timing;
-  page_load_metrics::InitPageLoadTimingForTest(&timing);
-  timing.navigation_start = base::Time::FromDoubleT(1);
-  timing.parse_timing->parse_start = base::Milliseconds(5);
-  timing.paint_timing->first_meaningful_paint = base::Milliseconds(10);
-  PopulateRequiredTimingFields(&timing);
-
-  NavigateAndCommit(GURL(kDefaultTestUrl));
-  tester()->SimulateTimingUpdate(timing);
-  NavigateAndCommit(GURL(kDefaultTestUrl2));
-
-  tester()->histogram_tester().ExpectTotalCount(
-      internal::kHistogramFirstMeaningfulPaint, 1);
-  tester()->histogram_tester().ExpectBucketCount(
-      internal::kHistogramFirstMeaningfulPaintStatus,
-      internal::FIRST_MEANINGFUL_PAINT_RECORDED, 1);
-}
-
 TEST_P(UmaPageLoadMetricsObserverTest, LargestImageLoading) {
   page_load_metrics::mojom::PageLoadTiming timing;
   page_load_metrics::InitPageLoadTimingForTest(&timing);
diff --git a/components/paint_preview/renderer/paint_preview_recorder_impl.cc b/components/paint_preview/renderer/paint_preview_recorder_impl.cc
index 164e74f7..94807c6 100644
--- a/components/paint_preview/renderer/paint_preview_recorder_impl.cc
+++ b/components/paint_preview/renderer/paint_preview_recorder_impl.cc
@@ -339,8 +339,7 @@
   response->frame_offsets = gfx::Point(bounds.x(), bounds.y());
 
   cc::PaintRecorder recorder;
-  cc::PaintCanvas* canvas =
-      recorder.beginRecording(bounds.width(), bounds.height());
+  cc::PaintCanvas* canvas = recorder.beginRecording();
   canvas->save();
   canvas->concat(SkMatrix::Translate(-bounds.x(), -bounds.y()));
   canvas->SetPaintPreviewTracker(tracker.get());
diff --git a/components/paint_preview/renderer/paint_preview_recorder_utils_unittest.cc b/components/paint_preview/renderer/paint_preview_recorder_utils_unittest.cc
index f65f8c0..040705d 100644
--- a/components/paint_preview/renderer/paint_preview_recorder_utils_unittest.cc
+++ b/components/paint_preview/renderer/paint_preview_recorder_utils_unittest.cc
@@ -43,8 +43,7 @@
 
 sk_sp<cc::PaintRecord> AddLink(const std::string& link, const SkRect& rect) {
   cc::PaintRecorder link_recorder;
-  cc::PaintCanvas* link_canvas = link_recorder.beginRecording(
-      rect.x() + rect.width(), rect.y() + rect.height());
+  cc::PaintCanvas* link_canvas = link_recorder.beginRecording();
   link_canvas->Annotate(cc::PaintCanvas::AnnotationType::URL, rect,
                         SkData::MakeWithCString(link.c_str()));
   return link_recorder.finishRecordingAsPicture();
@@ -62,10 +61,10 @@
 
   cc::PaintFlags flags;
   cc::PaintRecorder outer_recorder;
-  cc::PaintCanvas* outer_canvas = outer_recorder.beginRecording(100, 100);
+  cc::PaintCanvas* outer_canvas = outer_recorder.beginRecording();
   outer_canvas->drawTextBlob(blob_1, 10, 10, flags);
   cc::PaintRecorder inner_recorder;
-  cc::PaintCanvas* inner_canvas = inner_recorder.beginRecording(50, 50);
+  cc::PaintCanvas* inner_canvas = inner_recorder.beginRecording();
   inner_canvas->drawTextBlob(blob_2, 15, 20, flags);
   outer_canvas->drawPicture(inner_recorder.finishRecordingAsPicture());
   auto record = outer_recorder.finishRecordingAsPicture();
@@ -92,7 +91,7 @@
 TEST(PaintPreviewRecorderUtilsTest, TestParseLinks) {
   cc::PaintFlags flags;
   cc::PaintRecorder outer_recorder;
-  cc::PaintCanvas* outer_canvas = outer_recorder.beginRecording(500, 500);
+  cc::PaintCanvas* outer_canvas = outer_recorder.beginRecording();
 
   outer_canvas->save();
   outer_canvas->translate(10, 20);
@@ -109,7 +108,7 @@
   outer_canvas->drawPicture(AddLink(link_2, rect_2));
 
   cc::PaintRecorder inner_recorder;
-  cc::PaintCanvas* inner_canvas = inner_recorder.beginRecording(500, 500);
+  cc::PaintCanvas* inner_canvas = inner_recorder.beginRecording();
   inner_canvas->rotate(20);
   std::string link_3 = "http://www.baz.com/";
   SkRect rect_3 = SkRect::MakeXYWH(5, 7, 9, 13);
@@ -172,7 +171,7 @@
 
   cc::PaintFlags flags;
   cc::PaintRecorder recorder;
-  cc::PaintCanvas* canvas = recorder.beginRecording(500, 500);
+  cc::PaintCanvas* canvas = recorder.beginRecording();
   canvas->save();
   canvas->translate(10, 20);
   canvas->recordCustomData(old_id);
@@ -218,7 +217,7 @@
   void SetUp() override {
     base::DiscardableMemoryAllocator::SetInstance(&test_allocator_);
 
-    canvas = recorder.beginRecording(dimensions.width(), dimensions.width());
+    canvas = recorder.beginRecording();
     cc::PaintFlags flags;
     canvas->drawRect(SkRect::MakeWH(dimensions.width(), dimensions.height()),
                      flags);
@@ -434,8 +433,7 @@
        RoundtripWithLazyImage) {
   {
     cc::PaintRecorder inner_recorder;
-    cc::PaintCanvas* inner_canvas =
-        inner_recorder.beginRecording(dimensions.width(), dimensions.width());
+    cc::PaintCanvas* inner_canvas = inner_recorder.beginRecording();
     inner_canvas->drawColor(SkColors::kRed);
     cc::PaintImage paint_image =
         cc::PaintImageBuilder::WithDefault()
diff --git a/components/payments/content/BUILD.gn b/components/payments/content/BUILD.gn
index d574dce..2b3eeae 100644
--- a/components/payments/content/BUILD.gn
+++ b/components/payments/content/BUILD.gn
@@ -22,8 +22,6 @@
     "payment_app_factory.h",
     "payment_app_service.cc",
     "payment_app_service.h",
-    "payment_app_service_factory.cc",
-    "payment_app_service_factory.h",
     "payment_credential.cc",
     "payment_credential.h",
     "payment_credential_factory.cc",
diff --git a/components/payments/content/android/payment_app_service_bridge.cc b/components/payments/content/android/payment_app_service_bridge.cc
index 379cbd66..fd09dfb 100644
--- a/components/payments/content/android/payment_app_service_bridge.cc
+++ b/components/payments/content/android/payment_app_service_bridge.cc
@@ -20,7 +20,6 @@
 #include "components/payments/content/android/jni_payment_app.h"
 #include "components/payments/content/android/payment_request_spec.h"
 #include "components/payments/content/payment_app_service.h"
-#include "components/payments/content/payment_app_service_factory.h"
 #include "components/payments/content/payment_manifest_web_data_service.h"
 #include "components/payments/content/payment_request_spec.h"
 #include "components/url_formatter/elide_url.h"
@@ -113,11 +112,10 @@
               render_frame_host->GetBrowserContext(),
               ServiceAccessType::EXPLICIT_ACCESS);
 
-  payments::PaymentAppService* service =
-      payments::PaymentAppServiceFactory::GetForContext(
-          render_frame_host->GetBrowserContext());
   auto* bridge = payments::PaymentAppServiceBridge::Create(
-      service->GetNumberOfFactories(), render_frame_host, GURL(top_origin),
+      std::make_unique<payments::PaymentAppService>(
+          render_frame_host->GetBrowserContext()),
+      render_frame_host, GURL(top_origin),
       payments::android::PaymentRequestSpec::FromJavaPaymentRequestSpec(
           env, jpayment_request_spec),
       jtwa_package_name ? ConvertJavaStringToUTF8(env, jtwa_package_name) : "",
@@ -134,7 +132,7 @@
       base::BindRepeating(&SetCanMakePaymentEvenWithoutApps,
                           ScopedJavaGlobalRef<jobject>(env, jcallback)));
 
-  service->Create(bridge->GetWeakPtr());
+  bridge->CreatePaymentApps();
 }
 
 namespace payments {
@@ -173,7 +171,7 @@
 
 /* static */
 PaymentAppServiceBridge* PaymentAppServiceBridge::Create(
-    size_t number_of_factories,
+    std::unique_ptr<PaymentAppService> payment_app_service,
     content::RenderFrameHost* render_frame_host,
     const GURL& top_origin,
     base::WeakPtr<PaymentRequestSpec> spec,
@@ -189,7 +187,7 @@
   DCHECK(render_frame_host);
   // Not using std::make_unique, because that requires a public constructor.
   std::unique_ptr<PaymentAppServiceBridge> bridge(new PaymentAppServiceBridge(
-      number_of_factories, render_frame_host, top_origin, spec,
+      std::move(payment_app_service), render_frame_host, top_origin, spec,
       twa_package_name, std::move(web_data_service), is_off_the_record,
       csp_checker, std::move(can_make_payment_calculated_callback),
       std::move(payment_app_created_callback),
@@ -199,44 +197,14 @@
   return PaymentAppServiceBridgeStorage::GetInstance()->Add(std::move(bridge));
 }
 
-PaymentAppServiceBridge::PaymentAppServiceBridge(
-    size_t number_of_factories,
-    content::RenderFrameHost* render_frame_host,
-    const GURL& top_origin,
-    base::WeakPtr<PaymentRequestSpec> spec,
-    const std::string& twa_package_name,
-    scoped_refptr<PaymentManifestWebDataService> web_data_service,
-    bool is_off_the_record,
-    base::WeakPtr<CSPChecker> csp_checker,
-    CanMakePaymentCalculatedCallback can_make_payment_calculated_callback,
-    PaymentAppCreatedCallback payment_app_created_callback,
-    PaymentAppCreationErrorCallback payment_app_creation_error_callback,
-    base::OnceClosure done_creating_payment_apps_callback,
-    base::RepeatingClosure set_can_make_payment_even_without_apps_callback)
-    : number_of_pending_factories_(number_of_factories),
-      frame_routing_id_(render_frame_host->GetGlobalId()),
-      top_origin_(top_origin),
-      frame_origin_(url_formatter::FormatUrlForSecurityDisplay(
-          render_frame_host->GetLastCommittedURL())),
-      frame_security_origin_(render_frame_host->GetLastCommittedOrigin()),
-      spec_(spec),
-      twa_package_name_(twa_package_name),
-      payment_manifest_web_data_service_(web_data_service),
-      is_off_the_record_(is_off_the_record),
-      csp_checker_(csp_checker),
-      can_make_payment_calculated_callback_(
-          std::move(can_make_payment_calculated_callback)),
-      payment_app_created_callback_(std::move(payment_app_created_callback)),
-      payment_app_creation_error_callback_(
-          std::move(payment_app_creation_error_callback)),
-      done_creating_payment_apps_callback_(
-          std::move(done_creating_payment_apps_callback)),
-      set_can_make_payment_even_without_apps_callback_(
-          std::move(set_can_make_payment_even_without_apps_callback)) {}
-
 PaymentAppServiceBridge::~PaymentAppServiceBridge() = default;
 
-base::WeakPtr<PaymentAppServiceBridge> PaymentAppServiceBridge::GetWeakPtr() {
+void PaymentAppServiceBridge::CreatePaymentApps() {
+  payment_app_service_->Create(weak_ptr_factory_.GetWeakPtr());
+}
+
+base::WeakPtr<PaymentAppServiceBridge>
+PaymentAppServiceBridge::GetWeakPtrForTest() {
   return weak_ptr_factory_.GetWeakPtr();
 }
 
@@ -373,4 +341,41 @@
   return csp_checker_;
 }
 
+PaymentAppServiceBridge::PaymentAppServiceBridge(
+    std::unique_ptr<PaymentAppService> payment_app_service,
+    content::RenderFrameHost* render_frame_host,
+    const GURL& top_origin,
+    base::WeakPtr<PaymentRequestSpec> spec,
+    const std::string& twa_package_name,
+    scoped_refptr<PaymentManifestWebDataService> web_data_service,
+    bool is_off_the_record,
+    base::WeakPtr<CSPChecker> csp_checker,
+    CanMakePaymentCalculatedCallback can_make_payment_calculated_callback,
+    PaymentAppCreatedCallback payment_app_created_callback,
+    PaymentAppCreationErrorCallback payment_app_creation_error_callback,
+    base::OnceClosure done_creating_payment_apps_callback,
+    base::RepeatingClosure set_can_make_payment_even_without_apps_callback)
+    : payment_app_service_(std::move(payment_app_service)),
+      number_of_pending_factories_(
+          payment_app_service_->GetNumberOfFactories()),
+      frame_routing_id_(render_frame_host->GetGlobalId()),
+      top_origin_(top_origin),
+      frame_origin_(url_formatter::FormatUrlForSecurityDisplay(
+          render_frame_host->GetLastCommittedURL())),
+      frame_security_origin_(render_frame_host->GetLastCommittedOrigin()),
+      spec_(spec),
+      twa_package_name_(twa_package_name),
+      payment_manifest_web_data_service_(web_data_service),
+      is_off_the_record_(is_off_the_record),
+      csp_checker_(csp_checker),
+      can_make_payment_calculated_callback_(
+          std::move(can_make_payment_calculated_callback)),
+      payment_app_created_callback_(std::move(payment_app_created_callback)),
+      payment_app_creation_error_callback_(
+          std::move(payment_app_creation_error_callback)),
+      done_creating_payment_apps_callback_(
+          std::move(done_creating_payment_apps_callback)),
+      set_can_make_payment_even_without_apps_callback_(
+          std::move(set_can_make_payment_even_without_apps_callback)) {}
+
 }  // namespace payments
diff --git a/components/payments/content/android/payment_app_service_bridge.h b/components/payments/content/android/payment_app_service_bridge.h
index 15220354..f2f087a 100644
--- a/components/payments/content/android/payment_app_service_bridge.h
+++ b/components/payments/content/android/payment_app_service_bridge.h
@@ -26,6 +26,7 @@
 }  // namespace content
 
 namespace payments {
+class PaymentAppService;
 
 // A bridge that holds parameters needed by PaymentAppService and redirects
 // callbacks from PaymentAppFactory to callbacks set by the caller.
@@ -39,10 +40,10 @@
                                    AppCreationFailureReason)>;
 
   // Creates a new PaymentAppServiceBridge. This object is self-deleting; its
-  // memory is freed when OnDoneCreatingPaymentApps() is called
-  // `number_of_factories` times. The `spec` parameter should not be null.
+  // memory is freed after CreatePaymentApps() is invoked and
+  // OnDoneCreatingPaymentApps() is called `number_of_pending_factories_` times.
   static PaymentAppServiceBridge* Create(
-      size_t number_of_factories,
+      std::unique_ptr<PaymentAppService> payment_app_service,
       content::RenderFrameHost* render_frame_host,
       const GURL& top_origin,
       base::WeakPtr<PaymentRequestSpec> spec,
@@ -62,7 +63,9 @@
   PaymentAppServiceBridge(const PaymentAppServiceBridge&) = delete;
   PaymentAppServiceBridge& operator=(const PaymentAppServiceBridge&) = delete;
 
-  base::WeakPtr<PaymentAppServiceBridge> GetWeakPtr();
+  void CreatePaymentApps();
+
+  base::WeakPtr<PaymentAppServiceBridge> GetWeakPtrForTest();
 
   // PaymentAppFactory::Delegate
   content::WebContents* GetWebContents() override;
@@ -96,10 +99,9 @@
   base::WeakPtr<CSPChecker> GetCSPChecker() override;
 
  private:
-  // Prevents direct instantiation. Callers should use Create() instead. The
-  // `spec` parameter should not be null.
+  // Prevents direct instantiation. Callers should use Create() instead.
   PaymentAppServiceBridge(
-      size_t number_of_factories,
+      std::unique_ptr<PaymentAppService> payment_app_service,
       content::RenderFrameHost* render_frame_host,
       const GURL& top_origin,
       base::WeakPtr<PaymentRequestSpec> spec,
@@ -113,6 +115,7 @@
       base::OnceClosure done_creating_payment_apps_callback,
       base::RepeatingClosure set_can_make_payment_even_without_apps_callback);
 
+  const std::unique_ptr<PaymentAppService> payment_app_service_;
   size_t number_of_pending_factories_;
   content::GlobalRenderFrameHostId frame_routing_id_;
   const GURL top_origin_;
diff --git a/components/payments/content/payment_app_service.cc b/components/payments/content/payment_app_service.cc
index d81a841..906f7c57 100644
--- a/components/payments/content/payment_app_service.cc
+++ b/components/payments/content/payment_app_service.cc
@@ -53,10 +53,6 @@
   }
 }
 
-void PaymentAppService::Shutdown() {
-  factories_.clear();
-}
-
 void PaymentAppService::AddFactoryForTesting(
     std::unique_ptr<PaymentAppFactory> factory) {
   factories_.push_back(std::move(factory));
diff --git a/components/payments/content/payment_app_service.h b/components/payments/content/payment_app_service.h
index 54a584b7..379a1aa 100644
--- a/components/payments/content/payment_app_service.h
+++ b/components/payments/content/payment_app_service.h
@@ -10,7 +10,6 @@
 #include <vector>
 
 #include "base/memory/weak_ptr.h"
-#include "components/keyed_service/core/keyed_service.h"
 #include "components/payments/content/payment_app_factory.h"
 
 namespace content {
@@ -20,7 +19,7 @@
 namespace payments {
 
 // Retrieves payment apps of all types.
-class PaymentAppService : public KeyedService {
+class PaymentAppService {
  public:
   // The |context| pointer is not being saved.
   explicit PaymentAppService(content::BrowserContext* context);
@@ -28,7 +27,7 @@
   PaymentAppService(const PaymentAppService&) = delete;
   PaymentAppService& operator=(const PaymentAppService&) = delete;
 
-  ~PaymentAppService() override;
+  ~PaymentAppService();
 
   // Returns the number of payment app factories, which is the number of times
   // that |delegate->OnDoneCreatingPaymentApps()| will be called as a result of
@@ -38,9 +37,6 @@
   // Create payment apps for |delegate|.
   void Create(base::WeakPtr<PaymentAppFactory::Delegate> delegate);
 
-  // KeyedService implementation:
-  void Shutdown() override;
-
   void AddFactoryForTesting(std::unique_ptr<PaymentAppFactory> factory);
 
  private:
diff --git a/components/payments/content/payment_app_service_factory.cc b/components/payments/content/payment_app_service_factory.cc
deleted file mode 100644
index 39584dbf..0000000
--- a/components/payments/content/payment_app_service_factory.cc
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/payments/content/payment_app_service_factory.h"
-
-#include <utility>
-
-#include "base/memory/singleton.h"
-#include "components/keyed_service/content/browser_context_dependency_manager.h"
-#include "components/payments/content/payment_app_service.h"
-
-namespace payments {
-
-// static
-PaymentAppService* PaymentAppServiceFactory::GetForContext(
-    content::BrowserContext* context) {
-  auto* instance = GetInstance();
-  return instance->service_for_testing_
-             ? instance->service_for_testing_.get()
-             : static_cast<PaymentAppService*>(
-                   instance->GetServiceForBrowserContext(context,
-                                                         /*create=*/true));
-}
-
-// static
-void PaymentAppServiceFactory::SetForTesting(
-    std::unique_ptr<PaymentAppService> service) {
-  GetInstance()->service_for_testing_ = std::move(service);
-}
-
-// static
-PaymentAppServiceFactory* PaymentAppServiceFactory::GetInstance() {
-  return base::Singleton<PaymentAppServiceFactory>::get();
-}
-
-PaymentAppServiceFactory::PaymentAppServiceFactory()
-    : BrowserContextKeyedServiceFactory(
-          "PaymentAppService",
-          BrowserContextDependencyManager::GetInstance()) {}
-
-PaymentAppServiceFactory::~PaymentAppServiceFactory() = default;
-
-KeyedService* PaymentAppServiceFactory::BuildServiceInstanceFor(
-    content::BrowserContext* context) const {
-  return new PaymentAppService(context);
-}
-
-content::BrowserContext* PaymentAppServiceFactory::GetBrowserContextToUse(
-    content::BrowserContext* context) const {
-  // Returns non-null even for Incognito contexts so that a separate instance of
-  // a service is created for the Incognito context.
-  return context;
-}
-
-}  // namespace payments
diff --git a/components/payments/content/payment_app_service_factory.h b/components/payments/content/payment_app_service_factory.h
deleted file mode 100644
index 97c3b6e..0000000
--- a/components/payments/content/payment_app_service_factory.h
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_PAYMENTS_CONTENT_PAYMENT_APP_SERVICE_FACTORY_H_
-#define COMPONENTS_PAYMENTS_CONTENT_PAYMENT_APP_SERVICE_FACTORY_H_
-
-#include <memory>
-
-#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
-
-namespace base {
-template <typename>
-struct DefaultSingletonTraits;
-}  // namespace base
-
-namespace content {
-class BrowserContext;
-}  // namespace content
-
-namespace payments {
-
-class PaymentAppService;
-
-// Maps payment app service to browser context.
-class PaymentAppServiceFactory : public BrowserContextKeyedServiceFactory {
- public:
-  static PaymentAppService* GetForContext(content::BrowserContext* context);
-
-  PaymentAppServiceFactory(const PaymentAppServiceFactory&) = delete;
-  PaymentAppServiceFactory& operator=(const PaymentAppServiceFactory&) = delete;
-
-  // Used only in tests to set the |service| that is going to be returned from
-  // |GetForContext()|, even if |context| is null.
-  static void SetForTesting(std::unique_ptr<PaymentAppService> service);
-
- private:
-  friend struct base::DefaultSingletonTraits<PaymentAppServiceFactory>;
-
-  static PaymentAppServiceFactory* GetInstance();
-
-  PaymentAppServiceFactory();
-  ~PaymentAppServiceFactory() override;
-
-  // BrowserContextKeyedServiceFactory
-  KeyedService* BuildServiceInstanceFor(
-      content::BrowserContext* context) const override;
-  content::BrowserContext* GetBrowserContextToUse(
-      content::BrowserContext* context) const override;
-
-  std::unique_ptr<PaymentAppService> service_for_testing_;
-};
-
-}  // namespace payments
-
-#endif  // COMPONENTS_PAYMENTS_CONTENT_PAYMENT_APP_SERVICE_FACTORY_H_
diff --git a/components/payments/content/payment_request.cc b/components/payments/content/payment_request.cc
index ba4c542..a915441 100644
--- a/components/payments/content/payment_request.cc
+++ b/components/payments/content/payment_request.cc
@@ -196,6 +196,8 @@
       /*observer=*/weak_ptr_factory_.GetWeakPtr(),
       delegate_->GetApplicationLocale());
   state_ = std::make_unique<PaymentRequestState>(
+      std::make_unique<PaymentAppService>(
+          render_frame_host().GetBrowserContext()),
       &render_frame_host(), top_level_origin_, frame_origin_,
       frame_security_origin_, spec(),
       /*delegate=*/weak_ptr_factory_.GetWeakPtr(),
diff --git a/components/payments/content/payment_request_state.cc b/components/payments/content/payment_request_state.cc
index 155c7ebe..8c467a9 100644
--- a/components/payments/content/payment_request_state.cc
+++ b/components/payments/content/payment_request_state.cc
@@ -25,8 +25,6 @@
 #include "components/autofill/core/browser/validation.h"
 #include "components/payments/content/content_payment_request_delegate.h"
 #include "components/payments/content/payment_app.h"
-#include "components/payments/content/payment_app_service.h"
-#include "components/payments/content/payment_app_service_factory.h"
 #include "components/payments/content/payment_manifest_web_data_service.h"
 #include "components/payments/content/payment_response_helper.h"
 #include "components/payments/content/service_worker_payment_app.h"
@@ -60,6 +58,7 @@
 }  // namespace
 
 PaymentRequestState::PaymentRequestState(
+    std::unique_ptr<PaymentAppService> payment_app_service,
     content::RenderFrameHost* initiator_render_frame_host,
     const GURL& top_level_origin,
     const GURL& frame_origin,
@@ -71,7 +70,8 @@
     base::WeakPtr<ContentPaymentRequestDelegate> payment_request_delegate,
     base::WeakPtr<JourneyLogger> journey_logger,
     base::WeakPtr<CSPChecker> csp_checker)
-    : frame_routing_id_(initiator_render_frame_host->GetGlobalId()),
+    : payment_app_service_(std::move(payment_app_service)),
+      frame_routing_id_(initiator_render_frame_host->GetGlobalId()),
       top_origin_(top_level_origin),
       frame_origin_(frame_origin),
       frame_security_origin_(frame_security_origin),
@@ -85,10 +85,9 @@
       profile_comparator_(app_locale, *spec) {
   PopulateProfileCache();
 
-  PaymentAppService* service = PaymentAppServiceFactory::GetForContext(
-      initiator_render_frame_host->GetBrowserContext());
-  number_of_payment_app_factories_ = service->GetNumberOfFactories();
-  service->Create(weak_ptr_factory_.GetWeakPtr());
+  number_of_payment_app_factories_ =
+      payment_app_service_->GetNumberOfFactories();
+  payment_app_service_->Create(weak_ptr_factory_.GetWeakPtr());
 
   spec_->AddObserver(this);
 }
diff --git a/components/payments/content/payment_request_state.h b/components/payments/content/payment_request_state.h
index 53a78d2..35d545a6 100644
--- a/components/payments/content/payment_request_state.h
+++ b/components/payments/content/payment_request_state.h
@@ -14,6 +14,7 @@
 #include "base/observer_list.h"
 #include "components/payments/content/initialization_task.h"
 #include "components/payments/content/payment_app_factory.h"
+#include "components/payments/content/payment_app_service.h"
 #include "components/payments/content/payment_request_spec.h"
 #include "components/payments/content/payment_response_helper.h"
 #include "components/payments/content/service_worker_payment_app.h"
@@ -99,8 +100,8 @@
                               const std::string& error_message,
                               AppCreationFailureReason error_reason)>;
 
-  // The `spec` parameter should not be null.
   PaymentRequestState(
+      std::unique_ptr<PaymentAppService> payment_app_service,
       content::RenderFrameHost* initiator_render_frame_host,
       const GURL& top_level_origin,
       const GURL& frame_origin,
@@ -340,6 +341,8 @@
   bool GetCanMakePaymentValue() const;
   bool GetHasEnrolledInstrumentValue() const;
 
+  const std::unique_ptr<PaymentAppService> payment_app_service_;
+
   content::GlobalRenderFrameHostId frame_routing_id_;
   const GURL top_origin_;
   const GURL frame_origin_;
diff --git a/components/payments/content/payment_request_state_unittest.cc b/components/payments/content/payment_request_state_unittest.cc
index f77cce8..fe89285 100644
--- a/components/payments/content/payment_request_state_unittest.cc
+++ b/components/payments/content/payment_request_state_unittest.cc
@@ -17,7 +17,6 @@
 #include "components/autofill/core/browser/test_personal_data_manager.h"
 #include "components/payments/content/payment_app_factory.h"
 #include "components/payments/content/payment_app_service.h"
-#include "components/payments/content/payment_app_service_factory.h"
 #include "components/payments/content/payment_request_spec.h"
 #include "components/payments/content/test_content_payment_request_delegate.h"
 #include "components/payments/content/test_payment_app.h"
@@ -104,10 +103,9 @@
     spec_ = std::make_unique<PaymentRequestSpec>(
         std::move(options), std::move(details), std::move(method_data),
         /*observer=*/nullptr, "en-US");
-    PaymentAppServiceFactory::SetForTesting(std::move(app_service));
     state_ = std::make_unique<PaymentRequestState>(
-        web_contents_->GetPrimaryMainFrame(), GURL("https://example.com"),
-        GURL("https://example.com/pay"),
+        std::move(app_service), web_contents_->GetPrimaryMainFrame(),
+        GURL("https://example.com"), GURL("https://example.com/pay"),
         url::Origin::Create(GURL("https://example.com")), spec_->AsWeakPtr(),
         weak_ptr_factory_.GetWeakPtr(), "en-US", &test_personal_data_manager_,
         test_payment_request_delegate_.GetContentWeakPtr(),
diff --git a/components/payments/content/service_worker_payment_app_factory.cc b/components/payments/content/service_worker_payment_app_factory.cc
index 981708e..984a8f7 100644
--- a/components/payments/content/service_worker_payment_app_factory.cc
+++ b/components/payments/content/service_worker_payment_app_factory.cc
@@ -28,10 +28,9 @@
 
 class ServiceWorkerPaymentAppCreator {
  public:
-  ServiceWorkerPaymentAppCreator(
-      ServiceWorkerPaymentAppFactory* owner,
+  explicit ServiceWorkerPaymentAppCreator(
       base::WeakPtr<PaymentAppFactory::Delegate> delegate)
-      : owner_(owner), delegate_(delegate), log_(delegate->GetWebContents()) {}
+      : delegate_(delegate), log_(delegate->GetWebContents()) {}
 
   ServiceWorkerPaymentAppCreator(const ServiceWorkerPaymentAppCreator&) =
       delete;
@@ -155,10 +154,8 @@
   void FinishAndCleanup() {
     if (delegate_)
       delegate_->OnDoneCreatingPaymentApps();
-    owner_->DeleteCreator(this);
   }
 
-  raw_ptr<ServiceWorkerPaymentAppFactory> owner_;
   base::WeakPtr<PaymentAppFactory::Delegate> delegate_;
   std::map<PaymentApp*, std::unique_ptr<PaymentApp>> available_apps_;
   DeveloperConsoleLogger log_;
@@ -180,10 +177,7 @@
       !rfh->IsFeatureEnabled(blink::mojom::PermissionsPolicyFeature::kPayment))
     return;
 
-  auto creator = std::make_unique<ServiceWorkerPaymentAppCreator>(
-      /*owner=*/this, delegate);
-  ServiceWorkerPaymentAppCreator* creator_raw_pointer = creator.get();
-  creators_[creator_raw_pointer] = std::move(creator);
+  creator_ = std::make_unique<ServiceWorkerPaymentAppCreator>(delegate);
 
   ServiceWorkerPaymentAppFinder::GetOrCreateForCurrentDocument(rfh)
       ->GetAllPaymentApps(
@@ -191,17 +185,11 @@
           delegate->GetPaymentManifestWebDataService(),
           mojo::Clone(delegate->GetMethodData()), delegate->GetCSPChecker(),
           base::BindOnce(&ServiceWorkerPaymentAppCreator::CreatePaymentApps,
-                         creator_raw_pointer->GetWeakPtr()),
+                         creator_->GetWeakPtr()),
           base::BindOnce([]() {
             // Nothing needs to be done after writing cache. This callback is
             // used only in tests.
           }));
 }
 
-void ServiceWorkerPaymentAppFactory::DeleteCreator(
-    ServiceWorkerPaymentAppCreator* creator_raw_pointer) {
-  size_t number_of_deleted_creators = creators_.erase(creator_raw_pointer);
-  DCHECK_EQ(1U, number_of_deleted_creators);
-}
-
 }  // namespace payments
diff --git a/components/payments/content/service_worker_payment_app_factory.h b/components/payments/content/service_worker_payment_app_factory.h
index b2f48515..b0d17ea 100644
--- a/components/payments/content/service_worker_payment_app_factory.h
+++ b/components/payments/content/service_worker_payment_app_factory.h
@@ -29,14 +29,8 @@
   // PaymentAppFactory:
   void Create(base::WeakPtr<Delegate> delegate) override;
 
-  // Called by ServiceWorkerPaymentAppCreator to remove itself from the list of
-  // |creators_|, which deletes the caller object.
-  void DeleteCreator(ServiceWorkerPaymentAppCreator* creator_raw_pointer);
-
  private:
-  std::map<ServiceWorkerPaymentAppCreator*,
-           std::unique_ptr<ServiceWorkerPaymentAppCreator>>
-      creators_;
+  std::unique_ptr<ServiceWorkerPaymentAppCreator> creator_;
 };
 
 }  // namespace payments
diff --git a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionManifestV2Availability.yaml b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionManifestV2Availability.yaml
index 0b08ef66..1933fc0 100644
--- a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionManifestV2Availability.yaml
+++ b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionManifestV2Availability.yaml
@@ -1,7 +1,7 @@
 owners:
 - zmin@chromium.org
 - file://components/policy/OWNERS
-caption: Control manifest v2 extension availability.
+caption: Control manifest v2 extension availability
 desc: |-
   Control if manifest v2 extensions can be used by browser.
 
diff --git a/components/policy/resources/webui/status_box.html b/components/policy/resources/webui/status_box.html
index 4853fa6..0e54524 100644
--- a/components/policy/resources/webui/status_box.html
+++ b/components/policy/resources/webui/status_box.html
@@ -66,6 +66,10 @@
     <div class="client-id"></div>
   </div>
   <div class="status-entry" hidden>
+    <div class="label">$i18n{labelProfileId}</div>
+    <div class="profile-id"></div>
+  </div>
+  <div class="status-entry" hidden>
     <div class="label">$i18n{labelAssetId}</div>
     <div class="asset-id"></div>
   </div>
diff --git a/components/policy/resources/webui/status_box.js b/components/policy/resources/webui/status_box.js
index d407195..e8027fd 100644
--- a/components/policy/resources/webui/status_box.js
+++ b/components/policy/resources/webui/status_box.js
@@ -85,6 +85,8 @@
       // Populate the user gaia id.
       this.setLabelAndShow_('.gaia-id', status.gaiaId || notSpecifiedString);
       this.setLabelAndShow_('.client-id', status.clientId);
+      this.setLabelAndShow_('.profile-id', status.profileId);
+
       if (status.isAffiliated != null) {
         this.setLabelAndShow_(
             '.is-affiliated',
diff --git a/components/policy_strings.grdp b/components/policy_strings.grdp
index fbf20e94..e09af5f6 100644
--- a/components/policy_strings.grdp
+++ b/components/policy_strings.grdp
@@ -422,6 +422,9 @@
   <message name="IDS_POLICY_LABEL_CLIENT_ID" desc="Label for the client IDs in the policy status boxes.">
     Client ID:
   </message>
+  <message name="IDS_POLICY_LABEL_PROFILE_ID" desc="Label for the user profile ID in the user policy status box.">
+    Profile ID:
+  </message>
   <message name="IDS_POLICY_LABEL_ASSET_ID" desc="Label for the asset ID in the device policy status box.">
     Asset ID:
   </message>
diff --git a/components/policy_strings_grdp/IDS_POLICY_LABEL_PROFILE_ID.png.sha1 b/components/policy_strings_grdp/IDS_POLICY_LABEL_PROFILE_ID.png.sha1
new file mode 100644
index 0000000..4cc9b827
--- /dev/null
+++ b/components/policy_strings_grdp/IDS_POLICY_LABEL_PROFILE_ID.png.sha1
@@ -0,0 +1 @@
+cfa02041b09a032541ccd7e49381226210671cd9
\ No newline at end of file
diff --git a/components/power_bookmarks/core/proto/shopping_specifics.proto b/components/power_bookmarks/core/proto/shopping_specifics.proto
index b1324af33..f67a4c68 100644
--- a/components/power_bookmarks/core/proto/shopping_specifics.proto
+++ b/components/power_bookmarks/core/proto/shopping_specifics.proto
@@ -34,6 +34,10 @@
 
   // The country code of the offer.
   optional string country_code = 7;
+
+  // The previous price of the product if available. This value can be used
+  // for price change events when compared to |current_price|.
+  optional ProductPrice previous_price = 8;
 }
 
 message ProductPrice {
diff --git a/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier.cc b/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier.cc
index 71d396a..c331cb4 100644
--- a/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier.cc
+++ b/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier.cc
@@ -208,8 +208,7 @@
       base::UnguessableToken::Create(), frame->GetEmbeddingToken(),
       /*is_main_frame=*/true);
   cc::PaintRecorder recorder;
-  cc::PaintCanvas* canvas =
-      recorder.beginRecording(bounds.width(), bounds.height());
+  cc::PaintCanvas* canvas = recorder.beginRecording();
   canvas->SetPaintPreviewTracker(tracker.get());
 
   if (!frame->CapturePaintPreview(bounds, canvas,
diff --git a/components/safe_browsing/core/browser/db/v4_store.cc b/components/safe_browsing/core/browser/db/v4_store.cc
index d0d51cda..55be2ad 100644
--- a/components/safe_browsing/core/browser/db/v4_store.cc
+++ b/components/safe_browsing/core/browser/db/v4_store.cc
@@ -567,18 +567,23 @@
 }
 
 // static
-void V4Store::ReserveSpaceInPrefixMap(const HashPrefixMap& other_prefixes_map,
+void V4Store::ReserveSpaceInPrefixMap(const HashPrefixMap& old_map,
+                                      const HashPrefixMap& additions_map,
+                                      size_t removals_count,
                                       HashPrefixMap* prefix_map_to_update) {
-  for (const auto& pair : other_prefixes_map.view()) {
-    PrefixSize prefix_size = pair.first;
-    size_t prefix_length_to_add = pair.second.length();
+  std::unordered_map<PrefixSize, size_t> size_to_reserve;
+  for (const auto& [prefix_size, prefixes] : old_map.view())
+    size_to_reserve[prefix_size] += prefixes.size();
+  for (const auto& [prefix_size, prefixes] : additions_map.view())
+    size_to_reserve[prefix_size] += prefixes.size();
 
-    HashPrefixesView existing_prefixes =
-        prefix_map_to_update->view()[prefix_size];
-    size_t existing_capacity = existing_prefixes.size();
-
-    prefix_map_to_update->Reserve(prefix_size,
-                                  existing_capacity + prefix_length_to_add);
+  for (const auto& [prefix_size, capacity] : size_to_reserve) {
+    // Subtract the removals from capacity. Note this probably overcounts the
+    // removals since we subtract from all prefix sizes, but this shouldn't
+    // matter in practice since we usually only use a single prefix size per
+    // store.
+    size_t removals_size = std::min(capacity, removals_count * prefix_size);
+    prefix_map_to_update->Reserve(prefix_size, capacity - removals_size);
   }
 }
 
@@ -596,8 +601,9 @@
   }
 
   hash_prefix_map_->Clear();
-  ReserveSpaceInPrefixMap(old_prefixes_map, hash_prefix_map_.get());
-  ReserveSpaceInPrefixMap(additions_map, hash_prefix_map_.get());
+  ReserveSpaceInPrefixMap(old_prefixes_map, additions_map,
+                          raw_removals ? raw_removals->size() : 0,
+                          hash_prefix_map_.get());
 
   IteratorMap old_iterator_map;
   HashPrefix next_smallest_prefix_old;
diff --git a/components/safe_browsing/core/browser/db/v4_store.h b/components/safe_browsing/core/browser/db/v4_store.h
index c7c9fb90..f9002a8 100644
--- a/components/safe_browsing/core/browser/db/v4_store.h
+++ b/components/safe_browsing/core/browser/db/v4_store.h
@@ -254,6 +254,8 @@
   FRIEND_TEST_ALL_PREFIXES(V4StoreTest, MigrateToInMemoryFails);
   FRIEND_TEST_ALL_PREFIXES(V4StoreTest, CleanUpOldFiles);
   FRIEND_TEST_ALL_PREFIXES(V4StoreTest, FileSizeIncludesHashFiles);
+  FRIEND_TEST_ALL_PREFIXES(V4StoreTest, ReserveSpaceInPrefixMap);
+  FRIEND_TEST_ALL_PREFIXES(V4StoreTest, MergeUpdatesWithMmapHashPrefixMap);
   FRIEND_TEST_ALL_PREFIXES(V4StorePerftest, StressTest);
 
   friend class V4StoreTest;
@@ -293,7 +295,9 @@
   // deletions specified in the update because it is non-trivial to calculate
   // those deletions upfront. This isn't so bad since deletions are supposed to
   // be small and infrequent.
-  static void ReserveSpaceInPrefixMap(const HashPrefixMap& other_prefixes_map,
+  static void ReserveSpaceInPrefixMap(const HashPrefixMap& old_map,
+                                      const HashPrefixMap& additions_map,
+                                      size_t removals_count,
                                       HashPrefixMap* prefix_map_to_update);
 
   // Same as the public GetMatchingHashPrefix method, but takes a StringPiece,
diff --git a/components/safe_browsing/core/browser/db/v4_store_unittest.cc b/components/safe_browsing/core/browser/db/v4_store_unittest.cc
index a5d33f00..47ab575f 100644
--- a/components/safe_browsing/core/browser/db/v4_store_unittest.cc
+++ b/components/safe_browsing/core/browser/db/v4_store_unittest.cc
@@ -1142,4 +1142,83 @@
   EXPECT_EQ(read_store.file_size(), original_file_size + 4);
 }
 
+TEST_F(V4StoreTest, ReserveSpaceInPrefixMap) {
+  class ReserveTrackingHashPrefixMap : public InMemoryHashPrefixMap {
+   public:
+    void Reserve(PrefixSize size, size_t capacity) override {
+      reserve_map_[size] = capacity;
+    }
+
+    std::unordered_map<PrefixSize, size_t> reserve_map_;
+  };
+
+  InMemoryHashPrefixMap old_map;
+  InMemoryHashPrefixMap additions_map;
+  old_map.Append(4, "abcdefgh");
+  old_map.Append(5, "abcdefghij");
+  additions_map.Append(4, "123456789012zzzz");
+  additions_map.Append(5, "1234567890");
+
+  ReserveTrackingHashPrefixMap reserve_map;
+  V4Store::ReserveSpaceInPrefixMap(old_map, additions_map, 0, &reserve_map);
+
+  EXPECT_EQ(reserve_map.reserve_map_[4], 24u);
+  EXPECT_EQ(reserve_map.reserve_map_[5], 20u);
+
+  ReserveTrackingHashPrefixMap reserve_map_with_removals;
+  V4Store::ReserveSpaceInPrefixMap(old_map, additions_map, 2,
+                                   &reserve_map_with_removals);
+
+  EXPECT_EQ(reserve_map_with_removals.reserve_map_[4], 16u);
+  EXPECT_EQ(reserve_map_with_removals.reserve_map_[5], 10u);
+}
+
+TEST_F(V4StoreTest, MergeUpdatesWithMmapHashPrefixMap) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeatureWithParameters(
+      kMmapSafeBrowsingDatabase, {{"store-bytes-per-offset", "2"}});
+
+  InMemoryHashPrefixMap prefix_map_old;
+  prefix_map_old.Append(4, "abcdefgh");
+  prefix_map_old.Append(5, "54321abcde");
+
+  InMemoryHashPrefixMap prefix_map_additions;
+  prefix_map_additions.Append(4, "----1111bbbb");
+  prefix_map_additions.Append(5, "22222bcdef");
+
+  V4Store store(task_runner_, store_path_,
+                std::make_unique<MmapHashPrefixMap>(store_path_));
+  // Proof of checksum validity using python:
+  // >>> import hashlib
+  // >>> m = hashlib.sha256()
+  // >>> m.update("----11112222254321abcdabcdebbbbbcdefefgh")
+  // >>> m.digest()
+  // "\xbc\xb3\xedk\xe3x\xd1(\xa9\xedz7]"
+  // "x\x18\xbdn]\xa5\xa8R\xf7\xab\xcf\xc1\xa3\xa3\xc5Z,\xa6o"
+  std::string expected_checksum(
+      "\xBC\xB3\xEDk\xE3x\xD1(\xA9\xEDz7]x\x18\xBDn]"
+      "\xA5\xA8R\xF7\xAB\xCF\xC1\xA3\xA3\xC5Z,\xA6o",
+      crypto::kSHA256Length);
+  EXPECT_EQ(APPLY_UPDATE_SUCCESS,
+            store.MergeUpdate(prefix_map_old, prefix_map_additions, nullptr,
+                              expected_checksum));
+
+  EXPECT_EQ(WRITE_SUCCESS, store.WriteToDisk(Checksum()));
+  EXPECT_EQ(store.hash_prefix_map_->IsValid(), APPLY_UPDATE_SUCCESS);
+
+  HashPrefixMapView prefix_map = store.hash_prefix_map_->view();
+  EXPECT_EQ(2u, prefix_map.size());
+  EXPECT_EQ(prefix_map[4], "----1111abcdbbbbefgh");
+  EXPECT_EQ(prefix_map[5], "2222254321abcdebcdef");
+
+  std::string proto_contents;
+  EXPECT_TRUE(base::ReadFileToString(store_path_, &proto_contents));
+  V4StoreFileFormat file_format;
+  EXPECT_TRUE(file_format.ParseFromString(proto_contents));
+
+  EXPECT_EQ(file_format.hash_files().size(), 2);
+  for (const auto& hash_file : file_format.hash_files())
+    EXPECT_EQ(hash_file.offsets().size(), 10);
+}
+
 }  // namespace safe_browsing
diff --git a/components/search_engines/default_search_manager.cc b/components/search_engines/default_search_manager.cc
index 49c73ced..31639ae 100644
--- a/components/search_engines/default_search_manager.cc
+++ b/components/search_engines/default_search_manager.cc
@@ -68,6 +68,7 @@
     "side_image_search_param";
 const char DefaultSearchManager::kImageSearchBrandingLabel[] =
     "image_search_branding_label";
+const char DefaultSearchManager::kSearchIntentParams[] = "search_intent_params";
 
 const char DefaultSearchManager::kSafeForAutoReplace[] = "safe_for_autoreplace";
 const char DefaultSearchManager::kInputEncodings[] = "input_encodings";
diff --git a/components/search_engines/default_search_manager.h b/components/search_engines/default_search_manager.h
index 732acff..eff8ca3 100644
--- a/components/search_engines/default_search_manager.h
+++ b/components/search_engines/default_search_manager.h
@@ -48,6 +48,7 @@
   static const char kSideSearchParam[];
   static const char kSideImageSearchParam[];
   static const char kImageSearchBrandingLabel[];
+  static const char kSearchIntentParams[];
 
   static const char kSafeForAutoReplace[];
   static const char kInputEncodings[];
diff --git a/components/search_engines/prepopulated_engines.json b/components/search_engines/prepopulated_engines.json
index 5a0bf4d..b5e448c2 100644
--- a/components/search_engines/prepopulated_engines.json
+++ b/components/search_engines/prepopulated_engines.json
@@ -28,7 +28,7 @@
     // Increment this if you change the data in ways that mean users with
     // existing data should get a new version. Otherwise, existing data may
     // continue to be used and updates made here will not always appear.
-    "kCurrentDataVersion": 134
+    "kCurrentDataVersion": 135
   },
 
   // The following engines are included in country lists and are added to the
@@ -125,6 +125,7 @@
       "side_search_param": "sidesearch",
       "side_image_search_param": "sideimagesearch",
       "image_search_branding_label": "Google Lens",
+      "search_intent_params": ["si", "gs_ssp"],
       "alternate_urls": [
         "{google:baseURL}#q={searchTerms}",
         "{google:baseURL}search#q={searchTerms}",
diff --git a/components/search_engines/prepopulated_engines_schema.json b/components/search_engines/prepopulated_engines_schema.json
index 18d33392..208f636 100644
--- a/components/search_engines/prepopulated_engines_schema.json
+++ b/components/search_engines/prepopulated_engines_schema.json
@@ -57,6 +57,16 @@
     // The label to use for image search related queries. If omitted, will use the
     // value under the "name" field for image search queries.
     { "field": "image_search_branding_label", "type": "string16", "optional": true},
+    // The parameters making up the engine's canonical search URL in addition to
+    // the search terms. These params disambiguate the search terms and
+    // determine the fulfillment. For instance the same "Harry Potter" search
+    // terms may refer to the novel series or the film series.
+    {
+      "field": "search_intent_params",
+      "type": "array",
+      "contents": { "type": "string" },
+      "optional": true
+    },
     // A list of URL patterns that can be used, in addition to |search_url|,
     // to extract search terms from a URL.
     // If "search_url_post_params" is not empty, then all alternate URLs are
diff --git a/components/search_engines/template_url.cc b/components/search_engines/template_url.cc
index 30abb2c..5f52c4c 100644
--- a/components/search_engines/template_url.cc
+++ b/components/search_engines/template_url.cc
@@ -24,6 +24,7 @@
 #include "base/notreached.h"
 #include "base/ranges/algorithm.h"
 #include "base/strings/escape.h"
+#include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/string_split.h"
@@ -1612,6 +1613,38 @@
       !search_terms.empty();
 }
 
+bool TemplateURL::KeepSearchTermsInURL(const GURL& url,
+                                       const SearchTermsData& search_terms_data,
+                                       const bool keep_search_intent_params,
+                                       GURL* result) const {
+  std::u16string search_terms;
+  if (!ExtractSearchTermsFromURL(url, search_terms_data, &search_terms) ||
+      search_terms.empty()) {
+    return false;
+  }
+
+  if (!url_ref().SupportsReplacement(search_terms_data)) {
+    return false;
+  }
+
+  std::vector<std::string> query_params;
+  if (keep_search_intent_params && !data_.search_intent_params.empty()) {
+    for (net::QueryIterator it(url); !it.IsAtEnd(); it.Advance()) {
+      if (!base::Contains(data_.search_intent_params, it.GetKey())) {
+        continue;
+      }
+      query_params.push_back(base::StrCat({it.GetKey(), "=", it.GetValue()}));
+    }
+  }
+
+  TemplateURLRef::SearchTermsArgs search_terms_args(search_terms);
+  search_terms_args.additional_query_params =
+      base::JoinString(query_params, "&");
+  *result =
+      GURL(url_ref().ReplaceSearchTerms(search_terms_args, search_terms_data));
+  return true;
+}
+
 bool TemplateURL::ReplaceSearchTermsInURL(
     const GURL& url,
     const TemplateURLRef::SearchTermsArgs& search_terms_args,
diff --git a/components/search_engines/template_url.h b/components/search_engines/template_url.h
index 0c0d894..e72a1b66 100644
--- a/components/search_engines/template_url.h
+++ b/components/search_engines/template_url.h
@@ -726,6 +726,9 @@
                ? data_.image_search_branding_label
                : short_name();
   }
+  const std::vector<std::string>& search_intent_params() const {
+    return data_.search_intent_params;
+  }
   const std::vector<std::string>& alternate_urls() const {
     return data_.alternate_urls;
   }
@@ -829,6 +832,15 @@
   bool IsSearchURL(const GURL& url,
                    const SearchTermsData& search_terms_data) const;
 
+  // Given a |url| corresponding to this TemplateURL, keeps the search terms and
+  // optionally the search intent params and removes the other params. If |url|
+  // is not a search URL or replacement fails, leaves |result| untouched and
+  // returns false. Used to compare normalized (aka canonical) search URLs.
+  bool KeepSearchTermsInURL(const GURL& url,
+                            const SearchTermsData& search_terms_data,
+                            const bool keep_search_intent_params,
+                            GURL* result) const;
+
   // Given a |url| corresponding to this TemplateURL, identifies the search
   // terms and replaces them with the ones in |search_terms_args|, leaving the
   // other parameters untouched. If the replacement fails, returns false and
diff --git a/components/search_engines/template_url_data.cc b/components/search_engines/template_url_data.cc
index ae8d368..38a02d91 100644
--- a/components/search_engines/template_url_data.cc
+++ b/components/search_engines/template_url_data.cc
@@ -74,6 +74,7 @@
     base::StringPiece image_url_post_params,
     base::StringPiece side_search_param,
     base::StringPiece side_image_search_param,
+    std::vector<std::string> search_intent_params,
     base::StringPiece favicon_url,
     base::StringPiece encoding,
     base::StringPiece16 image_search_branding_label,
@@ -93,6 +94,7 @@
       side_search_param(side_search_param),
       side_image_search_param(side_image_search_param),
       image_search_branding_label(image_search_branding_label),
+      search_intent_params(search_intent_params),
       favicon_url(favicon_url),
       safe_for_autoreplace(true),
       id(0),
diff --git a/components/search_engines/template_url_data.h b/components/search_engines/template_url_data.h
index 71696f3..31766db 100644
--- a/components/search_engines/template_url_data.h
+++ b/components/search_engines/template_url_data.h
@@ -41,6 +41,7 @@
                   base::StringPiece image_url_post_params,
                   base::StringPiece side_search_param,
                   base::StringPiece side_image_search_param,
+                  std::vector<std::string> search_intent_params,
                   base::StringPiece favicon_url,
                   base::StringPiece encoding,
                   base::StringPiece16 image_search_branding_label,
@@ -106,6 +107,11 @@
   // will be used.
   std::u16string image_search_branding_label;
 
+  // The parameters making up the engine's canonical search URL in addition to
+  // the search terms. These params disambiguate the search terms and determine
+  // the fulfillment.
+  std::vector<std::string> search_intent_params;
+
   // Favicon for the TemplateURL.
   GURL favicon_url;
 
diff --git a/components/search_engines/template_url_data_unittest.cc b/components/search_engines/template_url_data_unittest.cc
index 35d3e22e..b4de428 100644
--- a/components/search_engines/template_url_data_unittest.cc
+++ b/components/search_engines/template_url_data_unittest.cc
@@ -15,7 +15,7 @@
       base::StringPiece(), base::StringPiece(), base::StringPiece(),
       base::StringPiece(), base::StringPiece(), base::StringPiece(),
       base::StringPiece(), base::StringPiece(), base::StringPiece(),
-      base::StringPiece(), base::StringPiece(), base::StringPiece(),
+      base::StringPiece(), base::StringPiece(), {}, base::StringPiece(),
       base::StringPiece(), base::StringPiece16(), base::ListValue(), false,
       false, 0);
 
diff --git a/components/search_engines/template_url_data_util.cc b/components/search_engines/template_url_data_util.cc
index 669f92a1..5cd6bade 100644
--- a/components/search_engines/template_url_data_util.cc
+++ b/components/search_engines/template_url_data_util.cc
@@ -124,6 +124,17 @@
     result->image_search_branding_label =
         base::UTF8ToUTF16(*image_search_branding_label);
   }
+  const base::Value::List* additional_params_list =
+      dict.FindList(DefaultSearchManager::kSearchIntentParams);
+  if (additional_params_list) {
+    for (const auto& additional_param_value : *additional_params_list) {
+      const auto* additional_param = additional_param_value.GetIfString();
+      DCHECK(additional_param && !additional_param->empty());
+      if (additional_param && !additional_param->empty()) {
+        result->search_intent_params.push_back(*additional_param);
+      }
+    }
+  }
   absl::optional<bool> safe_for_autoreplace =
       dict.FindBool(DefaultSearchManager::kSafeForAutoReplace);
   if (safe_for_autoreplace) {
@@ -240,6 +251,13 @@
   url_dict->SetStringKey(DefaultSearchManager::kImageSearchBrandingLabel,
                          data.image_search_branding_label);
 
+  base::Value::List additional_params_list;
+  for (const auto& additional_param : data.search_intent_params) {
+    additional_params_list.Append(additional_param);
+  }
+  url_dict->GetDict().Set(DefaultSearchManager::kSearchIntentParams,
+                          std::move(additional_params_list));
+
   url_dict->SetBoolKey(DefaultSearchManager::kSafeForAutoReplace,
                        data.safe_for_autoreplace);
 
@@ -281,6 +299,13 @@
 
 std::unique_ptr<TemplateURLData> TemplateURLDataFromPrepopulatedEngine(
     const TemplateURLPrepopulateData::PrepopulatedEngine& engine) {
+  std::vector<std::string> search_intent_params;
+  if (engine.search_intent_params) {
+    for (size_t i = 0; i < engine.search_intent_params_size; ++i) {
+      search_intent_params.emplace_back(engine.search_intent_params[i]);
+    }
+  }
+
   base::ListValue alternate_urls;
   if (engine.alternate_urls) {
     for (size_t i = 0; i < engine.alternate_urls_size; ++i)
@@ -303,8 +328,9 @@
       ToStringPiece(engine.image_url_post_params),
       ToStringPiece(engine.side_search_param),
       ToStringPiece(engine.side_image_search_param),
-      ToStringPiece(engine.favicon_url), ToStringPiece(engine.encoding),
-      image_search_branding_label, alternate_urls,
+      std::move(search_intent_params), ToStringPiece(engine.favicon_url),
+      ToStringPiece(engine.encoding), image_search_branding_label,
+      alternate_urls,
       ToStringPiece(engine.preconnect_to_search_url) == "ALLOWED",
       ToStringPiece(engine.prefetch_likely_navigations) == "ALLOWED",
       engine.id);
@@ -363,6 +389,7 @@
     std::string side_search_param;
     std::string side_image_search_param;
     std::u16string image_search_branding_label;
+    std::vector<std::string> search_intent_params;
     std::string preconnect_to_search_url;
     std::string prefetch_likely_navigations;
 
@@ -414,6 +441,16 @@
     if (string_value) {
       image_search_branding_label = base::UTF8ToUTF16(*string_value);
     }
+    const base::Value::List* additional_params_list =
+        engine.GetDict().FindList(DefaultSearchManager::kSearchIntentParams);
+    if (additional_params_list) {
+      for (const auto& additional_param_value : *additional_params_list) {
+        const auto* additional_param = additional_param_value.GetIfString();
+        if (additional_param && !additional_param->empty()) {
+          search_intent_params.push_back(*additional_param);
+        }
+      }
+    }
     string_value = engine.FindStringKey("preconnect_to_search_url");
     if (string_value) {
       preconnect_to_search_url = *string_value;
@@ -427,8 +464,8 @@
         name, keyword, search_url, suggest_url, image_url, new_tab_url,
         contextual_search_url, logo_url, doodle_url, search_url_post_params,
         suggest_url_post_params, image_url_post_params, side_search_param,
-        side_image_search_param, favicon_url, encoding,
-        image_search_branding_label, *alternate_urls,
+        side_image_search_param, std::move(search_intent_params), favicon_url,
+        encoding, image_search_branding_label, *alternate_urls,
         preconnect_to_search_url.compare("ALLOWED") == 0,
         prefetch_likely_navigations.compare("ALLOWED") == 0, *id);
   }
diff --git a/components/search_engines/template_url_service_util_unittest.cc b/components/search_engines/template_url_service_util_unittest.cc
index 76930f6..396b22de 100644
--- a/components/search_engines/template_url_service_util_unittest.cc
+++ b/components/search_engines/template_url_service_util_unittest.cc
@@ -26,8 +26,9 @@
       "" /* contextual_search_url */, "" /* logo_url */, "" /* doodle_url */,
       "" /* search_url_post_params */, "" /* suggest_url_post_params */,
       "" /* image_url_post_params */, "" /* side_search_param */,
-      "" /* side_image_search_param */, "" /* favicon_url */, "UTF-8",
-      u"" /* image_search_branding_label */,
+      "" /* side_image_search_param */,
+      std::vector<std::string>() /* search_intent_params */,
+      "" /* favicon_url */, "UTF-8", u"" /* image_search_branding_label */,
       base::ListValue() /* alternate_urls_list */,
       false /* preconnect_to_search_url */,
       false /* prefetch_likely_navigations */, prepopulate_id);
diff --git a/components/search_engines/template_url_unittest.cc b/components/search_engines/template_url_unittest.cc
index d43c841..2eeb81c3 100644
--- a/components/search_engines/template_url_unittest.cc
+++ b/components/search_engines/template_url_unittest.cc
@@ -2036,6 +2036,100 @@
       TemplateURL::GenerateKeyword(GURL("http://embeddedhtml.<head>/"))));
 }
 
+TEST_F(TemplateURLTest, KeepSearchTermsInURL) {
+  search_terms_data_.set_google_base_url("http://bar/");
+
+  TemplateURLData data;
+  data.SetURL("http://bar/search?q={searchTerms}&{google:sessionToken}xssi=t");
+  data.search_intent_params = {"gs_ssp", "si"};
+  TemplateURL turl(data);
+
+  TemplateURLRef::SearchTermsArgs search_terms_args(u"foo");
+  search_terms_args.session_token = "SESSIONTOKEN";
+
+  {
+    // Optionally keeps non-empty search intent params.
+    search_terms_args.additional_query_params = "gs_ssp=GS_SSP";
+    std::string original_search_url = turl.url_ref().ReplaceSearchTerms(
+        search_terms_args, search_terms_data_);
+    EXPECT_EQ("http://bar/search?gs_ssp=GS_SSP&q=foo&psi=SESSIONTOKEN&xssi=t",
+              original_search_url);
+
+    GURL canonical_search_url;
+    EXPECT_TRUE(turl.KeepSearchTermsInURL(
+        GURL(original_search_url), search_terms_data_,
+        /*keep_search_intent_params=*/true, &canonical_search_url));
+    EXPECT_EQ("http://bar/search?gs_ssp=GS_SSP&q=foo&xssi=t",
+              canonical_search_url);
+
+    EXPECT_TRUE(turl.KeepSearchTermsInURL(
+        GURL(original_search_url), search_terms_data_,
+        /*keep_search_intent_params=*/false, &canonical_search_url));
+    EXPECT_EQ("http://bar/search?q=foo&xssi=t", canonical_search_url);
+  }
+  {
+    // Optionally keeps empty search intent params.
+    search_terms_args.additional_query_params = "gs_ssp=";
+    std::string original_search_url = turl.url_ref().ReplaceSearchTerms(
+        search_terms_args, search_terms_data_);
+    EXPECT_EQ("http://bar/search?gs_ssp=&q=foo&psi=SESSIONTOKEN&xssi=t",
+              original_search_url);
+
+    GURL canonical_search_url;
+    EXPECT_TRUE(turl.KeepSearchTermsInURL(
+        GURL(original_search_url), search_terms_data_,
+        /*keep_search_intent_params=*/true, &canonical_search_url));
+    EXPECT_EQ("http://bar/search?gs_ssp=&q=foo&xssi=t", canonical_search_url);
+
+    EXPECT_TRUE(turl.KeepSearchTermsInURL(
+        GURL(original_search_url), search_terms_data_,
+        /*keep_search_intent_params=*/false, &canonical_search_url));
+    EXPECT_EQ("http://bar/search?q=foo&xssi=t", canonical_search_url);
+  }
+  {
+    // Discards params besides search terms and optionally search intent params.
+    search_terms_args.additional_query_params = "wiz=baz&gs_ssp=GS_SSP";
+    std::string original_search_url = turl.url_ref().ReplaceSearchTerms(
+        search_terms_args, search_terms_data_);
+    EXPECT_EQ(
+        "http://bar/search?wiz=baz&gs_ssp=GS_SSP&q=foo&psi=SESSIONTOKEN&xssi=t",
+        original_search_url);
+
+    GURL canonical_search_url;
+    EXPECT_TRUE(turl.KeepSearchTermsInURL(
+        GURL(original_search_url), search_terms_data_,
+        /*keep_search_intent_params=*/true, &canonical_search_url));
+    EXPECT_EQ("http://bar/search?gs_ssp=GS_SSP&q=foo&xssi=t",
+              canonical_search_url);
+
+    EXPECT_TRUE(turl.KeepSearchTermsInURL(
+        GURL(original_search_url), search_terms_data_,
+        /*keep_search_intent_params=*/false, &canonical_search_url));
+    EXPECT_EQ("http://bar/search?q=foo&xssi=t", canonical_search_url);
+  }
+  {
+    // Optionally keeps multiple search intent params.
+    search_terms_args.additional_query_params = "si=SI&gs_ssp=GS_SSP";
+    std::string original_search_url = turl.url_ref().ReplaceSearchTerms(
+        search_terms_args, search_terms_data_);
+    EXPECT_EQ(
+        "http://bar/search?si=SI&gs_ssp=GS_SSP&q=foo&psi=SESSIONTOKEN&xssi=t",
+        original_search_url);
+
+    GURL canonical_search_url;
+    EXPECT_TRUE(turl.KeepSearchTermsInURL(
+        GURL(original_search_url), search_terms_data_,
+        /*keep_search_intent_params=*/true, &canonical_search_url));
+    EXPECT_EQ("http://bar/search?si=SI&gs_ssp=GS_SSP&q=foo&xssi=t",
+              canonical_search_url);
+
+    EXPECT_TRUE(turl.KeepSearchTermsInURL(
+        GURL(original_search_url), search_terms_data_,
+        /*keep_search_intent_params=*/false, &canonical_search_url));
+    EXPECT_EQ("http://bar/search?q=foo&xssi=t", canonical_search_url);
+  }
+}
+
 TEST_F(TemplateURLTest, GenerateSearchURL) {
   struct GenerateSearchURLCase {
     const char* test_name;
diff --git a/components/segmentation_platform/internal/stats.cc b/components/segmentation_platform/internal/stats.cc
index 1315d6a..6ac3e31 100644
--- a/components/segmentation_platform/internal/stats.cc
+++ b/components/segmentation_platform/internal/stats.cc
@@ -374,6 +374,13 @@
                                      SegmentIdToHistogramVariant(segment_id),
                                  base::ClampRound(result));
     return;
+  } else if (return_type ==
+             proto::SegmentationModelMetadata::RETURN_TYPE_INTEGER) {
+    // This type of model return an unbound float score.
+    base::UmaHistogramPercentage("SegmentationPlatform.ModelExecution.Result." +
+                                     SegmentIdToHistogramVariant(segment_id),
+                                 static_cast<int>(result));
+    return;
   }
   // All other models type return score between 0 and 1.
   base::UmaHistogramPercentage("SegmentationPlatform.ModelExecution.Result." +
diff --git a/components/segmentation_platform/internal/stats_unittest.cc b/components/segmentation_platform/internal/stats_unittest.cc
index e9ee94ca..a6697407 100644
--- a/components/segmentation_platform/internal/stats_unittest.cc
+++ b/components/segmentation_platform/internal/stats_unittest.cc
@@ -174,7 +174,7 @@
   // Test default case of multiplying result by 100.
   stats::RecordModelExecutionResult(
       SegmentId::OPTIMIZATION_TARGET_SEGMENTATION_QUERY_TILES, 0.19,
-      proto::SegmentationModelMetadata::RETURN_TYPE_FLOAT);
+      proto::SegmentationModelMetadata::RETURN_TYPE_PROBABILITY);
   EXPECT_EQ(1,
             tester.GetBucketCount(
                 "SegmentationPlatform.ModelExecution.Result.QueryTiles", 19));
@@ -187,6 +187,14 @@
       1,
       tester.GetBucketCount(
           "SegmentationPlatform.ModelExecution.Result.SearchUserSegment", 75));
+
+  // Test segments that returns an unbound float result, which should be
+  // recorded as int.
+  stats::RecordModelExecutionResult(
+      SegmentId::OPTIMIZATION_TARGET_SEGMENTATION_SHARE, 75.6,
+      proto::SegmentationModelMetadata::RETURN_TYPE_INTEGER);
+  EXPECT_EQ(1, tester.GetBucketCount(
+                   "SegmentationPlatform.ModelExecution.Result.Share", 75));
 }
 
 }  // namespace stats
diff --git a/components/segmentation_platform/public/proto/model_metadata.proto b/components/segmentation_platform/public/proto/model_metadata.proto
index 6024a87..dfbbd91 100644
--- a/components/segmentation_platform/public/proto/model_metadata.proto
+++ b/components/segmentation_platform/public/proto/model_metadata.proto
@@ -365,10 +365,8 @@
     RETURN_TYPE_MULTISEGMENT = 2;
     // Model returns a float between 0 and 1.
     RETURN_TYPE_PROBABILITY = 3;
-    // Model returns any float value.
-    // TODO(haileywang): Add support for recording histograms of float return
-    // type.
-    RETURN_TYPE_FLOAT = 4;
+    // Model returns any integer value.
+    RETURN_TYPE_INTEGER = 4;
   }
   optional OutputDescription return_type = 14;
 }
diff --git a/components/url_formatter/spoof_checks/idn_spoof_checker.cc b/components/url_formatter/spoof_checks/idn_spoof_checker.cc
index 0ac70ea..aaff7c6 100644
--- a/components/url_formatter/spoof_checks/idn_spoof_checker.cc
+++ b/components/url_formatter/spoof_checks/idn_spoof_checker.cc
@@ -22,6 +22,7 @@
 #include "third_party/icu/source/i18n/unicode/regex.h"
 #include "third_party/icu/source/i18n/unicode/translit.h"
 #include "third_party/icu/source/i18n/unicode/uspoof.h"
+#include "url/url_features.h"
 
 namespace url_formatter {
 
@@ -375,8 +376,10 @@
   // chosen. On the other hand, 'fu<sharp-s>' typed or copy and pasted
   // as Unicode would be canonicalized to 'fuss' by GURL and is displayed as
   // such. See http://crbug.com/595263 .
-  if (deviation_characters_.containsSome(label_string))
+  if (!url::IsUsingIDNA2008NonTransitional() &&
+      deviation_characters_.containsSome(label_string)) {
     return Result::kDeviationCharacters;
+  }
 
   // Disallow Icelandic confusables for domains outside Iceland's ccTLD (.is).
   if (label_string.length() > 1 && top_level_domain != "is" &&
diff --git a/components/url_formatter/spoof_checks/idn_spoof_checker_unittest.cc b/components/url_formatter/spoof_checks/idn_spoof_checker_unittest.cc
index 80e29ea..1fef8d7 100644
--- a/components/url_formatter/spoof_checks/idn_spoof_checker_unittest.cc
+++ b/components/url_formatter/spoof_checks/idn_spoof_checker_unittest.cc
@@ -10,11 +10,13 @@
 #include "base/strings/string_piece.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
 #include "components/url_formatter/spoof_checks/skeleton_generator.h"
 #include "components/url_formatter/url_formatter.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
+#include "url/url_features.h"
 
 namespace url_formatter {
 
@@ -819,19 +821,6 @@
     {"xn--foog-opf.com", u"foog\u05b4.com", kUnsafe},    // Latin + Hebrew NSM
     {"xn--shb5495f.com", u"\uac00\u0650.com", kUnsafe},  // Hang + Arabic NSM
 
-    // 4 Deviation characters between IDNA 2003 and IDNA 2008
-    // When entered in Unicode, the first two are mapped to 'ss' and Greek sigma
-    // and the latter two are mapped away. However, the punycode form should
-    // remain in punycode.
-    // U+00DF(sharp-s)
-    {"xn--fu-hia.de", u"fu\u00df.de", kUnsafe},
-    // U+03C2(final-sigma)
-    {"xn--mxac2c.gr", u"\u03b1\u03b2\u03c2.gr", kUnsafe},
-    // U+200C(ZWNJ)
-    {"xn--h2by8byc123p.in", u"\u0924\u094d\u200c\u0930\u093f.in", kUnsafe},
-    // U+200C(ZWJ)
-    {"xn--11b6iy14e.in", u"\u0915\u094d\u200d.in", kUnsafe},
-
     // Math Monospace Small A. When entered in Unicode, it's canonicalized to
     // 'a'. The punycode form should remain in punycode.
     {"xn--bc-9x80a.xyz", u"\U0001d68abc.xyz", kInvalid},
@@ -1118,9 +1107,20 @@
 
 }  // namespace
 
-class IDNSpoofCheckerTest : public ::testing::Test {
+// IDNA mode to use in tests.
+enum class IDNAMode { kTransitional, kNonTransitional };
+
+class IDNSpoofCheckerTest : public ::testing::Test,
+                            public ::testing::WithParamInterface<IDNAMode> {
  protected:
   void SetUp() override {
+    if (GetParam() == IDNAMode::kNonTransitional) {
+      scoped_feature_list_.InitAndEnableFeature(
+          url::kUseIDNA2008NonTransitional);
+    } else {
+      scoped_feature_list_.InitAndDisableFeature(
+          url::kUseIDNA2008NonTransitional);
+    }
     IDNSpoofChecker::HuffmanTrieParams trie_params{
         test::kTopDomainsHuffmanTree, sizeof(test::kTopDomainsHuffmanTree),
         test::kTopDomainsTrie, test::kTopDomainsTrieBits,
@@ -1129,8 +1129,46 @@
   }
 
   void TearDown() override { IDNSpoofChecker::RestoreTrieParamsForTesting(); }
+
+  void RunIDNToUnicodeTest(const IDNTestCase& test) {
+    // Sanity check to ensure that the unicode output matches the input. Bypass
+    // all spoof checks by doing an unsafe conversion.
+    const IDNConversionResult unsafe_result =
+        UnsafeIDNToUnicodeWithDetails(test.input);
+
+    // Ignore inputs that can't be converted even with unsafe conversion because
+    // they contain certain characters not allowed in IDNs. E.g. U+24DF (Latin
+    // CIRCLED LATIN SMALL LETTER P) in hostname causes the conversion to fail
+    // before reaching spoof checks.
+    if (test.expected_result != kInvalid) {
+      // Also ignore domains that need to remain partially in punycode, such
+      // as ѕсоре-рау.한국 where scope-pay is a Cyrillic whole-script
+      // confusable but 한국 is safe. This would require adding yet another
+      // field to the the test struct.
+      if (!IsPunycode(test.unicode_output)) {
+        EXPECT_EQ(unsafe_result.result, test.unicode_output);
+      }
+    } else {
+      // Invalid punycode should not be converted.
+      EXPECT_EQ(unsafe_result.result, base::ASCIIToUTF16(test.input));
+    }
+
+    const std::u16string output(IDNToUnicode(test.input));
+    const std::u16string expected(test.expected_result == kSafe
+                                      ? test.unicode_output
+                                      : base::ASCIIToUTF16(test.input));
+    EXPECT_EQ(expected, output);
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
+INSTANTIATE_TEST_SUITE_P(All,
+                         IDNSpoofCheckerTest,
+                         ::testing::Values(IDNAMode::kTransitional,
+                                           IDNAMode::kNonTransitional));
+
 // Test that a domain entered as punycode is decoded to unicode if safe,
 // otherwise is left in punycode.
 //
@@ -1139,42 +1177,41 @@
 // certain unicode characters are canonicalized to other characters.
 // E.g. Mathematical Monospace Small A (U+1D68A) is canonicalized to "a" when
 // used in a domain name.
-TEST_F(IDNSpoofCheckerTest, IDNToUnicode) {
-  for (size_t i = 0; i < std::size(kIdnCases); i++) {
-    SCOPED_TRACE(
-        base::StringPrintf("input #%zu: \"%s\"", i, kIdnCases[i].input));
-
-    // Sanity check to ensure that the unicode output matches the input. Bypass
-    // all spoof checks by doing an unsafe conversion.
-    const IDNConversionResult unsafe_result =
-        UnsafeIDNToUnicodeWithDetails(kIdnCases[i].input);
-
-    // Ignore inputs that can't be converted even with unsafe conversion because
-    // they contain certain characters not allowed in IDNs. E.g. U+24DF (Latin
-    // CIRCLED LATIN SMALL LETTER P) in hostname causes the conversion to fail
-    // before reaching spoof checks.
-    if (kIdnCases[i].expected_result != kInvalid) {
-      // Also ignore domains that need to remain partially in punycode, such
-      // as ѕсоре-рау.한국 where scope-pay is a Cyrillic whole-script
-      // confusable but 한국 is safe. This would require adding yet another
-      // field to the the test struct.
-      if (!IsPunycode(kIdnCases[i].unicode_output)) {
-        EXPECT_EQ(unsafe_result.result, kIdnCases[i].unicode_output);
-      }
-    } else {
-      // Invalid punycode should not be converted.
-      EXPECT_EQ(unsafe_result.result, base::ASCIIToUTF16(kIdnCases[i].input));
-    }
-
-    const std::u16string output(IDNToUnicode(kIdnCases[i].input));
-    const std::u16string expected(kIdnCases[i].expected_result == kSafe
-                                      ? kIdnCases[i].unicode_output
-                                      : base::ASCIIToUTF16(kIdnCases[i].input));
-    EXPECT_EQ(expected, output);
+TEST_P(IDNSpoofCheckerTest, IDNToUnicode) {
+  for (const auto& test : kIdnCases) {
+    RunIDNToUnicodeTest(test);
   }
 }
 
-TEST_F(IDNSpoofCheckerTest, GetSimilarTopDomain) {
+// Same as IDNToUnicode but only tests hostnames with deviation characters.
+TEST_P(IDNSpoofCheckerTest, IDNToUnicodeDeviationCharacters) {
+  // Tests for 4 Deviation characters between IDNA 2003 and IDNA 2008. When
+  // entered in Unicode:
+  // - In Transitional mode, the first two are mapped to 'ss' and Greek sigma
+  //   and the latter two are mapped away. However, the punycode form should
+  //   remain in punycode.
+  // - In Non-Transitional mode, none of the characters should be mapped and
+  //   the hostnames should be considered safe.
+  const Result kExpectedSafety =
+      GetParam() == IDNAMode::kNonTransitional ? kSafe : kUnsafe;
+
+  const IDNTestCase kTestCases[] = {
+      // U+00DF(sharp-s)
+      {"xn--fu-hia.de", u"fu\u00df.de", kExpectedSafety},
+      // U+03C2(final-sigma)
+      {"xn--mxac2c.gr", u"\u03b1\u03b2\u03c2.gr", kExpectedSafety},
+      // U+200C(ZWNJ)
+      {"xn--h2by8byc123p.in", u"\u0924\u094d\u200c\u0930\u093f.in",
+       kExpectedSafety},
+      // U+200C(ZWJ)
+      {"xn--11b6iy14e.in", u"\u0915\u094d\u200d.in", kExpectedSafety},
+  };
+  for (const auto& test : kTestCases) {
+    RunIDNToUnicodeTest(test);
+  }
+}
+
+TEST_P(IDNSpoofCheckerTest, GetSimilarTopDomain) {
   struct TestCase {
     const char16_t* const hostname;
     const char* const expected_top_domain;
@@ -1207,7 +1244,7 @@
   }
 }
 
-TEST_F(IDNSpoofCheckerTest, LookupSkeletonInTopDomains) {
+TEST_P(IDNSpoofCheckerTest, LookupSkeletonInTopDomains) {
   {
     TopDomainEntry entry =
         IDNSpoofChecker().LookupSkeletonInTopDomains("d4OOO.corn");
diff --git a/components/viz/service/display/renderer_pixeltest.cc b/components/viz/service/display/renderer_pixeltest.cc
index 9a146e0..c387f20 100644
--- a/components/viz/service/display/renderer_pixeltest.cc
+++ b/components/viz/service/display/renderer_pixeltest.cc
@@ -61,6 +61,7 @@
 #include "ui/gfx/color_transform.h"
 #include "ui/gfx/geometry/mask_filter_info.h"
 #include "ui/gfx/geometry/skia_conversions.h"
+#include "ui/gfx/geometry/transform_util.h"
 #include "ui/gfx/test/icc_profiles.h"
 #include "ui/gfx/video_types.h"
 
@@ -3433,8 +3434,8 @@
   blue->SetNew(blue_shared_state, child_pass_rect, child_pass_rect,
                SkColors::kBlue, false);
 
-  auto child_to_root_transform = gfx::SkMatrixToTransform(SkMatrix::RectToRect(
-      RectToSkRect(child_pass_rect), RectToSkRect(viewport_rect)));
+  auto child_to_root_transform = gfx::TransformBetweenRects(
+      gfx::RectF(child_pass_rect), gfx::RectF(viewport_rect));
   SharedQuadState* child_pass_shared_state = CreateTestSharedQuadState(
       child_to_root_transform, child_pass_rect, root_pass.get(), gfx::MaskFilterInfo());
   auto* child_pass_quad =
diff --git a/components/webxr/OWNERS b/components/webxr/OWNERS
index d37fd2d1..3bd7798 100644
--- a/components/webxr/OWNERS
+++ b/components/webxr/OWNERS
@@ -1,7 +1,6 @@
 alcooper@chromium.org
 bajones@chromium.org
 bialpio@chromium.org
-klausw@chromium.org
 
 # WebXR Test related
 bsheedy@chromium.org
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 096f8de..cbd7787b 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -2474,8 +2474,6 @@
       "accessibility/browser_accessibility_manager_mac.h",
       "accessibility/browser_accessibility_manager_mac.mm",
       "accessibility/browser_accessibility_state_impl_mac.mm",
-      "browser_plugin/browser_plugin_popup_menu_helper_mac.h",
-      "browser_plugin/browser_plugin_popup_menu_helper_mac.mm",
       "child_process_launcher_helper_mac.cc",
       "child_process_task_port_provider_mac.cc",
       "child_process_task_port_provider_mac.h",
diff --git a/content/browser/attribution_reporting/attribution_test_utils.h b/content/browser/attribution_reporting/attribution_test_utils.h
index bf59c1c..23d6935 100644
--- a/content/browser/attribution_reporting/attribution_test_utils.h
+++ b/content/browser/attribution_reporting/attribution_test_utils.h
@@ -61,7 +61,7 @@
 
 namespace attribution_reporting {
 class AggregatableTriggerData;
-class TriggerRegistration;
+struct TriggerRegistration;
 }  // namespace attribution_reporting
 
 namespace mojo {
diff --git a/content/browser/browser_plugin/browser_plugin_guest.cc b/content/browser/browser_plugin/browser_plugin_guest.cc
index 5c05c4c..a731c5ef 100644
--- a/content/browser/browser_plugin/browser_plugin_guest.cc
+++ b/content/browser/browser_plugin/browser_plugin_guest.cc
@@ -21,10 +21,6 @@
 #include "content/public/browser/web_contents_observer.h"
 #include "third_party/blink/public/common/renderer_preferences/renderer_preferences.h"
 
-#if BUILDFLAG(IS_MAC)
-#include "content/browser/browser_plugin/browser_plugin_popup_menu_helper_mac.h"
-#endif
-
 namespace content {
 
 BrowserPluginGuest::BrowserPluginGuest(WebContentsImpl* web_contents,
@@ -143,27 +139,4 @@
   }
 }
 
-#if BUILDFLAG(IS_MAC)
-void BrowserPluginGuest::ShowPopupMenu(
-    RenderFrameHost* render_frame_host,
-    mojo::PendingRemote<blink::mojom::PopupMenuClient>* popup_client,
-    const gfx::Rect& bounds,
-    int32_t item_height,
-    double font_size,
-    int32_t selected_item,
-    std::vector<blink::mojom::MenuItemPtr>* menu_items,
-    bool right_aligned,
-    bool allow_multiple_selection) {
-  gfx::Rect translated_bounds(bounds);
-  auto* guest_rwhv = render_frame_host->GetView();
-  translated_bounds.set_origin(
-      guest_rwhv->TransformPointToRootCoordSpace(translated_bounds.origin()));
-  BrowserPluginPopupMenuHelper popup_menu_helper(render_frame_host,
-                                                 std::move(*popup_client));
-  popup_menu_helper.ShowPopupMenu(translated_bounds, item_height, font_size,
-                                  selected_item, std::move(*menu_items),
-                                  right_aligned, allow_multiple_selection);
-}
-#endif
-
 }  // namespace content
diff --git a/content/browser/browser_plugin/browser_plugin_guest.h b/content/browser/browser_plugin/browser_plugin_guest.h
index 4712cd8..aec6a0e 100644
--- a/content/browser/browser_plugin/browser_plugin_guest.h
+++ b/content/browser/browser_plugin/browser_plugin_guest.h
@@ -65,20 +65,6 @@
 
   void PrimaryMainFrameRenderProcessGone(
       base::TerminationStatus status) override;
-#if BUILDFLAG(IS_MAC)
-  // On MacOS X popups are painted by the browser process. We handle them here
-  // so that they are positioned correctly.
-  void ShowPopupMenu(
-      RenderFrameHost* render_frame_host,
-      mojo::PendingRemote<blink::mojom::PopupMenuClient>* popup_client,
-      const gfx::Rect& bounds,
-      int32_t item_height,
-      double font_size,
-      int32_t selected_item,
-      std::vector<blink::mojom::MenuItemPtr>* menu_items,
-      bool right_aligned,
-      bool allow_multiple_selection);
-#endif
 
   WebContentsImpl* GetWebContents() const;
 
diff --git a/content/browser/browser_plugin/browser_plugin_popup_menu_helper_mac.h b/content/browser/browser_plugin/browser_plugin_popup_menu_helper_mac.h
deleted file mode 100644
index 995f8a8a..0000000
--- a/content/browser/browser_plugin/browser_plugin_popup_menu_helper_mac.h
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2012 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_POPUP_MENU_HELPER_MAC_H_
-#define CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_POPUP_MENU_HELPER_MAC_H_
-
-#include "content/browser/renderer_host/popup_menu_helper_mac.h"
-#include "mojo/public/cpp/bindings/pending_remote.h"
-#include "third_party/blink/public/mojom/choosers/popup_menu.mojom.h"
-
-namespace content {
-
-class RenderFrameHost;
-
-// This class is similiar to PopupMenuHelperMac but positions the popup relative
-// to the embedder, and issues a reply to the guest.
-// TODO(533069): This class no longer appears to serve a purpose. The base
-// PopupMenuHelper already handles the coordinate transformations correctly.
-class BrowserPluginPopupMenuHelper : public PopupMenuHelper,
-                                     public PopupMenuHelper::Delegate {
- public:
-  // Creates a BrowserPluginPopupMenuHelper that positions popups relative to
-  // the embedder of `guest_rfh` and will notify `guest_rfh` when a user
-  // selects or cancels the popup.
-  BrowserPluginPopupMenuHelper(
-      RenderFrameHost* guest_rfh,
-      mojo::PendingRemote<blink::mojom::PopupMenuClient> popup_client);
-
-  BrowserPluginPopupMenuHelper(const BrowserPluginPopupMenuHelper&) = delete;
-  BrowserPluginPopupMenuHelper& operator=(const BrowserPluginPopupMenuHelper&) =
-      delete;
-
- private:
-  // PopupMenuHelper:Delegate:
-  void OnMenuClosed() override;
-};
-
-}  // namespace content
-
-#endif  // CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_POPUP_MENU_HELPER_MAC_H_
diff --git a/content/browser/browser_plugin/browser_plugin_popup_menu_helper_mac.mm b/content/browser/browser_plugin/browser_plugin_popup_menu_helper_mac.mm
deleted file mode 100644
index f4544e4..0000000
--- a/content/browser/browser_plugin/browser_plugin_popup_menu_helper_mac.mm
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2012 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "content/browser/browser_plugin/browser_plugin_popup_menu_helper_mac.h"
-
-namespace content {
-
-BrowserPluginPopupMenuHelper::BrowserPluginPopupMenuHelper(
-    RenderFrameHost* guest_rfh,
-    mojo::PendingRemote<blink::mojom::PopupMenuClient> popup_client)
-    : PopupMenuHelper(this, guest_rfh, std::move(popup_client)) {}
-
-void BrowserPluginPopupMenuHelper::OnMenuClosed() {
-  // BrowserPluginGuest doesn't support cancellation of popup menus, so the
-  // MenuHelper is its own delegate and OnMenuClosed() is ignored.
-}
-
-}  // namespace content
diff --git a/content/browser/direct_sockets/direct_sockets_open_browsertest.cc b/content/browser/direct_sockets/direct_sockets_open_browsertest.cc
index d6662536..f623fce 100644
--- a/content/browser/direct_sockets/direct_sockets_open_browsertest.cc
+++ b/content/browser/direct_sockets/direct_sockets_open_browsertest.cc
@@ -12,11 +12,11 @@
 #include "base/strings/stringprintf.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
 #include "content/browser/direct_sockets/direct_sockets_service_impl.h"
 #include "content/browser/direct_sockets/direct_sockets_test_utils.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/common/content_features.h"
-#include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/content_browser_test.h"
@@ -176,7 +176,13 @@
  protected:
   void SetUpOnMainThread() override {
     ContentBrowserTest::SetUpOnMainThread();
-    EXPECT_TRUE(NavigateToURL(shell(), GetTestOpenPageURL()));
+
+    client_ = std::make_unique<test::IsolatedWebAppContentBrowserClient>(
+        url::Origin::Create(GetTestOpenPageURL()));
+    scoped_client_ =
+        std::make_unique<ScopedContentBrowserClientSetting>(client_.get());
+
+    ASSERT_TRUE(NavigateToURL(shell(), GetTestOpenPageURL()));
   }
 
   void SetUp() override {
@@ -186,12 +192,11 @@
     ContentBrowserTest::SetUp();
   }
 
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    ContentBrowserTest::SetUpCommandLine(command_line);
-    std::string origin_list = GetTestOpenPageURL().spec();
+ private:
+  base::test::ScopedFeatureList feature_list_{features::kIsolatedWebApps};
 
-    command_line->AppendSwitchASCII(switches::kIsolatedAppOrigins, origin_list);
-  }
+  std::unique_ptr<test::IsolatedWebAppContentBrowserClient> client_;
+  std::unique_ptr<ScopedContentBrowserClientSetting> scoped_client_;
 };
 
 IN_PROC_BROWSER_TEST_F(DirectSocketsOpenBrowserTest, OpenTcp_Success_Hostname) {
diff --git a/content/browser/direct_sockets/direct_sockets_tcp_browsertest.cc b/content/browser/direct_sockets/direct_sockets_tcp_browsertest.cc
index 6e615df..03b6963 100644
--- a/content/browser/direct_sockets/direct_sockets_tcp_browsertest.cc
+++ b/content/browser/direct_sockets/direct_sockets_tcp_browsertest.cc
@@ -19,7 +19,6 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_client.h"
 #include "content/public/common/content_features.h"
-#include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/content_browser_test.h"
@@ -286,9 +285,15 @@
  protected:
   void SetUpOnMainThread() override {
     ContentBrowserTest::SetUpOnMainThread();
-    ASSERT_TRUE(NavigateToURL(shell(), GetTestPageURL()));
+
+    client_ = std::make_unique<test::IsolatedWebAppContentBrowserClient>(
+        url::Origin::Create(GetTestPageURL()));
+    scoped_client_ =
+        std::make_unique<ScopedContentBrowserClientSetting>(client_.get());
     runner_ =
         std::make_unique<content::test::AsyncJsRunner>(shell()->web_contents());
+
+    ASSERT_TRUE(NavigateToURL(shell(), GetTestPageURL()));
   }
 
   void SetUp() override {
@@ -297,26 +302,18 @@
     ContentBrowserTest::SetUp();
   }
 
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    ContentBrowserTest::SetUpCommandLine(command_line);
-    std::string origin_list =
-        GetTestOpenPageURL().spec() + "," + GetTestPageURL().spec();
-
-    command_line->AppendSwitchASCII(switches::kIsolatedAppOrigins, origin_list);
-  }
-
  private:
   BrowserContext* browser_context() {
     return shell()->web_contents()->GetBrowserContext();
   }
 
  private:
-  test::IsolatedWebAppContentBrowserClient client_;
-  ScopedContentBrowserClientSetting setting{&client_};
-  base::test::ScopedFeatureList feature_list_;
+  base::test::ScopedFeatureList feature_list_{features::kIsolatedWebApps};
   mojo::Remote<network::mojom::MdnsResponder> mdns_responder_;
   mojo::Remote<network::mojom::TCPServerSocket> tcp_server_socket_;
 
+  std::unique_ptr<test::IsolatedWebAppContentBrowserClient> client_;
+  std::unique_ptr<ScopedContentBrowserClientSetting> scoped_client_;
   std::unique_ptr<content::test::AsyncJsRunner> runner_;
 };
 
diff --git a/content/browser/direct_sockets/direct_sockets_test_utils.cc b/content/browser/direct_sockets/direct_sockets_test_utils.cc
index a2c2e65b..1debc3d 100644
--- a/content/browser/direct_sockets/direct_sockets_test_utils.cc
+++ b/content/browser/direct_sockets/direct_sockets_test_utils.cc
@@ -14,6 +14,7 @@
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/web_contents_tester.h"
 #include "third_party/blink/public/common/permissions_policy/origin_with_possible_wildcards.h"
+#include "url/origin.h"
 
 namespace content::test {
 
@@ -206,11 +207,15 @@
       script.c_str(), token_.ToString().c_str()));
 }
 
+IsolatedWebAppContentBrowserClient::IsolatedWebAppContentBrowserClient(
+    const url::Origin& isolated_app_origin)
+    : isolated_app_origin_(isolated_app_origin) {}
+
 bool IsolatedWebAppContentBrowserClient::ShouldUrlUseApplicationIsolationLevel(
     BrowserContext* browser_context,
     const GURL& url,
     bool origin_matches_flag) {
-  return origin_matches_flag;
+  return isolated_app_origin_ == url::Origin::Create(url);
 }
 
 absl::optional<blink::ParsedPermissionsPolicy>
diff --git a/content/browser/direct_sockets/direct_sockets_test_utils.h b/content/browser/direct_sockets/direct_sockets_test_utils.h
index 698a58a02..ff2a902fb 100644
--- a/content/browser/direct_sockets/direct_sockets_test_utils.h
+++ b/content/browser/direct_sockets/direct_sockets_test_utils.h
@@ -29,6 +29,10 @@
 #include "services/network/test/test_udp_socket.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
+namespace url {
+class Origin;
+}  // namespace url
+
 namespace content::test {
 
 // Mock Host Resolver for Direct Sockets browsertests.
@@ -197,6 +201,9 @@
 // for isolated apps.
 class IsolatedWebAppContentBrowserClient : public ContentBrowserClient {
  public:
+  explicit IsolatedWebAppContentBrowserClient(
+      const url::Origin& isolated_app_origin);
+
   bool ShouldUrlUseApplicationIsolationLevel(BrowserContext* browser_context,
                                              const GURL& url,
                                              bool origin_matches_flag) override;
@@ -205,6 +212,9 @@
   GetPermissionsPolicyForIsolatedWebApp(
       content::BrowserContext* browser_context,
       const url::Origin& app_origin) override;
+
+ private:
+  url::Origin isolated_app_origin_;
 };
 
 }  // namespace content::test
diff --git a/content/browser/direct_sockets/direct_sockets_udp_browsertest.cc b/content/browser/direct_sockets/direct_sockets_udp_browsertest.cc
index 0734985d..7bebc89 100644
--- a/content/browser/direct_sockets/direct_sockets_udp_browsertest.cc
+++ b/content/browser/direct_sockets/direct_sockets_udp_browsertest.cc
@@ -15,7 +15,6 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_client.h"
 #include "content/public/common/content_features.h"
-#include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/content_browser_test.h"
@@ -77,9 +76,15 @@
  protected:
   void SetUpOnMainThread() override {
     ContentBrowserTest::SetUpOnMainThread();
-    EXPECT_TRUE(NavigateToURL(shell(), GetTestPageURL()));
+
+    client_ = std::make_unique<test::IsolatedWebAppContentBrowserClient>(
+        url::Origin::Create(GetTestPageURL()));
+    scoped_client_ =
+        std::make_unique<ScopedContentBrowserClientSetting>(client_.get());
     runner_ =
         std::make_unique<content::test::AsyncJsRunner>(shell()->web_contents());
+
+    ASSERT_TRUE(NavigateToURL(shell(), GetTestPageURL()));
   }
 
   void SetUp() override {
@@ -89,13 +94,6 @@
     ContentBrowserTest::SetUp();
   }
 
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    ContentBrowserTest::SetUpCommandLine(command_line);
-    std::string origin_list = GetTestPageURL().spec();
-
-    command_line->AppendSwitchASCII(switches::kIsolatedAppOrigins, origin_list);
-  }
-
   std::pair<net::IPEndPoint, network::test::UDPSocketTestHelper>
   CreateUDPServerSocket(mojo::PendingRemote<network::mojom::UDPSocketListener>
                             listener_receiver_remote) {
@@ -124,11 +122,11 @@
     return shell()->web_contents()->GetBrowserContext();
   }
 
-  test::IsolatedWebAppContentBrowserClient client_;
-  ScopedContentBrowserClientSetting setting{&client_};
-  base::test::ScopedFeatureList feature_list_;
+  base::test::ScopedFeatureList feature_list_{features::kIsolatedWebApps};
   mojo::Remote<network::mojom::UDPSocket> server_socket_;
 
+  std::unique_ptr<test::IsolatedWebAppContentBrowserClient> client_;
+  std::unique_ptr<ScopedContentBrowserClientSetting> scoped_client_;
   std::unique_ptr<content::test::AsyncJsRunner> runner_;
 };
 
diff --git a/content/browser/media/capture/screen_capture_kit_device_mac.mm b/content/browser/media/capture/screen_capture_kit_device_mac.mm
index eeb2d94..a79afc2 100644
--- a/content/browser/media/capture/screen_capture_kit_device_mac.mm
+++ b/content/browser/media/capture/screen_capture_kit_device_mac.mm
@@ -6,15 +6,18 @@
 
 #import <ScreenCaptureKit/ScreenCaptureKit.h>
 
+#include "base/mac/foundation_util.h"
 #include "base/mac/scoped_nsobject.h"
 #include "base/task/bind_post_task.h"
 #include "base/threading/thread_checker.h"
 #include "base/timer/timer.h"
 #include "content/browser/media/capture/io_surface_capture_device_base_mac.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/webrtc/modules/desktop_capture/desktop_capture_types.h"
 #include "ui/gfx/native_widget_types.h"
 
-using SampleCallback = base::RepeatingCallback<void(gfx::ScopedInUseIOSurface)>;
+using SampleCallback = base::RepeatingCallback<void(gfx::ScopedInUseIOSurface,
+                                                    absl::optional<gfx::Size>)>;
 using ErrorCallback = base::RepeatingClosure;
 
 API_AVAILABLE(macos(12.3))
@@ -45,17 +48,64 @@
   CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
   if (!pixelBuffer)
     return;
+
+  // Read out width, height and scaling from metadata to determine the captured
+  // content size.
+  absl::optional<gfx::Size> contentSize;
+  CFArrayRef attachmentsArray =
+      CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, false);
+  if (attachmentsArray && CFArrayGetCount(attachmentsArray) > 0) {
+    CFDictionaryRef attachment = base::mac::CFCast<CFDictionaryRef>(
+        CFArrayGetValueAtIndex(attachmentsArray, 0));
+    if (attachment) {
+      CFDictionaryRef contentRectValue = base::mac::CFCast<CFDictionaryRef>(
+          CFDictionaryGetValue(attachment, SCStreamFrameInfoContentRect));
+      CFNumberRef contentScaleValue = base::mac::CFCast<CFNumberRef>(
+          CFDictionaryGetValue(attachment, SCStreamFrameInfoContentScale));
+      if (contentRectValue && contentScaleValue) {
+        CGRect contentRect = {};
+        bool succeed = CGRectMakeWithDictionaryRepresentation(contentRectValue,
+                                                              &contentRect);
+        float contentScale = 1.0f;
+        succeed &= CFNumberGetValue(contentScaleValue, kCFNumberFloatType,
+                                    &contentScale);
+        if (succeed) {
+          contentSize.emplace(round(contentRect.size.width / contentScale),
+                              round(contentRect.size.height / contentScale));
+        }
+      }
+    }
+  }
   IOSurfaceRef ioSurface = CVPixelBufferGetIOSurface(pixelBuffer);
   if (!ioSurface)
     return;
   _sampleCallback.Run(
-      gfx::ScopedInUseIOSurface(ioSurface, base::scoped_policy::RETAIN));
+      gfx::ScopedInUseIOSurface(ioSurface, base::scoped_policy::RETAIN),
+      contentSize);
 }
 
 - (void)stream:(SCStream*)stream didStopWithError:(NSError*)error {
   _errorCallback.Run();
 }
 
++ (base::scoped_nsobject<SCStreamConfiguration>)
+    createStreamConfigurationWithFrameSize:(gfx::Size)frameSize
+                           destRectInFrame:(gfx::RectF)destRectInFrame
+                                 frameRate:(float)frameRate {
+  base::scoped_nsobject<SCStreamConfiguration> config(
+      [[SCStreamConfiguration alloc] init]);
+  [config setWidth:frameSize.width()];
+  [config setHeight:frameSize.height()];
+  [config setPixelFormat:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange];
+  [config setDestinationRect:destRectInFrame.ToCGRect()];
+  [config setBackgroundColor:CGColorGetConstantColor(kCGColorBlack)];
+  [config setScalesToFit:YES];
+  [config setShowsCursor:YES];
+  [config setColorSpaceName:kCGColorSpaceSRGB];
+  [config setMinimumFrameInterval:CMTimeMakeWithSeconds(1 / frameRate, 1)];
+  return config;
+}
+
 @end
 
 namespace content {
@@ -95,7 +145,6 @@
     }
 
     base::scoped_nsobject<SCContentFilter> filter;
-    gfx::Size content_size;
     switch (source_.type) {
       case DesktopMediaID::TYPE_SCREEN:
         for (SCDisplay* display : [content displays]) {
@@ -104,7 +153,8 @@
             filter.reset([[SCContentFilter alloc]
                  initWithDisplay:display
                 excludingWindows:exclude_windows]);
-            content_size = gfx::Size([display width], [display height]);
+            stream_config_content_size_ =
+                gfx::Size([display width], [display height]);
             break;
           }
         }
@@ -115,7 +165,7 @@
             filter.reset([[SCContentFilter alloc]
                 initWithDesktopIndependentWindow:window]);
             CGRect frame = [window frame];
-            content_size = gfx::Size(frame.size);
+            stream_config_content_size_ = gfx::Size(frame.size);
             break;
           }
         }
@@ -134,22 +184,16 @@
     gfx::RectF dest_rect_in_frame;
     actual_capture_format_ = capture_params().requested_format;
     actual_capture_format_.pixel_format = media::PIXEL_FORMAT_NV12;
-    ComputeFrameSizeAndDestRect(content_size, actual_capture_format_.frame_size,
+    ComputeFrameSizeAndDestRect(stream_config_content_size_,
+                                actual_capture_format_.frame_size,
                                 dest_rect_in_frame);
-
-    base::scoped_nsobject<SCStreamConfiguration> config(
-        [[SCStreamConfiguration alloc] init]);
-    [config setWidth:actual_capture_format_.frame_size.width()];
-    [config setHeight:actual_capture_format_.frame_size.height()];
-    [config setPixelFormat:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange];
-    [config setDestinationRect:dest_rect_in_frame.ToCGRect()];
-    [config setBackgroundColor:CGColorGetConstantColor(kCGColorBlack)];
-    [config setScalesToFit:YES];
-    [config setShowsCursor:YES];
-    [config setColorSpaceName:kCGColorSpaceSRGB];
-    [config
-        setMinimumFrameInterval:CMTimeMakeWithSeconds(
-                                    1 / actual_capture_format_.frame_rate, 1)];
+    base::scoped_nsobject<SCStreamConfiguration> config =
+        [ScreenCaptureKitDeviceHelper
+            createStreamConfigurationWithFrameSize:actual_capture_format_
+                                                       .frame_size
+                                   destRectInFrame:dest_rect_in_frame
+                                         frameRate:actual_capture_format_
+                                                       .frame_rate];
     stream_.reset([[SCStream alloc] initWithFilter:filter
                                      configuration:config
                                           delegate:helper_]);
@@ -195,9 +239,71 @@
       return;
     }
   }
-  void OnStreamSample(gfx::ScopedInUseIOSurface io_surface) {
-    // TODO(https://crbug.com/1309653): Reconfigure the stream if the IOSurface
-    // should be resized.
+  void OnStreamSample(gfx::ScopedInUseIOSurface io_surface,
+                      absl::optional<gfx::Size> content_size) {
+    if (requested_capture_format_) {
+      // Does the size of io_surface match the requested format?
+      size_t io_surface_width = IOSurfaceGetWidth(io_surface);
+      size_t io_surface_height = IOSurfaceGetHeight(io_surface);
+      DVLOG(3) << "Waiting for new capture format, "
+               << requested_capture_format_->frame_size.width() << " x "
+               << requested_capture_format_->frame_size.height()
+               << ". IO surface size " << io_surface_width << " x "
+               << io_surface_height;
+      if (static_cast<size_t>(requested_capture_format_->frame_size.width()) ==
+              io_surface_width &&
+          static_cast<size_t>(requested_capture_format_->frame_size.height()) ==
+              io_surface_height) {
+        actual_capture_format_ = requested_capture_format_.value();
+        requested_capture_format_.reset();
+      }
+    } else {
+      // No current request for new capture format. Check to see if content_size
+      // has changed and requires an updated configuration.
+      if (content_size &&
+          (stream_config_content_size_.width() != content_size->width() ||
+           stream_config_content_size_.height() != content_size->height())) {
+        DVLOG(3) << "Content size changed to " << content_size->width() << " x "
+                 << content_size->height() << ". It was "
+                 << stream_config_content_size_.width() << " x "
+                 << stream_config_content_size_.height();
+        stream_config_content_size_ = content_size.value();
+        gfx::RectF dest_rect_in_frame;
+        gfx::Size new_frame_size;
+        ComputeFrameSizeAndDestRect(stream_config_content_size_, new_frame_size,
+                                    dest_rect_in_frame);
+        if (new_frame_size.width() !=
+                actual_capture_format_.frame_size.width() ||
+            new_frame_size.height() !=
+                actual_capture_format_.frame_size.height()) {
+          DVLOG(3) << "Calling updateConfiguration with new frame size: "
+                   << new_frame_size.width() << " x "
+                   << new_frame_size.height();
+          requested_capture_format_ = actual_capture_format_;
+          requested_capture_format_->frame_size = new_frame_size;
+          // Update stream configuration.
+          base::scoped_nsobject<SCStreamConfiguration> config =
+              [ScreenCaptureKitDeviceHelper
+                  createStreamConfigurationWithFrameSize:
+                      requested_capture_format_->frame_size
+                                         destRectInFrame:dest_rect_in_frame
+                                               frameRate:
+                                                   requested_capture_format_->
+                                                   frame_rate];
+          [stream_
+              updateConfiguration:config
+                completionHandler:^(NSError* _Nullable error) {
+                  if (error) {
+                    client()->OnError(
+                        media::VideoCaptureError::kScreenCaptureKitStreamError,
+                        FROM_HERE, "Error on updateConfiguration");
+                  }
+                }];
+        }
+      }
+    }
+    // TODO(https://crbug.com/1352405): Set visible rect to make it possible to
+    // crop the frame when it's rendered/encoded.
     OnReceivedIOSurfaceFromStream(io_surface, actual_capture_format_);
   }
   void OnStreamError() {
@@ -249,6 +355,13 @@
   // The actual format of the video frames that are sent to `client`.
   media::VideoCaptureFormat actual_capture_format_;
 
+  // The requested format if a request to update the configuration has been
+  // sent.
+  absl::optional<media::VideoCaptureFormat> requested_capture_format_;
+
+  // The size of the content at the time that we configured the stream.
+  gfx::Size stream_config_content_size_;
+
   // Helper class that acts as output and delegate for `stream_`.
   base::scoped_nsobject<ScreenCaptureKitDeviceHelper> helper_;
 
diff --git a/content/browser/renderer_host/recently_destroyed_hosts.cc b/content/browser/renderer_host/recently_destroyed_hosts.cc
index fdc9c81..04fe132 100644
--- a/content/browser/renderer_host/recently_destroyed_hosts.cc
+++ b/content/browser/renderer_host/recently_destroyed_hosts.cc
@@ -96,26 +96,6 @@
     instance->RemoveExpiredEntries();
 }
 
-// static
-base::TimeDelta RecentlyDestroyedHosts::GetPercentileReuseInterval(
-    int percentile,
-    BrowserContext* browser_context) {
-  DCHECK_GE(percentile, 0);
-  DCHECK_LE(percentile, 100);
-  auto* instance = GetInstance(browser_context);
-  if (instance->reuse_intervals_.empty())
-    return base::TimeDelta();
-  auto iter = instance->reuse_intervals_.begin();
-  if (percentile == 0)
-    return iter->interval;
-  const size_t percentile_index =
-      std::ceil(instance->reuse_intervals_.size() * (percentile / 100.0)) - 1;
-  DCHECK_GE(percentile_index, 0u);
-  DCHECK_LT(percentile_index, instance->reuse_intervals_.size());
-  std::advance(iter, percentile_index);
-  return iter->interval;
-}
-
 RecentlyDestroyedHosts::RecentlyDestroyedHosts() = default;
 
 RecentlyDestroyedHosts* RecentlyDestroyedHosts::GetInstance(
diff --git a/content/browser/renderer_host/recently_destroyed_hosts.h b/content/browser/renderer_host/recently_destroyed_hosts.h
index 221c955..ebe6315 100644
--- a/content/browser/renderer_host/recently_destroyed_hosts.h
+++ b/content/browser/renderer_host/recently_destroyed_hosts.h
@@ -34,8 +34,7 @@
  public:
   // Storage time for information about recently destroyed processes. Intended
   // to be long enough to capture a large portion of the process-reuse
-  // opportunity. This also serves as the maximum reuse interval that can be
-  // returned by GetPercentileReuseInterval().
+  // opportunity.
   static constexpr base::TimeDelta kRecentlyDestroyedStorageTimeout =
       base::Seconds(15);
 
@@ -58,13 +57,6 @@
                   const base::TimeDelta& time_spent_running_unload_handlers,
                   BrowserContext* browser_context);
 
-  // Returns the given |percentile| of the recent intervals between process
-  // shutdown and creation of a process for the same site. A maximum value of
-  // |kRecentlyDestroyedStorageTimeout| can be returned.
-  static base::TimeDelta GetPercentileReuseInterval(
-      int percentile,
-      BrowserContext* browser_context);
-
  private:
   friend class RecentlyDestroyedHostsTest;
 
diff --git a/content/browser/renderer_host/recently_destroyed_hosts_unittest.cc b/content/browser/renderer_host/recently_destroyed_hosts_unittest.cc
index 4048e25..e8effda6 100644
--- a/content/browser/renderer_host/recently_destroyed_hosts_unittest.cc
+++ b/content/browser/renderer_host/recently_destroyed_hosts_unittest.cc
@@ -35,8 +35,6 @@
     task_environment_.FastForwardBy(base::Seconds(1));
   }
 
-  void ClearReuseIntervals() { instance_->reuse_intervals_.clear(); }
-
   std::vector<base::TimeDelta> GetReuseIntervals() {
     std::vector<base::TimeDelta> intervals;
     for (auto& reuse_interval : instance_->reuse_intervals_) {
@@ -146,45 +144,4 @@
   EXPECT_THAT(GetReuseIntervals(), testing::ElementsAre(t1, t3, t4, t5, t6));
 }
 
-TEST_F(RecentlyDestroyedHostsTest, GetPercentileReuseInterval) {
-  struct PercentileReuseIntervalTestCase {
-    std::vector<double> reuse_interval_seconds;
-    double percentile_0;
-    double percentile_33;
-    double percentile_50;
-    double percentile_75;
-    double percentile_100;
-  } kPercentileReuseIntervalTestCases[] = {
-      // All percentiles should be zero if empty.
-      {{}, 0, 0, 0, 0, 0},
-      {{0, 0, 0, 0, 0}, 0, 0, 0, 0, 0},
-      {{1, 2}, 1, 1, 1, 2, 2},
-      {{1, 2, 3}, 1, 1, 2, 3, 3},
-      {{1, 2, 3, 4}, 1, 2, 2, 3, 4},
-      {{1, 2, 3, 4, 5}, 1, 2, 3, 4, 5},
-      {{1.2, 5, 11, 13.3, 15}, 1.2, 5, 11, 13.3, 15}};
-
-  for (auto& test_case : kPercentileReuseIntervalTestCases) {
-    for (auto& interval : test_case.reuse_interval_seconds) {
-      AddReuseInterval(base::Seconds(interval));
-    }
-    EXPECT_EQ(base::Seconds(test_case.percentile_0),
-              RecentlyDestroyedHosts::GetPercentileReuseInterval(
-                  0, &browser_context_));
-    EXPECT_EQ(base::Seconds(test_case.percentile_33),
-              RecentlyDestroyedHosts::GetPercentileReuseInterval(
-                  33, &browser_context_));
-    EXPECT_EQ(base::Seconds(test_case.percentile_50),
-              RecentlyDestroyedHosts::GetPercentileReuseInterval(
-                  50, &browser_context_));
-    EXPECT_EQ(base::Seconds(test_case.percentile_75),
-              RecentlyDestroyedHosts::GetPercentileReuseInterval(
-                  75, &browser_context_));
-    EXPECT_EQ(base::Seconds(test_case.percentile_100),
-              RecentlyDestroyedHosts::GetPercentileReuseInterval(
-                  100, &browser_context_));
-    ClearReuseIntervals();
-  }
-}
-
 }  // namespace content
diff --git a/content/browser/renderer_host/render_frame_host_delegate.cc b/content/browser/renderer_host/render_frame_host_delegate.cc
index 79a0659..37824d7 100644
--- a/content/browser/renderer_host/render_frame_host_delegate.cc
+++ b/content/browser/renderer_host/render_frame_host_delegate.cc
@@ -179,14 +179,7 @@
 
 bool RenderFrameHostDelegate::ShowPopupMenu(
     RenderFrameHostImpl* render_frame_host,
-    mojo::PendingRemote<blink::mojom::PopupMenuClient>* popup_client,
-    const gfx::Rect& bounds,
-    int32_t item_height,
-    double font_size,
-    int32_t selected_item,
-    std::vector<blink::mojom::MenuItemPtr>* menu_items,
-    bool right_aligned,
-    bool allow_multiple_selection) {
+    const gfx::Rect& bounds) {
   return false;
 }
 
diff --git a/content/browser/renderer_host/render_frame_host_delegate.h b/content/browser/renderer_host/render_frame_host_delegate.h
index 32faf10c..6c5fe58a 100644
--- a/content/browser/renderer_host/render_frame_host_delegate.h
+++ b/content/browser/renderer_host/render_frame_host_delegate.h
@@ -575,19 +575,12 @@
           blink_widget_host,
       mojo::PendingAssociatedRemote<blink::mojom::Widget> blink_widget);
 
-  // Return true if the popup is shown through WebContentsObserver.
-  // BrowserPluginGuest for the guest WebContents will show the popup on Mac,
-  // then, we should skip to show the popup at RenderViewHostDelegateView.
-  virtual bool ShowPopupMenu(
-      RenderFrameHostImpl* render_frame_host,
-      mojo::PendingRemote<blink::mojom::PopupMenuClient>* popup_client,
-      const gfx::Rect& bounds,
-      int32_t item_height,
-      double font_size,
-      int32_t selected_item,
-      std::vector<blink::mojom::MenuItemPtr>* menu_items,
-      bool right_aligned,
-      bool allow_multiple_selection);
+  // Returns true if the popup is shown through WebContentsObserver. Else, the
+  // Android / Mac flavors of `RenderViewHostDelegateView` will show the popup
+  // menu correspondingly, and `WebContentsViewChildFrame` will show the popup
+  // for Mac's GuestView.
+  virtual bool ShowPopupMenu(RenderFrameHostImpl* render_frame_host,
+                             const gfx::Rect& bounds);
 
   virtual void DidLoadResourceFromMemoryCache(
       RenderFrameHostImpl* source,
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 9d0f0a67..9c4ad55 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -35,7 +35,6 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_piece.h"
 #include "base/syslog_logging.h"
-#include "base/system/sys_info.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/task/thread_pool.h"
 #include "base/threading/sequence_bound.h"
@@ -114,7 +113,6 @@
 #include "content/browser/renderer_host/pending_beacon_host.h"
 #include "content/browser/renderer_host/pending_beacon_service.h"
 #include "content/browser/renderer_host/private_network_access_util.h"
-#include "content/browser/renderer_host/recently_destroyed_hosts.h"
 #include "content/browser/renderer_host/render_frame_host_delegate.h"
 #include "content/browser/renderer_host/render_frame_host_owner.h"
 #include "content/browser/renderer_host/render_frame_proxy_host.h"
@@ -374,8 +372,8 @@
 
 namespace {
 
-constexpr int kSubframeProcessShutdownLongDelayInMSec = 8 * 1000;
-static_assert(kSubframeProcessShutdownLongDelayInMSec +
+constexpr int kSubframeProcessShutdownDelayInMSec = 2 * 1000;
+static_assert(kSubframeProcessShutdownDelayInMSec +
                       RenderViewHostImpl::kUnloadTimeoutInMSec <
                   RenderProcessHostImpl::kKeepAliveHandleFactoryTimeoutInMSec,
               "The maximum process shutdown delay should not exceed the "
@@ -866,8 +864,8 @@
 }
 
 // Returns an experimental process shutdown delay if the SubframeShutdownDelay
-// experiment is enabled, 0 if not or if under memory pressure. This experiment
-// keeps subframe processes alive for a few seconds in case they can be reused.
+// experiment is enabled, 0 if not or if under memory pressure. This is used to
+// keep subframe processes alive for a few seconds in case they can be reused.
 base::TimeDelta GetSubframeProcessShutdownDelay(
     BrowserContext* browser_context) {
   static constexpr base::TimeDelta kZeroDelay;
@@ -883,62 +881,7 @@
     return kZeroDelay;
   }
 
-  static constexpr base::TimeDelta kShortDelay = base::Seconds(2);
-  static constexpr base::TimeDelta kLongDelay =
-      base::Milliseconds(kSubframeProcessShutdownLongDelayInMSec);
-  // Added to delay if based on recent performance (i.e., |kHistoryBased| and
-  // |kHistoryBasedLong|) to account for small variations in timing.
-  static constexpr base::TimeDelta kDelayBuffer = base::Seconds(1);
-
-  switch (features::kSubframeShutdownDelayTypeParam.Get()) {
-    case features::SubframeShutdownDelayType::kConstant: {
-      return kShortDelay;
-    }
-    case features::SubframeShutdownDelayType::kConstantLong: {
-      return kLongDelay;
-    }
-    case features::SubframeShutdownDelayType::kHistoryBased: {
-      const base::TimeDelta reuse_interval =
-          RecentlyDestroyedHosts::GetPercentileReuseInterval(50,
-                                                             browser_context);
-      // If no subframe reuse has happened recently, don't delay process
-      // shutdown at all.
-      if (reuse_interval.is_zero())
-        return kZeroDelay;
-      return std::min(reuse_interval + kDelayBuffer, kLongDelay);
-    }
-    case features::SubframeShutdownDelayType::kHistoryBasedLong: {
-      const base::TimeDelta reuse_interval =
-          RecentlyDestroyedHosts::GetPercentileReuseInterval(75,
-                                                             browser_context);
-      // If no subframe reuse has happened recently, don't delay process
-      // shutdown at all.
-      if (reuse_interval.is_zero())
-        return kZeroDelay;
-      return std::min(reuse_interval + kDelayBuffer, kLongDelay);
-    }
-    case features::SubframeShutdownDelayType::kMemoryBased: {
-      // See subframe-reuse design doc for more detail on these values.
-      // docs.google.com/document/d/1x_h4Gg4ForILEj8A4rMBX6d84uHWyQ9RSXmGVqMlBTk
-      static constexpr uint64_t kHighMemoryThreshold = 8'000'000'000;
-      static constexpr uint64_t kMaxMemoryThreshold = 16'000'000'000;
-
-      const uint64_t available_memory =
-          base::SysInfo::AmountOfAvailablePhysicalMemory();
-      if (available_memory <= kHighMemoryThreshold)
-        return kShortDelay;
-      if (available_memory >= kMaxMemoryThreshold)
-        return kLongDelay;
-
-      // Scale delay linearly based on where |available_memory| lies between
-      // |kHighMemoryThreshold| and |kMaxMemoryThreshold|.
-      const uint64_t available_memory_factor =
-          (available_memory - kHighMemoryThreshold) /
-          (kMaxMemoryThreshold - kHighMemoryThreshold);
-      return kShortDelay + (kLongDelay - kShortDelay) * available_memory_factor;
-    }
-  }
-  NOTREACHED();
+  return base::Milliseconds(kSubframeProcessShutdownDelayInMSec);
 }
 
 // Returns the "document" URL used for a navigation, which might be different
@@ -6799,9 +6742,7 @@
     return;
   }
 
-  if (delegate()->ShowPopupMenu(this, &popup_client, bounds, item_height,
-                                font_size, selected_item, &menu_items,
-                                right_aligned, allow_multiple_selection)) {
+  if (delegate()->ShowPopupMenu(this, bounds)) {
     return;
   }
 
diff --git a/content/browser/renderer_host/render_frame_host_impl_browsertest.cc b/content/browser/renderer_host/render_frame_host_impl_browsertest.cc
index e3c270c6..b52f71d 100644
--- a/content/browser/renderer_host/render_frame_host_impl_browsertest.cc
+++ b/content/browser/renderer_host/render_frame_host_impl_browsertest.cc
@@ -5529,10 +5529,7 @@
     : public RenderFrameHostImplBrowserTest {
  public:
   RenderFrameHostImplSubframeReuseBrowserTest() {
-    scoped_feature_list_.InitAndEnableFeatureWithParameters(
-        features::kSubframeShutdownDelay, {{"type", "constant-long"}});
-    EXPECT_EQ(features::kSubframeShutdownDelayTypeParam.Get(),
-              features::SubframeShutdownDelayType::kConstantLong);
+    scoped_feature_list_.InitAndEnableFeature(features::kSubframeShutdownDelay);
   }
 
  protected:
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 3d03fa1..0893457f 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -9078,16 +9078,8 @@
   last_screen_orientation_change_time_ = ui::EventTimeForNow();
 }
 
-bool WebContentsImpl::ShowPopupMenu(
-    RenderFrameHostImpl* render_frame_host,
-    mojo::PendingRemote<blink::mojom::PopupMenuClient>* popup_client,
-    const gfx::Rect& bounds,
-    int32_t item_height,
-    double font_size,
-    int32_t selected_item,
-    std::vector<blink::mojom::MenuItemPtr>* menu_items,
-    bool right_aligned,
-    bool allow_multiple_selection) {
+bool WebContentsImpl::ShowPopupMenu(RenderFrameHostImpl* render_frame_host,
+                                    const gfx::Rect& bounds) {
   OPTIONAL_TRACE_EVENT1("content", "WebContentsImpl::ShowPopupMenu",
                         "render_frame_host", render_frame_host);
   DCHECK(render_frame_host->IsActive());
@@ -9095,14 +9087,6 @@
     std::move(show_poup_menu_callback_).Run(bounds);
     return true;
   }
-#if BUILDFLAG(IS_MAC)
-  if (browser_plugin_guest_) {
-    browser_plugin_guest_->ShowPopupMenu(
-        render_frame_host, popup_client, bounds, item_height, font_size,
-        selected_item, menu_items, right_aligned, allow_multiple_selection);
-    return true;
-  }
-#endif
   return false;
 }
 
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index 44879fc..c12caff 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -768,16 +768,8 @@
           blink_widget_host,
       mojo::PendingAssociatedRemote<blink::mojom::Widget> blink_widget)
       override;
-  bool ShowPopupMenu(
-      RenderFrameHostImpl* render_frame_host,
-      mojo::PendingRemote<blink::mojom::PopupMenuClient>* popup_client,
-      const gfx::Rect& bounds,
-      int32_t item_height,
-      double font_size,
-      int32_t selected_item,
-      std::vector<blink::mojom::MenuItemPtr>* menu_items,
-      bool right_aligned,
-      bool allow_multiple_selection) override;
+  bool ShowPopupMenu(RenderFrameHostImpl* render_frame_host,
+                     const gfx::Rect& bounds) override;
   void DidLoadResourceFromMemoryCache(
       RenderFrameHostImpl* source,
       const GURL& url,
diff --git a/content/browser/web_contents/web_contents_view_child_frame.cc b/content/browser/web_contents/web_contents_view_child_frame.cc
index 19317eff..14b04eef 100644
--- a/content/browser/web_contents/web_contents_view_child_frame.cc
+++ b/content/browser/web_contents/web_contents_view_child_frame.cc
@@ -17,10 +17,26 @@
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/size.h"
 
+#if BUILDFLAG(IS_MAC)
+#include "content/browser/renderer_host/popup_menu_helper_mac.h"
+#endif
+
 using blink::DragOperationsMask;
 
 namespace content {
 
+namespace {
+#if BUILDFLAG(IS_MAC)
+class NoOpPopupMenuHelperDelegate : public PopupMenuHelper::Delegate {
+ public:
+  void OnMenuClosed() final {
+    // Nothing to clean up, as `PopupMenuHelper` deletes itself at the end of
+    // `WebContentsViewChildFrame::ShowPopupMenu`.
+  }
+};
+#endif
+}  // namespace
+
 WebContentsViewChildFrame::WebContentsViewChildFrame(
     WebContentsImpl* web_contents,
     std::unique_ptr<WebContentsViewDelegate> delegate,
@@ -157,6 +173,27 @@
   NOTREACHED();
 }
 
+#if BUILDFLAG(USE_EXTERNAL_POPUP_MENU)
+void WebContentsViewChildFrame::ShowPopupMenu(
+    RenderFrameHost* render_frame_host,
+    mojo::PendingRemote<blink::mojom::PopupMenuClient> popup_client,
+    const gfx::Rect& bounds,
+    int item_height,
+    double item_font_size,
+    int selected_item,
+    std::vector<blink::mojom::MenuItemPtr> menu_items,
+    bool right_aligned,
+    bool allow_multiple_selection) {
+#if BUILDFLAG(IS_MAC)
+  NoOpPopupMenuHelperDelegate delegate;
+  PopupMenuHelper helper(&delegate, render_frame_host, std::move(popup_client));
+  helper.ShowPopupMenu(bounds, item_height, item_font_size, selected_item,
+                       std::move(menu_items), right_aligned,
+                       allow_multiple_selection);
+#endif  // BUILDFLAG(IS_MAC)
+}
+#endif  // BUILDFLAG(USE_EXTERNAL_POPUP_MENU)
+
 void WebContentsViewChildFrame::StartDragging(
     const DropData& drop_data,
     DragOperationsMask ops,
diff --git a/content/browser/web_contents/web_contents_view_child_frame.h b/content/browser/web_contents/web_contents_view_child_frame.h
index 86d4c18..7cbd8f3 100644
--- a/content/browser/web_contents/web_contents_view_child_frame.h
+++ b/content/browser/web_contents/web_contents_view_child_frame.h
@@ -73,6 +73,18 @@
   void UpdateDragCursor(ui::mojom::DragOperation operation) override;
   void GotFocus(RenderWidgetHostImpl* render_widget_host) override;
   void TakeFocus(bool reverse) override;
+#if BUILDFLAG(USE_EXTERNAL_POPUP_MENU)
+  void ShowPopupMenu(
+      RenderFrameHost* render_frame_host,
+      mojo::PendingRemote<blink::mojom::PopupMenuClient> popup_client,
+      const gfx::Rect& bounds,
+      int item_height,
+      double item_font_size,
+      int selected_item,
+      std::vector<blink::mojom::MenuItemPtr> menu_items,
+      bool right_aligned,
+      bool allow_multiple_selection) override;
+#endif
 
   static RenderWidgetHostViewChildFrame*
   CreateRenderWidgetHostViewForInnerFrameTree(
diff --git a/content/child/dwrite_font_proxy/dwrite_font_proxy_init_impl_win.cc b/content/child/dwrite_font_proxy/dwrite_font_proxy_init_impl_win.cc
index 6c782ac..2a7380d 100644
--- a/content/child/dwrite_font_proxy/dwrite_font_proxy_init_impl_win.cc
+++ b/content/child/dwrite_font_proxy/dwrite_font_proxy_init_impl_win.cc
@@ -79,17 +79,8 @@
 
   skia::OverrideDefaultSkFontMgr(std::move(skia_font_manager));
 
-  // When IDWriteFontFallback is not available (prior to Win8.1) Skia will
-  // still attempt to use DirectWrite to determine fallback fonts (in
-  // SkFontMgr_DirectWrite::onMatchFamilyStyleCharacter), which will likely
-  // result in trying to load the system font collection. To avoid that and
-  // instead fall back on WebKit's fallback logic, we don't use Skia's font
-  // fallback if IDWriteFontFallback is not available.
-  // This flag can be removed when Win8.0 and earlier are no longer supported.
-  bool fallback_available = g_font_fallback != nullptr;
-  DCHECK_EQ(fallback_available,
-            base::win::GetVersion() > base::win::Version::WIN8);
-  blink::WebFontRendering::SetUseSkiaFontFallback(fallback_available);
+  DCHECK(g_font_fallback);
+  blink::WebFontRendering::SetUseSkiaFontFallback(true);
 }
 
 void UninitializeDWriteFontProxy() {
diff --git a/content/public/android/java/src/org/chromium/content/browser/BindingManager.java b/content/public/android/java/src/org/chromium/content/browser/BindingManager.java
index 812ef9e..ac6d0d0c 100644
--- a/content/public/android/java/src/org/chromium/content/browser/BindingManager.java
+++ b/content/public/android/java/src/org/chromium/content/browser/BindingManager.java
@@ -12,6 +12,7 @@
 import androidx.collection.ArraySet;
 
 import org.chromium.base.Log;
+import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.process_launcher.ChildProcessConnection;
 import org.chromium.content_public.browser.ContentFeatureList;
 import org.chromium.content_public.common.ContentFeatures;
@@ -48,6 +49,8 @@
     // by BindingManager.
     private ChildProcessConnection mWaivedConnection;
 
+    private int mConnectionsDroppedDueToMaxSize;
+
     @Override
     public void onTrimMemory(final int level) {
         LauncherThread.post(new Runnable() {
@@ -142,6 +145,12 @@
      */
     void onSentToBackground() {
         assert LauncherThread.runningOnLauncherThread();
+
+        RecordHistogram.recordCount1000Histogram(
+                "Android.BindingManger.ConnectionsDroppedDueToMaxSize",
+                mConnectionsDroppedDueToMaxSize);
+        mConnectionsDroppedDueToMaxSize = 0;
+
         if (mConnections.isEmpty()) return;
         LauncherThread.postDelayed(mDelayedClearer, BINDING_POOL_CLEARER_DELAY_MILLIS);
     }
@@ -243,6 +252,7 @@
         addBinding(connection);
 
         if (mMaxSize != NO_MAX_SIZE && mConnections.size() == mMaxSize + 1) {
+            mConnectionsDroppedDueToMaxSize++;
             removeOldConnections(1);
             ensureLowestRankIsWaived();
         }
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index ce5a163..b116a794 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -1005,16 +1005,6 @@
              base::FEATURE_DISABLED_BY_DEFAULT
 #endif  // BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
 );
-const base::FeatureParam<SubframeShutdownDelayType>::Option delay_types[] = {
-    {SubframeShutdownDelayType::kConstant, "constant"},
-    {SubframeShutdownDelayType::kConstantLong, "constant-long"},
-    {SubframeShutdownDelayType::kHistoryBased, "history-based"},
-    {SubframeShutdownDelayType::kHistoryBasedLong, "history-based-long"},
-    {SubframeShutdownDelayType::kMemoryBased, "memory-based"}};
-const base::FeatureParam<SubframeShutdownDelayType>
-    kSubframeShutdownDelayTypeParam{&kSubframeShutdownDelay, "type",
-                                    SubframeShutdownDelayType::kConstant,
-                                    &delay_types};
 
 // If enabled, GetUserMedia API will only work when the concerned tab is in
 // focus
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index aa3ce940..171b491 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -230,25 +230,6 @@
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kStopVideoCaptureOnScreenLock);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kStrictOriginIsolation);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kSubframeShutdownDelay);
-enum class SubframeShutdownDelayType {
-  // A flat 2s shutdown delay.
-  kConstant,
-  // A flat 8s shutdown delay.
-  kConstantLong,
-  // A variable delay from 0s to 8s based on the median interval between
-  // subframe shutdown and process reuse over the past 5 subframe navigations.
-  // A subframe that could not be reused is counted as 0s.
-  kHistoryBased,
-  // A variable delay from 0s to 8s based on the 75th-percentile interval
-  // between subframe shutdown and process reuse over the past 5 subframe
-  // navigations. A subframe that could not be reused is counted as 0s.
-  kHistoryBasedLong,
-  // A 2s base delay at 8 GB available memory or lower. Above 8 GB available
-  // memory, scales up linearly to a maximum 8s delay at 16 GB or more.
-  kMemoryBased
-};
-CONTENT_EXPORT extern const base::FeatureParam<SubframeShutdownDelayType>
-    kSubframeShutdownDelayTypeParam;
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kSuppressDifferentOriginSubframeJSDialogs);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kSurfaceSyncFullscreenKillswitch);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kSyntheticPointerActions);
diff --git a/content/test/gpu/gpu_tests/gpu_integration_test.py b/content/test/gpu/gpu_tests/gpu_integration_test.py
index e5969a16..eab0a18 100644
--- a/content/test/gpu/gpu_tests/gpu_integration_test.py
+++ b/content/test/gpu/gpu_tests/gpu_integration_test.py
@@ -31,9 +31,9 @@
 _SUPPORTED_WIN_GPU_VENDORS = [0x8086, 0x10de, 0x1002]
 _SUPPORTED_WIN_AMD_GPUS = [0x6613, 0x699f, 0x7340]
 _SUPPORTED_WIN_AMD_GPUS_WITH_NV12_OVERLAYS = [0x7340]
-_SUPPORTED_WIN_INTEL_GPUS = [0x5912, 0x3e92]
-_SUPPORTED_WIN_INTEL_GPUS_WITH_YUY2_OVERLAYS = [0x5912, 0x3e92]
-_SUPPORTED_WIN_INTEL_GPUS_WITH_NV12_OVERLAYS = [0x5912, 0x3e92]
+_SUPPORTED_WIN_INTEL_GPUS = [0x5912, 0x3e92, 0x9bc5]
+_SUPPORTED_WIN_INTEL_GPUS_WITH_YUY2_OVERLAYS = [0x5912, 0x3e92, 0x9bc5]
+_SUPPORTED_WIN_INTEL_GPUS_WITH_NV12_OVERLAYS = [0x5912, 0x3e92, 0x9bc5]
 # Hardware overlays are disabled in 26.20.100.8141 per crbug.com/1079393#c105
 _UNSUPPORTED_WIN_INTEL_GPU_DRIVERS_WITH_NV12_OVERLAYS = ['5912-26.20.100.8141']
 
diff --git a/content/test/gpu/gpu_tests/test_expectations/trace_test_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/trace_test_expectations.txt
index ca7adf8f..41cee7c 100644
--- a/content/test/gpu/gpu_tests/test_expectations/trace_test_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/trace_test_expectations.txt
@@ -153,6 +153,7 @@
 
 # VP9 videos fail to trigger zero copy video presentation path.
 crbug.com/930343 [ win10 intel-0x3e92 ] VideoPathTraceTest_DirectComposition_Video_VP9_Fullsize [ Failure ]
+crbug.com/1385486 [ win10 intel-0x9bc5 ] VideoPathTraceTest_DirectComposition_Video_VP9_Fullsize [ Failure ]
 
 # Incorrectly reporting SCALING instead of DIRECT on Windows 10 UHD 630 GPUs.
 crbug.com/1079393 [ win10 intel-0x3e92 ] OverlayModeTraceTest_DirectComposition_Underlay [ Failure ]
@@ -179,7 +180,9 @@
 
 # Zero copy also needs scaling to work.
 crbug.com/1079393 [ win10 intel-0x3e92 ] VideoPathTraceTest_DirectComposition_Underlay_Fullsize [ Failure ]
+crbug.com/1385486 [ win10 intel-0x9bc5 ] VideoPathTraceTest_DirectComposition_Underlay_Fullsize [ Failure ]
 crbug.com/1079393 [ win10 intel-0x3e92 ] VideoPathTraceTest_DirectComposition_Video_MP4_Fullsize [ Failure ]
+crbug.com/1385486 [ win10 intel-0x9bc5 ] VideoPathTraceTest_DirectComposition_Video_MP4_Fullsize [ Failure ]
 
 # Using YUY2 instead of NV12 on Windows 10 HD 630 GPUs due to NV12 support
 # showing up as SOFTWARE which causes zero copy to fail.
diff --git a/device/bluetooth/floss/bluetooth_device_floss.cc b/device/bluetooth/floss/bluetooth_device_floss.cc
index 9540435..377e8416 100644
--- a/device/bluetooth/floss/bluetooth_device_floss.cc
+++ b/device/bluetooth/floss/bluetooth_device_floss.cc
@@ -44,6 +44,9 @@
 // Link supervision timeout for LE connections.
 const int32_t kDefaultConnectionTimeout = 2000;
 
+// Maximum MTU size that can be requested by Android.
+const int32_t kMaxMtuSize = 517;
+
 void OnCreateBond(DBusResult<bool> ret) {
   if (ret.has_value() && !*ret) {
     BLUETOOTH_LOG(ERROR) << "CreateBond returned failure";
@@ -697,15 +700,11 @@
 
   DCHECK(num_connecting_calls_ >= 0);
 
-  // Trigger service discovery only when connected.
+  // Request for maximum MTU only when connected.
   if (connected) {
-    if (search_uuid.has_value()) {
-      FlossDBusManager::Get()->GetGattClient()->DiscoverServiceByUuid(
-          base::DoNothing(), address_, search_uuid.value());
-    } else {
-      FlossDBusManager::Get()->GetGattClient()->DiscoverAllServices(
-          base::DoNothing(), address_);
-    }
+    FlossDBusManager::Get()->GetGattClient()->ConfigureMTU(
+        base::DoNothing(), address_, kMaxMtuSize);
+    return;
   }
 
   // Complete GATT connection callback.
@@ -772,4 +771,26 @@
   }
 }
 
+void BluetoothDeviceFloss::GattConfigureMtu(std::string address,
+                                            int32_t mtu,
+                                            GattStatus status) {
+  if (address != GetAddress())
+    return;
+
+  VLOG(1) << "GattConfigureMtu on " << GetAddress() << "; mtu=" << mtu
+          << "; status=" << static_cast<uint32_t>(status);
+
+  // Discover services after configuring MTU
+  // This can be done even if configuring MTU failed.
+  if (search_uuid.has_value()) {
+    FlossDBusManager::Get()->GetGattClient()->DiscoverServiceByUuid(
+        base::DoNothing(), address_, search_uuid.value());
+  } else if (!IsGattServicesDiscoveryComplete()) {
+    FlossDBusManager::Get()->GetGattClient()->DiscoverAllServices(
+        base::DoNothing(), address_);
+  }
+
+  DidConnectGatt(absl::nullopt);
+}
+
 }  // namespace floss
diff --git a/device/bluetooth/floss/bluetooth_device_floss.h b/device/bluetooth/floss/bluetooth_device_floss.h
index 7e93916..d7cec39 100644
--- a/device/bluetooth/floss/bluetooth_device_floss.h
+++ b/device/bluetooth/floss/bluetooth_device_floss.h
@@ -136,6 +136,9 @@
                              int32_t latency,
                              int32_t timeout,
                              GattStatus status) override;
+  void GattConfigureMtu(std::string address,
+                        int32_t mtu,
+                        GattStatus status) override;
 
   // Returns the adapter which owns this device instance.
   BluetoothAdapterFloss* adapter() const {
diff --git a/device/bluetooth/floss/bluetooth_gatt_floss_unittest.cc b/device/bluetooth/floss/bluetooth_gatt_floss_unittest.cc
index 6b96847..3d0190b1 100644
--- a/device/bluetooth/floss/bluetooth_gatt_floss_unittest.cc
+++ b/device/bluetooth/floss/bluetooth_gatt_floss_unittest.cc
@@ -152,6 +152,12 @@
     fake_floss_gatt_client_->GattSearchComplete(address, services, status);
   }
 
+  void SetGattConfigureMtu(std::string address,
+                           int32_t mtu,
+                           GattStatus status) {
+    fake_floss_gatt_client_->GattConfigureMtu(address, mtu, status);
+  }
+
   GattService CreateFakeServiceFor(const device::BluetoothUUID& uuid) {
     GattService underlying_service;
     underlying_service.uuid = uuid;
@@ -230,6 +236,9 @@
   // Create a gatt connection with partial service discovery.
   paired_device->CreateGattConnection(base::DoNothing(), fake_uuid_optional);
 
+  // Fake a successful configure MTU.
+  SetGattConfigureMtu(paired_device->GetAddress(), 500, GattStatus::kSuccess);
+
   // Fake a connection completion.
   SetGattConnectionState(GattStatus::kSuccess, /*connected=*/true,
                          paired_device->GetAddress());
diff --git a/device/bluetooth/floss/bluetooth_remote_gatt_descriptor_floss.cc b/device/bluetooth/floss/bluetooth_remote_gatt_descriptor_floss.cc
index e8efd49..897890a 100644
--- a/device/bluetooth/floss/bluetooth_remote_gatt_descriptor_floss.cc
+++ b/device/bluetooth/floss/bluetooth_remote_gatt_descriptor_floss.cc
@@ -8,6 +8,8 @@
 
 #include "base/memory/ptr_util.h"
 #include "base/strings/stringprintf.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/device_event_log/device_event_log.h"
 #include "device/bluetooth/floss/bluetooth_adapter_floss.h"
 #include "device/bluetooth/floss/bluetooth_remote_gatt_characteristic_floss.h"
 #include "device/bluetooth/floss/bluetooth_remote_gatt_service_floss.h"
@@ -16,6 +18,8 @@
 
 namespace floss {
 
+const int kGattTimeoutMs = 2000;
+
 // static
 std::unique_ptr<BluetoothRemoteGattDescriptorFloss>
 BluetoothRemoteGattDescriptorFloss::Create(
@@ -131,6 +135,8 @@
   }
 
   auto [callback, error_callback, data] = std::move(pending_write_callbacks_);
+  DCHECK(callback);
+  DCHECK(error_callback);
 
   if (status == GattStatus::kSuccess) {
     cached_data_ = data;
@@ -184,6 +190,23 @@
 
   pending_write_callbacks_ = std::make_tuple(
       std::move(callback), std::move(error_callback), std::move(data));
+
+  // Ensure callbacks don't get dropped if no |GattDescriptorWrite| received.
+  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE,
+      base::BindOnce(&BluetoothRemoteGattDescriptorFloss::OnWriteTimeout,
+                     weak_ptr_factory_.GetWeakPtr()),
+      base::Milliseconds(kGattTimeoutMs));
+}
+
+void BluetoothRemoteGattDescriptorFloss::OnWriteTimeout() {
+  if (std::get<0>(pending_write_callbacks_)) {
+    BLUETOOTH_LOG(ERROR)
+        << "Timeout waiting for GattDescriptorWrite for Device "
+        << service_->GetDevice()->GetAddress();
+    GattDescriptorWrite(service_->GetDevice()->GetAddress(), GattStatus::kError,
+                        descriptor_->instance_id);
+  }
 }
 
 void BluetoothRemoteGattDescriptorFloss::NotifyValueChanged() {
diff --git a/device/bluetooth/floss/bluetooth_remote_gatt_descriptor_floss.h b/device/bluetooth/floss/bluetooth_remote_gatt_descriptor_floss.h
index ea1c84c..974636c 100644
--- a/device/bluetooth/floss/bluetooth_remote_gatt_descriptor_floss.h
+++ b/device/bluetooth/floss/bluetooth_remote_gatt_descriptor_floss.h
@@ -77,6 +77,9 @@
                          std::vector<uint8_t> data,
                          DBusResult<Void> result);
 
+  // Handle timeout for receiving a |GattDescriptorWrite|.
+  void OnWriteTimeout();
+
   // Send notifications to observer on adapter.
   void NotifyValueChanged();
 
diff --git a/docs/lacros/test_dut_lacros.md b/docs/lacros/test_dut_lacros.md
new file mode 100644
index 0000000..c949336
--- /dev/null
+++ b/docs/lacros/test_dut_lacros.md
@@ -0,0 +1,99 @@
+# Test Lacros using real devices or cros VM
+
+## How to run a test?
+
+### Lacros Tast test on DUT/VM
+
+#### Run Tast from Chrome OS chroot
+
+Currently, the official way to write and run Tast tests from workstation is to
+use the ChromeOS Chroot, which needs chromiumos repository setup. Here are
+reference documents:
+
+[Checking out the ChromiumOS repo and create a chroot](https://chromium.googlesource.com/chromiumos/docs/+/main/developer_guide.md)
+
+[Run Tast tests: Quick Start](https://chromium.googlesource.com/chromiumos/platform/tast/+/HEAD/docs/quickstart.md)
+
+If you don’t have a chromiumos repo already, it’s worth noting that it will take
+at least a few hours to get one set up for the first time.
+
+Example command to run the tast test in Chroot:
+```
+(chroot)% tast run ${your-device-ip}:${port} lacros.Basic
+```
+By default, the test will use the RootFS Lacros that is packaged as part of the
+ChromeOS image, to test a newly built Lacros, first deploy the Lacros to the DUT:
+```
+./third_party/chromite/bin/deploy_chrome --build-dir=out_device_lacros/Release \
+--device="${your-device-ip}:${port}" --nostrip --lacros
+```
+
+And then invoke tast command with a var:
+```
+(chroot)% tast run -var=lacros.DeployedBinary=/usr/local/lacros-chrome
+${your-device-ip}:${port} lacros.Basic
+```
+
+#### Using chromium bots
+
+If you don’t need to write or modify Tast tests, and just want to run them to
+check if your change works with the current Tast tests, the quickest way is to
+trigger the Chromium bots: lacros-amd64-generic-rel. Compared to 1 above, it is
+very easy for those who do not have a ChromiumOS checkout locally. However, it
+only supports running pre-built tests, so there’s no flexibility for modifying
+or debugging the tests themselves, so If you hit any unexpected issues, please
+fallback to using the Chrome OS chroot workflow described above.
+
+#### Chromium-style GN/Ninja approach
+
+One can also reproduce the 2nd workflow locally with the following steps:
+
+First of all, build the lacros_all_tast_tests test target
+```
+autoninja -C out_device_lacros/Release/ lacros_all_tast_tests
+```
+
+Confirm that the build successfully generated a test runner script
+```
+ls out_device_lacros/Release/bin/run_lacros_all_tast_tests
+```
+
+Run tast test on the DUT
+```
+./out_device_lacros/Release/bin/run_lacros_all_tast_tests --board=${board} \
+--device="${your-device-ip}:${port}" --logs-dir ~/test/logs
+```
+
+For how to set up a DUT, please see [Build DUT Lacros](build_dut_lacros.md).
+If you don’t have a physical device, you can use a VM with the following command:
+```
+./out_device_lacros/Release/bin/run_lacros_all_tast_tests –board=amd64-generic \
+ –use-vm --logs-dir ~/test/logs
+```
+
+Run unit tests on DUT
+
+It’s almost identical to the Chromium-style GN/Ninja approach for running Tast
+tests. Take cc_unittests for example:
+
+First of all, build the cc_unittests test target
+```
+autoninja -C out_device_lacros/Release/ cc_unittests
+```
+
+Confirm that the build successfully generated a test runner script
+```
+ls out_device_lacros/Release/bin/run_cc_unittests
+```
+
+### How to write a new test?
+
+#### Tast test
+
+[tast-writing](https://chromium.googlesource.com/chromiumos/platform/tast/+/HEAD/docs/writing_tests.md)
+
+(Internal only)[go/lacros-tast-porting](https://goto.google.com/lacros-tast-porting)
+
+(Internal only)[go/tast-failures](https://goto.google.com/tast-failures),
+[go/tast-debugging-guide](https://goto.google.com/tast-debugging-guide)
+Investigate test failures or debug.
diff --git a/docs/lacros/test_instructions.md b/docs/lacros/test_instructions.md
new file mode 100644
index 0000000..e028bce
--- /dev/null
+++ b/docs/lacros/test_instructions.md
@@ -0,0 +1,106 @@
+# Test Lacros
+
+Please read [Build instructions](build_instructions.md) first.
+This document focuses on testing related topics.
+
+## Instructions for Google Employees
+
+Are you a Google employee? See
+[go/lacros-test](https://goto.google.com/lacros-test) instead.
+
+
+## Overview
+
+Depends on which workflow you use, they support different test targets.
+
+### Building and running on a Linux workstation.
+
+  See [Lacros: Test instructions(linux)](test_linux_lacros.md).
+
+  These tests are running on CI: linux-lacros-tester-rel and CQ: linux-lacros-rel.
+
+*    Lacros gtest unit tests
+*    Lacros browser tests
+*    Lacros Interactive_ui_tests
+*    crosapi tests: lacros_chrome_browsertests,
+lacros_chrome_browsertests_run_in_series
+*    Ash browser tests that requires Lacros: Only On Linux ChromeOS.
+(linux-chromeos-rel).
+*    Version skew testing: lacros_chrome_browsertests,
+lacros_chrome_browsertests_run_in_series, interactive_ui_tests.
+
+### Building on a Linux workstation and running on a physical ChromeOS device.
+
+  See [Lacros: Test instructions(DUT)](test_dut_lacros.md)
+
+*    Lacros gtest unit tests
+*    Lacros Tast tests: On ChromeOS DUT/VM(lacros-amd64-generic-rel,
+lacros-amd64-generic-chrome, etc)
+*    Version skew testing:
+     Lacros Tast tests on ChromeOS DUT/VM(lacros-amd64-generic-chrome-skylab,
+lacros-arm-generic-chrome-skylab)
+
+### Building on a Linux workstation and running on a ChromeOS VM running on the same Linux workstation.
+
+  Same as above.
+
+## What tests should you provide for your feature?
+
+### gtest unit test
+
+Unit tests should always be provided. We’re targeting per file unit test
+coverage of 70%.
+
+### Lacros browser tests
+
+This refers to the tests in the browser_tests target. If your test is primarily
+testing that your feature should work on Lacros, and your test will not cause
+Lacros to call mojo crosapi, you should add a Lacros browser tests.
+crosapi browser tests (lacros_chrome_browsertests)
+If you’re testing crosapi, you need to add your test to
+lacros_chrome_browsertests.
+
+### Ash browser tests require Lacros
+
+If your test is primarily testing Ash, but also requires Lacros to present,
+you should add ash browser tests. The main difference is that browser() call
+in the test will return an ash browser instance.
+
+Note: if it’s fine to use lacros browser test or ash browser test, then please
+add lacros browser test. Lacros browser test only start Ash once, then create
+new Lacros for every test case.
+
+However for ash browser test, for every test case it starts Ash as wayland
+server every time, and then starts an ash browser and a Lacros browser. So in
+theory, for the same test written in different approaches, Lacros browser test
+is faster and more stable.
+
+### Tast test
+
+If your feature is hard/impossible to test on Linux, you should provide a Tast
+test running on real cros devices or cros VM.
+
+## Lacros related builders
+
+*    https://ci.chromium.org/p/chromium/builders/ci/linux-lacros-builder-rel
+*    https://ci.chromium.org/p/chromium/builders/ci/linux-lacros-tester-rel
+*    https://ci.chromium.org/p/chromium/builders/try/linux-lacros-rel
+
+CI/CQ linux lacros builder runs unit tests, browser tests, version skew tests.
+
+*    https://ci.chromium.org/p/chrome/builders/ci/lacros-amd64-generic-chrome
+
+CI builder using official gn args and running some Tast tests on DUT.
+
+*    https://ci.chromium.org/p/chrome/builders/ci/lacros-arm-generic-chrome
+
+Corresponding arm builder.
+
+*    https://ci.chromium.org/p/chromium/builders/ci/linux-lacros-dbg
+*    https://ci.chromium.org/p/chromium/builders/try/linux-lacros-dbg
+
+CI and optional tryjob for running lacros tests with dbg gn args.
+
+And also various of ash builders like chromeos-eve-chrome. If your cl only
+changes ash, you can check ash builders which ensure your ash change doesn’t
+break lacros.
diff --git a/docs/lacros/test_linux_lacros.md b/docs/lacros/test_linux_lacros.md
new file mode 100644
index 0000000..3dd0aaf
--- /dev/null
+++ b/docs/lacros/test_linux_lacros.md
@@ -0,0 +1,194 @@
+# Test Lacros using linux-chromeos workflow
+
+## How to run a test?
+
+Lacros gtest unit tests, browser tests
+This instructions is helpful if you want to repro/debug a test failure on
+bot(linux-lacros-rel, linux-lacros-tester-rel).
+
+1.  .gclient includes chromeos
+
+Make sure your gclient config has ‘target_os=["chromeos"]’.
+```
+$ cat ../.gclient
+solutions = [
+  {
+    "name": "src",
+    "url": "https://chromium.googlesource.com/chromium/src.git",
+    "managed": False,
+    "custom_deps": {},
+    "custom_vars": {
+    },
+  },
+]
+target_os=["chromeos"]
+```
+
+Note: don’t forget to run ‘gclient sync’ after you make changes.
+
+2.  Use the correct gn args
+
+Only 2 sets of gn args are officially supported. Some small changes should
+likely work but there’s no guarantee. If you are new to this, highly recommend
+you do not change any gn args. For non-Googlers, you can ignore the use_goma arg.
+
+CQ gn args:
+```
+also_build_ash_chrome = true
+chromeos_is_browser_only = true
+dcheck_always_on = true
+is_component_build = false
+is_debug = false
+symbol_level = 0
+target_os = "chromeos"
+use_goma = true
+```
+
+CI gn args:
+```
+also_build_ash_chrome = true
+chromeos_is_browser_only = true
+dcheck_always_on = false
+is_component_build = false
+is_debug = false
+target_os = "chromeos"
+use_goma = true
+```
+
+3.  Build your target
+
+Here is an example for browser_tests:
+```
+autoninja -C out_linux_lacros/Release browser_tests
+```
+
+4.  Run your test
+
+```
+build/lacros/test_runner.py test out_linux_lacros/Release/browser_tests \
+--gtest_filter=BrowserTest.Title \
+--ash-chrome-path out_linux_lacros/Release/ash_clang_x64/test_ash_chrome
+```
+Or
+```
+out_linux_lacros/Release/bin/run_browser_tests --gtest_filter=BrowserTest.Title
+```
+
+You can use this to run Chrome tests, such as browser_tests, unit_tests,
+interactive_ui_tests, lacros_chrome_browsertests,
+lacros_chrome_browsertests_run_in_series etc.
+
+Note: Some tests are disabled by filter file. e.g. This
+[file](https://source.chromium.org/chromium/chromium/src/+/main:testing/buildbot/filters/linux-lacros.browser_tests.filter)
+is for browser_tests.
+
+Note: interactive_ui_tests that rely on weston-test cannot run on release builds
+(is_official_build must be false). i.e. BrowserActionInteractiveTest*,
+MenuItemViewTest*, etc.
+
+Note: If you're sshing to your desktop, please prefix the command with
+./testing/xvfb.py.
+
+For frequent Lacros developers:
+
+This can help increase developer productivity. Don’t use this if you’re trying
+to repro a bot failure.
+
+1.  Use a prebulit ash chrome
+
+By default, //build/lacros/test_runner.py downloads a prebuilt test_ash_chrome.
+If you only change Lacros, this would save your time to not build ash.
+```
+./build/lacros/test_runner.py test out_linux_lacros/Release/lacros_chrome_browsertests --gtest_filter=ScreenManagerLacrosBrowserTest.*
+```
+
+2.  Build linux Ash in a separate folder
+
+Build test_ash_chrome in out_linux_ash and pass in it using –ash-chrome-path.
+```
+./build/lacros/test_runner.py test \
+--ash-chrome-path=out_linux_ash/Release/test_ash_chrome \
+out_linux_lacros/Release/lacros_chrome_browsertests \
+--gtest_filter=ScreenManagerLacrosBrowserTest.*
+```
+
+## Linux version skew testing
+
+If you see a test step name like “lacros_chrome_browsertests_Lacros version skew
+testing ash 101.0.4951.1 on Ubuntu-18.04”, this means it’s version skew testing
+that “lacros_chrome_browsertests” target is running against a pre-built ash with
+version 101.0.4951.13.
+
+There are two ways to run Linux based version skew testing:
+
+1. Use a prebuilt ash (recommended)
+
+First follow the previous section to build your target. Then download ash.
+Assuming you want to test against ash 92.0.4515.130.
+```
+cipd auth-login
+echo "chromium/testing/linux-ash-chromium/x86_64/ash.zip version:92.0.4515.130" > /tmp/ensure-file.txt
+cipd ensure -ensure-file /tmp/ensure-file.txt -root lacros_version_skew_tests_v92.0.4515.130
+```
+
+Then you can use
+```
+./build/lacros/test_runner.py test \
+out_linux_lacros_lacros/Release/lacros_chrome_browsertests \
+--ash-chrome-path-override=lacros_version_skew_tests_v92.0.4515.130/test_ash_chrome
+```
+to run the test against that version of ash.
+
+2. Build ash locally
+
+Follow [working with release branches](https://www.chromium.org/developers/how-tos/get-the-code/working-with-release-branches/)
+to first build ash test_ash_chrome.
+
+Assuming the target is at /absolute/path/out/ashdesktop/test_ash_chrome. Then
+you can pass --ash-chrome-path-override=/absolute/path/out/ashdesktop/test_ash_chrome
+
+If you’re debugging, we suggest you have 2 checkouts, one for (older) ash and
+the other for (newer) lacros.
+
+### Ash browser tests require Lacros
+
+Use the following gn args((this is the bot config, other args would likely to
+work as well) to build Ash browser tests, and the alternate toolchain for
+building Lacros in a subfolder:
+```
+also_build_lacros_chrome = true
+dcheck_always_on = false
+ffmpeg_branding = "ChromeOS"
+is_component_build = false
+is_debug = false
+proprietary_codecs = true
+target_os = "chromeos"
+use_goma = true
+```
+Run the demo test with:
+```
+out/ashdesktop/browser_tests --lacros-chrome-path=out/ashdesktop/lacros_clang_x64 --gtest_filter=DemoAshRequiresLacrosTest*
+```
+Demo test is at
+[demo_ash_requires_lacros_browsertest.cc](https://source.chromium.org/chromium/chromium/src/+/main:chrome/test/base/chromeos/demo_ash_requires_lacros_browsertest.cc)
+
+
+## How to write a new test?
+
+### Lacros browser tests
+
+Writing a browser test for Lacros is similar to that on other platforms.
+
+If you need to fake some components in ash, you can add it in
+[fake_ash_test_chrome_browser_main_extra_parts.cc](https://source.chromium.org/chromium/chromium/src/+/main:chrome/test/base/chromeos/fake_ash_test_chrome_browser_main_extra_parts.cc).
+
+If you need Lacros to control Ash behavior, you can modify
+[TestControllerAsh](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ash/crosapi/test_controller_ash.h?q=TestControllerAsh&ss=chromium%2Fchromium%2Fsrc).
+
+### Ash browser tests require Lacros
+
+See
+[demo_ash_requires_lacros_browsertest.cc](https://source.chromium.org/chromium/chromium/src/+/main:chrome/test/base/chromeos/demo_ash_requires_lacros_browsertest.cc)
+ for how to write/run ash browser
+tests. We are using a positive test filter. So if you’re adding a new test to
+browser_tests_require_lacros, you need to add your test in the filter file.
diff --git a/docs/webapps/README.md b/docs/webapps/README.md
new file mode 100644
index 0000000..9a20d52
--- /dev/null
+++ b/docs/webapps/README.md
@@ -0,0 +1,357 @@
+## Web Apps
+
+### What are web apps?
+
+Simply put web apps are sites that the user installs onto their machine mimicking a native app installed on their respective operating system.
+
+#### User entry points
+
+Sites that meet our install promotion requirements will have an install prompt appear in the omnibox on the right. Users can also install any site they like via `Menu > More tools > Create shortcut`....
+
+- Example site: https://developers.google.com/
+
+Users can see all of their web apps on chrome://apps (viewable on non-ChromeOS).
+
+#### Developer interface
+
+Sites customize how their installed site integrates at the OS level using a [web app manifest][2]. See developer guides for in depth overviews:
+
+- https://web.dev/progressive-web-apps/
+- https://web.dev/codelab-make-installable/
+
+#### Presentation
+
+See [https://tinyurl.com/dpwa-architecture-public][3] for presentation slides.
+
+### Terms & Phrases
+
+See [Web Apps - Concepts][4].
+
+### Debugging
+
+Use [chrome://web-app-internals][5] to inspect internal web app state. For Chromium versions prior to M93 use [chrome://internals/web-app][6].
+
+### Documentation Guidelines
+
+- Markdown documentation (files like this):
+  - Contains information that can't be documented in class-level documentation.
+  - Answers questions like: What is the goal of a group of classes together? How does a group of classes work together?
+  - Explains concepts that are used across different files.
+  - Should be unlkely to become out-of-date.
+    - Any source links should link to a codesearch 'search' page and not the specific line number.
+    - Avoid implementation details.
+- Class-level documentation (documentation in header files):
+  - Answers questions like: Why does this class exist? What is the responsibility of this class? If this class involves a process with stages, what are those stages / steps?
+  - Should be updated actively when that given file is changed.
+- Documentation inside of methods
+  - Should explain the "why" of code if it is not clear.
+  - Should be avoided otherwise.
+
+### What makes up Chromium's implementation?
+
+The task of turning websites into "apps" in the user's OS environment has many parts to it. Before going into the parts, here is where they live:
+
+![](webappprovider_component_ownership.jpg)
+
+See source [here][7].
+
+- The `WebAppProvider` core system lives on the `Profile` object.
+- The `WebAppUiManagerImpl` also lives on the `Profile` object (to avoid deps issues).
+- The `AppBrowserController` (typically `WebAppBrowserController` for our interests) lives on the `Browser` object.
+- The `WebAppTabHelper` lives on the `WebContents` object.
+
+While most on-disk storage is done in the [`WebAppSyncBridge`][8], the system also sometimes uses the `PrefService`. Most of these prefs live on the `Profile` (`profile->GetPrefs()`), but some prefs are in the global browser prefs (`g_browser_process->local_state()`).
+
+Presentation: [https://tinyurl.com/dpwa-architecture-public][3]
+
+Older presentation: [https://tinyurl.com/bmo-public][9]
+
+### Architecture Philosophy
+
+There are a lot of great guidelines within Chromium
+
+- [Style guides][45]
+- [Dos and Don'ts][47]
+- etc.
+
+Other than general guidance of minimal complexity and having single-responsibility classes, some goals of our system:
+
+- Tests should operate on the [public interface][48] as much as possible. Refactors to the internal system should not involve fixing / modifying tests.
+- [External dependencies][49] should be behind fake-able interfaces, allowing unit & browser tests to swap these out. However, internal parts of our system should not be mocked out or faked - this tightly couples the internal implementation to our tests. If it is impossible to trigger a condition with the public interface, then that condition should be removed (or the public interface improved).
+  - Here is a nice [presentation][44] about testing that might clarify our approach.
+
+### Public Interface
+
+This public interface should (and will) be improved, however this is the basic state as of 2022/11/09:
+
+- `WebAppCommandScheduler`. Internally this schedules `WebAppCommand`s to do safe operations on the system.
+  - This already includes a variety of operations like installation, launching, etc.
+- Observers like the `AppRegistrarObserver` or `WebAppInstallManagerObserver`. However, users of these MUST NOT modify the web app system in the observation call - this can cause race conditions and weird re-entry bugs.
+- Items exposed from the locks given to commands or callbacks:
+  - `WebAppRegistrar`
+  - Writing to the database using `ScopedRegistryUpdate` and the `WebAppSyncBridge`.
+  - Pref reading & writing
+  - etc - see the documentation on the lock for more guidance.
+- `WebAppIconManager` supports icon fetch for a given web app. This isn't yet normally protected in a command / lock, and due to performance needs with things like right-click menus this integration might happen last.
+
+Some parts of the system that are used within commands:
+
+- `WebAppUrlLoader` & `WebAppDataRetriever` are used in commands, but this interface could be improved & does not have a formal factory yet.
+- `WebAppInstallFinalizer` is used in commands and could be improved.
+
+### External Dependencies
+
+The goal is to have all of these behind an abstraction that has a fake to allow easy unit testing of our system. Some of these dependencies are behind a nice fake-able interface, and some are not (yet).
+
+- **Extensions** - Some of our code still talks to the extensions system, specifically the `PreinstalledWebAppManager`.
+- **`content::WebContents`**
+  - **`WebAppUrlLoader`** - load a given url in a `WebContents`. Faked by `FakeWebAppUrlLoader`.
+  - **`WebAppDataRetriever`** - retrieve installability information, the manifest, or icons from a **`WebContents`**. Faked by `FakeWebAppDataRetriever`.
+  - Misc:
+    - Navigation completion in WebAppTabHelper, used to kick off update commands.
+    - Listening to destruction.
+    - IsPrimaryMainFrame() and other filtering.
+    - etc
+- **OS Integration**: Each OS integration has fairly custom code on each OS to do the operation. This is difficult to coordinate and test. Currently the `OsIntegrationManger` manages this, which has a fake version.
+- **Sync system**
+  - There is a tight coupling between our system and the sync system through the WebAppSyncBridge.
+  - Faking this is easy and is handled by the `FakeWebAppProvider`.
+- **UI**: There are parts of the system that are coupled to UI, like showing dialogs, determining information about app windows, etc. These are put behind the `WebAppUiManager`, and faked by the `FakeWebAppUiManager`.
+- **Policy**: Our code depends on the policy system setting it's policies in appropriate prefs for us to read. Because we just look at prefs, we don't need a "fake" here.
+
+### Databases / sources of truth
+
+These store data for our system. Some of it is per-web-app, and some of it is global.
+
+- **`WebAppRegistrar`**: This attempts to unify the reading of much of this data, and also holds an in-memory copy of the database data (in WebApp objects).
+- **`WebAppDatabase`** / **`WebAppSyncBridge`**: This stores the web_app.proto object in a database, which is the preferred place to store information about a web app.
+- **Icons on disk**: These are managed by the `WebAppIconManager` and stored on disk in the user's profile.
+- **Prefs**: The `PrefService` is used to store information that is either global, or needs to persist after a web app is uninstalled. Most of these prefs live on the `Profile` (`profile->GetPrefs()`), but some prefs are in the global browser prefs (`g_browser_process->local_state()`). Some users of prefs:
+  - AppShimRegistry
+  - UserUninstalledPreinstalledWebAppPrefs
+- **OS Integration**: Various OS integration requires storing state on the operating system. Sometimes we are able to read this state back, sometimes not.
+
+None of this information should be accessed without an applicable 'lock' on the system.
+
+### Managers
+
+These are used to encapsulate common responsibilities or in-memory state that needs to be stored.
+
+### Commands
+
+Commands are used to encapsulate operations in the system, and use Locks to ensure that your operation has isolation from other operations.
+
+- If you need to change something in the WebAppProvider system, you should probably use a command.
+- Commands talk to the system using locks they are granted. The locks should offer access to "managers" that the commands can use.
+
+### Locks / `WebAppLockManager`
+
+Locks allow operations to receive appropriate protections for what they are doing. For example, an `AppLock` will guarantee that no one is modifying (or uninstalling) an app while it is granted.
+
+Locks contain assessors that allow the user to access parts of the web app system. This is the safest way to read from the system.
+
+### OS Integration
+
+Anything that involves talking to the operating system. Usually has to do with adding, modifying, or removing the os entity that we register for the web app.
+
+## Deep Dives
+
+- [/docs/webapps/installation_pipeline.md][34]
+- [/docs/webapps/manifest_representations.md][35]
+- [/docs/webapps/integration-testing-framework.md][11]
+- [/docs/webapps/os_integration.md][50]
+- TODO: manifest update
+
+## How To Use
+
+See the [public interface][48] section about which areas are generally "publicly available".
+
+The system is generally unit-test-compabible through the `FakeWebAppProvider`, which is created by default in the `TestingProfile`. Sometimes tests require using the [`AwaitStartWebAppProviderAndSubsystems`][41] function in the setup function of the test to actually start the web app system & wait for it to complete startup.
+
+There is a long-term goal of having the system be easily fake-able for customers using it. The best current 'public interface' distinction of the system is the `WebAppCommandScheduler`, but this hopefully will get more clear in the future.
+
+To access or change information about a web app:
+
+- Obtain a lock from the `WebAppLockManager`, or (preferably) create a command with the relevant lock description.
+- When the lock is obtained (or the command is started with the lock), use the lock to access the data you need.
+  - Generally, you should use the `WebAppRegistrar` to get the data you need. This unifies many of our data sources into one place.
+- If changing data, change the data depending on the source of truth.
+  - For information in the database, use a `ScopedRegistryUpdate`.
+  - Otherwise use the relevant manager / helper to modify the data.
+  - Some things expect "observers" to be notified. That integration is currently in `WebAppSyncBridge`, but can be pulled out.
+
+Other guides:
+
+- [/docs/webapps/why-is-this-test-failing.md][36]
+- [/docs/webapps/how-to-create-webapp-integration-tests.md][37]
+
+## Testing
+
+Please read [Testing In Chromium][42] for general guidance on writing tests in chromium.
+
+The following tests are expected for writing code in this system:
+
+* Unit tests
+* Browser tests
+* Integration tests
+
+### Unit tests
+
+Unit tests have the following benefits:
+
+* They are very efficient.
+* They run on all relevant CQ trybots.
+* They will always be supported by the [code coverage][43] framework.
+
+Unit tests are the fastest tests to execute and are expected to be used to test most cases, especially error cases. They are usually built on the `WebAppTest` base class, and use the `FakeWebAppProvider` to customize (or not) the [dependencies][49] of the `WebAppProvider` system.
+
+Notes
+
+- WebContents and other UI elements do not work in unit tests, and the appropriate fakes must be used (see [External Dependencies][49]).
+- If one of the external dependencies of the system cannot be faked out yet or the feature is tightly coupled to this, then it might make sense to use a browser test instead (or make that dependency fake-able).
+
+### Browser tests
+
+With improved web app test support, most of the components should using unittests to cover the detailed test cases.
+
+Creating an integration test (using the integration framework) should satisfy the need for end-to-end tests for major use-cases of your feature. However, you may need to create one due to:
+
+- The unittest framework doesn’t support certain needs.
+- You need end-to-end test, but using integration test framework has too much overhead in current state.
+
+Browser tests are much more expensive to run, as they basically run a fully functional browser with it's own profile directory. These tests are usually only created to test functionality that requires multiple parts of the system to be running or dependencies like the Sync service to be fully running and functional. It is good practice to have browsertests be as true-to-user-action as possible, to make sure that as much of our stack is exercised.
+
+An example set of browser tests are in [`web_app_browsertest.cc`][38]
+
+### Integration tests
+
+We have a custom integration testing framework that we use due to the complexity of our use-cases. See [integration-testing-framework.md][11] for more information.
+
+**It is a good idea to think about your integration tests early & figure out your CUJs with the team. Having your CUJs and integration tests working early greatly speeds up development & launch time.**
+
+### `Fake*` or `Test*` classes
+
+A class that starts with `Fake` or `Test` is meant to completely replace a component of the system. They should be inheriting from a base class (often pure virtual) and then implement a version of that component that will seem to be working correctly to other system components, but not actually do anything.
+
+An example is [fake_os_integration_manager.h][39], which pretends to successfully do install, update, and uninstall operations, but actually just does nothing.
+
+### `Mock*` classes
+
+A class that start with `Mock` is a [gmock][46] version of the class. This allows the user to have complete control of exactly what that class does, verify it is called exactly as expected, etc. These tend to be much more powerful to use than a `Fake`, as you can easily specify every possible case you might want to check, like which arguments are called and the exact calling order of multiple functions, even across multiple mocks. The downsides are
+* They end up being very verbose to use, often at the expense of test readiability
+* They require creating a mock class & learning how to use gmock.
+
+These are generally not preferred to a "Fake".
+
+### Tool: `FakeWebAppProvider`
+
+The [`FakeWebAppProvider`][40] is basically a fake version of the WebAppProvider system, that uses the  [`WebAppProvider`][12] root class to set up subsystems and can be used to selectively set fake subsystems or shut them
+down on a per-demand basis to test system shutdown use-cases.
+
+### Common issue: External Dependency that isn't faked
+Sometimes classes use a dependency that either doesn't work or isn't fake-able in our system.
+
+1. Can you just not depend on that? The best way is to remove the dependency entirely if possible.
+1. If there is a way to easily fake the dependency that is already supported, then do that next.
+    - e.g. if it's a `KeyedService`, and the authors have a fake version you can use, then use that. See how it is used elsewhere.
+1. Create a new interface for this new external dependency, put it on the `WebAppProvider`, and create a fake for it so that you can test with it faked.
+1. If all else fails, use a browser test.
+
+## Relevant Classes
+
+#### [`WebAppProvider`][12]
+
+This is a per-profile object housing all the various web app subsystems. This is the "main()" of the web app implementation where everything starts.
+
+#### [`WebApp`][13]
+
+This is the representation of an installed web app in RAM. Its member fields largely reflect all the ways a site can configure their [web app manifest][2] plus miscellaneous internal bookkeeping and user settings.
+
+#### [`WebAppRegistrar`][14]
+
+This is where all the [`WebApps`][13] live in memory, and what many other subsystems query to look up any given web app's fields. Mutations to the registry have to go via ScopedRegistryUpdate or [WebAppSyncBridge][16].
+
+Accessing the registrar should happen through a Lock. If you access it through the `WebAppProvider`, then know that you are reading uncommitted (and thus unsafe) data.
+
+Why is it full of `GetAppXYZ()` getters for every field instead of just returning a `WebApp` reference? This is primarily done because the value may depend on multiple sources of truth. For example, whether the app should be run on OS login depends on both the user preference (stored in our database) and the administrator's policy (stored separately & given to us in-memory using prefs) Historically this was originally done because WebApps used be stored both in our database and extensions, and this served to unify the two.
+
+#### [`WebAppSyncBridge`][16]
+
+This is "bridge" between the WebAppProvider system's in-memory representation of web apps and the sync system's database representation (along with sync system functionality like add/remove/modify operations). This integration is a little complex and deserves it's own document, but it basically: _Stores all WebApps into a database and updates the database if any fields change_. Updates the system when there are changes from the sync system. _Installs new apps, uninstalls apps the user uninstalled elsewhere, updates metadata like user display mode preference, etc_. Tells the sync system if there are local changes (installs, uninstalls, etc).
+
+There is also a slide in a presentation [here][18] which illustrates how this system works, but it may be out of date.
+
+Note: This only stores per-web-app data, and that data will be deleted if the web app is uninstalled. To store data that persists after uninstall, or applies to a more general scope than a single web app, then the `PrefService` can be used, either on the `Profile` object (per-profile data, `profile->GetPrefs()`) or on the browser process `(`g_browser_process->local_state()``). Example of needing prefs: Storing if an app was previously installed as a preinstalled app in the past. Information is needed during chrome startup before profiles are loaded. A feature needs to store global data - e.g. "When was the last time we showed the in-product-help banner for any webapp?"
+
+#### [`ExternallyManagedAppManager`][19]
+
+This is for all installs that are not initiated by the user. This includes [preinstalled apps][20], [policy installed apps][21] and [system web apps][22].
+
+These all specify a set of [install URLs][23] which the `ExternallyManagedAppManager` synchronises the set of currently installed web apps with.
+
+#### [`WebAppInstallFinalizer`][24]
+
+This is the tail end of the installation process where we write all our web app metadata to [disk][25] and deploy OS integrations (like [desktop shortcuts][26] and [file handlers][27] using the [`OsIntegrationManager`][28].
+
+This also manages the uninstallation process.
+
+#### [`WebAppUiManager`][29]
+
+Sometimes we need to query window state from chrome/browser/ui land even though our BUILD.gn targets disallow this as it would be a circular dependency. This [abstract class][30] + [impl][31] injects the dependency at link time (see [`WebAppUiManager::Create()`'s`][32] `declaration and definition locations`).
+
+#### [`AppShimRegistry`][33]
+
+On Mac OS we sometimes need to reason about the state of installed PWAs in all profiles without loading those profiles into memory. For this purpose, `AppShimRegistry` stores the needed information in Chrome's "Local State" (global preferences). The information stored here includes:
+
+  - All profiles a particular web app is installed in.
+  - What profiles a particular web app was open in when it was last used.
+  - What file and protocol handlers are enabled for a web app in each profile it is installed in.
+
+This information is used when launching a web app (to determine what profile or profiles to open the web app in), as well as when updating an App Shim (to make sure all file and protocol handlers for the app are accounted for).
+
+[2]: https://www.w3.org/TR/appmanifest/
+[3]: https://tinyurl.com/dpwa-architecture-public
+[4]: concepts.md
+[5]: chrome://web-app-internals
+[6]: chrome://internals/web-app
+[7]: https://docs.google.com/drawings/d/1TqUF2Pqh2S5qPGyA6njQWxOgSgKQBPePKPIH_srGeRk/edit?usp=sharing
+[8]: #webappsyncbridge
+[9]: https://tinyurl.com/bmo-public
+[11]: integration-testing-framework.md
+[12]: /chrome/browser/web_applications/web_app_provider.h
+[13]: /chrome/browser/web_applications/web_app.h
+[14]: /chrome/browser/web_applications/web_app_registrar.h
+[16]: /chrome/browser/web_applications/web_app_sync_bridge.h
+[18]: https://docs.google.com/presentation/d/e/2PACX-1vQxYZoCyhZ4xHS4pVuBC9YoE0O-QpW2Wj3scl6jtr3TEYheeod5Ch4b7OVEQEj_Hc6PM1RBGzovug3C/pub?start=false&loop=false&delayms=3000&slide=id.g59d9cb05b6_6_5
+[19]: /chrome/browser/web_applications/externally_managed_app_manager.h
+[20]: /chrome/browser/web_applications/preinstalled_web_app_manager.h
+[21]: /chrome/browser/web_applications/policy/web_app_policy_manager.h
+[22]: /chrome/browser/ash/system_web_apps/system_web_app_manager.h
+[23]: /chrome/browser/web_applications/external_install_options.h
+[24]: /chrome/browser/web_applications/web_app_install_finalizer.h
+[25]: /chrome/browser/web_applications/web_app_database.h
+[26]: /chrome/browser/web_applications/web_app_shortcut.h
+[27]: /chrome/browser/web_applications/web_app_file_handler_manager.h
+[28]: /chrome/browser/web_applications/os_integration/os_integration_manager.h
+[29]: /chrome/browser/ui/web_applications/web_app_ui_manager_impl.h
+[30]: /chrome/browser/web_applications/web_app_ui_manager.h
+[31]: /chrome/browser/ui/web_applications/web_app_ui_manager_impl.h
+[32]: https://source.chromium.org/search?q=WebAppUiManager::Create
+[33]: /chrome/browser/web_applications/app_shim_registry_mac.h
+[34]: installation_pipeline.md
+[35]: manifest_representations.md
+[36]: why-is-this-test-failing.md
+[37]: how-to-create-webapp-integration-tests.md
+[38]: /chrome/browser/ui/web_applications/web_app_browsertest.cc
+[39]: /chrome/browser/web_applications/test/fake_os_integration_manager.h
+[40]: /chrome/browser/web_applications/test/fake_web_app_provider.h
+[41]: https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/web_applications/test/web_app_install_test_utils.cc;l=40?q=AwaitStartWebAppProviderAndSubsystems&ss=chromium
+[42]: ../testing/testing_in_chromium.md
+[43]: ../testing/code_coverage.md
+[44]: https://www.youtube.com/watch?v=EZ05e7EMOLM
+[45]: /styleguide/styleguide.md
+[46]: https://github.com/google/googletest/tree/HEAD/googlemock
+[47]: /styleguide/c++/c++-dos-and-donts.md
+[48]: #public-interface
+[49]: #external-dependencies
+[50]: os-integration.md
diff --git a/docs/webapps/concepts.md b/docs/webapps/concepts.md
new file mode 100644
index 0000000..9442983
--- /dev/null
+++ b/docs/webapps/concepts.md
@@ -0,0 +1,144 @@
+## Web Apps - Concepts
+
+### Manifest, or WebManifest
+
+This refers to the document described by the [appmanifest][2] spec, with some extra features described by [manifest-incubations][3]. This document describes metadata and developer configuration of an installable web app.
+
+For code representations of the manifest see [the list][4].
+
+### Manifest Link
+
+A manifest link is something that looks like this in a html document:
+
+```html
+<link rel="manifest" href="manifest.webmanifest">
+```
+
+This link ties the manifest to the document, and subsequently used in the spec algorithms defined in [appmanifest][2] or [manifest-incubations][3] to describe the webapp and determine if it is installable.
+
+### Installable
+
+If a document or page is considered "installable", then the user agent can create some form of installed web app for that page. To be installable, [web_app::CanCreateWebApp][5] must return true, where:
+
+- The user profile must allow webapps to be installed
+- The web contents of the page must not be crashed
+- The last navigation on the web contents must not be an error (like a 404)
+- The url must be `http, https`, or `chrome-extension`
+
+This is different from [promotable][6] below, which determines if Chrome will promote installation of the page.
+
+### Promotable
+
+A document is considered "promotable" if it fulfills a set of criteria. This criteria may change to further encourage a better user experience for installable web apps. There are also a few optional checks that depend on the promotability checker. This general criteria as of 2022/09/08:
+
+- _The document contains a manifest link_.
+- The linked manifest can be processed [according][7] to the spec and is valid.
+- The processed manifest contains the fields:
+  - `name`
+  - `start_url`
+  - `icons` with at least one icon with a valid response that is a parsable image.
+  - `display` field that is not `"browser`"
+- "Serviceworker check": The `start_url` is 'controlled' (can be served by) a [serviceworker][8] with a fetch handler. **Optionally turned off**
+  - Note: This is expected to be removed in Q4 2022.
+- _"Engagement check": The user has engaged with, or interacted with, the page or origin a certain amount (currently at least one click and some seconds on the site). **Optionally turned off**_
+
+Notes:
+
+- Per spec, the document origin and the `start_url` origin must match.
+- Per spec, the `start_url` origin does not have to match the `manifest_url` origin
+- The `start_url` could be different from the `document_url`.
+
+### Manifest id
+
+The `id` specified in the manifest represents the identity of the web app. The manifest id is processed following the algorithm described in [appmanifest specification][9] to produce the app's identity. In the web app system, the app's [identifier][10] is [hashed][11] to be stored to [WebApp->app_id()][12].
+
+If a manifest is discovered during any sort of page load, then the update process is initiated for that manifest. If it resolves to an `app_id` that is installed, then it will perform an update. See https://web.dev/manifest-updates/ for more information.
+
+### Scope
+
+Scope refers to the prefix that a WebApp controls. All paths at or nested inside of a WebApp's scope are thought of as "controlled" or "in-scope" of that WebApp. This is a simple string prefix match. For example, if `scope` is `/my-app`, then the following will be "in-scope":
+
+- `/my-app/index.html`
+- `/my-app/sub/dir/hello.html`
+- `/my-app-still-prefixed/index.html` (Note: if the scope was `/`, then this would not be out-of-scope)
+
+And the following will be "out-of-scope":
+
+- `/my-other-app/index.html`
+- `/index.html`
+
+### Display Mode
+
+The `display` of a web app determines how the developer would like the app to look like to the user. See the [spec][13] for how the `display` member is processed in the manifest and what the display modes mean.
+
+### User Display Mode
+
+In addition to the developer-specified [`display`][14], the user can specify how they want a WebApp to be displayed, with the only option being whether to "open in a window" or not. Internally, this is expressed in the same display mode enumeration type as [`display`][14], but only the `kStandalone` and `kBrowser` values are used to specify "open in a window" and "do not open in a window", respectively.
+
+#### Effective Display Mode
+
+The pseudocode to determine the ACTUAL display mode a WebApp is displayed is:
+
+```js
+if (user_display_mode == kStandalone)
+  return developer_specified_display_mode;
+else
+  return kBrowser; // Open in a tab.
+```
+
+#### Open-in-window
+
+This refers to the user specifying that a WebApp should open in the developer specified display mode.
+
+#### Open-in-browser-tab
+
+This refers to the user specifying that a WebApp should NOT open in a window, and thus the WebApp, if launched, will just be opened in a browser tab.
+
+### App Management
+
+Each app has one or more 'management source', specified by the [`WebAppManagement`][17] enumeration. This signifies the system that is 'managing' the install, AKA responsible for installing or uninstalling the app. Internally, the web app system will ensure that the app will only be uninstalled if there are no sources left in the app.
+
+When a user installs an app, the `kSync` management source is specified, because user installs are considered 'managed' by the sync system (and installs will by synced to all devices). See the [`WebAppManagement`][17] enumeration for the description of other management sources.
+
+Installation by certain sources can cause the app to no longer be "uninstallable" by the user. The method [`CanUserUninstallWebApp`][18] function determines if this is the case.
+
+#### Placeholder app
+
+There are some webapps which are managed by external sources - for example, the enterprise policy force-install apps, or the system web apps for ChromeOS. These are generally not installed by user interaction, and the WebAppProvider needs to install something for each of these apps.
+
+Sometimes, the installation of these apps can fail because the install url is not reachable (usually a cert or login needs to occur, and the url is redirected). When this happens, the system [can][15] install a "placeholder" app, which is a fake application that, when launched, navigates to the install url of the application, given by the external app manager.
+
+To resolve placeholder apps back into the intended installation, web contents (either in-(placeholder)-app or in the browser) are all listened to. If any web content successfully [navigates][16] to a placeholder app's `install_url`, then:
+
+1. The placeholder app is uninstalled.
+2. After uninstallation, the non-placeholder app is [installed][19].
+
+### Locally Installed
+
+When signing into a non-ChromeOS device, all web apps are installed but not **locally installed**. This means that OS integration is not triggered (so there are no platform shortcuts created), install icons will still show up for the app websites, and the app icon will appear grayed out on chrome://apps.
+
+For an app to become locally installed, the user must do one of the following:
+
+- Navigate to `chrome://apps`, find the grayed-out icon of the app, right click on it, and select "Install".
+- Follow any of the normal installation routes to install that app (e.g. visit the app page in the browser and interact with the omnibox install icon)
+
+This was done because on non-ChromeOS devices it was considered a bad user experience to fully install all of the profile's web apps (creating platform shortcuts, etc), as this might not be expected by the user.
+
+[2]: https://www.w3.org/TR/appmanifest/
+[3]: https://wicg.github.io/manifest-incubations/index.html
+[4]: manifest_representations.md
+[5]: https://source.chromium.org/search?q=web_app::CanCreateWebApp
+[6]: #promotable
+[7]: https://www.w3.org/TR/appmanifest/#processing
+[8]: https://developers.google.com/web/ilt/pwa/introduction-to-service-worker
+[9]: https://www.w3.org/TR/appmanifest/#id-member
+[10]: https://www.w3.org/TR/appmanifest/#dfn-identity
+[11]: https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/web_applications/web_app_helpers.cc;l=69;drc=cafa646efbb6f668d3ba20ff482c1f729159ae97
+[12]: https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/web_applications/web_app.h;l=43;drc=cafa646efbb6f668d3ba20ff482c1f729159ae97;bpv=1;bpt=1
+[13]: https://www.w3.org/TR/appmanifest/#display-modes
+[14]: #display-mode
+[15]: https://source.chromium.org/search?q=ExternalInstallOptions::install_placeholder
+[16]: https://source.chromium.org/search?q=WebAppTabHelper::ReinstallPlaceholderAppIfNecessary
+[17]: https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/web_applications/web_app_constants.h;l=32?q=WebAppManagement&ss=chromium%2Fchromium%2Fsrc
+[18]: https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/web_applications/web_app_utils.cc;l=481?q=CanUserUninstallWebApp&ss=chromium%2Fchromium%2Fsrc
+[19]: https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/web_applications/externally_managed_app_install_task.cc;l=215?q=OnPlaceholderUninstalled&ss=chromium%2Fchromium%2Fsrc
diff --git a/chrome/browser/web_applications/docs/file_handling.md b/docs/webapps/file_handling.md
similarity index 100%
rename from chrome/browser/web_applications/docs/file_handling.md
rename to docs/webapps/file_handling.md
diff --git a/docs/webapps/how-to-create-webapp-integration-tests.md b/docs/webapps/how-to-create-webapp-integration-tests.md
index d55c40e..f4a7964 100644
--- a/docs/webapps/how-to-create-webapp-integration-tests.md
+++ b/docs/webapps/how-to-create-webapp-integration-tests.md
@@ -73,6 +73,8 @@
 1. Print new tests that need to be manually copied to the integration browsertest files.
 2. Print out test ids that need to be removed.
 
+Note: The option `--delete-in-place` can be used to remove all tests that aren't disabled by sheriffs.
+
 After you make changes to the integration browsertests, please re-run the above command to verify that all of the changes were performed and no mistakes were made. If all looks right, the script will output nothing to console when run a second time.
 
 Possible issues / Things to know:
diff --git a/docs/webapps/installation_pipeline.md b/docs/webapps/installation_pipeline.md
new file mode 100644
index 0000000..7b2e718c
--- /dev/null
+++ b/docs/webapps/installation_pipeline.md
@@ -0,0 +1,140 @@
+## [Web Apps][2] - Installation
+
+Installing a webapp can come from a variety of channels. This section serves to enumerate them all and show how they fit together in the installation pipeline.
+
+### Flowchart
+
+Here is a graphic of the installation flow:
+
+![](webapp_installation_process.png)
+
+[https://tinyurl.com/dpwa-installation-flowchart][4]
+
+Note: The ExternallyManagedAppManager adds a few steps before this, and will sometimes (for placeholder apps) build a custom `WebAppInstallInfo` object to skip the 'build' steps.
+
+### Installation Commands
+
+There are a variety of [commands][5] used to install web apps. If introducing a new installation source, consider making a new command to isolate your operation (and prevent it from being complicated by other use-cases).
+
+### Installation Sources
+
+There are a variety of installation sources and expectations tied to those sources.
+
+#### Omnibox install icon
+
+User-initiated installation. To make the omnibox install icon visible, the document must: _Be promotable and installable_. NOT be inside of the scope of an installed WebApp with an effective [display mode display][6] mode that isn't `kBrowser`.
+
+Triggers an install view that will show the name & icon to the user to confirm install. If the manifest also includes screenshots with a wide form-factor, then a more detailed install dialog will be shown.
+
+This uses the [`FetchManifestAndInstallCommand`][7], providing just the `WebContents` of the installable page.
+
+Fails if, after the user clicks : _After clicking on the install icon, the `WebContents` is no longer_ [_promotable_][8], _skipping engagement checks_. The user rejects the installation dialog.
+
+#### 3-dot menu option "Install {App_Name}..."
+
+User-initiated installation. To make the install menu option visible, the document must: _Be promotable and installable_. NOT be inside of the scope of an installed WebApp with an effective [display mode display][6] mode that isn't `kBrowser`.
+
+Triggers an install view that will show the name & icon to the user to confirm install. If the manifest also includes screenshots with a wide form-factor, then a more detailed install dialog will be shown.
+
+Calls [`FetchManifestAndInstallCommand`][7] with the `WebContents` of the installable page, and `use_fallback = true`.
+
+Fails if: _The user rejects the installation dialog_.
+
+Notably, this option does not go through the same exact pathway as the [omnibox install icon][10], as it shares the call-site as the "Create Shortcut" method below. The main functional difference here is that if the site becomes no longer [promotable][8] in between clicking on the menu option and the install actually happening, it will not fail and instead fall back to a fake manifest and/or fake icons based on the favicon. Practically, this option doesn't show up if the site is [promotable][8]. Should it share installation pathways as the the [omnibox install icon?][10] Probably, yes.
+
+#### 3-dot menu option "Create Shortcut..."
+
+User-initiated installation. This menu option is always available, except for internal chrome urls like chrome://settings.
+
+Prompts the user whether the shortcut should "open in a window". If the user checks this option, then the resulting WebApp will have the [user display][11] set to `kStandalone` / open-in-a-window.
+
+The document does not need to have a manifest for this install path to work. If no manifest is found, then a fake one is created with `start_url` equal to the document url, `name` equal to the document title, and the icons are generated from the favicon (if present).
+
+Calls [`FetchManifestAndInstallCommand`][7] with the `WebContents` of the installable page, and `use_fallback = true`.
+
+Fails if: _The user rejects the shortcut creation dialog_.
+
+#### ChromeOS Management API
+
+Checks [promotability][8] before installing, skipping engagement and serviceworker checks
+
+Calls [`WebAppInstallManager::InstallWebAppFromManifest`][12], providing just the `WebContents` of the installable page.
+
+TODO: Document when this API is called & why.
+
+#### Externally Managed Apps
+
+There are a number of apps that are managed externally. This means that there is an external manager keeps it's own list of web apps that need to be installed for a given external install source.
+
+See the [`web_app::ExternalInstallSource`][13] enum to see all types of externally managed apps. Each source type should have an associated "manager" that gives the list of apps to `ExternallyManagedAppProvider::SynchronizeInstalledApps`.
+
+These installations are customizable than user installations, as these external app management surfaces need to specify all of the options up front (e.g. create shortcut on desktop, open in window, run on login, etc). Thus the [`ExternallyManagedInstallCommand`][14] is called here, with the params generated by [`web_app::ConvertExternalInstallOptionsToParams`][15].
+
+The general installation flow of an externally managed app is:
+
+1. A call to [`ExternallyManagedAppProvider::SynchronizeInstalledApps`][16]
+1. Finding all apps that need to be uninstalled and uninstalling them, find all apps that need to be installed and:
+1. Queue an `ExternallyManagedAppInstallTask` to install each app sequentially.
+1. Each task loads the url for the app.
+1. If the url is successfully loaded, then use [`ExternallyManagedInstallCommand`][14], and continue installation on the normal pipeline (described above, and [flowchart][17] above).
+1. If the url fails to fully load (usually a redirect if the user needs to sign in or corp credentials are not installed), and the external app manager specified a [placeholder app was required][18] then:
+    1. Synthesize a web app with `start_url` as the document url, and `name` as the document title
+    1. Install that webapp by using the finalizer directly. This is **not** part of the regular install pipeline, and basically directly saves the webapp into the database without running OS integration.
+
+These placeholder apps are not meant to stay, and to replace them with the intended apps, the following occurs:
+
+1. The WebAppProvider system listens to every page load.
+1. If a [navigation is successful][20] to a url that the placeholder app is installed for, then
+    1. The installation is started again with a call to [`WebAppInstallManager::InstallWebAppWithParams`][14].
+    1. If successful, the placeholder app is uninstalled.
+
+#### Sync
+
+When the sync system receives an WebApp to install, it uses the [InstallFromSyncCommand`][21]. One major difference is if the installation fails for any reason (manifest is invalid or fails to load, etc), then a backup installation happens using information from the icon urls from the sync data, and document/favicons if available.
+
+If the platform is not ChromeOS, then the app will not become [locally installed][23]. This means that OS integration will not be triggered, no platform shortcuts created, etc. 1. If the platform is ChromeOS, it will become [locally installed][23], and all OS integrations will be triggered (just like a normal user-initiated install.)
+
+##### Retry on startup
+
+Sync installs have a few extra complications:
+
+- They need to be immediately saved to the database & be installed eventually.
+- Many are often queued up during a new profile sign-in, and it's not uncommon for the user to quit before the installation queue finishes.
+
+Due to this, unlike other installs, a special [`WebApp::is_from_sync_and_pending_installation`][24] (protobuf variable is saved in the database. WebApps with this set to true are treated as not fully installed, and are often left out of app listings. This variable is reset back to `false` when the app is finished installing.
+
+To handle the cases above, on startup when the database is loaded, any WebApp with `is_from_sync_and_pending_installation` of `true` will be re-installed inside of [`WebAppSyncBridge::MaybeInstallAppsFromSyncAndPendingInstallation`][25]
+
+### Installation State Modifications
+
+#### Installing locally
+
+On non-ChromeOS devices, an app can be [not locally installed][23]. To become locally installed, the user can follow a normal install method (install icon will show up), or they can interact with the app on `chrome://apps`.
+
+The `chrome://apps` code is unique here, and instead of re-installing the app, in manually sets the locally_installed bit to true in [`AppLauncherHandler::HandleInstallAppLocally`][26], and triggers OS integration in [`AppLauncherHandler::InstallOsHooks`][26]
+
+#### Creating Shortcuts
+
+Similarly to above, in `chrome://apps` the user can "Create Shortcuts..." for a web app. This should overwrite any shortcuts already created, and basically triggers OS integration to install shortcuts again in [`AppLauncherHandler::HandleCreateAppShortcut`][27]
+
+[2]: README.md
+[4]: https://tinyurl.com/dpwa-installation-flowchart
+[5]: https://source.chromium.org/search?q=f:install%20f:web_applications%2Fcommands&sq=&ss=chromium
+[6]: concepts.md#effective-display-mode
+[7]: https://source.chromium.org/search?q=FetchManifestAndInstallCommand&ss=chromium
+[8]: concepts.md#promotable
+[10]: #omnibox-install-icon
+[11]: concepts.md#user-display-mode
+[13]: https://source.chromium.org/search?q=web_app::ExternalInstallSource
+[14]: https://source.chromium.org/search?q=ExternallyManagedInstallCommand&sq=&ss=chromium%2Fchromium%2Fsrc
+[15]: https://source.chromium.org/search?q=web_app::ConvertExternalInstallOptionsToParams
+[16]: https://source.chromium.org/search?q=ExternallyManagedAppProvider::SynchronizeInstalledApps
+[17]: #flowchart
+[18]: https://source.chromium.org/search?q=ExternalInstallOptions::install_placeholder
+[20]: https://source.chromium.org/search?q=WebAppTabHelper::ReinstallPlaceholderAppIfNecessary
+[21]: https://source.chromium.org/search?q=InstallFromSyncCommand&ss=chromium%2Fchromium%2Fsrc
+[23]: ../README.md#locally-installed
+[24]: https://source.chromium.org/search?q=WebApp::is_from_sync_and_pending_installation
+[25]: https://source.chromium.org/search?q=WebAppSyncBridge::MaybeInstallAppsFromSyncAndPendingInstallation
+[26]: https://source.chromium.org/search?q=AppLauncherHandler::HandleInstallAppLocally
+[27]: https://source.chromium.org/search?q=AppLauncherHandler::HandleCreateAppShortcut
\ No newline at end of file
diff --git a/chrome/browser/web_applications/docs/manifest_representations.md b/docs/webapps/manifest_representations.md
similarity index 100%
rename from chrome/browser/web_applications/docs/manifest_representations.md
rename to docs/webapps/manifest_representations.md
diff --git a/chrome/browser/web_applications/docs/os_integration.md b/docs/webapps/os_integration.md
similarity index 87%
rename from chrome/browser/web_applications/docs/os_integration.md
rename to docs/webapps/os_integration.md
index 9b67fac9..b34e19c 100644
--- a/chrome/browser/web_applications/docs/os_integration.md
+++ b/docs/webapps/os_integration.md
@@ -1,13 +1,13 @@
-# [Web Apps](../README.md) - Operating System Integration
+# [Web Apps](README.md) - Operating System Integration
 
-The WebAppProvider system has to provide a lot of integrations with operating system surfaces for web apps. This functionality is usually different per operating system, and is usually invoked through the [`OsIntegrationManager`](../os_integration_manager.h).
+The WebAppProvider system has to provide a lot of integrations with operating system surfaces for web apps. This functionality is usually different per operating system, and is usually invoked through the [`OsIntegrationManager`][2].
 
-The [`OsIntegrationManager`](../os_integration_manager.h)'s main responsibility is support the following operations:
+The [`OsIntegrationManager`][2]'s main responsibility is support the following operations:
 1. Install operating system integration for a given web app.
 1. Update operating system integration for a given web app.
 1. Uninstall/remove operating system integration for a given web app.
 
-It owns sub-managers who are responsible for each individual operating system integration functionality (e.g. [`web_app_file_handler_manager.h`](../web_app_file_handler_manager.h) which owns the file handling feature). That manager will implement the non-os-specific logic, and then call into functions that have os-specific implementations (e.g. `web_app_file_handler_registration.h/_mac.h/_win.h/_linux.h` files).
+It owns sub-managers who are responsible for each individual operating system integration functionality (e.g. [`web_app_file_handler_manager.h`][1] which owns the file handling feature). That manager will implement the non-os-specific logic, and then call into functions that have os-specific implementations (e.g. `web_app_file_handler_registration.h/_mac.h/_win.h/_linux.h` files).
 
 Below are sections describing how each OS integration works.
 
@@ -93,3 +93,5 @@
 needs to be updated with a profile specific name and executes required update.
 
 
+[1]: /chrome/browser/web_applications/os_integration/web_app_file_handler_manager.h
+[2]: /chrome/browser/web_applications/os_integration/os_integration_manager.h
\ No newline at end of file
diff --git a/chrome/browser/web_applications/docs/signed_web_bundle_parser_class_structure.png b/docs/webapps/signed_web_bundle_parser_class_structure.png
similarity index 100%
rename from chrome/browser/web_applications/docs/signed_web_bundle_parser_class_structure.png
rename to docs/webapps/signed_web_bundle_parser_class_structure.png
Binary files differ
diff --git a/chrome/browser/web_applications/docs/testing.md b/docs/webapps/testing.md
similarity index 100%
rename from chrome/browser/web_applications/docs/testing.md
rename to docs/webapps/testing.md
diff --git a/docs/webapps/webapp_installation_process.png b/docs/webapps/webapp_installation_process.png
new file mode 100644
index 0000000..79e6387
--- /dev/null
+++ b/docs/webapps/webapp_installation_process.png
Binary files differ
diff --git a/chrome/browser/web_applications/docs/webappprovider_component_ownership.jpg b/docs/webapps/webappprovider_component_ownership.jpg
similarity index 100%
rename from chrome/browser/web_applications/docs/webappprovider_component_ownership.jpg
rename to docs/webapps/webappprovider_component_ownership.jpg
Binary files differ
diff --git a/extensions/browser/api/declarative/declarative_api.cc b/extensions/browser/api/declarative/declarative_api.cc
index caa1aaa..dc98ce4e 100644
--- a/extensions/browser/api/declarative/declarative_api.cc
+++ b/extensions/browser/api/declarative/declarative_api.cc
@@ -90,9 +90,9 @@
                             kDeclarativeApiFunctionCallTypeMax);
 }
 
-void ConvertBinaryDictionaryValuesToBase64(base::Value& dict);
+void ConvertBinaryDictValuesToBase64(base::Value::Dict& dict);
 
-// Encodes |binary| as base64 and returns a new StringValue populated with the
+// Encodes |binary| as base64 and returns a new string value populated with the
 // encoded string.
 base::Value ConvertBinaryToBase64(const base::Value& binary) {
   std::string binary_data(binary.GetBlob().begin(), binary.GetBlob().end());
@@ -101,9 +101,9 @@
   return base::Value(std::move(data64));
 }
 
-// Parses through |args| replacing any BinaryValues with base64 encoded
-// StringValues. Recurses over any nested ListValues, and calls
-// ConvertBinaryDictionaryValuesToBase64 for any nested DictionaryValues.
+// Parses through |args| replacing any binary values with base64 encoded
+// string values. Recurses over any nested List values, and calls
+// ConvertBinaryDictValuesToBase64 for any nested Dict values.
 void ConvertBinaryListElementsToBase64(base::Value::List& args) {
   for (auto& value : args) {
     if (value.is_blob()) {
@@ -111,23 +111,23 @@
     } else if (value.is_list() && !value.GetList().empty()) {
       ConvertBinaryListElementsToBase64(value.GetList());
     } else if (value.is_dict()) {
-      ConvertBinaryDictionaryValuesToBase64(value);
+      ConvertBinaryDictValuesToBase64(value.GetDict());
     }
   }
 }
 
 // Parses through |dict| replacing any BinaryValues with base64 encoded
-// StringValues. Recurses over any nested DictionaryValues, and calls
-// ConvertBinaryListElementsToBase64 for any nested ListValues.
-void ConvertBinaryDictionaryValuesToBase64(base::Value& dict) {
-  for (auto it : dict.DictItems()) {
+// string values. Recurses over any nested Dict values, and calls
+// ConvertBinaryListElementsToBase64 for any nested List values.
+void ConvertBinaryDictValuesToBase64(base::Value::Dict& dict) {
+  for (auto it : dict) {
     auto& value = it.second;
     if (value.is_blob()) {
       value = ConvertBinaryToBase64(value);
     } else if (value.is_list() && !value.GetList().empty()) {
       ConvertBinaryListElementsToBase64(value.GetList());
     } else if (value.is_dict()) {
-      ConvertBinaryDictionaryValuesToBase64(value);
+      ConvertBinaryDictValuesToBase64(value.GetDict());
     }
   }
 }
diff --git a/extensions/browser/api/declarative/declarative_rule_unittest.cc b/extensions/browser/api/declarative/declarative_rule_unittest.cc
index f9132c8e..41d822c0 100644
--- a/extensions/browser/api/declarative/declarative_rule_unittest.cc
+++ b/extensions/browser/api/declarative/declarative_rule_unittest.cc
@@ -14,6 +14,7 @@
 #include "extensions/common/extension_builder.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 using base::test::ParseJson;
 using url_matcher::URLMatcher;
@@ -24,12 +25,12 @@
 
 namespace {
 
-std::unique_ptr<base::DictionaryValue> SimpleManifest() {
+base::Value::Dict SimpleManifest() {
   return DictionaryBuilder()
       .Set("name", "extension")
       .Set("manifest_version", 2)
       .Set("version", "1.0")
-      .Build();
+      .BuildDict();
 }
 
 }  // namespace
@@ -50,8 +51,7 @@
       URLMatcherConditionFactory* url_matcher_condition_factory,
       const base::Value& condition,
       std::string* error) {
-    const base::DictionaryValue* dict = nullptr;
-    if (condition.GetAsDictionary(&dict) && dict->FindKey("bad_key")) {
+    if (condition.is_dict() && condition.GetDict().Find("bad_key")) {
       *error = "Found error key";
       return nullptr;
     }
@@ -132,16 +132,16 @@
       const base::Value& condition,
       std::string* error) {
     std::unique_ptr<FulfillableCondition> result(new FulfillableCondition());
-    const base::DictionaryValue* dict;
-    if (!condition.GetAsDictionary(&dict)) {
+    if (!condition.is_dict()) {
       *error = "Expected dict";
       return result;
     }
-    const auto id = dict->FindIntKey("url_id");
+    const base::Value::Dict& dict = condition.GetDict();
+    const auto id = dict.FindInt("url_id");
     result->condition_set_id =
         id.has_value() ? static_cast<base::MatcherStringPattern::ID>(id.value())
                        : base::MatcherStringPattern::kInvalidId;
-    if (absl::optional<int> max_value_int = dict->FindIntKey("max")) {
+    if (absl::optional<int> max_value_int = dict.FindInt("max")) {
       result->max_value = *max_value_int;
     } else {
       *error = "Expected integer at ['max']";
@@ -220,20 +220,21 @@
       const base::Value& action,
       std::string* error,
       bool* bad_message) {
-    const base::DictionaryValue* dict = nullptr;
-    EXPECT_TRUE(action.GetAsDictionary(&dict));
-    if (dict->FindKey("error")) {
-      EXPECT_TRUE(dict->GetString("error", error));
+    EXPECT_TRUE(action.is_dict());
+    const base::Value::Dict& dict = action.GetDict();
+    if (const base::Value* value = dict.Find("error")) {
+      EXPECT_TRUE(value->is_string());
+      *error = value->GetString();
       return nullptr;
     }
-    if (dict->FindKey("bad")) {
+    if (dict.Find("bad")) {
       *bad_message = true;
       return nullptr;
     }
 
-    absl::optional<int> increment = dict->FindIntKey("value");
+    absl::optional<int> increment = dict.FindInt("value");
     EXPECT_TRUE(increment);
-    int min_priority = dict->FindIntKey("priority").value_or(0);
+    int min_priority = dict.FindInt("priority").value_or(0);
     return scoped_refptr<const SummingAction>(
         new SummingAction(*increment, min_priority));
   }
diff --git a/extensions/browser/api/declarative/deduping_factory_unittest.cc b/extensions/browser/api/declarative/deduping_factory_unittest.cc
index b451c0f..aae20df 100644
--- a/extensions/browser/api/declarative/deduping_factory_unittest.cc
+++ b/extensions/browser/api/declarative/deduping_factory_unittest.cc
@@ -62,9 +62,8 @@
                                          const base::Value* value,
                                          std::string* error,
                                          bool* bad_message) {
-  const base::DictionaryValue* dict = nullptr;
-  CHECK(value->GetAsDictionary(&dict));
-  absl::optional<int> parameter = dict->FindIntKey("parameter");
+  CHECK(value->is_dict());
+  absl::optional<int> parameter = value->GetDict().FindInt("parameter");
   if (!parameter) {
     *error = "No parameter";
     *bad_message = true;
@@ -73,10 +72,10 @@
   return scoped_refptr<const BaseClass>(new Foo(*parameter));
 }
 
-std::unique_ptr<base::DictionaryValue> CreateDictWithParameter(int parameter) {
-  std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
-  dict->SetIntKey("parameter", parameter);
-  return dict;
+base::Value CreateDictWithParameter(int parameter) {
+  base::Value::Dict dict;
+  dict.Set("parameter", parameter);
+  return base::Value(std::move(dict));
 }
 
 }  // namespace
@@ -90,19 +89,19 @@
   factory.RegisterFactoryMethod(kTypeName, FactoryT::IS_PARAMETERIZED,
                                 &CreateFoo);
 
-  std::unique_ptr<base::DictionaryValue> d1(CreateDictWithParameter(1));
-  std::unique_ptr<base::DictionaryValue> d2(CreateDictWithParameter(2));
-  std::unique_ptr<base::DictionaryValue> d3(CreateDictWithParameter(3));
-  std::unique_ptr<base::DictionaryValue> d4(CreateDictWithParameter(4));
+  base::Value d1 = CreateDictWithParameter(1);
+  base::Value d2 = CreateDictWithParameter(2);
+  base::Value d3 = CreateDictWithParameter(3);
+  base::Value d4 = CreateDictWithParameter(4);
 
   std::string error;
   bool bad_message = false;
 
   // Fill factory with 2 different types.
   scoped_refptr<const BaseClass> c1(
-      factory.Instantiate(kTypeName, d1.get(), &error, &bad_message));
+      factory.Instantiate(kTypeName, &d1, &error, &bad_message));
   scoped_refptr<const BaseClass> c2(
-      factory.Instantiate(kTypeName, d2.get(), &error, &bad_message));
+      factory.Instantiate(kTypeName, &d2, &error, &bad_message));
   ASSERT_TRUE(c1.get());
   ASSERT_TRUE(c2.get());
   EXPECT_EQ(1, static_cast<const Foo*>(c1.get())->parameter());
@@ -110,13 +109,13 @@
 
   // This one produces an overflow, now the cache contains [2, 3]
   scoped_refptr<const BaseClass> c3(
-      factory.Instantiate(kTypeName, d3.get(), &error, &bad_message));
+      factory.Instantiate(kTypeName, &d3, &error, &bad_message));
   ASSERT_TRUE(c3.get());
   EXPECT_EQ(3, static_cast<const Foo*>(c3.get())->parameter());
 
   // Reuse 2, this should give the same instance as c2.
   scoped_refptr<const BaseClass> c2_b(
-      factory.Instantiate(kTypeName, d2.get(), &error, &bad_message));
+      factory.Instantiate(kTypeName, &d2, &error, &bad_message));
   EXPECT_EQ(2, static_cast<const Foo*>(c2_b.get())->parameter());
   EXPECT_EQ(c2, c2_b);
 
@@ -124,10 +123,10 @@
   // now [3, 2] and 3 is discarded before 2.
   // This discards 3, so the cache becomes [2, 1]
   scoped_refptr<const BaseClass> c1_b(
-      factory.Instantiate(kTypeName, d1.get(), &error, &bad_message));
+      factory.Instantiate(kTypeName, &d1, &error, &bad_message));
 
   scoped_refptr<const BaseClass> c2_c(
-      factory.Instantiate(kTypeName, d2.get(), &error, &bad_message));
+      factory.Instantiate(kTypeName, &d2, &error, &bad_message));
   EXPECT_EQ(2, static_cast<const Foo*>(c2_c.get())->parameter());
   EXPECT_EQ(c2, c2_c);
 }
@@ -137,8 +136,8 @@
   factory.RegisterFactoryMethod(kTypeName, FactoryT::IS_NOT_PARAMETERIZED,
                                 &CreateFoo);
 
-  std::unique_ptr<base::DictionaryValue> d1(CreateDictWithParameter(1));
-  std::unique_ptr<base::DictionaryValue> d2(CreateDictWithParameter(2));
+  base::Value d1 = CreateDictWithParameter(1);
+  base::Value d2 = CreateDictWithParameter(2);
 
   std::string error;
   bool bad_message = false;
@@ -146,9 +145,9 @@
   // We create two instances with different dictionaries but because the type is
   // declared to be not parameterized, we should get the same instance.
   scoped_refptr<const BaseClass> c1(
-      factory.Instantiate(kTypeName, d1.get(), &error, &bad_message));
+      factory.Instantiate(kTypeName, &d1, &error, &bad_message));
   scoped_refptr<const BaseClass> c2(
-      factory.Instantiate(kTypeName, d2.get(), &error, &bad_message));
+      factory.Instantiate(kTypeName, &d2, &error, &bad_message));
   ASSERT_TRUE(c1.get());
   ASSERT_TRUE(c2.get());
   EXPECT_EQ(1, static_cast<const Foo*>(c1.get())->parameter());
@@ -163,15 +162,15 @@
   factory.RegisterFactoryMethod(kTypeName2, FactoryT::IS_PARAMETERIZED,
                                 &CreateFoo);
 
-  std::unique_ptr<base::DictionaryValue> d1(CreateDictWithParameter(1));
+  base::Value d1 = CreateDictWithParameter(1);
 
   std::string error;
   bool bad_message = false;
 
   scoped_refptr<const BaseClass> c1_a(
-      factory.Instantiate(kTypeName, d1.get(), &error, &bad_message));
+      factory.Instantiate(kTypeName, &d1, &error, &bad_message));
   scoped_refptr<const BaseClass> c1_b(
-      factory.Instantiate(kTypeName2, d1.get(), &error, &bad_message));
+      factory.Instantiate(kTypeName2, &d1, &error, &bad_message));
 
   ASSERT_TRUE(c1_a.get());
   ASSERT_TRUE(c1_b.get());
@@ -183,18 +182,18 @@
   factory.RegisterFactoryMethod(kTypeName, FactoryT::IS_PARAMETERIZED,
                                 &CreateFoo);
 
-  std::unique_ptr<base::DictionaryValue> d1(CreateDictWithParameter(1));
+  base::Value d1 = CreateDictWithParameter(1);
 
   std::string error;
   bool bad_message = false;
 
   scoped_refptr<const BaseClass> c1_a(
-      factory.Instantiate(kTypeName, d1.get(), &error, &bad_message));
+      factory.Instantiate(kTypeName, &d1, &error, &bad_message));
 
   factory.ClearPrototypes();
 
   scoped_refptr<const BaseClass> c1_b(
-      factory.Instantiate(kTypeName, d1.get(), &error, &bad_message));
+      factory.Instantiate(kTypeName, &d1, &error, &bad_message));
 
   ASSERT_TRUE(c1_a.get());
   ASSERT_TRUE(c1_b.get());
diff --git a/extensions/browser/api/declarative/rules_registry_unittest.cc b/extensions/browser/api/declarative/rules_registry_unittest.cc
index f6d2a49..8b4ea86 100644
--- a/extensions/browser/api/declarative/rules_registry_unittest.cc
+++ b/extensions/browser/api/declarative/rules_registry_unittest.cc
@@ -192,7 +192,7 @@
   content::BrowserTaskEnvironment task_environment;
 
   // Create extension
-  std::unique_ptr<base::DictionaryValue> manifest = ParseDictionary(
+  absl::optional<base::Value::Dict> manifest = ParseDictionary(
       "{"
       "  \"name\": \"Test\","
       "  \"version\": \"1\","
@@ -225,7 +225,7 @@
       "}");
   scoped_refptr<const Extension> extension =
       ExtensionBuilder()
-          .SetManifest(std::move(manifest))
+          .SetManifest(std::move(*manifest))
           .SetID(kExtensionId)
           .Build();
 
@@ -238,7 +238,7 @@
   registry->GetAllRules(kExtensionId, &get_rules);
 
   ASSERT_EQ(2u, get_rules.size());
-  std::unique_ptr<base::DictionaryValue> expected_rule_0 = ParseDictionary(
+  absl::optional<base::Value::Dict> expected_rule_0 = ParseDictionary(
       "{"
       "  \"id\": \"000\","
       "  \"priority\": 200,"
@@ -253,7 +253,7 @@
       "}");
   EXPECT_EQ(*expected_rule_0, get_rules[0]->ToValue());
 
-  std::unique_ptr<base::DictionaryValue> expected_rule_1 = ParseDictionary(
+  absl::optional<base::Value::Dict> expected_rule_1 = ParseDictionary(
       "{"
       "  \"id\": \"_0_\","
       "  \"priority\": 100,"
@@ -274,7 +274,7 @@
   content::BrowserTaskEnvironment task_environment;
 
   // Create extension
-  std::unique_ptr<base::DictionaryValue> manifest = ParseDictionary(
+  absl::optional<base::Value::Dict> manifest = ParseDictionary(
       "{"
       "  \"name\": \"Test\","
       "  \"version\": \"1\","
@@ -293,7 +293,7 @@
       "}");
   scoped_refptr<const Extension> extension =
       ExtensionBuilder()
-          .SetManifest(std::move(manifest))
+          .SetManifest(std::move(*manifest))
           .SetID(kExtensionId)
           .Build();
 
diff --git a/extensions/browser/api_test_utils.cc b/extensions/browser/api_test_utils.cc
index 8830240..3c02bb9 100644
--- a/extensions/browser/api_test_utils.cc
+++ b/extensions/browser/api_test_utils.cc
@@ -62,9 +62,11 @@
   run_loop_.Run();
 }
 
-std::unique_ptr<base::DictionaryValue> ParseDictionary(
-    const std::string& data) {
-  return base::DictionaryValue::From(base::JSONReader::ReadDeprecated(data));
+absl::optional<base::Value::Dict> ParseDictionary(const std::string& data) {
+  absl::optional<base::Value> value = base::JSONReader::Read(data);
+  if (!value || !value->is_dict())
+    return absl::nullopt;
+  return std::move(*value).TakeDict();
 }
 
 bool GetBoolean(const base::Value::Dict& dict, const std::string& key) {
diff --git a/extensions/browser/api_test_utils.h b/extensions/browser/api_test_utils.h
index 5cfbd0b6..9e74a4d5c 100644
--- a/extensions/browser/api_test_utils.h
+++ b/extensions/browser/api_test_utils.h
@@ -59,9 +59,9 @@
 
 enum RunFunctionFlags { NONE = 0, INCLUDE_INCOGNITO = 1 << 0 };
 
-// Parse JSON and return as the specified type, or NULL if the JSON is invalid
-// or not the specified type.
-std::unique_ptr<base::DictionaryValue> ParseDictionary(const std::string& data);
+// Parses JSON and returns the dictionary, or absl::nullopt if the JSON is
+// invalid or not a dictionary.
+absl::optional<base::Value::Dict> ParseDictionary(const std::string& data);
 
 // Get |key| from |val| as the specified type. If |key| does not exist, or is
 // not of the specified type, adds a failure to the current test and returns
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h
index f870bde2..5db97a5 100644
--- a/extensions/browser/extension_function_histogram_value.h
+++ b/extensions/browser/extension_function_histogram_value.h
@@ -1178,7 +1178,7 @@
   DELETED_WEBRTCLOGGINGPRIVATE_STOPRTCEVENTLOGGING = 1117,
   PASSWORDSPRIVATE_GETSAVEDPASSWORDLIST = 1118,
   PASSWORDSPRIVATE_GETPASSWORDEXCEPTIONLIST = 1119,
-  DELETED_INPUTMETHODPRIVATE_OPENOPTIONSPAGE = 1120,
+  INPUTMETHODPRIVATE_OPENOPTIONSPAGE = 1120,
   DELETED_FEEDBACKPRIVATE_LOGSRTPROMPTRESULT = 1121,
   BLUETOOTHLOWENERGY_CREATESERVICE = 1122,
   BLUETOOTHLOWENERGY_CREATECHARACTERISTIC = 1123,
diff --git a/extensions/common/api/declarative/declarative_manifest_unittest.cc b/extensions/common/api/declarative/declarative_manifest_unittest.cc
index d6c69bc..029836a 100644
--- a/extensions/common/api/declarative/declarative_manifest_unittest.cc
+++ b/extensions/common/api/declarative/declarative_manifest_unittest.cc
@@ -22,7 +22,7 @@
   std::vector<DeclarativeManifestData::Rule> rules =
       manifest_data->RulesForEvent("foo");
   EXPECT_EQ(1u, rules.size());
-  std::unique_ptr<base::DictionaryValue> expected_rule = ParseDictionary(
+  absl::optional<base::Value::Dict> expected_rule = ParseDictionary(
       "{"
       "  \"actions\": [{"
       "    \"instanceType\": \"action_type\""
diff --git a/extensions/common/api/runtime.json b/extensions/common/api/runtime.json
index 378d236..b43e0dd 100644
--- a/extensions/common/api/runtime.json
+++ b/extensions/common/api/runtime.json
@@ -300,7 +300,7 @@
         "name": "connect",
         "type": "function",
         "nocompile": true,
-        "description": "Attempts to connect to connect listeners within an extension/app (such as the background page), or other extensions/apps. This is useful for content scripts connecting to their extension processes, inter-app/extension communication, and <a href=\"manifest/externally_connectable.html\">web messaging</a>. Note that this does not connect to any listeners in a content script. Extensions may connect to content scripts embedded in tabs via $(ref:tabs.connect).",
+        "description": "Attempts to connect listeners within an extension/app (such as the background page), or other extensions/apps. This is useful for content scripts connecting to their extension processes, inter-app/extension communication, and <a href=\"manifest/externally_connectable.html\">web messaging</a>. Note that this does not connect to any listeners in a content script. Extensions may connect to content scripts embedded in tabs via $(ref:tabs.connect).",
         "parameters": [
           {"type": "string", "name": "extensionId", "optional": true, "description": "The ID of the extension or app to connect to. If omitted, a connection will be attempted with your own extension. Required if sending messages from a web page for <a href=\"manifest/externally_connectable.html\">web messaging</a>."},
           {
diff --git a/extensions/common/extension_builder.cc b/extensions/common/extension_builder.cc
index cf55eab..2446215e 100644
--- a/extensions/common/extension_builder.cc
+++ b/extensions/common/extension_builder.cc
@@ -241,6 +241,11 @@
   return *this;
 }
 
+ExtensionBuilder& ExtensionBuilder::SetManifest(base::Value::Dict manifest) {
+  return SetManifest(base::DictionaryValue::From(
+      std::make_unique<base::Value>(std::move(manifest))));
+}
+
 ExtensionBuilder& ExtensionBuilder::MergeManifest(const base::Value& to_merge) {
   CHECK(to_merge.is_dict());
   if (manifest_data_) {
diff --git a/extensions/common/extension_builder.h b/extensions/common/extension_builder.h
index 20fa9101..a49875c 100644
--- a/extensions/common/extension_builder.h
+++ b/extensions/common/extension_builder.h
@@ -13,6 +13,7 @@
 #include "base/files/file_path.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/strings/string_piece.h"
+#include "base/values.h"
 #include "extensions/common/api/extension_action/action_info.h"
 #include "extensions/common/manifest.h"
 #include "extensions/common/mojom/manifest.mojom-shared.h"
@@ -157,6 +158,9 @@
   ExtensionBuilder& SetManifest(
       std::unique_ptr<base::DictionaryValue> manifest);
 
+  // Assigns the extension's manifest to |manifest|.
+  ExtensionBuilder& SetManifest(base::Value::Dict manifest);
+
   //////////////////////////////////////////////////////////////////////////////
   // Common utility methods (usable with both aided and custom manifest
   // creation).
diff --git a/gpu/BUILD.gn b/gpu/BUILD.gn
index 7b458fce..fd1c65a 100644
--- a/gpu/BUILD.gn
+++ b/gpu/BUILD.gn
@@ -296,6 +296,7 @@
   use_xvfb = use_xvfb_in_this_config
 
   sources = [
+    "command_buffer/service/gles2_external_framebuffer_unittest.cc",
     "command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc",
     "command_buffer/service/shared_image/shared_image_factory_unittest.cc",
     "command_buffer/service/shared_image/shared_image_manager_unittest.cc",
diff --git a/gpu/command_buffer/service/gles2_external_framebuffer_unittest.cc b/gpu/command_buffer/service/gles2_external_framebuffer_unittest.cc
new file mode 100644
index 0000000..156f6333
--- /dev/null
+++ b/gpu/command_buffer/service/gles2_external_framebuffer_unittest.cc
@@ -0,0 +1,374 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gpu/command_buffer/service/gles2_external_framebuffer.h"
+
+#include "base/bits.h"
+#include "base/command_line.h"
+#include "build/build_config.h"
+#include "components/viz/common/resources/resource_format.h"
+#include "components/viz/common/resources/resource_format_utils.h"
+#include "gpu/command_buffer/common/mailbox.h"
+#include "gpu/command_buffer/common/shared_image_usage.h"
+#include "gpu/command_buffer/service/service_utils.h"
+#include "gpu/command_buffer/service/shared_context_state.h"
+#include "gpu/command_buffer/service/shared_image/shared_image_backing.h"
+#include "gpu/command_buffer/service/shared_image/shared_image_factory.h"
+#include "gpu/command_buffer/service/shared_image/shared_image_manager.h"
+#include "gpu/command_buffer/service/shared_image/shared_image_representation.h"
+#include "gpu/command_buffer/service/shared_image/test_utils.h"
+#include "gpu/config/gpu_driver_bug_workarounds.h"
+#include "gpu/config/gpu_feature_info.h"
+#include "gpu/config/gpu_preferences.h"
+#include "gpu/config/gpu_test_config.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/color_space.h"
+#include "ui/gl/gl_surface.h"
+#include "ui/gl/gl_utils.h"
+#include "ui/gl/gl_version_info.h"
+#include "ui/gl/init/gl_factory.h"
+
+using testing::AtLeast;
+
+namespace gpu {
+namespace {
+
+void CreateSharedContext(const GpuPreferences& preferences,
+                         const GpuDriverBugWorkarounds& workarounds,
+                         scoped_refptr<gl::GLSurface>& surface,
+                         scoped_refptr<gl::GLContext>& context,
+                         scoped_refptr<SharedContextState>& context_state,
+                         scoped_refptr<gles2::FeatureInfo>& feature_info) {
+  surface =
+      gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplay(), gfx::Size());
+  ASSERT_TRUE(surface);
+  context =
+      gl::init::CreateGLContext(nullptr, surface.get(), gl::GLContextAttribs());
+  ASSERT_TRUE(context);
+  bool result = context->MakeCurrent(surface.get());
+  ASSERT_TRUE(result);
+
+  scoped_refptr<gl::GLShareGroup> share_group =
+      base::MakeRefCounted<gl::GLShareGroup>();
+  feature_info =
+      base::MakeRefCounted<gles2::FeatureInfo>(workarounds, GpuFeatureInfo());
+  context_state = base::MakeRefCounted<SharedContextState>(
+      std::move(share_group), surface, context,
+      /*use_virtualized_gl_contexts=*/false, base::DoNothing());
+  context_state->InitializeGrContext(GpuPreferences(), workarounds, nullptr);
+  context_state->InitializeGL(GpuPreferences(), feature_info);
+}
+
+using TestParamsTuple =
+    testing::tuple<viz::SharedImageFormat, int, bool, bool, bool>;
+class GLES2ExternalFrameBufferTest
+    : public testing::TestWithParam<TestParamsTuple> {
+ public:
+  explicit GLES2ExternalFrameBufferTest()
+      : shared_image_manager_(
+            std::make_unique<SharedImageManager>(/*is_thread_safe=*/false)) {}
+  ~GLES2ExternalFrameBufferTest() override {
+    // |context_state_| must be destroyed on its own context.
+    bool have_context =
+        context_state_->MakeCurrent(surface_.get(), true /* needs_gl */);
+    gles2_external_framebuffer_->Destroy(have_context);
+    backing_factory_->DestroyAllSharedImages(have_context);
+  }
+
+  void SetUp() override {
+    scoped_refptr<gles2::FeatureInfo> feature_info;
+    GpuDriverBugWorkarounds workarounds;
+    GpuPreferences preferences;
+    preferences.use_passthrough_cmd_decoder = use_passthrough();
+    CreateSharedContext(preferences, workarounds, surface_, context_,
+                        context_state_, feature_info);
+
+    backing_factory_ = std::make_unique<SharedImageFactory>(
+        preferences, workarounds, GpuFeatureInfo(), context_state_.get(),
+        shared_image_manager_.get(), nullptr, context_state_->memory_tracker(),
+        /*is_for_display_compositor=*/false);
+
+    memory_type_tracker_ = std::make_unique<MemoryTypeTracker>(nullptr);
+    shared_image_representation_factory_ =
+        std::make_unique<SharedImageRepresentationFactory>(
+            shared_image_manager_.get(), nullptr);
+
+    gles2_external_framebuffer_ =
+        std::make_unique<gles2::GLES2ExternalFramebuffer>(
+            use_passthrough(), *feature_info,
+            shared_image_representation_factory_.get());
+
+    const bool multisampled_framebuffers_supported =
+        feature_info->feature_flags().chromium_framebuffer_multisample;
+    const bool rgb8_supported = feature_info->feature_flags().oes_rgb8_rgba8;
+    // The only available default render buffer formats in GLES2 have very
+    // little precision.  Don't enable multisampling unless 8-bit render
+    // buffer formats are available--instead fall back to 8-bit textures.
+
+    if (multisampled_framebuffers_supported && rgb8_supported) {
+      glGetIntegerv(GL_MAX_SAMPLES_EXT, &max_sample_count_);
+    }
+  }
+
+  bool use_passthrough() {
+    return gles2::UsePassthroughCommandDecoder(
+               base::CommandLine::ForCurrentProcess()) &&
+           gles2::PassthroughCommandDecoderSupported();
+  }
+
+ protected:
+  std::unique_ptr<GLTextureImageRepresentationBase> ProduceGLTextureBase(
+      const Mailbox& mailbox) {
+    if (use_passthrough())
+      return shared_image_representation_factory_->ProduceGLTexturePassthrough(
+          mailbox);
+    else
+      return shared_image_representation_factory_->ProduceGLTexture(mailbox);
+  }
+
+  Mailbox CreateSharedImage(const viz::SharedImageFormat& format) {
+    auto mailbox = Mailbox::GenerateForSharedImage();
+    backing_factory_->CreateSharedImage(
+        mailbox, format, gfx::Size(64, 64), gfx::ColorSpace::CreateSRGB(),
+        kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, SurfaceHandle(),
+        SHARED_IMAGE_USAGE_GLES2);
+    return mailbox;
+  }
+
+  void ReadColors(gl::GLApi* const api,
+                  const Mailbox& mailbox,
+                  std::array<SkColor, 4>& quadrants) {
+    auto rep = ProduceGLTextureBase(mailbox);
+    auto access = rep->BeginScopedAccess(
+        GLTextureImageRepresentationBase::kReadAccessMode,
+        GLTextureImageRepresentationBase::AllowUnclearedAccess::kYes);
+    EXPECT_TRUE(rep->IsCleared());
+
+    GLuint fbo;
+    api->glGenFramebuffersEXTFn(1, &fbo);
+    api->glBindFramebufferEXTFn(GL_FRAMEBUFFER, fbo);
+    api->glFramebufferTexture2DEXTFn(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+                                     GL_TEXTURE_2D,
+                                     rep->GetTextureBase()->service_id(), 0);
+
+    api->glDisableFn(GL_SCISSOR_TEST);
+
+    // Initialize to gray in case read pixels will fail.
+    for (auto& color : quadrants)
+      color = SK_ColorGRAY;
+
+    api->glReadPixelsFn(16, 16, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &quadrants[0]);
+    api->glReadPixelsFn(48, 16, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &quadrants[1]);
+    api->glReadPixelsFn(16, 46, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &quadrants[2]);
+    api->glReadPixelsFn(48, 48, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &quadrants[3]);
+
+    // SkColor is BGRA and GL is RGBA, so we need to swap bytes.
+    for (auto& color : quadrants) {
+      color = SkColorSetARGB(SkColorGetA(color), SkColorGetB(color),
+                             SkColorGetG(color), SkColorGetR(color));
+    }
+
+    api->glDeleteFramebuffersEXTFn(1, &fbo);
+  }
+
+  scoped_refptr<gl::GLSurface> surface_;
+  scoped_refptr<gl::GLContext> context_;
+  scoped_refptr<SharedContextState> context_state_;
+  std::unique_ptr<SharedImageFactory> backing_factory_;
+  std::unique_ptr<SharedImageManager> shared_image_manager_;
+  std::unique_ptr<MemoryTypeTracker> memory_type_tracker_;
+  std::unique_ptr<SharedImageRepresentationFactory>
+      shared_image_representation_factory_;
+  std::unique_ptr<gles2::GLES2ExternalFramebuffer> gles2_external_framebuffer_;
+  GLint max_sample_count_ = 0;
+};
+
+struct TestParams {
+  explicit TestParams(const TestParamsTuple& params) {
+    format = testing::get<0>(params);
+    samples = testing::get<1>(params);
+    preserve = testing::get<2>(params);
+    need_depth = testing::get<3>(params);
+    need_stencil = testing::get<4>(params);
+  }
+
+  viz::SharedImageFormat format;
+  int samples;
+  bool preserve;
+  bool need_depth;
+  bool need_stencil;
+};
+
+std::string TestParamToString(
+    const testing::TestParamInfo<TestParamsTuple>& param_info) {
+  auto params = TestParams(param_info.param);
+
+  std::string result;
+  result += params.format.ToString();
+
+  if (params.samples)
+    result += "Multisampling";
+
+  if (params.preserve)
+    result += "Preserve";
+  if (params.need_depth)
+    result += "Depth";
+  if (params.need_stencil)
+    result += "Stencil";
+
+  return result;
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    ,
+    GLES2ExternalFrameBufferTest,
+    ::testing::Combine(::testing::Values(viz::SharedImageFormat::SinglePlane(
+                                             viz::ResourceFormat::RGBA_8888),
+                                         viz::SharedImageFormat::SinglePlane(
+                                             viz::ResourceFormat::RGBX_8888)),
+                       ::testing::Values(0, 8),
+                       ::testing::Bool(),
+                       ::testing::Bool(),
+                       ::testing::Bool()),
+    TestParamToString);
+
+TEST_P(GLES2ExternalFrameBufferTest, Test) {
+  auto params = TestParams(GetParam());
+  auto mailbox1 = CreateSharedImage(params.format);
+
+  gl::GLApi* const api = gl::g_current_gl_context;
+
+  {
+    GLint prev_fbo;
+    api->glGetIntegervFn(GL_FRAMEBUFFER_BINDING, &prev_fbo);
+    EXPECT_TRUE(gles2_external_framebuffer_->AttachSharedImage(
+        mailbox1, params.samples, params.preserve, params.need_depth,
+        params.need_stencil));
+
+    GLint new_fbo;
+    api->glGetIntegervFn(GL_FRAMEBUFFER_BINDING, &new_fbo);
+
+    // Verify that it doesn't update FBO binding
+    EXPECT_EQ(new_fbo, prev_fbo);
+  }
+
+  api->glBindFramebufferEXTFn(GL_FRAMEBUFFER,
+                              gles2_external_framebuffer_->GetFramebufferId());
+  api->glViewportFn(0, 0, 64, 64);
+
+  GLint depth_bits = 0;
+  GLint stencil_bits = 0;
+  GLint alpha_bits = 0;
+
+  if (context_state_->feature_info()
+          ->gl_version_info()
+          .is_desktop_core_profile) {
+    api->glGetFramebufferAttachmentParameterivEXTFn(
+        GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+        GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE, &alpha_bits);
+    api->glGetFramebufferAttachmentParameterivEXTFn(
+        GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
+        GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE, &depth_bits);
+    api->glGetFramebufferAttachmentParameterivEXTFn(
+        GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
+        GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE, &stencil_bits);
+  } else {
+    api->glGetIntegervFn(GL_ALPHA_BITS, &alpha_bits);
+    api->glGetIntegervFn(GL_DEPTH_BITS, &depth_bits);
+    api->glGetIntegervFn(GL_STENCIL_BITS, &stencil_bits);
+  }
+
+  // If we requested depth, expect it to be there.
+  if (params.need_depth)
+    EXPECT_GT(depth_bits, 0);
+
+  // If we requested depth, expect it to be there.
+  if (params.need_stencil)
+    EXPECT_GT(stencil_bits, 0);
+
+  // If we didn't request neither depth nor stencil. Note, that we prefer using
+  // packed depth-stencil, so requesting one of them might have both.
+  if (!params.need_depth && !params.need_stencil) {
+    EXPECT_EQ(depth_bits, 0);
+    EXPECT_EQ(stencil_bits, 0);
+  }
+
+  EXPECT_EQ(params.format.HasAlpha(), alpha_bits > 0);
+
+  const bool draw_direct =
+      !params.preserve && (std::min(max_sample_count_, params.samples) == 0);
+
+  if (draw_direct) {
+    auto rep = ProduceGLTextureBase(mailbox1);
+    // AttachSharedImage should have cleared image if we draw directly.
+    EXPECT_TRUE(rep->IsCleared());
+  }
+
+  api->glScissorFn(0, 0, 32, 32);
+  api->glEnableFn(GL_SCISSOR_TEST);
+  api->glClearColorFn(0.0, 1.0, 0.0, 1.0);  // Green
+  api->glClearFn(GL_COLOR_BUFFER_BIT);
+
+  // Detach and resolve mailbox1 from the framebuffer. This always expected to
+  // succeed.
+  EXPECT_TRUE(gles2_external_framebuffer_->AttachSharedImage(
+      Mailbox(), 0, false, false, false));
+
+  const SkColor clear_color =
+      params.format.HasAlpha() ? SK_ColorTRANSPARENT : SK_ColorBLACK;
+  {
+    std::array<SkColor, 4> colors;
+    ReadColors(api, mailbox1, colors);
+    // We draw this one.
+    EXPECT_EQ(colors[0], SK_ColorGREEN);
+
+    // We didn't draw those, so it should have been cleared to zero.
+    EXPECT_EQ(colors[1], clear_color);
+    EXPECT_EQ(colors[2], clear_color);
+    EXPECT_EQ(colors[3], clear_color);
+  }
+
+  auto mailbox2 = CreateSharedImage(params.format);
+
+  EXPECT_TRUE(gles2_external_framebuffer_->AttachSharedImage(
+      mailbox2, params.samples, params.preserve, params.need_depth,
+      params.need_stencil));
+  api->glBindFramebufferEXTFn(GL_FRAMEBUFFER,
+                              gles2_external_framebuffer_->GetFramebufferId());
+  api->glViewportFn(0, 0, 64, 64);
+
+  // Clear bottom right portion.
+  api->glScissorFn(32, 32, 32, 32);
+  api->glEnableFn(GL_SCISSOR_TEST);
+  api->glClearColorFn(0.0, 0.0, 1.0, 1.0);  // Blue
+  api->glClearFn(GL_COLOR_BUFFER_BIT);
+
+  // Detach and resolve mailbox1 from the framebuffer. This always expected to
+  // succeed.
+  EXPECT_TRUE(gles2_external_framebuffer_->AttachSharedImage(
+      Mailbox(), 0, false, false, false));
+
+  std::array<SkColor, 4> colors;
+  ReadColors(api, mailbox2, colors);
+
+  // If we requested to preserve content or if there is multisampling, the
+  // contents of the back buffer is expected to stay, so top-left quadrant
+  // should be green from the first draw.
+  if (!draw_direct) {
+    EXPECT_EQ(colors[0], SK_ColorGREEN);
+  } else {
+    EXPECT_EQ(colors[0], clear_color);
+  }
+
+  // We didn't draw those, so it should have been cleared to zero.
+  EXPECT_EQ(colors[1], clear_color);
+  EXPECT_EQ(colors[2], clear_color);
+
+  // We just draw this one as blue.
+  EXPECT_EQ(colors[3], SK_ColorBLUE);
+}
+
+}  // namespace
+}  // namespace gpu
\ No newline at end of file
diff --git a/gpu/command_buffer/service/shared_image/compound_image_backing.cc b/gpu/command_buffer/service/shared_image/compound_image_backing.cc
index dd2c6d25..b20ada9 100644
--- a/gpu/command_buffer/service/shared_image/compound_image_backing.cc
+++ b/gpu/command_buffer/service/shared_image/compound_image_backing.cc
@@ -4,6 +4,7 @@
 
 #include "gpu/command_buffer/service/shared_image/compound_image_backing.h"
 
+#include "base/feature_list.h"
 #include "base/logging.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/trace_event/memory_allocator_dump_guid.h"
@@ -31,6 +32,11 @@
 namespace gpu {
 namespace {
 
+// TODO(crbug.com/1293509): Remove after M110 branch.
+BASE_FEATURE(kSkipReadbackToSharedMemory,
+             "SkipReadbackToSharedMemory",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 bool IsValidSharedMemoryBufferFormat(const gfx::Size& size,
                                      gfx::BufferFormat buffer_format,
                                      gfx::BufferPlane plane) {
@@ -403,9 +409,11 @@
 }
 
 bool CompoundImageBacking::CopyToGpuMemoryBuffer() {
-  // TODO(crbug.com/1293509): Return early if `shm_has_latest_content_` is true
-  // since shared memory should already be up to date. Just need to verify GL
-  // isn't modifying the texture without acquiring write access first.
+  // If shared memory already contains the latest content skip readback.
+  if (shm_has_latest_content_ &&
+      base::FeatureList::IsEnabled(kSkipReadbackToSharedMemory)) {
+    return true;
+  }
 
   auto& wrapper = static_cast<SharedMemoryImageBacking*>(shm_backing_.get())
                       ->shared_memory_wrapper();
diff --git a/gpu/command_buffer/service/shared_image/iosurface_image_backing.h b/gpu/command_buffer/service/shared_image/iosurface_image_backing.h
index f6bba4b..530abbb 100644
--- a/gpu/command_buffer/service/shared_image/iosurface_image_backing.h
+++ b/gpu/command_buffer/service/shared_image/iosurface_image_backing.h
@@ -19,13 +19,65 @@
 
 namespace gpu {
 
-// Interface through which a representation that has a GL texture calls into its
-// IOSurface backing.
-class GLTextureIOSurfaceRepresentationClient {
- public:
-  virtual bool GLTextureImageRepresentationBeginAccess(bool readonly) = 0;
-  virtual void GLTextureImageRepresentationEndAccess(bool readonly) = 0;
-  virtual void GLTextureImageRepresentationRelease(bool have_context) = 0;
+// The state associated with an EGL texture representation of an IOSurface.
+// This is used by the representations GLTextureIOSurfaceRepresentation and
+// SkiaIOSurfaceRepresentation (when the underlying GrContext uses GL).
+struct IOSurfaceBackingEGLState : base::RefCounted<IOSurfaceBackingEGLState> {
+  // The interface through which IOSurfaceBackingEGLState calls into
+  // IOSurfaceImageBacking.
+  class Client {
+   public:
+    virtual bool IOSurfaceBackingEGLStateBeginAccess(
+        IOSurfaceBackingEGLState* egl_state,
+        bool readonly) = 0;
+    virtual void IOSurfaceBackingEGLStateEndAccess(
+        IOSurfaceBackingEGLState* egl_state,
+        bool readonly) = 0;
+    virtual void IOSurfaceBackingEGLStateBeingCreated(
+        IOSurfaceBackingEGLState* egl_state) = 0;
+    virtual void IOSurfaceBackingEGLStateBeingDestroyed(
+        IOSurfaceBackingEGLState* egl_state,
+        bool have_context) = 0;
+  };
+
+  IOSurfaceBackingEGLState(Client* client,
+                           EGLDisplay egl_display,
+                           GLuint gl_target,
+                           scoped_refptr<gles2::TexturePassthrough> gl_texture);
+  GLenum GetGLTarget() const { return gl_target_; }
+  GLuint GetGLServiceId() const;
+  const scoped_refptr<gles2::TexturePassthrough>& GetGLTexture() const {
+    return gl_texture_;
+  }
+  bool BeginAccess(bool readonly);
+  void EndAccess(bool readonly);
+  void WillRelease(bool have_context);
+
+ private:
+  friend class base::RefCounted<IOSurfaceBackingEGLState>;
+
+  // This class was cleaved off of state from IOSurfaceImageBacking, and so
+  // IOSurfaceImageBacking still accesses its internals.
+  friend class IOSurfaceImageBacking;
+
+  // The interface through which to call into IOSurfaceImageBacking.
+  const raw_ptr<Client> client_;
+
+  // The display for this GL representation.
+  const EGLDisplay egl_display_;
+
+  // The GL (not EGL) target to which this texture is to be bound.
+  const GLuint gl_target_;
+
+  // The EGL, GLES, and Skia internals for this IOSurface.
+  std::unique_ptr<gl::ScopedEGLSurfaceIOSurface> egl_surface_;
+  scoped_refptr<gles2::TexturePassthrough> gl_texture_;
+  sk_sp<SkPromiseImageTexture> cached_promise_texture_;
+
+  // Set to true if the context is known to be lost.
+  bool context_lost_ = false;
+
+  ~IOSurfaceBackingEGLState();
 };
 
 // Representation of a GLTextureImageBacking or
@@ -36,9 +88,8 @@
   GLTextureIOSurfaceRepresentation(
       SharedImageManager* manager,
       SharedImageBacking* backing,
-      GLTextureIOSurfaceRepresentationClient* client,
-      MemoryTypeTracker* tracker,
-      scoped_refptr<gles2::TexturePassthrough> texture_passthrough);
+      scoped_refptr<IOSurfaceBackingEGLState> egl_state,
+      MemoryTypeTracker* tracker);
   ~GLTextureIOSurfaceRepresentation() override;
 
  private:
@@ -48,22 +99,16 @@
   bool BeginAccess(GLenum mode) override;
   void EndAccess() override;
 
-  const raw_ptr<GLTextureIOSurfaceRepresentationClient> client_ = nullptr;
-  scoped_refptr<gles2::TexturePassthrough> texture_;
+  scoped_refptr<IOSurfaceBackingEGLState> egl_state_;
   GLenum mode_ = 0;
 };
 
 // Skia representation for both GLTextureImageBackingHelper.
 class SkiaIOSurfaceRepresentation : public SkiaImageRepresentation {
  public:
-  class Client {
-   public:
-    virtual bool OnSkiaBeginReadAccess() = 0;
-    virtual bool OnSkiaBeginWriteAccess() = 0;
-  };
   SkiaIOSurfaceRepresentation(SharedImageManager* manager,
                               SharedImageBacking* backing,
-                              GLTextureIOSurfaceRepresentationClient* client,
+                              scoped_refptr<IOSurfaceBackingEGLState> egl_state,
                               scoped_refptr<SharedContextState> context_state,
                               sk_sp<SkPromiseImageTexture> promise_texture,
                               MemoryTypeTracker* tracker);
@@ -95,7 +140,7 @@
 
   void CheckContext();
 
-  const raw_ptr<GLTextureIOSurfaceRepresentationClient> client_ = nullptr;
+  scoped_refptr<IOSurfaceBackingEGLState> egl_state_;
   scoped_refptr<SharedContextState> context_state_;
   sk_sp<SkPromiseImageTexture> promise_texture_;
   sk_sp<SkSurface> write_surface_;
@@ -163,7 +208,7 @@
 // mailbox implementation.
 class GPU_GLES2_EXPORT IOSurfaceImageBacking
     : public SharedImageBacking,
-      public GLTextureIOSurfaceRepresentationClient {
+      public IOSurfaceBackingEGLState::Client {
  public:
   IOSurfaceImageBacking(
       scoped_refptr<gl::GLImage> image,
@@ -181,8 +226,6 @@
 
   void InitializePixels(GLenum format, GLenum type, const uint8_t* data);
 
-  GLenum GetGLTarget() const;
-  GLuint GetGLServiceId() const;
   std::unique_ptr<gfx::GpuFence> GetLastWriteGpuFence();
   void SetReleaseFence(gfx::GpuFenceHandle release_fence);
 
@@ -221,10 +264,16 @@
       MemoryTypeTracker* tracker) override;
   void Update(std::unique_ptr<gfx::GpuFence> in_fence) override;
 
-  // GLTextureIOSurfaceRepresentationClient:
-  bool GLTextureImageRepresentationBeginAccess(bool readonly) override;
-  void GLTextureImageRepresentationEndAccess(bool readonly) override;
-  void GLTextureImageRepresentationRelease(bool have_context) override;
+  // IOSurfaceBackingEGLState::Client:
+  bool IOSurfaceBackingEGLStateBeginAccess(IOSurfaceBackingEGLState* egl_state,
+                                           bool readonly) override;
+  void IOSurfaceBackingEGLStateEndAccess(IOSurfaceBackingEGLState* egl_state,
+                                         bool readonly) override;
+  void IOSurfaceBackingEGLStateBeingCreated(
+      IOSurfaceBackingEGLState* egl_state) override;
+  void IOSurfaceBackingEGLStateBeingDestroyed(
+      IOSurfaceBackingEGLState* egl_state,
+      bool have_context) override;
 
   bool IsPassthrough() const { return true; }
 
@@ -237,10 +286,8 @@
   // disallowed concurrent read/write accesses.
   bool ongoing_write_access_ = false;
 
-  void RetainGLTexture();
-  void ReleaseGLTexture(bool have_context);
-  size_t gl_texture_retain_count_ = 0;
-  bool gl_texture_retained_for_legacy_mailbox_ = false;
+  scoped_refptr<IOSurfaceBackingEGLState> RetainGLTexture();
+  void ReleaseGLTexture(IOSurfaceBackingEGLState* egl_state, bool have_context);
 
   const GLTextureImageBackingHelper::InitializeGLTextureParams gl_params_;
 
@@ -248,10 +295,10 @@
   // |texture_| is nullptr.
   gfx::Rect cleared_rect_;
 
-  std::unique_ptr<gl::ScopedEGLSurfaceIOSurface> egl_surface_;
-  scoped_refptr<gles2::TexturePassthrough> gl_texture_;
+  // This map tracks all IOSurfaceBackingEGLState instances that exist.
+  std::map<EGLDisplay, IOSurfaceBackingEGLState*> egl_state_map_;
+  scoped_refptr<IOSurfaceBackingEGLState> egl_state_for_legacy_mailbox_;
 
-  sk_sp<SkPromiseImageTexture> cached_promise_texture_;
   std::unique_ptr<gl::GLFence> last_write_gl_fence_;
 
   // If this backing was displayed as an overlay, this fence may be set.
diff --git a/gpu/command_buffer/service/shared_image/iosurface_image_backing.mm b/gpu/command_buffer/service/shared_image/iosurface_image_backing.mm
index 89fe89f..b88a6025 100644
--- a/gpu/command_buffer/service/shared_image/iosurface_image_backing.mm
+++ b/gpu/command_buffer/service/shared_image/iosurface_image_backing.mm
@@ -41,39 +41,72 @@
 }  // namespace
 
 ///////////////////////////////////////////////////////////////////////////////
+// IOSurfaceBackingEGLState
+
+IOSurfaceBackingEGLState::IOSurfaceBackingEGLState(
+    Client* client,
+    EGLDisplay egl_display,
+    GLuint gl_target,
+    scoped_refptr<gles2::TexturePassthrough> gl_texture)
+    : client_(client),
+      egl_display_(egl_display),
+      gl_target_(gl_target),
+      gl_texture_(gl_texture) {
+  client_->IOSurfaceBackingEGLStateBeingCreated(this);
+}
+
+IOSurfaceBackingEGLState::~IOSurfaceBackingEGLState() {
+  client_->IOSurfaceBackingEGLStateBeingDestroyed(this, !context_lost_);
+  DCHECK(!gl_texture_);
+}
+
+GLuint IOSurfaceBackingEGLState::GetGLServiceId() const {
+  return gl_texture_->service_id();
+}
+
+bool IOSurfaceBackingEGLState::BeginAccess(bool readonly) {
+  gl::GLDisplayEGL* display = gl::GLDisplayEGL::GetDisplayForCurrentContext();
+  if (!display || display->GetDisplay() != egl_display_)
+    LOG(FATAL) << "Expected GLDisplayEGL not current.";
+  return client_->IOSurfaceBackingEGLStateBeginAccess(this, readonly);
+}
+
+void IOSurfaceBackingEGLState::EndAccess(bool readonly) {
+  client_->IOSurfaceBackingEGLStateEndAccess(this, readonly);
+}
+
+void IOSurfaceBackingEGLState::WillRelease(bool have_context) {
+  context_lost_ |= !have_context;
+}
+
+///////////////////////////////////////////////////////////////////////////////
 // GLTextureIOSurfaceRepresentation
 
 GLTextureIOSurfaceRepresentation::GLTextureIOSurfaceRepresentation(
     SharedImageManager* manager,
     SharedImageBacking* backing,
-    GLTextureIOSurfaceRepresentationClient* client,
-    MemoryTypeTracker* tracker,
-    scoped_refptr<gles2::TexturePassthrough> texture_passthrough)
+    scoped_refptr<IOSurfaceBackingEGLState> egl_state,
+    MemoryTypeTracker* tracker)
     : GLTexturePassthroughImageRepresentation(manager, backing, tracker),
-      client_(client),
-      texture_(std::move(texture_passthrough)) {
-  // TODO(https://crbug.com/1172769): Remove this CHECK.
-  CHECK(texture_);
-}
+      egl_state_(egl_state) {}
 
 GLTextureIOSurfaceRepresentation::~GLTextureIOSurfaceRepresentation() {
-  texture_.reset();
-  if (client_)
-    client_->GLTextureImageRepresentationRelease(has_context());
+  egl_state_->WillRelease(has_context());
+  egl_state_.reset();
 }
 
 const scoped_refptr<gles2::TexturePassthrough>&
 GLTextureIOSurfaceRepresentation::GetTexturePassthrough(int plane_index) {
   DCHECK_EQ(plane_index, 0);
-  return texture_;
+  return egl_state_->GetGLTexture();
 }
 
 bool GLTextureIOSurfaceRepresentation::BeginAccess(GLenum mode) {
   DCHECK(mode_ == 0);
   mode_ = mode;
   bool readonly = mode_ != GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM;
-  if (client_ && mode != GL_SHARED_IMAGE_ACCESS_MODE_OVERLAY_CHROMIUM)
-    return client_->GLTextureImageRepresentationBeginAccess(readonly);
+  if (mode != GL_SHARED_IMAGE_ACCESS_MODE_OVERLAY_CHROMIUM)
+    return egl_state_->BeginAccess(readonly);
   return true;
 }
 
@@ -81,9 +114,8 @@
   DCHECK(mode_ != 0);
   GLenum current_mode = mode_;
   mode_ = 0;
-  if (client_)
-    return client_->GLTextureImageRepresentationEndAccess(
-        current_mode != GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM);
+  egl_state_->EndAccess(current_mode !=
+                        GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -92,12 +124,12 @@
 SkiaIOSurfaceRepresentation::SkiaIOSurfaceRepresentation(
     SharedImageManager* manager,
     SharedImageBacking* backing,
-    GLTextureIOSurfaceRepresentationClient* client,
+    scoped_refptr<IOSurfaceBackingEGLState> egl_state,
     scoped_refptr<SharedContextState> context_state,
     sk_sp<SkPromiseImageTexture> promise_texture,
     MemoryTypeTracker* tracker)
     : SkiaImageRepresentation(manager, backing, tracker),
-      client_(client),
+      egl_state_(egl_state),
       context_state_(std::move(context_state)),
       promise_texture_(promise_texture) {
   DCHECK(promise_texture_);
@@ -113,9 +145,10 @@
                 << "open for write access.";
   }
   promise_texture_.reset();
-  if (client_) {
+  if (egl_state_) {
     DCHECK(context_state_->GrContextIsGL());
-    client_->GLTextureImageRepresentationRelease(has_context());
+    egl_state_->WillRelease(has_context());
+    egl_state_.reset();
   }
 }
 
@@ -127,10 +160,9 @@
     std::vector<GrBackendSemaphore>* end_semaphores,
     std::unique_ptr<GrBackendSurfaceMutableState>* end_state) {
   CheckContext();
-  if (client_) {
+  if (egl_state_) {
     DCHECK(context_state_->GrContextIsGL());
-    if (!client_->GLTextureImageRepresentationBeginAccess(
-            /*readonly=*/false)) {
+    if (!egl_state_->BeginAccess(/*readonly=*/false)) {
       return {};
     }
   }
@@ -164,10 +196,9 @@
     std::vector<GrBackendSemaphore>* end_semaphores,
     std::unique_ptr<GrBackendSurfaceMutableState>* end_state) {
   CheckContext();
-  if (client_) {
+  if (egl_state_) {
     DCHECK(context_state_->GrContextIsGL());
-    if (!client_->GLTextureImageRepresentationBeginAccess(
-            /*readonly=*/false)) {
+    if (!egl_state_->BeginAccess(/*readonly=*/false)) {
       return {};
     }
   }
@@ -184,8 +215,8 @@
     write_surface_.reset();
   }
 
-  if (client_)
-    client_->GLTextureImageRepresentationEndAccess(false /* readonly */);
+  if (egl_state_)
+    egl_state_->EndAccess(false /* readonly */);
 }
 
 std::vector<sk_sp<SkPromiseImageTexture>>
@@ -194,10 +225,9 @@
     std::vector<GrBackendSemaphore>* end_semaphores,
     std::unique_ptr<GrBackendSurfaceMutableState>* end_state) {
   CheckContext();
-  if (client_) {
+  if (egl_state_) {
     DCHECK(context_state_->GrContextIsGL());
-    if (!client_->GLTextureImageRepresentationBeginAccess(
-            /*readonly=*/true)) {
+    if (!egl_state_->BeginAccess(/*readonly=*/true)) {
       return {};
     }
   }
@@ -207,8 +237,8 @@
 }
 
 void SkiaIOSurfaceRepresentation::EndReadAccess() {
-  if (client_)
-    client_->GLTextureImageRepresentationEndAccess(true /* readonly */);
+  if (egl_state_)
+    egl_state_->EndAccess(true /* readonly */);
 }
 
 bool SkiaIOSurfaceRepresentation::SupportsMultipleConcurrentReadAccess() {
@@ -329,74 +359,70 @@
 
   // NOTE: Mac currently retains GLTexture and reuses it. Not sure if this is
   // best approach as it can lead to issues with context losses.
-  if (!gl_texture_retained_for_legacy_mailbox_) {
-    RetainGLTexture();
-    gl_texture_retained_for_legacy_mailbox_ = true;
-  }
+  egl_state_for_legacy_mailbox_ = RetainGLTexture();
 }
 
 IOSurfaceImageBacking::~IOSurfaceImageBacking() {
-  if (gl_texture_retained_for_legacy_mailbox_)
-    ReleaseGLTexture(have_context());
-  DCHECK_EQ(gl_texture_retain_count_, 0u);
+  if (egl_state_for_legacy_mailbox_) {
+    egl_state_for_legacy_mailbox_->WillRelease(have_context());
+    egl_state_for_legacy_mailbox_ = nullptr;
+  }
+  DCHECK(egl_state_map_.empty());
 }
 
-void IOSurfaceImageBacking::RetainGLTexture() {
-  gl_texture_retain_count_ += 1;
-  if (gl_texture_retain_count_ > 1)
-    return;
+scoped_refptr<IOSurfaceBackingEGLState>
+IOSurfaceImageBacking::RetainGLTexture() {
+  gl::GLDisplayEGL* display = gl::GLDisplayEGL::GetDisplayForCurrentContext();
+  if (!display) {
+    LOG(ERROR) << "No GLDisplayEGL current.";
+    return nullptr;
+  }
+  const EGLDisplay egl_display = display->GetDisplay();
+
+  auto found = egl_state_map_.find(egl_display);
+  if (found != egl_state_map_.end())
+    return found->second;
 
   // Allocate the GL texture.
+  scoped_refptr<gles2::TexturePassthrough> gl_texture;
   GLTextureImageBackingHelper::MakeTextureAndSetParameters(
       gl_params_.target, 0 /* service_id */,
-      gl_params_.framebuffer_attachment_angle, &gl_texture_, nullptr);
+      gl_params_.framebuffer_attachment_angle, &gl_texture, nullptr);
 
   // Set the GLImage to be initially unbound from the GL texture.
-  gl_texture_->SetEstimatedSize(
+  gl_texture->SetEstimatedSize(
       viz::ResourceSizes::UncheckedSizeInBytes<size_t>(size(), format()));
-  gl_texture_->SetLevelImage(gl_params_.target, 0, image_.get());
-  gl_texture_->set_is_bind_pending(true);
+  gl_texture->SetLevelImage(gl_params_.target, 0, image_.get());
+  gl_texture->set_is_bind_pending(true);
+
+  return new IOSurfaceBackingEGLState(this, egl_display, gl_params_.target,
+                                      gl_texture);
 }
 
-void IOSurfaceImageBacking::ReleaseGLTexture(bool have_context) {
-  DCHECK_GT(gl_texture_retain_count_, 0u);
-  gl_texture_retain_count_ -= 1;
-  if (gl_texture_retain_count_ > 0)
-    return;
-
-  // If the cached promise texture is referencing the GL texture, then it needs
-  // to be deleted, too.
-  if (cached_promise_texture_) {
-    if (cached_promise_texture_->backendTexture().backend() ==
-        GrBackendApi::kOpenGL) {
-      cached_promise_texture_.reset();
-    }
+void IOSurfaceImageBacking::ReleaseGLTexture(
+    IOSurfaceBackingEGLState* egl_state,
+    bool have_context) {
+  // The cached promise texture is referencing the GL texture so it needs to be
+  // deleted, too.
+  if (egl_state->cached_promise_texture_) {
+    egl_state->cached_promise_texture_.reset();
   }
 
-  if (gl_texture_) {
+  if (egl_state->gl_texture_) {
     if (have_context) {
-      if (egl_surface_) {
+      if (egl_state->egl_surface_) {
         ScopedRestoreTexture scoped_restore(gl::g_current_gl_context,
-                                            GetGLTarget(), GetGLServiceId());
-        egl_surface_.reset();
+                                            egl_state->GetGLTarget(),
+                                            egl_state->GetGLServiceId());
+        egl_state->egl_surface_.reset();
       }
     } else {
-      gl_texture_->MarkContextLost();
+      egl_state->gl_texture_->MarkContextLost();
     }
-    gl_texture_.reset();
+    egl_state->gl_texture_.reset();
   }
 }
 
-GLenum IOSurfaceImageBacking::GetGLTarget() const {
-  return gl_params_.target;
-}
-
-GLuint IOSurfaceImageBacking::GetGLServiceId() const {
-  if (gl_texture_)
-    return gl_texture_->service_id();
-  return 0;
-}
-
 std::unique_ptr<gfx::GpuFence> IOSurfaceImageBacking::GetLastWriteGpuFence() {
   return last_write_gl_fence_ ? last_write_gl_fence_->GetGpuFence() : nullptr;
 }
@@ -427,8 +453,9 @@
 
   // Add a |service_guid| which expresses shared ownership between the
   // various GPU dumps.
-  if (auto service_id = GetGLServiceId()) {
-    auto service_guid = gl::GetGLTextureServiceGUIDForTracing(GetGLServiceId());
+  for (auto iter : egl_state_map_) {
+    auto service_id = iter.second->GetGLServiceId();
+    auto service_guid = gl::GetGLTextureServiceGUIDForTracing(service_id);
     pmd->CreateSharedGlobalAllocatorDump(service_guid);
     pmd->AddOwnershipEdge(client_guid, service_guid, kOwningEdgeImportance);
   }
@@ -457,11 +484,9 @@
 IOSurfaceImageBacking::ProduceGLTexturePassthrough(SharedImageManager* manager,
                                                    MemoryTypeTracker* tracker) {
   // The corresponding release will be done when the returned representation is
-  // destroyed, in GLTextureImageRepresentationRelease.
-  RetainGLTexture();
-  DCHECK(gl_texture_);
+  // destroyed, in GLTextureImageRepresentationBeingDestroyed.
   return std::make_unique<GLTextureIOSurfaceRepresentation>(
-      manager, this, this, tracker, gl_texture_);
+      manager, this, RetainGLTexture(), tracker);
 }
 
 std::unique_ptr<OverlayImageRepresentation>
@@ -494,32 +519,36 @@
     SharedImageManager* manager,
     MemoryTypeTracker* tracker,
     scoped_refptr<SharedContextState> context_state) {
-  GLTextureIOSurfaceRepresentationClient* gl_client = nullptr;
+  scoped_refptr<IOSurfaceBackingEGLState> egl_state;
+  sk_sp<SkPromiseImageTexture> promise_texture;
+
   if (context_state->GrContextIsGL()) {
-    // The corresponding release will be done when the returned representation
-    // is destroyed, in GLTextureImageRepresentationRelease.
-    RetainGLTexture();
-    gl_client = this;
+    egl_state = RetainGLTexture();
+    promise_texture = egl_state->cached_promise_texture_;
   }
 
-  if (!cached_promise_texture_) {
+  if (!promise_texture) {
     if (context_state->GrContextIsMetal()) {
-      cached_promise_texture_ =
+      promise_texture =
           IOSurfaceImageBackingFactory::ProduceSkiaPromiseTextureMetal(
               this, context_state, image_);
-      DCHECK(cached_promise_texture_);
+      DCHECK(promise_texture);
     } else {
       GrBackendTexture backend_texture;
-      GetGrBackendTexture(context_state->feature_info(), GetGLTarget(), size(),
-                          GetGLServiceId(), format().resource_format(),
-                          context_state->gr_context()->threadSafeProxy(),
-                          &backend_texture);
-      cached_promise_texture_ = SkPromiseImageTexture::Make(backend_texture);
+      GetGrBackendTexture(
+          context_state->feature_info(), egl_state->GetGLTarget(), size(),
+          egl_state->GetGLServiceId(), format().resource_format(),
+          context_state->gr_context()->threadSafeProxy(), &backend_texture);
+      promise_texture = SkPromiseImageTexture::Make(backend_texture);
     }
   }
+
+  if (egl_state)
+    egl_state->cached_promise_texture_ = promise_texture;
+
   return std::make_unique<SkiaIOSurfaceRepresentation>(
-      manager, this, gl_client, std::move(context_state),
-      cached_promise_texture_, tracker);
+      manager, this, egl_state, std::move(context_state), promise_texture,
+      tracker);
 }
 
 MemoryIOSurfaceRepresentation::MemoryIOSurfaceRepresentation(
@@ -559,11 +588,13 @@
         gl::GLFence::CreateFromGpuFence(*in_fence.get());
     egl_fence->ServerWait();
   }
-  if (gl_texture_)
-    gl_texture_->set_is_bind_pending(true);
+  for (auto iter : egl_state_map_) {
+    iter.second->gl_texture_->set_is_bind_pending(true);
+  }
 }
 
-bool IOSurfaceImageBacking::GLTextureImageRepresentationBeginAccess(
+bool IOSurfaceImageBacking::IOSurfaceBackingEGLStateBeginAccess(
+    IOSurfaceBackingEGLState* egl_state,
     bool readonly) {
   DCHECK(!ongoing_write_access_);
   if (readonly) {
@@ -586,7 +617,7 @@
 
   // If the GL texture is already bound (the bind is not marked as pending),
   // then early-out.
-  if (!gl_texture_->is_bind_pending())
+  if (!egl_state->gl_texture_->is_bind_pending())
     return true;
 
   if (usage() & SHARED_IMAGE_USAGE_WEBGPU &&
@@ -609,40 +640,38 @@
 
   // Create the EGL surface to bind to the GL texture, if it doesn't exist
   // already.
-  if (!egl_surface_) {
+  if (!egl_state->egl_surface_) {
     auto* gl_image_io_surface =
         static_cast<gl::GLImageIOSurface*>(image_.get());
-    gl::GLDisplayEGL* display = gl::GLDisplayEGL::GetDisplayForCurrentContext();
-    if (!display) {
-      LOG(ERROR) << "No GLDisplayEGL current.";
-      return false;
-    }
-    egl_surface_ = gl::ScopedEGLSurfaceIOSurface::Create(
-        display->GetDisplay(), GetGLTarget(), gl_image_io_surface->io_surface(),
+    egl_state->egl_surface_ = gl::ScopedEGLSurfaceIOSurface::Create(
+        egl_state->egl_display_, egl_state->GetGLTarget(),
+        gl_image_io_surface->io_surface(),
         gl_image_io_surface->io_surface_plane(), gl_image_io_surface->format());
-    if (!egl_surface_) {
+    if (!egl_state->egl_surface_) {
       LOG(ERROR) << "Failed to create ScopedEGLSurfaceIOSurface.";
       return false;
     }
   }
 
-  ScopedRestoreTexture scoped_restore(gl::g_current_gl_context, GetGLTarget(),
-                                      GetGLServiceId());
+  ScopedRestoreTexture scoped_restore(gl::g_current_gl_context,
+                                      egl_state->GetGLTarget(),
+                                      egl_state->GetGLServiceId());
 
   // Un-bind the IOSurface from the GL texture (this will be a no-op if it is
   // not yet bound).
-  egl_surface_->ReleaseTexImage();
+  egl_state->egl_surface_->ReleaseTexImage();
 
   // Bind the IOSurface to the GL texture.
-  if (!egl_surface_->BindTexImage()) {
+  if (!egl_state->egl_surface_->BindTexImage()) {
     LOG(ERROR) << "Failed to bind ScopedEGLSurfaceIOSurface to target";
     return false;
   }
-  gl_texture_->set_is_bind_pending(false);
+  egl_state->gl_texture_->set_is_bind_pending(false);
   return true;
 }
 
-void IOSurfaceImageBacking::GLTextureImageRepresentationEndAccess(
+void IOSurfaceImageBacking::IOSurfaceBackingEGLStateEndAccess(
+    IOSurfaceBackingEGLState* egl_state,
     bool readonly) {
   if (readonly) {
     DCHECK(num_ongoing_read_accesses_ > 0);
@@ -700,7 +729,7 @@
   if (needs_synchronization) {
     if (needs_sync_for_metal) {
       if (@available(macOS 10.14, *)) {
-        if (egl_surface_) {
+        if (egl_state->egl_surface_) {
           gl::GLDisplayEGL* display =
               gl::GLDisplayEGL::GetDisplayForCurrentContext();
           if (display) {
@@ -716,20 +745,35 @@
       }
     }
 
-    if (!gl_texture_->is_bind_pending()) {
-      if (egl_surface_) {
+    if (!egl_state->gl_texture_->is_bind_pending()) {
+      if (egl_state->egl_surface_) {
         ScopedRestoreTexture scoped_restore(gl::g_current_gl_context,
-                                            GetGLTarget(), GetGLServiceId());
-        egl_surface_->ReleaseTexImage();
+                                            egl_state->GetGLTarget(),
+                                            egl_state->GetGLServiceId());
+        egl_state->egl_surface_->ReleaseTexImage();
       }
-      gl_texture_->set_is_bind_pending(true);
+      egl_state->gl_texture_->set_is_bind_pending(true);
     }
   }
 }
 
-void IOSurfaceImageBacking::GLTextureImageRepresentationRelease(
+void IOSurfaceImageBacking::IOSurfaceBackingEGLStateBeingCreated(
+    IOSurfaceBackingEGLState* egl_state) {
+  auto insert_result =
+      egl_state_map_.insert(std::make_pair(egl_state->egl_display_, egl_state));
+  CHECK(insert_result.second);
+}
+
+void IOSurfaceImageBacking::IOSurfaceBackingEGLStateBeingDestroyed(
+    IOSurfaceBackingEGLState* egl_state,
     bool has_context) {
-  ReleaseGLTexture(has_context);
+  ReleaseGLTexture(egl_state, has_context);
+
+  // Remove `egl_state` from `egl_state_map_`.
+  auto found = egl_state_map_.find(egl_state->egl_display_);
+  CHECK(found != egl_state_map_.end());
+  CHECK(found->second == egl_state);
+  egl_state_map_.erase(found);
 }
 
 void IOSurfaceImageBacking::InitializePixels(GLenum format,
diff --git a/gpu/ipc/service/gles2_command_buffer_stub.cc b/gpu/ipc/service/gles2_command_buffer_stub.cc
index 33e48e90..ecbb7447 100644
--- a/gpu/ipc/service/gles2_command_buffer_stub.cc
+++ b/gpu/ipc/service/gles2_command_buffer_stub.cc
@@ -150,8 +150,9 @@
     // We hit this code path when creating the onscreen render context
     // used for compositing on low-end Android devices.
     //
-    // TODO(klausw): explicitly copy rgba sizes? Currently the formats
-    // supported are only RGB565 and default (RGBA8888).
+    // Currently the only formats supported are RGB565 and default (RGBA8888).
+    // See also comments in ui/gl/gl_surface_format.h in case there's
+    // a use case requiring more fine-grained control.
     surface_format.SetRGB565();
     DVLOG(1) << __FUNCTION__ << ": Choosing RGB565 mode.";
   }
@@ -222,9 +223,8 @@
       }
       // Currently, we can't separately control alpha channel for surfaces,
       // it's generally enabled by default except for RGB565 and (on desktop)
-      // smaller-than-32bit formats.
-      //
-      // TODO(klausw): use init_params.attribs.alpha_size here if possible.
+      // smaller-than-32bit formats. If there's a future use case that
+      // requires this, it should use init_params.attribs.alpha_size here.
     }
     if (!surface_format.IsCompatible(default_surface->GetFormat())) {
       DVLOG(1) << __FUNCTION__ << ": Hit the OwnOffscreenSurface path";
diff --git a/infra/config/generated/builders/ci/linux-blink-v8-sandbox-future-rel/properties.json b/infra/config/generated/builders/ci/linux-blink-v8-sandbox-future-rel/properties.json
deleted file mode 100644
index 5312bfe..0000000
--- a/infra/config/generated/builders/ci/linux-blink-v8-sandbox-future-rel/properties.json
+++ /dev/null
@@ -1,59 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "ci",
-              "builder": "linux-blink-v8-sandbox-future-rel",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "builder_group": "chromium.fyi",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "mb"
-                ],
-                "build_config": "Release",
-                "config": "chromium",
-                "target_bits": 64
-              },
-              "legacy_gclient_config": {
-                "config": "chromium"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "ci",
-          "builder": "linux-blink-v8-sandbox-future-rel",
-          "project": "chromium"
-        }
-      ],
-      "mirroring_builder_group_and_names": [
-        {
-          "builder": "linux-blink-v8-sandbox-future-rel",
-          "group": "tryserver.chromium.linux"
-        }
-      ]
-    }
-  },
-  "$build/reclient": {
-    "instance": "rbe-chromium-trusted",
-    "jobs": 250,
-    "metrics_project": "chromium-reclient-metrics"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium.fyi",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git a/infra/config/generated/builders/try/linux-bfcache-rel/properties.json b/infra/config/generated/builders/try/linux-bfcache-rel/properties.json
index 3ea9d07..0ec271d 100644
--- a/infra/config/generated/builders/try/linux-bfcache-rel/properties.json
+++ b/infra/config/generated/builders/try/linux-bfcache-rel/properties.json
@@ -39,6 +39,7 @@
   },
   "$build/reclient": {
     "instance": "rbe-chromium-untrusted",
+    "jobs": 150,
     "metrics_project": "chromium-reclient-metrics"
   },
   "$recipe_engine/resultdb/test_presentation": {
diff --git a/infra/config/generated/builders/try/linux-blink-v8-sandbox-future-rel/properties.json b/infra/config/generated/builders/try/linux-blink-v8-sandbox-future-rel/properties.json
deleted file mode 100644
index 1f1a330..0000000
--- a/infra/config/generated/builders/try/linux-blink-v8-sandbox-future-rel/properties.json
+++ /dev/null
@@ -1,57 +0,0 @@
-{
-  "$build/chromium_tests_builder_config": {
-    "builder_config": {
-      "builder_db": {
-        "entries": [
-          {
-            "builder_id": {
-              "bucket": "ci",
-              "builder": "linux-blink-v8-sandbox-future-rel",
-              "project": "chromium"
-            },
-            "builder_spec": {
-              "builder_group": "chromium.fyi",
-              "execution_mode": "COMPILE_AND_TEST",
-              "legacy_chromium_config": {
-                "apply_configs": [
-                  "mb"
-                ],
-                "build_config": "Release",
-                "config": "chromium",
-                "target_bits": 64
-              },
-              "legacy_gclient_config": {
-                "config": "chromium"
-              }
-            }
-          }
-        ]
-      },
-      "builder_ids": [
-        {
-          "bucket": "ci",
-          "builder": "linux-blink-v8-sandbox-future-rel",
-          "project": "chromium"
-        }
-      ]
-    }
-  },
-  "$build/goma": {
-    "enable_ats": true,
-    "rpc_extra_params": "?prod",
-    "server_host": "goma.chromium.org"
-  },
-  "$build/reclient": {
-    "instance": "rbe-chromium-untrusted",
-    "metrics_project": "chromium-reclient-metrics"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "tryserver.chromium.linux",
-  "recipe": "chromium_trybot"
-}
\ No newline at end of file
diff --git a/infra/config/generated/cq-builders.md b/infra/config/generated/cq-builders.md
index 447f254..8ccde38 100644
--- a/infra/config/generated/cq-builders.md
+++ b/infra/config/generated/cq-builders.md
@@ -483,7 +483,7 @@
   * Experiment percentage: 60.0
 
 * [android-arm64-rel](https://ci.chromium.org/p/chromium/builders/try/android-arm64-rel) ([definition](https://cs.chromium.org/search?q=+file:/try/.*\.star$+""android-arm64-rel"")) ([matching builders](https://cs.chromium.org/search?q=+file:trybots.py+""android-arm64-rel""))
-  * Experiment percentage: 20.0
+  * Experiment percentage: 50.0
 
 * [android-pie-arm64-coverage-experimental-rel](https://ci.chromium.org/p/chromium/builders/try/android-pie-arm64-coverage-experimental-rel) ([definition](https://cs.chromium.org/search?q=+file:/try/.*\.star$+""android-pie-arm64-coverage-experimental-rel"")) ([matching builders](https://cs.chromium.org/search?q=+file:trybots.py+""android-pie-arm64-coverage-experimental-rel""))
   * Experiment percentage: 3.0
diff --git a/infra/config/generated/luci/commit-queue.cfg b/infra/config/generated/luci/commit-queue.cfg
index abf09f13..3b792a5 100644
--- a/infra/config/generated/luci/commit-queue.cfg
+++ b/infra/config/generated/luci/commit-queue.cfg
@@ -362,7 +362,7 @@
       }
       builders {
         name: "chromium/try/android-arm64-rel"
-        experiment_percentage: 20
+        experiment_percentage: 50
         location_filters {
           gerrit_host_regexp: ".*"
           gerrit_project_regexp: ".*"
@@ -2333,10 +2333,6 @@
         }
       }
       builders {
-        name: "chromium/try/linux-blink-v8-sandbox-future-rel"
-        includable_only: true
-      }
-      builders {
         name: "chromium/try/linux-blink-web-tests-force-accessibility-rel"
         location_filters {
           gerrit_host_regexp: ".*"
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index 55f6bf6..b63736d 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -38096,95 +38096,6 @@
       }
     }
     builders {
-      name: "linux-blink-v8-sandbox-future-rel"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builderless:1"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "free_space:standard"
-      dimensions: "os:Ubuntu-18.04"
-      dimensions: "pool:luci.chromium.ci"
-      dimensions: "ssd:0"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/ci/linux-blink-v8-sandbox-future-rel/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium.fyi",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      priority: 35
-      execution_timeout_secs: 36000
-      build_numbers: YES
-      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "chromium_swarming.expose_merge_script_failures"
-        value: 100
-      }
-      experiments {
-        key: "luci.buildbucket.omit_python2"
-        value: 100
-      }
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-        bq_exports {
-          project: "chrome-luci-data"
-          dataset: "chromium"
-          table: "ci_test_results"
-          test_results {}
-        }
-        bq_exports {
-          project: "chrome-luci-data"
-          dataset: "chromium"
-          table: "gpu_ci_test_results"
-          test_results {
-            predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
-            }
-          }
-        }
-        bq_exports {
-          project: "chrome-luci-data"
-          dataset: "chromium"
-          table: "blink_web_tests_ci_test_results"
-          test_results {
-            predicate {
-              test_id_regexp: "(ninja://[^/]*blink_web_tests/.+)|(ninja://[^/]*blink_wpt_tests/.+)"
-            }
-          }
-        }
-        history_options {
-          use_invocation_timestamp: true
-        }
-      }
-    }
-    builders {
       name: "linux-blink-web-tests-force-accessibility-rel"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
@@ -56645,6 +56556,10 @@
         value: 5
       }
       experiments {
+        key: "chromium_rts.inverted_rts"
+        value: 100
+      }
+      experiments {
         key: "chromium_swarming.expose_merge_script_failures"
         value: 100
       }
@@ -59167,7 +59082,7 @@
       }
       experiments {
         key: "chromium_rts.inverted_rts"
-        value: 0
+        value: 100
       }
       experiments {
         key: "chromium_swarming.expose_merge_script_failures"
@@ -60281,6 +60196,10 @@
         value: 5
       }
       experiments {
+        key: "chromium_rts.inverted_rts"
+        value: 100
+      }
+      experiments {
         key: "chromium_swarming.expose_merge_script_failures"
         value: 100
       }
@@ -76477,116 +76396,6 @@
       }
     }
     builders {
-      name: "linux-blink-v8-sandbox-future-rel"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builderless:1"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-18.04"
-      dimensions: "pool:luci.chromium.try"
-      dimensions: "ssd:0"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/try/linux-blink-v8-sandbox-future-rel/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "tryserver.chromium.linux",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium_trybot"'
-        '}'
-      execution_timeout_secs: 14400
-      expiration_secs: 7200
-      grace_period {
-        seconds: 120
-      }
-      caches {
-        name: "win_toolchain"
-        path: "win_toolchain"
-      }
-      build_numbers: YES
-      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
-      task_template_canary_percentage {
-        value: 5
-      }
-      experiments {
-        key: "chromium_swarming.expose_merge_script_failures"
-        value: 100
-      }
-      experiments {
-        key: "enable_weetbix_queries"
-        value: 100
-      }
-      experiments {
-        key: "luci.buildbucket.omit_python2"
-        value: 100
-      }
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      experiments {
-        key: "weetbix.enable_weetbix_exonerations"
-        value: 100
-      }
-      experiments {
-        key: "weetbix.retry_weak_exonerations"
-        value: 100
-      }
-      resultdb {
-        enable: true
-        bq_exports {
-          project: "chrome-luci-data"
-          dataset: "chromium"
-          table: "try_test_results"
-          test_results {}
-        }
-        bq_exports {
-          project: "chrome-luci-data"
-          dataset: "chromium"
-          table: "gpu_try_test_results"
-          test_results {
-            predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
-            }
-          }
-        }
-        bq_exports {
-          project: "chrome-luci-data"
-          dataset: "chromium"
-          table: "blink_web_tests_try_test_results"
-          test_results {
-            predicate {
-              test_id_regexp: "(ninja://[^/]*blink_web_tests/.+)|(ninja://[^/]*blink_wpt_tests/.+)"
-            }
-          }
-        }
-        history_options {
-          use_invocation_timestamp: true
-        }
-      }
-    }
-    builders {
       name: "linux-blink-web-tests-force-accessibility-rel"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
@@ -79000,7 +78809,7 @@
       dimensions: "cpu:x86-64"
       dimensions: "os:Ubuntu-18.04"
       dimensions: "pool:luci.chromium.try"
-      dimensions: "ssd:0"
+      dimensions: "ssd:1"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
         cipd_version: "latest"
@@ -80580,7 +80389,7 @@
       }
       experiments {
         key: "chromium_rts.inverted_rts"
-        value: 0
+        value: 100
       }
       experiments {
         key: "chromium_swarming.expose_merge_script_failures"
@@ -81831,6 +81640,10 @@
         value: 5
       }
       experiments {
+        key: "chromium_rts.inverted_rts"
+        value: 100
+      }
+      experiments {
         key: "chromium_swarming.expose_merge_script_failures"
         value: 100
       }
@@ -83162,6 +82975,10 @@
         value: 5
       }
       experiments {
+        key: "chromium_rts.inverted_rts"
+        value: 100
+      }
+      experiments {
         key: "chromium_swarming.expose_merge_script_failures"
         value: 100
       }
@@ -84717,6 +84534,10 @@
         value: 5
       }
       experiments {
+        key: "chromium_rts.inverted_rts"
+        value: 100
+      }
+      experiments {
         key: "chromium_swarming.expose_merge_script_failures"
         value: 100
       }
@@ -86769,7 +86590,7 @@
       }
       experiments {
         key: "chromium_rts.inverted_rts"
-        value: 0
+        value: 100
       }
       experiments {
         key: "chromium_swarming.expose_merge_script_failures"
@@ -92016,7 +91837,7 @@
       }
       experiments {
         key: "chromium_rts.inverted_rts"
-        value: 0
+        value: 100
       }
       experiments {
         key: "chromium_swarming.expose_merge_script_failures"
@@ -93745,7 +93566,7 @@
       }
       experiments {
         key: "chromium_rts.inverted_rts"
-        value: 0
+        value: 100
       }
       experiments {
         key: "chromium_swarming.expose_merge_script_failures"
diff --git a/infra/config/generated/luci/luci-milo.cfg b/infra/config/generated/luci/luci-milo.cfg
index b65ca260..753ba05 100644
--- a/infra/config/generated/luci/luci-milo.cfg
+++ b/infra/config/generated/luci/luci-milo.cfg
@@ -8623,11 +8623,6 @@
     short_name: "BIr"
   }
   builders {
-    name: "buildbucket/luci.chromium.ci/linux-blink-v8-sandbox-future-rel"
-    category: "linux|blink"
-    short_name: "SB"
-  }
-  builders {
     name: "buildbucket/luci.chromium.ci/linux-blink-heap-verification"
     category: "linux|blink"
     short_name: "VF"
@@ -10511,6 +10506,11 @@
     short_name: "rel"
   }
   builders {
+    name: "buildbucket/luci.chromium.ci/Win10 FYI x64 Exp Release (Intel HD 630)"
+    category: "Windows|10|x64|Intel"
+    short_name: "exp"
+  }
+  builders {
     name: "buildbucket/luci.chromium.ci/Win10 FYI x64 Release (NVIDIA)"
     category: "Windows|10|x64|Nvidia"
     short_name: "rel"
@@ -16846,9 +16846,6 @@
     name: "buildbucket/luci.chromium.try/linux-blink-rel"
   }
   builders {
-    name: "buildbucket/luci.chromium.try/linux-blink-v8-sandbox-future-rel"
-  }
-  builders {
     name: "buildbucket/luci.chromium.try/linux-blink-web-tests-force-accessibility-rel"
   }
   builders {
@@ -17964,9 +17961,6 @@
     name: "buildbucket/luci.chromium.try/linux-blink-heap-verification-try"
   }
   builders {
-    name: "buildbucket/luci.chromium.try/linux-blink-v8-sandbox-future-rel"
-  }
-  builders {
     name: "buildbucket/luci.chromium.try/linux-dcheck-off-rel"
   }
   builders {
diff --git a/infra/config/generated/luci/luci-notify.cfg b/infra/config/generated/luci/luci-notify.cfg
index b3f4ab3..abf7424 100644
--- a/infra/config/generated/luci/luci-notify.cfg
+++ b/infra/config/generated/luci/luci-notify.cfg
@@ -3217,19 +3217,6 @@
   notifications {
     on_new_status: FAILURE
     email {
-      recipients: "saelo+fyi-bots@chromium.org"
-    }
-  }
-  builders {
-    bucket: "ci"
-    name: "linux-blink-v8-sandbox-future-rel"
-    repository: "https://chromium.googlesource.com/chromium/src"
-  }
-}
-notifiers {
-  notifications {
-    on_new_status: FAILURE
-    email {
       recipients: "chrome-a11y-alerts@google.com"
     }
   }
diff --git a/infra/config/generated/luci/luci-scheduler.cfg b/infra/config/generated/luci/luci-scheduler.cfg
index 47c21e2..85dfc81b 100644
--- a/infra/config/generated/luci/luci-scheduler.cfg
+++ b/infra/config/generated/luci/luci-scheduler.cfg
@@ -4760,15 +4760,6 @@
   }
 }
 job {
-  id: "linux-blink-v8-sandbox-future-rel"
-  realm: "ci"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "ci"
-    builder: "linux-blink-v8-sandbox-future-rel"
-  }
-}
-job {
   id: "linux-blink-web-tests-force-accessibility-rel"
   realm: "ci"
   buildbucket {
@@ -6176,7 +6167,6 @@
   triggers: "linux-bfcache-rel"
   triggers: "linux-blink-animation-use-time-delta"
   triggers: "linux-blink-heap-verification"
-  triggers: "linux-blink-v8-sandbox-future-rel"
   triggers: "linux-blink-web-tests-force-accessibility-rel"
   triggers: "linux-blink-wpt-reset-rel"
   triggers: "linux-cfm-rel"
diff --git a/infra/config/notifiers.star b/infra/config/notifiers.star
index d27fa772..c8e4bc2 100644
--- a/infra/config/notifiers.star
+++ b/infra/config/notifiers.star
@@ -279,14 +279,6 @@
 )
 
 luci.notifier(
-    name = "v8-sandbox-fyi-bots",
-    notify_emails = [
-        "saelo+fyi-bots@chromium.org",
-    ],
-    on_new_status = ["FAILURE"],
-)
-
-luci.notifier(
     name = "cr-accessibility",
     notify_emails = [
         "chrome-a11y-alerts@google.com",
diff --git a/infra/config/subprojects/chromium/ci/chromium.fyi.star b/infra/config/subprojects/chromium/ci/chromium.fyi.star
index be0cdccd..cb72cbaa 100644
--- a/infra/config/subprojects/chromium/ci/chromium.fyi.star
+++ b/infra/config/subprojects/chromium/ci/chromium.fyi.star
@@ -415,27 +415,6 @@
 )
 
 ci.builder(
-    name = "linux-blink-v8-sandbox-future-rel",
-    console_view_entry = consoles.console_view_entry(
-        category = "linux|blink",
-        short_name = "SB",
-    ),
-    notifies = ["v8-sandbox-fyi-bots"],
-    os = os.LINUX_DEFAULT,
-    builder_spec = builder_config.builder_spec(
-        chromium_config = builder_config.chromium_config(
-            config = "chromium",
-            apply_configs = ["mb"],
-            build_config = builder_config.build_config.RELEASE,
-            target_bits = 64,
-        ),
-        gclient_config = builder_config.gclient_config(
-            config = "chromium",
-        ),
-    ),
-)
-
-ci.builder(
     name = "linux-example-builder",
     console_view_entry = consoles.console_view_entry(
         category = "linux",
diff --git a/infra/config/subprojects/chromium/ci/chromium.gpu.fyi.star b/infra/config/subprojects/chromium/ci/chromium.gpu.fyi.star
index dbe2852a..0513e2d 100644
--- a/infra/config/subprojects/chromium/ci/chromium.gpu.fyi.star
+++ b/infra/config/subprojects/chromium/ci/chromium.gpu.fyi.star
@@ -1323,10 +1323,10 @@
         run_tests_serially = True,
     ),
     # Uncomment this entry when this experimental tester is actually in use.
-    # console_view_entry = consoles.console_view_entry(
-    #     category = "Windows|10|x64|Intel",
-    #     short_name = "exp",
-    # ),
+    console_view_entry = consoles.console_view_entry(
+        category = "Windows|10|x64|Intel",
+        short_name = "exp",
+    ),
     list_view = "chromium.gpu.experimental",
     triggered_by = ["GPU FYI Win x64 Builder"],
 )
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.android.star b/infra/config/subprojects/chromium/try/tryserver.chromium.android.star
index 3493b8728..8e671857 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.android.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.android.star
@@ -109,8 +109,11 @@
     # branch_selector = branches.STANDARD_MILESTONE,
     main_list_view = "try",
     tryjob = try_.job(
-        experiment_percentage = 20,
+        experiment_percentage = 50,
     ),
+    experiments = {
+        "chromium_rts.inverted_rts": 100,
+    },
     # TODO(crbug.com/1372179): Use orchestrator pool once overloaded test pools
     # are addressed
     # use_orchestrator_pool = True,
@@ -318,8 +321,7 @@
     main_list_view = "try",
     tryjob = try_.job(),
     experiments = {
-        # TODO (crbug.com/1382577): Reenable after cq active is reliable
-        "chromium_rts.inverted_rts": 0,
+        "chromium_rts.inverted_rts": 100,
     },
     # TODO(crbug.com/1372179): Use orchestrator pool once overloaded test pools
     # are addressed
@@ -428,6 +430,9 @@
             condition = builder_config.rts_condition.QUICK_RUN_ONLY,
         ),
     ),
+    experiments = {
+        "chromium_rts.inverted_rts": 100,
+    },
     compilator = "android-pie-arm64-rel-compilator",
     check_for_flakiness = True,
     branch_selector = branches.STANDARD_MILESTONE,
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star b/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star
index 4e0739e..780f1f5 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star
@@ -94,6 +94,7 @@
         "ci/linux-bfcache-rel",
     ],
     goma_backend = None,
+    reclient_jobs = reclient.jobs.LOW_JOBS_FOR_CQ,
 )
 
 try_.builder(
@@ -103,11 +104,6 @@
 )
 
 try_.builder(
-    name = "linux-blink-v8-sandbox-future-rel",
-    mirrors = ["ci/linux-blink-v8-sandbox-future-rel"],
-)
-
-try_.builder(
     name = "linux-dcheck-off-rel",
     mirrors = builder_config.copy_from("linux-rel"),
 )
@@ -219,8 +215,7 @@
     coverage_test_types = ["unit", "overall"],
     tryjob = try_.job(),
     experiments = {
-        # TODO (crbug.com/1382577): Reenable after cq active is reliable
-        "chromium_rts.inverted_rts": 0,
+        "chromium_rts.inverted_rts": 100,
     },
     # TODO(crbug.com/1372179): Use orchestrator pool once overloaded test pools
     # are addressed
@@ -268,6 +263,9 @@
     ),
     main_list_view = "try",
     tryjob = try_.job(),
+    experiments = {
+        "chromium_rts.inverted_rts": 100,
+    },
 )
 
 try_.compilator_builder(
@@ -372,6 +370,9 @@
             condition = builder_config.rts_condition.QUICK_RUN_ONLY,
         ),
     ),
+    experiments = {
+        "chromium_rts.inverted_rts": 100,
+    },
     # TODO (crbug.com/1372179): Use orchestrator pool once overloaded test pools
     # are addressed
     # use_orchestrator_pool = True,
@@ -558,6 +559,9 @@
     branch_selector = branches.STANDARD_MILESTONE,
     main_list_view = "try",
     tryjob = try_.job(),
+    experiments = {
+        "chromium_rts.inverted_rts": 100,
+    },
     # TODO (crbug.com/1372179): Use orchestrator pool once overloaded test pools
     # are addressed
     # use_orchestrator_pool = True,
@@ -604,6 +608,7 @@
     ],
     goma_jobs = goma.jobs.J150,
     cores = 16,
+    ssd = True,
 )
 
 try_.builder(
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star b/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star
index 155a408..bf21cc32 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star
@@ -5,7 +5,7 @@
 
 load("//lib/branches.star", "branches")
 load("//lib/builder_config.star", "builder_config")
-load("//lib/builders.star", "cpu", "goma", "os", "xcode")
+load("//lib/builders.star", "cpu", "goma", "os", "reclient", "xcode")
 load("//lib/try.star", "try_")
 load("//lib/consoles.star", "consoles")
 
@@ -114,8 +114,7 @@
     coverage_test_types = ["overall", "unit"],
     tryjob = try_.job(),
     experiments = {
-        # TODO (crbug.com/1382577): Reenable after cq active is reliable
-        "chromium_rts.inverted_rts": 0,
+        "chromium_rts.inverted_rts": 100,
     },
     # TODO (crbug.com/1372179): Use orchestrator pool once overloaded test pools
     # are addressed
@@ -148,6 +147,7 @@
     branch_selector = branches.DESKTOP_EXTENDED_STABLE_MILESTONE,
     main_list_view = "try",
     os = os.MAC_DEFAULT,
+    reclient_jobs = reclient.jobs.HIGH_JOBS_FOR_CQ,
 )
 
 try_.builder(
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.win.star b/infra/config/subprojects/chromium/try/tryserver.chromium.win.star
index 76fc520..ebd1a6a 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.win.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.win.star
@@ -92,8 +92,7 @@
     main_list_view = "try",
     tryjob = try_.job(),
     experiments = {
-        # TODO (crbug.com/1382577): Reenable after cq active is reliable
-        "chromium_rts.inverted_rts": 0,
+        "chromium_rts.inverted_rts": 100,
     },
     # TODO (crbug.com/1372179): Use orchestrator pool once overloaded test pools
     # are addressed
@@ -243,8 +242,7 @@
     coverage_test_types = ["unit", "overall"],
     main_list_view = "try",
     experiments = {
-        # TODO (crbug.com/1382577): Reenable after cq active is reliable
-        "chromium_rts.inverted_rts": 0,
+        "chromium_rts.inverted_rts": 100,
     },
     description_html = "<h1>NOTE: This bot is now deprecated. Please use 'win-rel' instead.</h1>",
     # TODO (crbug.com/1372179): Use orchestrator pool once overloaded test pools
diff --git a/ios/chrome/app/application_delegate/mock_metrickit_metric_payload.mm b/ios/chrome/app/application_delegate/mock_metrickit_metric_payload.mm
index dd0c990..208cf47 100644
--- a/ios/chrome/app/application_delegate/mock_metrickit_metric_payload.mm
+++ b/ios/chrome/app/application_delegate/mock_metrickit_metric_payload.mm
@@ -100,6 +100,7 @@
   // returns needs to change each time it's called.
   OCMStub([histogram bucketEnumerator]).andDo(^(NSInvocation* invocation) {
     NSEnumerator* enumerator = buckets.objectEnumerator;
+    [invocation retainArguments];
     [invocation setReturnValue:&enumerator];
   });
   return histogram;
diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm
index a95975e..a38dd67 100644
--- a/ios/chrome/browser/flags/about_flags.mm
+++ b/ios/chrome/browser/flags/about_flags.mm
@@ -243,24 +243,6 @@
          std::size(kDefaultBrowserFullscreenPromoExperimentRemindMeLater),
          nullptr}};
 
-const FeatureEntry::FeatureParam kDiscoverFeedInNtpEnableNativeUI[] = {
-    {kDiscoverFeedIsNativeUIEnabled, "true"}};
-const FeatureEntry::FeatureVariation kDiscoverFeedInNtpVariations[] = {
-    {"Native UI", kDiscoverFeedInNtpEnableNativeUI,
-     std::size(kDiscoverFeedInNtpEnableNativeUI), nullptr}};
-
-const FeatureEntry::FeatureParam kDiscoverFeedSRSReconstructedTemplates[] = {
-    {kDiscoverFeedSRSReconstructedTemplatesEnabled, "true"}};
-const FeatureEntry::FeatureParam kDiscoverFeedSRSPreloadTemplates[] = {
-    {kDiscoverFeedSRSPreloadTemplatesEnabled, "true"}};
-const FeatureEntry::FeatureVariation
-    kEnableDiscoverFeedStaticResourceServingVariations[] = {
-        {"Reconstruct Templates", kDiscoverFeedSRSReconstructedTemplates,
-         std::size(kDiscoverFeedSRSReconstructedTemplates), nullptr},
-        {"Preload Templates", kDiscoverFeedSRSPreloadTemplates,
-         std::size(kDiscoverFeedSRSPreloadTemplates), nullptr},
-};
-
 const FeatureEntry::FeatureParam kDiscoverFeedTopSyncPromoFullWithTitle[] = {
     {kDiscoverFeedTopSyncPromoStyleFullWithTitle, "true"},
     {kDiscoverFeedTopSyncPromoStyleCompact, "false"}};
@@ -654,10 +636,6 @@
      flag_descriptions::kAutofillCreditCardUploadName,
      flag_descriptions::kAutofillCreditCardUploadDescription, flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(autofill::features::kAutofillUpstream)},
-    {"enable-discover-feed-preview",
-     flag_descriptions::kEnableDiscoverFeedPreviewName,
-     flag_descriptions::kEnableDiscoverFeedPreviewDescription, flags_ui::kOsIos,
-     FEATURE_VALUE_TYPE(kEnableDiscoverFeedPreview)},
     {"use-sync-sandbox", flag_descriptions::kSyncSandboxName,
      flag_descriptions::kSyncSandboxDescription, flags_ui::kOsIos,
      SINGLE_VALUE_TYPE_AND_VALUE(
@@ -751,11 +729,6 @@
      flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(
          autofill::features::kAutofillSaveCardDismissOnNavigation)},
-    {"new-content-suggestions-feed", flag_descriptions::kDiscoverFeedInNtpName,
-     flag_descriptions::kDiscoverFeedInNtpDescription, flags_ui::kOsIos,
-     FEATURE_WITH_PARAMS_VALUE_TYPE(kDiscoverFeedInNtp,
-                                    kDiscoverFeedInNtpVariations,
-                                    "IOSDiscoverFeed")},
     {"expanded-tab-strip", flag_descriptions::kExpandedTabStripName,
      flag_descriptions::kExpandedTabStripDescription, flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(kExpandedTabStrip)},
@@ -910,14 +883,6 @@
      flag_descriptions::kUseLoadSimulatedRequestForOfflinePageDescription,
      flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(web::features::kUseLoadSimulatedRequestForOfflinePage)},
-    {"enable-discover-feed-static-resource-serving",
-     flag_descriptions::kEnableDiscoverFeedStaticResourceServingName,
-     flag_descriptions::kEnableDiscoverFeedStaticResourceServingDescription,
-     flags_ui::kOsIos,
-     FEATURE_WITH_PARAMS_VALUE_TYPE(
-         kEnableDiscoverFeedStaticResourceServing,
-         kEnableDiscoverFeedStaticResourceServingVariations,
-         "IOSDiscoverFeedStaticResourceServing")},
     {"enable-disco-feed-endpoint",
      flag_descriptions::kEnableDiscoverFeedDiscoFeedEndpointName,
      flag_descriptions::kEnableDiscoverFeedDiscoFeedEndpointDescription,
@@ -954,7 +919,7 @@
     {"ntp-view-hierarchy-repair",
      flag_descriptions::kNTPViewHierarchyRepairName,
      flag_descriptions::kNTPViewHierarchyRepairDescription, flags_ui::kOsIos,
-     FEATURE_VALUE_TYPE(kNTPViewHierarchyRepair)},
+     FEATURE_VALUE_TYPE(kEnableNTPViewHierarchyRepair)},
     {"synthesized-restore-session",
      flag_descriptions::kSynthesizedRestoreSessionName,
      flag_descriptions::kSynthesizedRestoreSessionDescription, flags_ui::kOsIos,
@@ -1139,7 +1104,7 @@
     {"enable-discover-feed-ghost-cards",
      flag_descriptions::kEnableDiscoverFeedGhostCardsName,
      flag_descriptions::kEnableDiscoverFeedGhostCardsDescription,
-     flags_ui::kOsIos, FEATURE_VALUE_TYPE(kDiscoverFeedGhostCardsEnabled)},
+     flags_ui::kOsIos, FEATURE_VALUE_TYPE(kEnableDiscoverFeedGhostCards)},
     {"dm-token-deletion", flag_descriptions::kDmTokenDeletionName,
      flag_descriptions::kDmTokenDeletionDescription, flags_ui::kOsIos,
      FEATURE_WITH_PARAMS_VALUE_TYPE(policy::features::kDmTokenDeletion,
@@ -1317,9 +1282,9 @@
      flag_descriptions::kFollowingFeedDefaultSortTypeName,
      flag_descriptions::kFollowingFeedDefaultSortTypeDescription,
      flags_ui::kOsIos,
-     FEATURE_WITH_PARAMS_VALUE_TYPE(kFollowingFeedDefaultSortType,
+     FEATURE_WITH_PARAMS_VALUE_TYPE(kEnableFollowingFeedDefaultSortType,
                                     kFollowingFeedDefaultSortTypeVariations,
-                                    "FollowingFeedDefaultSortType")},
+                                    "EnableFollowingFeedDefaultSortType")},
     {"omnibox-carousel-dynamic-spacing",
      flag_descriptions::kOmniboxCarouselDynamicSpacingName,
      flag_descriptions::kOmniboxCarouselDynamicSpacingDescription,
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
index 3f5db75..1de0af1 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
@@ -202,11 +202,6 @@
     "A crash report will be uploaded if the main thread is frozen more than "
     "the time specified by this flag.";
 
-const char kDiscoverFeedInNtpName[] = "Enable new content Suggestion Feed";
-const char kDiscoverFeedInNtpDescription[] =
-    "When enabled, replaces articles feed with new content Suggestion Feed in "
-    "the NTP.";
-
 const char kEnableDiscoverFeedTopSyncPromoName[] =
     "Enable the sync promo on top of the feed.";
 const char kEnableDiscoverFeedTopSyncPromoDescription[] =
@@ -252,21 +247,11 @@
 const char kEnableDiscoverFeedDiscoFeedEndpointDescription[] =
     "Enable using the discofeed endpoint for the discover feed.";
 
-const char kEnableDiscoverFeedPreviewName[] = "Enable discover feed preview";
-const char kEnableDiscoverFeedPreviewDescription[] =
-    "Enable showing a live preview for discover feed long-press menu.";
-
 const char kEnableDiscoverFeedGhostCardsName[] =
     "Enable discover feed ghost cards";
 const char kEnableDiscoverFeedGhostCardsDescription[] =
     "Show ghost cards when refreshing the discover feed.";
 
-const char kEnableDiscoverFeedStaticResourceServingName[] =
-    "Enable discover feed static resource serving";
-const char kEnableDiscoverFeedStaticResourceServingDescription[] =
-    "When enabled the discover feed will optimize the request of resources "
-    "coming from the server.";
-
 const char kEnableFREDefaultBrowserPromoScreenName[] =
     "Enable FRE default browser screen";
 const char kEnableFREDefaultBrowserPromoScreenDescription[] =
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
index 1142ce7..fe2b523 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
@@ -173,11 +173,6 @@
 extern const char kDetectMainThreadFreezeName[];
 extern const char kDetectMainThreadFreezeDescription[];
 
-// Title and description for the flag to replace the Zine feed with the
-// Discover feed in the Bling NTP.
-extern const char kDiscoverFeedInNtpName[];
-extern const char kDiscoverFeedInNtpDescription[];
-
 // Title and description for the flag to enable checking feed visibility on
 // attention log start.
 extern const char kEnableCheckVisibilityOnAttentionLogStartName[];
@@ -237,21 +232,11 @@
 extern const char kEnableDiscoverFeedDiscoFeedEndpointName[];
 extern const char kEnableDiscoverFeedDiscoFeedEndpointDescription[];
 
-// Title and description for the flag to enable the discover feed live preview
-// in long-press feed context menu.
-extern const char kEnableDiscoverFeedPreviewName[];
-extern const char kEnableDiscoverFeedPreviewDescription[];
-
 // Title and description for the flag to show ghost cards when refreshing the
 // discover feed.
 extern const char kEnableDiscoverFeedGhostCardsName[];
 extern const char kEnableDiscoverFeedGhostCardsDescription[];
 
-// Title and description for the flag to enable the discover feed static
-// resource serving.
-extern const char kEnableDiscoverFeedStaticResourceServingName[];
-extern const char kEnableDiscoverFeedStaticResourceServingDescription[];
-
 // Title and description for the flag to remove the Feed from the NTP.
 extern const char kEnableFeedAblationName[];
 extern const char kEnableFeedAblationDescription[];
diff --git a/ios/chrome/browser/lens/BUILD.gn b/ios/chrome/browser/lens/BUILD.gn
new file mode 100644
index 0000000..eb3b1b6
--- /dev/null
+++ b/ios/chrome/browser/lens/BUILD.gn
@@ -0,0 +1,27 @@
+# Copyright 2022 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("lens") {
+  sources = [
+    "lens_browser_agent.h",
+    "lens_browser_agent.mm",
+  ]
+  deps = [
+    "//components/search_engines",
+    "//ios/chrome/browser/browser_state:browser_state",
+    "//ios/chrome/browser/main:public",
+    "//ios/chrome/browser/search_engines:template_url_service_factory",
+    "//ios/chrome/browser/ui:feature_flags",
+    "//ios/chrome/browser/ui/commands",
+    "//ios/chrome/browser/ui/lens:lens_entrypoint",
+    "//ios/chrome/browser/web_state_list",
+    "//ios/public/provider/chrome/browser/lens:lens_api",
+    "//ios/web/public",
+  ]
+  public_deps = [
+    "//base",
+    "//third_party/abseil-cpp:absl",
+  ]
+  configs += [ "//build/config/compiler:enable_arc" ]
+}
diff --git a/ios/chrome/browser/lens/DEPS b/ios/chrome/browser/lens/DEPS
new file mode 100644
index 0000000..3f8853f7
--- /dev/null
+++ b/ios/chrome/browser/lens/DEPS
@@ -0,0 +1,6 @@
+specific_include_rules = {
+  "^lens_browser_agent.mm": [
+    "+ios/chrome/browser/ui/lens/lens_entrypoint.h",
+    "+ios/chrome/browser/ui/ui_feature_flags.h",
+  ],
+}
diff --git a/ios/chrome/browser/lens/lens_browser_agent.h b/ios/chrome/browser/lens/lens_browser_agent.h
new file mode 100644
index 0000000..0f30f61
--- /dev/null
+++ b/ios/chrome/browser/lens/lens_browser_agent.h
@@ -0,0 +1,53 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_LENS_LENS_BROWSER_AGENT_H_
+#define IOS_CHROME_BROWSER_LENS_LENS_BROWSER_AGENT_H_
+
+#import "base/scoped_multi_source_observation.h"
+#import "ios/chrome/browser/main/browser_observer.h"
+#import "ios/chrome/browser/main/browser_user_data.h"
+#import "third_party/abseil-cpp/absl/types/optional.h"
+
+enum class LensEntrypoint;
+class Browser;
+
+// A browser agent to help with Lens navigation.
+class LensBrowserAgent : public BrowserObserver,
+                         public BrowserUserData<LensBrowserAgent> {
+ public:
+  LensBrowserAgent(const LensBrowserAgent&) = delete;
+  LensBrowserAgent& operator=(const LensBrowserAgent&) = delete;
+
+  ~LensBrowserAgent() override;
+
+  // Returns true if the active browser WebState's visible URL is
+  // a supported Lens camera results URL. In this case, back navigation
+  // should open the Lens camera experience if the user is at the
+  // bottom of the navigation stack.
+  bool CanGoBackToLensViewFinder() const;
+
+  // Returns the user to the Lens camera experience if the active
+  // browser WebState's visible URL is a supported Lens camera results URL.
+  void GoBackToLensViewFinder() const;
+
+ private:
+  friend class BrowserUserData<LensBrowserAgent>;
+
+  explicit LensBrowserAgent(Browser* browser);
+
+  // BrowserObserver methods.
+  void BrowserDestroyed(Browser* browser) override;
+
+  // Returns the Lens entrypoint for the current WebState URL if it is a
+  // Lens Web results URL from an enabled camera entrypoint.
+  absl::optional<LensEntrypoint> CurrentResultsEntrypoint() const;
+
+  // The Browser that this agent is attached to.
+  Browser* browser_ = nullptr;
+
+  BROWSER_USER_DATA_KEY_DECL();
+};
+
+#endif  // IOS_CHROME_BROWSER_LENS_LENS_BROWSER_AGENT_H_
diff --git a/ios/chrome/browser/lens/lens_browser_agent.mm b/ios/chrome/browser/lens/lens_browser_agent.mm
new file mode 100644
index 0000000..c2070f18
--- /dev/null
+++ b/ios/chrome/browser/lens/lens_browser_agent.mm
@@ -0,0 +1,128 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/lens/lens_browser_agent.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "components/search_engines/template_url.h"
+#import "components/search_engines/template_url_service.h"
+#import "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#import "ios/chrome/browser/search_engines/template_url_service_factory.h"
+#import "ios/chrome/browser/ui/commands/command_dispatcher.h"
+#import "ios/chrome/browser/ui/commands/lens_commands.h"
+#import "ios/chrome/browser/ui/lens/lens_entrypoint.h"
+#import "ios/chrome/browser/ui/ui_feature_flags.h"
+#import "ios/chrome/browser/web_state_list/web_state_list.h"
+#import "ios/public/provider/chrome/browser/lens/lens_api.h"
+#import "ios/web/public/navigation/navigation_manager.h"
+#import "ios/web/public/web_state.h"
+
+LensBrowserAgent::LensBrowserAgent(Browser* browser) : browser_(browser) {
+  browser->AddObserver(this);
+}
+
+LensBrowserAgent::~LensBrowserAgent() = default;
+
+#pragma mark - Public
+
+bool LensBrowserAgent::CanGoBackToLensViewFinder() const {
+  return CurrentResultsEntrypoint().has_value();
+}
+
+void LensBrowserAgent::GoBackToLensViewFinder() const {
+  DCHECK(browser_);
+
+  absl::optional<LensEntrypoint> lens_entrypoint = CurrentResultsEntrypoint();
+  if (!lens_entrypoint) {
+    return;
+  }
+
+  id<LensCommands> lens_commands_handler =
+      HandlerForProtocol(browser_->GetCommandDispatcher(), LensCommands);
+  [lens_commands_handler
+      openInputSelectionForEntrypoint:lens_entrypoint.value()];
+
+  // Since the user is returning to the camera experience that opened the
+  // tab in the first place, close the tab.
+  WebStateList* web_state_list = browser_->GetWebStateList();
+  const int index = web_state_list->active_index();
+  DCHECK_NE(index, WebStateList::kInvalidIndex);
+  web_state_list->CloseWebStateAt(index, WebStateList::CLOSE_USER_ACTION);
+}
+
+#pragma mark - Private
+
+absl::optional<LensEntrypoint> LensBrowserAgent::CurrentResultsEntrypoint()
+    const {
+  DCHECK(browser_);
+
+  if (!ios::provider::IsLensSupported()) {
+    return absl::nullopt;
+  }
+
+  WebStateList* web_state_list = browser_->GetWebStateList();
+  web::WebState* web_state = web_state_list->GetActiveWebState();
+  // Return null optional if there is no active WebState.
+  if (!web_state) {
+    return absl::nullopt;
+  }
+
+  // Lens camera is unsupported if the default search engine is not Google.
+  ChromeBrowserState* browser_state =
+      ChromeBrowserState::FromBrowserState(web_state->GetBrowserState());
+  const TemplateURLService* url_service =
+      ios::TemplateURLServiceFactory::GetForBrowserState(browser_state);
+  DCHECK(url_service);
+
+  const TemplateURL* default_url = url_service->GetDefaultSearchProvider();
+  if (!default_url ||
+      default_url->GetEngineType(url_service->search_terms_data()) !=
+          SEARCH_ENGINE_GOOGLE) {
+    return absl::nullopt;
+  }
+
+  // The URL must be a valid Lens Web results URL from the camera with a defined
+  // entry point.
+  absl::optional<LensEntrypoint> entry_point =
+      ios::provider::GetLensEntryPointFromURL(web_state->GetVisibleURL());
+  if (!entry_point) {
+    return absl::nullopt;
+  }
+
+  // Return a null optional if the entrypoint is not an enabled camera
+  // experience.
+  switch (entry_point.value()) {
+    case LensEntrypoint::Keyboard:
+      if (!base::FeatureList::IsEnabled(kEnableLensInKeyboard)) {
+        return absl::nullopt;
+      }
+      break;
+    case LensEntrypoint::NewTabPage:
+      if (!base::FeatureList::IsEnabled(kEnableLensInNTP)) {
+        return absl::nullopt;
+      }
+      break;
+    case LensEntrypoint::HomeScreenWidget:
+      if (!base::FeatureList::IsEnabled(kEnableLensInHomeScreenWidget)) {
+        return absl::nullopt;
+      }
+      break;
+    default:
+      return absl::nullopt;
+  }
+
+  return entry_point;
+}
+
+#pragma mark - BrowserObserver
+
+void LensBrowserAgent::BrowserDestroyed(Browser* browser) {
+  browser->RemoveObserver(this);
+  browser_ = nullptr;
+}
+
+BROWSER_USER_DATA_KEY_IMPL(LensBrowserAgent)
diff --git a/ios/chrome/browser/main/BUILD.gn b/ios/chrome/browser/main/BUILD.gn
index e947985d..8c1cb87 100644
--- a/ios/chrome/browser/main/BUILD.gn
+++ b/ios/chrome/browser/main/BUILD.gn
@@ -52,6 +52,7 @@
     "//ios/chrome/browser/device_sharing",
     "//ios/chrome/browser/follow:browser_agent",
     "//ios/chrome/browser/infobars/overlays/browser_agent:browser_agent_util",
+    "//ios/chrome/browser/lens",
     "//ios/chrome/browser/metrics:metrics_browser_agent",
     "//ios/chrome/browser/ntp:features",
     "//ios/chrome/browser/policy",
diff --git a/ios/chrome/browser/main/browser_agent_util.mm b/ios/chrome/browser/main/browser_agent_util.mm
index 5838a51f..22f6786 100644
--- a/ios/chrome/browser/main/browser_agent_util.mm
+++ b/ios/chrome/browser/main/browser_agent_util.mm
@@ -12,6 +12,7 @@
 #import "ios/chrome/browser/device_sharing/device_sharing_browser_agent.h"
 #import "ios/chrome/browser/follow/follow_browser_agent.h"
 #import "ios/chrome/browser/infobars/overlays/browser_agent/infobar_overlay_browser_agent_util.h"
+#import "ios/chrome/browser/lens/lens_browser_agent.h"
 #import "ios/chrome/browser/metrics/tab_usage_recorder_browser_agent.h"
 #import "ios/chrome/browser/ntp/features.h"
 #import "ios/chrome/browser/policy/policy_watcher_browser_agent.h"
@@ -54,6 +55,9 @@
   DeviceSharingBrowserAgent::CreateForBrowser(browser);
   UrlLoadingNotifierBrowserAgent::CreateForBrowser(browser);
   AppLauncherBrowserAgent::CreateForBrowser(browser);
+
+  // LensBrowserAgent must be created before WebNavigationBrowserAgent.
+  LensBrowserAgent::CreateForBrowser(browser);
   WebNavigationBrowserAgent::CreateForBrowser(browser);
   TabParentingBrowserAgent::CreateForBrowser(browser);
 
diff --git a/ios/chrome/browser/policy/policy_egtest.mm b/ios/chrome/browser/policy/policy_egtest.mm
index 3a631b7..f2af784 100644
--- a/ios/chrome/browser/policy/policy_egtest.mm
+++ b/ios/chrome/browser/policy/policy_egtest.mm
@@ -388,7 +388,7 @@
   // Relaunch the app with Discover enabled, as it is required for this test.
   AppLaunchConfiguration config = [self appConfigurationForTestCase];
   config.relaunch_policy = ForceRelaunchByCleanShutdown;
-  config.features_enabled.push_back(kDiscoverFeedInNtp);
+  config.features_disabled.push_back(kEnableFeedAblation);
   [[AppLaunchManager sharedManager] ensureAppLaunchedWithConfiguration:config];
 
   NSString* feedTitle = l10n_util::GetNSString(IDS_IOS_DISCOVER_FEED_TITLE);
diff --git a/ios/chrome/browser/providers/lens/chromium_lens.mm b/ios/chrome/browser/providers/lens/chromium_lens.mm
index 4ed2035..9949743a 100644
--- a/ios/chrome/browser/providers/lens/chromium_lens.mm
+++ b/ios/chrome/browser/providers/lens/chromium_lens.mm
@@ -49,6 +49,10 @@
   return false;
 }
 
+absl::optional<LensEntrypoint> GetLensEntryPointFromURL(const GURL& url) {
+  return absl::nullopt;
+}
+
 web::NavigationManager::WebLoadParams GenerateLensLoadParamsForImage(
     UIImage* image,
     LensEntrypoint entry_point,
diff --git a/ios/chrome/browser/search_engines/BUILD.gn b/ios/chrome/browser/search_engines/BUILD.gn
index 32399ebd..940e83c 100644
--- a/ios/chrome/browser/search_engines/BUILD.gn
+++ b/ios/chrome/browser/search_engines/BUILD.gn
@@ -111,6 +111,18 @@
   ]
 }
 
+source_set("template_url_service_factory") {
+  # TODO(crbug.com/1382864): This target was added to prevent dependency
+  # cycles for Lens navigation.
+  # Remove this target after fixing the Lens / Web dependency cycle.
+  sources = [ "template_url_service_factory.h" ]
+  deps = [
+    "//base",
+    "//components/keyed_service/ios",
+  ]
+  configs += [ "//build/config/compiler:enable_arc" ]
+}
+
 optimize_js("search_engine_js") {
   primary_script = "resources/search_engine.js"
   sources = [ "resources/search_engine.js" ]
diff --git a/ios/chrome/browser/search_engines/extension_search_engine_data_updater_unittest.mm b/ios/chrome/browser/search_engines/extension_search_engine_data_updater_unittest.mm
index 1e296ca..7e3024e 100644
--- a/ios/chrome/browser/search_engines/extension_search_engine_data_updater_unittest.mm
+++ b/ios/chrome/browser/search_engines/extension_search_engine_data_updater_unittest.mm
@@ -92,7 +92,7 @@
       base::StringPiece(), base::StringPiece(), base::StringPiece(),
       base::StringPiece(), base::StringPiece(), base::StringPiece(),
       base::StringPiece(), base::StringPiece(), base::StringPiece(),
-      base::StringPiece(), base::StringPiece(), base::StringPiece(),
+      base::StringPiece(), {}, base::StringPiece(), base::StringPiece(),
       base::StringPiece16(), base::ListValue(), false, false, 0);
   TemplateURL google_template_url(google_template_url_data);
 
diff --git a/ios/chrome/browser/signin/BUILD.gn b/ios/chrome/browser/signin/BUILD.gn
index 2dc10f9..4dae641 100644
--- a/ios/chrome/browser/signin/BUILD.gn
+++ b/ios/chrome/browser/signin/BUILD.gn
@@ -289,6 +289,7 @@
     "//ios/chrome/browser/browser_state:test_support",
     "//ios/chrome/browser/content_settings",
     "//ios/chrome/browser/flags:system_flags",
+    "//ios/chrome/browser/lens",
     "//ios/chrome/browser/main:test_support",
     "//ios/chrome/browser/policy:policy_util",
     "//ios/chrome/browser/prefs:browser_prefs",
diff --git a/ios/chrome/browser/signin/account_consistency_browser_agent_unittest.mm b/ios/chrome/browser/signin/account_consistency_browser_agent_unittest.mm
index b6bae078..e552e255 100644
--- a/ios/chrome/browser/signin/account_consistency_browser_agent_unittest.mm
+++ b/ios/chrome/browser/signin/account_consistency_browser_agent_unittest.mm
@@ -9,6 +9,7 @@
 #endif
 
 #import "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
+#import "ios/chrome/browser/lens/lens_browser_agent.h"
 #import "ios/chrome/browser/main/test_browser.h"
 #import "ios/chrome/browser/ui/commands/application_commands.h"
 #import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
@@ -30,6 +31,7 @@
 
     application_commands_mock_ =
         OCMStrictProtocolMock(@protocol(ApplicationCommands));
+    LensBrowserAgent::CreateForBrowser(browser_.get());
     WebNavigationBrowserAgent::CreateForBrowser(browser_.get());
     AccountConsistencyBrowserAgent::CreateForBrowser(
         browser_.get(), nil, application_commands_mock_);
diff --git a/ios/chrome/browser/ui/activity_services/activities/BUILD.gn b/ios/chrome/browser/ui/activity_services/activities/BUILD.gn
index 876cb23..9f1984d 100644
--- a/ios/chrome/browser/ui/activity_services/activities/BUILD.gn
+++ b/ios/chrome/browser/ui/activity_services/activities/BUILD.gn
@@ -74,6 +74,7 @@
     "//components/prefs:test_support",
     "//ios/chrome/app/strings",
     "//ios/chrome/browser/browser_state:test_support",
+    "//ios/chrome/browser/lens",
     "//ios/chrome/browser/main:public",
     "//ios/chrome/browser/main:test_support",
     "//ios/chrome/browser/ui/activity_services/data",
diff --git a/ios/chrome/browser/ui/activity_services/activities/request_desktop_or_mobile_site_activity_unittest.mm b/ios/chrome/browser/ui/activity_services/activities/request_desktop_or_mobile_site_activity_unittest.mm
index c2cb2bd..416cee9 100644
--- a/ios/chrome/browser/ui/activity_services/activities/request_desktop_or_mobile_site_activity_unittest.mm
+++ b/ios/chrome/browser/ui/activity_services/activities/request_desktop_or_mobile_site_activity_unittest.mm
@@ -5,6 +5,7 @@
 #import "ios/chrome/browser/ui/activity_services/activities/request_desktop_or_mobile_site_activity.h"
 
 #import "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
+#import "ios/chrome/browser/lens/lens_browser_agent.h"
 #import "ios/chrome/browser/main/test_browser.h"
 #import "ios/chrome/browser/ui/commands/browser_coordinator_commands.h"
 #import "ios/chrome/browser/web/web_navigation_browser_agent.h"
@@ -30,6 +31,7 @@
   RequestDesktopOrMobileSiteActivityTest() {
     browser_state_ = TestChromeBrowserState::Builder().Build();
     browser_ = std::make_unique<TestBrowser>(browser_state_.get());
+    LensBrowserAgent::CreateForBrowser(browser_.get());
     WebNavigationBrowserAgent::CreateForBrowser(browser_.get());
     agent_ = WebNavigationBrowserAgent::FromBrowser(browser_.get());
     WebStateOpener opener;
diff --git a/ios/chrome/browser/ui/browser_view/BUILD.gn b/ios/chrome/browser/ui/browser_view/BUILD.gn
index 1580538b..4518902 100644
--- a/ios/chrome/browser/ui/browser_view/BUILD.gn
+++ b/ios/chrome/browser/ui/browser_view/BUILD.gn
@@ -266,6 +266,7 @@
     "//ios/chrome/browser/favicon",
     "//ios/chrome/browser/find_in_page",
     "//ios/chrome/browser/history",
+    "//ios/chrome/browser/lens",
     "//ios/chrome/browser/main:test_support",
     "//ios/chrome/browser/ntp",
     "//ios/chrome/browser/prerender",
diff --git a/ios/chrome/browser/ui/browser_view/browser_coordinator_unittest.mm b/ios/chrome/browser/ui/browser_view/browser_coordinator_unittest.mm
index 3ea82baa..b8dc8bff7 100644
--- a/ios/chrome/browser/ui/browser_view/browser_coordinator_unittest.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_coordinator_unittest.mm
@@ -14,6 +14,7 @@
 #import "ios/chrome/browser/favicon/ios_chrome_favicon_loader_factory.h"
 #import "ios/chrome/browser/favicon/ios_chrome_large_icon_service_factory.h"
 #import "ios/chrome/browser/history/history_service_factory.h"
+#import "ios/chrome/browser/lens/lens_browser_agent.h"
 #import "ios/chrome/browser/main/test_browser.h"
 #import "ios/chrome/browser/prerender/prerender_service_factory.h"
 #import "ios/chrome/browser/search_engines/template_url_service_factory.h"
@@ -82,6 +83,7 @@
     browser_ = std::make_unique<TestBrowser>(chrome_browser_state_.get());
     UrlLoadingNotifierBrowserAgent::CreateForBrowser(browser_.get());
     SceneStateBrowserAgent::CreateForBrowser(browser_.get(), scene_state_);
+    LensBrowserAgent::CreateForBrowser(browser_.get());
     WebNavigationBrowserAgent::CreateForBrowser(browser_.get());
     TabInsertionBrowserAgent::CreateForBrowser(browser_.get());
     WebStateDelegateBrowserAgent::CreateForBrowser(
diff --git a/ios/chrome/browser/ui/browser_view/browser_view_controller_unittest.mm b/ios/chrome/browser/ui/browser_view/browser_view_controller_unittest.mm
index 09f6096..023b5d23 100644
--- a/ios/chrome/browser/ui/browser_view/browser_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller_unittest.mm
@@ -18,6 +18,7 @@
 #import "ios/chrome/browser/favicon/ios_chrome_favicon_loader_factory.h"
 #import "ios/chrome/browser/favicon/ios_chrome_large_icon_service_factory.h"
 #import "ios/chrome/browser/history/history_service_factory.h"
+#import "ios/chrome/browser/lens/lens_browser_agent.h"
 #import "ios/chrome/browser/main/test_browser.h"
 #import "ios/chrome/browser/prerender/fake_prerender_service.h"
 #import "ios/chrome/browser/search_engines/template_url_service_factory.h"
@@ -128,6 +129,7 @@
     browser_ = std::make_unique<TestBrowser>(chrome_browser_state_.get());
     WebUsageEnablerBrowserAgent::CreateForBrowser(browser_.get());
     UrlLoadingNotifierBrowserAgent::CreateForBrowser(browser_.get());
+    LensBrowserAgent::CreateForBrowser(browser_.get());
     WebNavigationBrowserAgent::CreateForBrowser(browser_.get());
 
     WebUsageEnablerBrowserAgent::FromBrowser(browser_.get())
diff --git a/ios/chrome/browser/ui/browser_view/key_commands_provider_unittest.mm b/ios/chrome/browser/ui/browser_view/key_commands_provider_unittest.mm
index 700e876..6279d9e 100644
--- a/ios/chrome/browser/ui/browser_view/key_commands_provider_unittest.mm
+++ b/ios/chrome/browser/ui/browser_view/key_commands_provider_unittest.mm
@@ -9,6 +9,7 @@
 #import "base/test/task_environment.h"
 #import "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
 #import "ios/chrome/browser/find_in_page/find_tab_helper.h"
+#import "ios/chrome/browser/lens/lens_browser_agent.h"
 #import "ios/chrome/browser/main/test_browser.h"
 #import "ios/chrome/browser/ntp/new_tab_page_tab_helper.h"
 #import "ios/chrome/browser/ntp/new_tab_page_tab_helper_delegate.h"
@@ -59,6 +60,7 @@
     browser_state_ = builder.Build();
     browser_ = std::make_unique<TestBrowser>(browser_state_.get());
     web_state_list_ = browser_->GetWebStateList();
+    LensBrowserAgent::CreateForBrowser(browser_.get());
     WebNavigationBrowserAgent::CreateForBrowser(browser_.get());
     scene_state_ = [[SceneState alloc] initWithAppState:nil];
     SceneStateBrowserAgent::CreateForBrowser(browser_.get(), scene_state_);
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_egtest.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_egtest.mm
index 703e18f..2c0eead 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_egtest.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_egtest.mm
@@ -17,6 +17,7 @@
 #import "ios/chrome/browser/ui/content_suggestions/new_tab_page_app_interface.h"
 #import "ios/chrome/browser/ui/content_suggestions/ntp_home_constant.h"
 #import "ios/chrome/browser/ui/ntp/new_tab_page_constants.h"
+#import "ios/chrome/browser/ui/ntp/new_tab_page_feature.h"
 #import "ios/chrome/grit/ios_strings.h"
 #import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
 #import "ios/chrome/test/earl_grey/chrome_earl_grey_ui.h"
@@ -69,7 +70,7 @@
 
 - (AppLaunchConfiguration)appConfigurationForTestCase {
   AppLaunchConfiguration config;
-  config.features_disabled.push_back(kDiscoverFeedInNtp);
+  config.features_enabled.push_back(kEnableFeedAblation);
   return config;
 }
 
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_feature.h b/ios/chrome/browser/ui/content_suggestions/content_suggestions_feature.h
index c076fba..7a5dbfae 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_feature.h
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_feature.h
@@ -19,6 +19,7 @@
 // Feature to choose between the old Zine feed or the new Discover feed in the
 // Bling new tab page.
 // Use IsDiscoverFeedEnabled() instead of this constant directly.
+// TODO(crbug.com/1385512): Remove this.
 BASE_DECLARE_FEATURE(kDiscoverFeedInNtp);
 
 // Feature to use one NTP for all tabs in a Browser.
@@ -56,9 +57,11 @@
 
 // A parameter to indicate whether the native UI is enabled for the discover
 // feed.
+// TODO(crbug.com/1385512): Remove this.
 extern const char kDiscoverFeedIsNativeUIEnabled[];
 
 // Whether the Discover feed is enabled instead of the Zine feed.
+// TODO(crbug.com/1385512): Remove this.
 bool IsDiscoverFeedEnabled();
 
 // Whether the Content Suggestions UI Module Refresh feature is enabled.
diff --git a/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm b/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm
index 7c3f976..1f89cc4 100644
--- a/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm
+++ b/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm
@@ -145,7 +145,7 @@
   AppLaunchConfiguration config = [super appConfigurationForTestCase];
   config.additional_args.push_back(std::string("--") +
                                    switches::kEnableDiscoverFeed);
-  config.features_enabled.push_back(kDiscoverFeedInNtp);
+  config.features_disabled.push_back(kEnableFeedAblation);
 
   config.features_enabled.push_back(kContentSuggestionsUIModuleRefresh);
   if ([self isRunningTest:@selector(MAYBE_testTrendingQueries)]) {
diff --git a/ios/chrome/browser/ui/ntp/discover_feed_constants.h b/ios/chrome/browser/ui/ntp/discover_feed_constants.h
index c02d6b9..2734402 100644
--- a/ios/chrome/browser/ui/ntp/discover_feed_constants.h
+++ b/ios/chrome/browser/ui/ntp/discover_feed_constants.h
@@ -11,6 +11,7 @@
 extern const char kDefaultDiscoverReferrer[];
 
 // The feature parameter to specify the referrer for Discover Feed navigations.
+// TODO(crbug.com/1385512): Remove this.
 extern const char kDiscoverReferrerParameter[];
 
 // The width of the feed content. Currently hard coded in Mulder.
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_coordinator.mm b/ios/chrome/browser/ui/ntp/new_tab_page_coordinator.mm
index 876c00c..96761dd 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_coordinator.mm
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_coordinator.mm
@@ -816,7 +816,7 @@
 
 - (UIViewController*)discoverFeedPreviewWithURL:(const GURL)URL {
   std::string referrerURL = base::GetFieldTrialParamValueByFeature(
-      kEnableDiscoverFeedPreview, kDiscoverReferrerParameter);
+      kOverrideFeedSettings, kFeedSettingDiscoverReferrerParameter);
   if (referrerURL.empty()) {
     referrerURL = kDefaultDiscoverReferrer;
   }
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_feature.h b/ios/chrome/browser/ui/ntp/new_tab_page_feature.h
index 5b37cf6..3fc723a 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_feature.h
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_feature.h
@@ -8,27 +8,35 @@
 #include "base/feature_list.h"
 #include "components/prefs/pref_service.h"
 
+#pragma mark - Feature declarations
+
 // Feature flag to enable showing a live preview for Discover feed when opening
 // the feed context menu.
 BASE_DECLARE_FEATURE(kEnableDiscoverFeedPreview);
 
 // Feature flag to show ghost cards when refreshing the discover feed.
-BASE_DECLARE_FEATURE(kDiscoverFeedGhostCardsEnabled);
+BASE_DECLARE_FEATURE(kEnableDiscoverFeedGhostCards);
 
 // Feature flag to enable static resource serving for the Discover feed.
+// TODO(crbug.com/1385512): Remove this.
 BASE_DECLARE_FEATURE(kEnableDiscoverFeedStaticResourceServing);
 
 // Feature flag to enable discofeed endpoint for the Discover feed.
 BASE_DECLARE_FEATURE(kEnableDiscoverFeedDiscoFeedEndpoint);
 
-// Feature flag to enable static resource serving for the Discover feed.
-BASE_DECLARE_FEATURE(kEnableDiscoverFeedStaticResourceServing);
-
 // Feature flag to enable the sync promo on top of the discover feed.
 BASE_DECLARE_FEATURE(kEnableDiscoverFeedTopSyncPromo);
 
 // Feature flag to enable a default Following feed sort type.
-BASE_DECLARE_FEATURE(kFollowingFeedDefaultSortType);
+BASE_DECLARE_FEATURE(kEnableFollowingFeedDefaultSortType);
+
+// Feature flag to fix the NTP view hierarchy if it is broken before applying
+// constraints.
+// TODO(crbug.com/1262536): Remove this when it is fixed.
+BASE_DECLARE_FEATURE(kEnableNTPViewHierarchyRepair);
+
+// Feature flag to remove the Feed from the NTP.
+BASE_DECLARE_FEATURE(kEnableFeedAblation);
 
 // Feature flag to enable checking feed visibility on attention log start.
 BASE_DECLARE_FEATURE(kEnableCheckVisibilityOnAttentionLogStart);
@@ -37,12 +45,20 @@
 // very short attention log.
 BASE_DECLARE_FEATURE(kEnableRefineDataSourceReloadReporting);
 
+// Flag to override feed settings through the server. Enabling this feature on
+// its own does nothing; relies on feature parameters.
+BASE_DECLARE_FEATURE(kOverrideFeedSettings);
+
+#pragma mark - Feature parameters
+
 // A parameter to indicate whether Reconstructed Templates is enabled for static
 // resource serving.
+// TODO(crbug.com/1385512): Remove this.
 extern const char kDiscoverFeedSRSReconstructedTemplatesEnabled[];
 
 // A parameter to indicate whether Preload Templates is enabled for static
 // resource serving.
+// TODO(crbug.com/1385512): Remove this.
 extern const char kDiscoverFeedSRSPreloadTemplatesEnabled[];
 
 // A parameter value used for displaying the full with title promo style.
@@ -58,13 +74,19 @@
 // Publisher.
 extern const char kFollowingFeedDefaultSortTypeGroupedByPublisher[];
 
-// Feature flag to fix the NTP view hierarchy if it is broken before applying
-// constraints.
-// TODO(crbug.com/1262536): Remove this when it is fixed.
-BASE_DECLARE_FEATURE(kNTPViewHierarchyRepair);
+// A parameter value for the feed's refresh threshold.
+extern const char kFeedSettingRefreshThresholdInSeconds[];
 
-// Feature flag to remove the Feed from the NTP.
-BASE_DECLARE_FEATURE(kEnableFeedAblation);
+// A parameter value for the feed's maximum data cache age.
+extern const char kFeedSettingMaximumDataCacheAgeInSeconds[];
+
+// A parameter value for the timeout threshold after clearing browsing data.
+extern const char kFeedSettingTimeoutThresholdAfterClearBrowsingData[];
+
+// A parameter value for the feed referrer.
+extern const char kFeedSettingDiscoverReferrerParameter[];
+
+#pragma mark - Helpers
 
 // Whether the Discover feed content preview is shown in the context menu.
 bool IsDiscoverFeedPreviewEnabled();
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_feature.mm b/ios/chrome/browser/ui/ntp/new_tab_page_feature.mm
index fab5c0d..4575219d 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_feature.mm
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_feature.mm
@@ -13,54 +13,38 @@
 #error "This file requires ARC support."
 #endif
 
+#pragma mark - Feature declarations
+
 BASE_FEATURE(kEnableDiscoverFeedPreview,
              "EnableDiscoverFeedPreview",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-BASE_FEATURE(kDiscoverFeedGhostCardsEnabled,
-             "DiscoverFeedGhostCardsEnabled",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
-BASE_FEATURE(kEnableDiscoverFeedDiscoFeedEndpoint,
-             "EnableDiscoFeedEndpoint",
+BASE_FEATURE(kEnableDiscoverFeedGhostCards,
+             "EnableDiscoverFeedGhostCards",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
 BASE_FEATURE(kEnableDiscoverFeedStaticResourceServing,
              "EnableDiscoverFeedStaticResourceServing",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+BASE_FEATURE(kEnableDiscoverFeedDiscoFeedEndpoint,
+             "EnableDiscoFeedEndpoint",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 BASE_FEATURE(kEnableDiscoverFeedTopSyncPromo,
              "EnableDiscoverFeedTopSyncPromo",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-BASE_FEATURE(kFollowingFeedDefaultSortType,
-             "FollowingFeedDefaultSortType",
+BASE_FEATURE(kEnableFollowingFeedDefaultSortType,
+             "EnableFollowingFeedDefaultSortType",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-// A parameter value for the number of impressions before autodismissing the
-// promo.
-const char kDiscoverFeedTopSyncPromoAutodismissImpressions[] =
-    "autodismissImpressions";
-
-const char kDiscoverFeedSRSReconstructedTemplatesEnabled[] =
-    "DiscoverFeedSRSReconstructedTemplatesEnabled";
-
-const char kDiscoverFeedSRSPreloadTemplatesEnabled[] =
-    "DiscoverFeedSRSPreloadTemplatesEnabled";
-
-BASE_FEATURE(kNTPViewHierarchyRepair,
+BASE_FEATURE(kEnableNTPViewHierarchyRepair,
              "NTPViewHierarchyRepair",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-const char kDiscoverFeedTopSyncPromoStyleFullWithTitle[] = "fullWithTitle";
-const char kDiscoverFeedTopSyncPromoStyleCompact[] = "compact";
-
-const char kFollowingFeedDefaultSortTypeSortByLatest[] = "SortByLatest";
-const char kFollowingFeedDefaultSortTypeGroupedByPublisher[] =
-    "GroupedByPublisher";
-
 BASE_FEATURE(kEnableFeedAblation,
-             "FeedAblationEnabled",
+             "EnableFeedAblation",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
 BASE_FEATURE(kEnableCheckVisibilityOnAttentionLogStart,
@@ -71,16 +55,53 @@
              "EnableRefineDataSourceReloadReporting",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Flag to override feed settings through the server. Enabling this feature on
+// its own does nothing; relies on feature parameters.
+BASE_FEATURE(kOverrideFeedSettings,
+             "OverrideFeedSettings",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
+#pragma mark - Feature parameters
+
+const char kDiscoverFeedSRSReconstructedTemplatesEnabled[] =
+    "DiscoverFeedSRSReconstructedTemplatesEnabled";
+
+const char kDiscoverFeedSRSPreloadTemplatesEnabled[] =
+    "DiscoverFeedSRSPreloadTemplatesEnabled";
+
+// EnableDiscoverFeedTopSyncPromo parameters.
+const char kDiscoverFeedTopSyncPromoAutodismissImpressions[] =
+    "autodismissImpressions";
+const char kDiscoverFeedTopSyncPromoStyleFullWithTitle[] = "fullWithTitle";
+const char kDiscoverFeedTopSyncPromoStyleCompact[] = "compact";
+
+// EnableFollowingFeedDefaultSortType parameters.
+const char kFollowingFeedDefaultSortTypeSortByLatest[] = "SortByLatest";
+const char kFollowingFeedDefaultSortTypeGroupedByPublisher[] =
+    "GroupedByPublisher";
+
+// Feature parameters for `kOverrideFeedSettings`.
+const char kFeedSettingRefreshThresholdInSeconds[] =
+    "RefreshThresholdInSeconds";
+const char kFeedSettingMaximumDataCacheAgeInSeconds[] =
+    "MaximumDataCacheAgeInSeconds";
+const char kFeedSettingTimeoutThresholdAfterClearBrowsingData[] =
+    "TimeoutThresholdAfterClearBrowsingData";
+const char kFeedSettingDiscoverReferrerParameter[] =
+    "DiscoverReferrerParameter";
+
+#pragma mark - Helpers
+
 bool IsDiscoverFeedPreviewEnabled() {
   return base::FeatureList::IsEnabled(kEnableDiscoverFeedPreview);
 }
 
 bool IsDiscoverFeedGhostCardsEnabled() {
-  return base::FeatureList::IsEnabled(kDiscoverFeedGhostCardsEnabled);
+  return base::FeatureList::IsEnabled(kEnableDiscoverFeedGhostCards);
 }
 
 bool IsNTPViewHierarchyRepairEnabled() {
-  return base::FeatureList::IsEnabled(kNTPViewHierarchyRepair);
+  return base::FeatureList::IsEnabled(kEnableNTPViewHierarchyRepair);
 }
 
 bool IsDiscoverFeedTopSyncPromoEnabled() {
@@ -100,12 +121,12 @@
 }
 
 bool IsFollowingFeedDefaultSortTypeEnabled() {
-  return base::FeatureList::IsEnabled(kFollowingFeedDefaultSortType);
+  return base::FeatureList::IsEnabled(kEnableFollowingFeedDefaultSortType);
 }
 
 bool IsDefaultFollowingFeedSortTypeGroupedByPublisher() {
   return base::GetFieldTrialParamByFeatureAsBool(
-      kFollowingFeedDefaultSortType,
+      kEnableFollowingFeedDefaultSortType,
       kFollowingFeedDefaultSortTypeGroupedByPublisher, true);
 }
 
diff --git a/ios/chrome/browser/ui/sad_tab/BUILD.gn b/ios/chrome/browser/ui/sad_tab/BUILD.gn
index 8ad660b..e1d19894 100644
--- a/ios/chrome/browser/ui/sad_tab/BUILD.gn
+++ b/ios/chrome/browser/ui/sad_tab/BUILD.gn
@@ -77,6 +77,7 @@
   deps = [
     "//components/strings",
     "//ios/chrome/browser/browser_state:test_support",
+    "//ios/chrome/browser/lens",
     "//ios/chrome/browser/main:test_support",
     "//ios/chrome/browser/ui/commands",
     "//ios/chrome/browser/ui/sad_tab",
diff --git a/ios/chrome/browser/ui/sad_tab/sad_tab_coordinator_unittest.mm b/ios/chrome/browser/ui/sad_tab/sad_tab_coordinator_unittest.mm
index aa110ed..4281125 100644
--- a/ios/chrome/browser/ui/sad_tab/sad_tab_coordinator_unittest.mm
+++ b/ios/chrome/browser/ui/sad_tab/sad_tab_coordinator_unittest.mm
@@ -5,6 +5,7 @@
 #import "ios/chrome/browser/ui/sad_tab/sad_tab_coordinator.h"
 
 #import "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
+#import "ios/chrome/browser/lens/lens_browser_agent.h"
 #import "ios/chrome/browser/main/test_browser.h"
 #import "ios/chrome/browser/ui/commands/application_commands.h"
 #import "ios/chrome/browser/ui/commands/browser_commands.h"
@@ -34,6 +35,7 @@
     UILayoutGuide* guide = [[NamedGuide alloc] initWithName:kContentAreaGuide];
     [base_view_controller_.view addLayoutGuide:guide];
     AddSameConstraints(guide, base_view_controller_.view);
+    LensBrowserAgent::CreateForBrowser(browser_.get());
     WebNavigationBrowserAgent::CreateForBrowser(browser_.get());
   }
   web::WebTaskEnvironment task_environment_;
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_view_controller.h b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_view_controller.h
index 6d062eb2..cf2b5294 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_view_controller.h
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_view_controller.h
@@ -26,6 +26,22 @@
 @protocol ThumbStripCommands;
 @protocol SuggestedActionsDelegate;
 
+// Key of the UMA IOS.TabSwitcher.DragDropTabs histogram.
+extern const char kUmaDragDropTabs[];
+
+// Values of the UMA IOS.TabSwitcher.DragDropTabs histogram. These values are
+// persisted to logs. Entries should not be renumbered and numeric values should
+// never be reused.
+enum class DragDropTabs {
+  // A tab is dragged.
+  kDragBegin = 0,
+  // A tab is dropped at the same index position.
+  kDragEndAtSameIndex = 1,
+  // A tab is dropped at a new index position
+  kDragEndAtNewIndex = 2,
+  kMaxValue = kDragEndAtNewIndex
+};
+
 // Protocol used to relay relevant user interactions from a grid UI.
 @protocol GridViewControllerDelegate
 // Tells the delegate that the item with `itemID` was selected in
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_view_controller.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_view_controller.mm
index 8f6909b8..4f40381 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_view_controller.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_view_controller.mm
@@ -9,6 +9,7 @@
 #import "base/ios/block_types.h"
 #import "base/ios/ios_util.h"
 #import "base/mac/foundation_util.h"
+#import "base/metrics/histogram_functions.h"
 #import "base/metrics/user_metrics.h"
 #import "base/metrics/user_metrics_action.h"
 #import "base/notreached.h"
@@ -48,6 +49,8 @@
 #error "This file requires ARC support."
 #endif
 
+extern const char kUmaDragDropTabs[] = "IOS.TabSwitcher.DragDropTabs";
+
 namespace {
 
 // Constants used in the spring animation when inserting items in the thumb
@@ -157,6 +160,9 @@
 // YES while the grid has the suggested actions section.
 @property(nonatomic) BOOL showingSuggestedActions;
 
+// YES if the dragged tab moved to a new index.
+@property(nonatomic, assign) BOOL dragEndAtNewIndex;
+
 @end
 
 @implementation GridViewController
@@ -714,11 +720,19 @@
 
 - (void)collectionView:(UICollectionView*)collectionView
     dragSessionWillBegin:(id<UIDragSession>)session {
+  self.dragEndAtNewIndex = NO;
+  base::UmaHistogramEnumeration(kUmaDragDropTabs, DragDropTabs::kDragBegin);
+
   [self.delegate gridViewControllerDragSessionWillBegin:self];
 }
 
 - (void)collectionView:(UICollectionView*)collectionView
      dragSessionDidEnd:(id<UIDragSession>)session {
+  DragDropTabs dragEvent = self.dragEndAtNewIndex
+                               ? DragDropTabs::kDragEndAtNewIndex
+                               : DragDropTabs::kDragEndAtSameIndex;
+  base::UmaHistogramEnumeration(kUmaDragDropTabs, dragEvent);
+
   [self.delegate gridViewControllerDragSessionDidEnd:self];
 }
 
@@ -816,7 +830,6 @@
     performDropWithCoordinator:
         (id<UICollectionViewDropCoordinator>)coordinator {
   NSArray<id<UICollectionViewDropItem>>* items = coordinator.items;
-
   for (id<UICollectionViewDropItem> item in items) {
     // Append to the end of the collection, unless drop index is specified.
     // The sourceIndexPath is nil if the drop item is not from the same
@@ -828,6 +841,8 @@
       destinationIndex =
           base::checked_cast<NSUInteger>(coordinator.destinationIndexPath.item);
     }
+    self.dragEndAtNewIndex = YES;
+
     if (self.thumbStripEnabled) {
       // The sourceIndexPath is nil if the drop item is not from the same
       // collection view.
diff --git a/ios/chrome/browser/ui/toolbar/BUILD.gn b/ios/chrome/browser/ui/toolbar/BUILD.gn
index 3bf838e..c53899e4 100644
--- a/ios/chrome/browser/ui/toolbar/BUILD.gn
+++ b/ios/chrome/browser/ui/toolbar/BUILD.gn
@@ -168,6 +168,7 @@
     "//ios/chrome/browser/ui/toolbar/public",
     "//ios/chrome/browser/ui/toolbar/test",
     "//ios/chrome/browser/url:constants",
+    "//ios/chrome/browser/web",
     "//ios/chrome/browser/web_state_list",
     "//ios/chrome/browser/web_state_list:test_support",
     "//ios/chrome/test:test_support",
diff --git a/ios/chrome/browser/ui/toolbar/adaptive_toolbar_coordinator.mm b/ios/chrome/browser/ui/toolbar/adaptive_toolbar_coordinator.mm
index ba340b7..415d5ba 100644
--- a/ios/chrome/browser/ui/toolbar/adaptive_toolbar_coordinator.mm
+++ b/ios/chrome/browser/ui/toolbar/adaptive_toolbar_coordinator.mm
@@ -59,28 +59,30 @@
 - (void)start {
   if (self.started)
     return;
+  Browser* browser = self.browser;
 
   self.started = YES;
 
   self.viewController.longPressDelegate = self.longPressDelegate;
   self.viewController.overrideUserInterfaceStyle =
-      self.browser->GetBrowserState()->IsOffTheRecord()
+      browser->GetBrowserState()->IsOffTheRecord()
           ? UIUserInterfaceStyleDark
           : UIUserInterfaceStyleUnspecified;
-  self.viewController.layoutGuideCenter =
-      LayoutGuideCenterForBrowser(self.browser);
+  self.viewController.layoutGuideCenter = LayoutGuideCenterForBrowser(browser);
 
   self.mediator = [[ToolbarMediator alloc] init];
-  self.mediator.incognito = self.browser->GetBrowserState()->IsOffTheRecord();
+  self.mediator.incognito = browser->GetBrowserState()->IsOffTheRecord();
   self.mediator.consumer = self.viewController;
-  self.mediator.webStateList = self.browser->GetWebStateList();
-  self.mediator.webContentAreaOverlayPresenter = OverlayPresenter::FromBrowser(
-      self.browser, OverlayModality::kWebContentArea);
+  self.mediator.navigationBrowserAgent =
+      WebNavigationBrowserAgent::FromBrowser(browser);
+  self.mediator.webStateList = browser->GetWebStateList();
+  self.mediator.webContentAreaOverlayPresenter =
+      OverlayPresenter::FromBrowser(browser, OverlayModality::kWebContentArea);
   self.mediator.templateURLService =
       ios::TemplateURLServiceFactory::GetForBrowserState(
-          self.browser->GetBrowserState());
+          browser->GetBrowserState());
   self.mediator.actionFactory = [[BrowserActionFactory alloc]
-      initWithBrowser:self.browser
+      initWithBrowser:browser
              scenario:MenuScenarioHistogram::kToolbarMenu];
 
   self.viewController.menuProvider = self.mediator;
diff --git a/ios/chrome/browser/ui/toolbar/toolbar_mediator.h b/ios/chrome/browser/ui/toolbar/toolbar_mediator.h
index 36feb42..8143b757 100644
--- a/ios/chrome/browser/ui/toolbar/toolbar_mediator.h
+++ b/ios/chrome/browser/ui/toolbar/toolbar_mediator.h
@@ -16,6 +16,7 @@
 class OverlayPresenter;
 class TemplateURLService;
 @protocol ToolbarConsumer;
+class WebNavigationBrowserAgent;
 class WebStateList;
 
 // A mediator object that provides the relevant properties of a web state
@@ -45,6 +46,9 @@
 // Action factory.
 @property(nonatomic, strong) BrowserActionFactory* actionFactory;
 
+// Helper for Web navigation.
+@property(nonatomic, assign) WebNavigationBrowserAgent* navigationBrowserAgent;
+
 // Updates the consumer to conforms to `webState`.
 - (void)updateConsumerForWebState:(web::WebState*)webState;
 
diff --git a/ios/chrome/browser/ui/toolbar/toolbar_mediator.mm b/ios/chrome/browser/ui/toolbar/toolbar_mediator.mm
index 2534237b..462ddbb 100644
--- a/ios/chrome/browser/ui/toolbar/toolbar_mediator.mm
+++ b/ios/chrome/browser/ui/toolbar/toolbar_mediator.mm
@@ -27,6 +27,7 @@
 #import "ios/chrome/browser/url_loading/image_search_param_generator.h"
 #import "ios/chrome/browser/url_loading/url_loading_browser_agent.h"
 #import "ios/chrome/browser/url_loading/url_loading_params.h"
+#import "ios/chrome/browser/web/web_navigation_browser_agent.h"
 #import "ios/chrome/browser/web_state_list/web_state_list.h"
 #import "ios/chrome/browser/web_state_list/web_state_list_observer_bridge.h"
 #import "ios/chrome/common/ui/colors/semantic_color_names.h"
@@ -297,9 +298,10 @@
 - (void)updateNavigationBackAndForwardStateForWebState:
     (web::WebState*)webState {
   DCHECK(webState);
-  [self.consumer
-      setCanGoForward:webState->GetNavigationManager()->CanGoForward()];
-  [self.consumer setCanGoBack:webState->GetNavigationManager()->CanGoBack()];
+  const id<ToolbarConsumer> consumer = self.consumer;
+  [consumer
+      setCanGoForward:self.navigationBrowserAgent->CanGoForward(webState)];
+  [consumer setCanGoBack:self.navigationBrowserAgent->CanGoBack(webState)];
 }
 
 // Updates the Share Menu button of the consumer.
diff --git a/ios/chrome/browser/ui/toolbar/toolbar_mediator_unittest.mm b/ios/chrome/browser/ui/toolbar/toolbar_mediator_unittest.mm
index 8418683e..08a5e6bc 100644
--- a/ios/chrome/browser/ui/toolbar/toolbar_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/toolbar/toolbar_mediator_unittest.mm
@@ -24,6 +24,7 @@
 #import "ios/chrome/browser/ui/toolbar/test/toolbar_test_navigation_manager.h"
 #import "ios/chrome/browser/ui/toolbar/toolbar_consumer.h"
 #import "ios/chrome/browser/url/chrome_url_constants.h"
+#import "ios/chrome/browser/web/web_navigation_browser_agent.h"
 #import "ios/chrome/browser/web_state_list/fake_web_state_list_delegate.h"
 #import "ios/chrome/browser/web_state_list/web_state_list.h"
 #import "ios/chrome/browser/web_state_list/web_state_list_observer_bridge.h"
@@ -69,6 +70,7 @@
 
     chrome_browser_state_ = test_cbs_builder.Build();
     test_browser_ = std::make_unique<TestBrowser>(chrome_browser_state_.get());
+    WebNavigationBrowserAgent::CreateForBrowser(test_browser_.get());
 
     std::unique_ptr<ToolbarTestNavigationManager> navigation_manager =
         std::make_unique<ToolbarTestNavigationManager>();
@@ -79,6 +81,8 @@
     test_web_state_->SetLoading(true);
     web_state_ = test_web_state_.get();
     mediator_ = [[TestToolbarMediator alloc] init];
+    mediator_.navigationBrowserAgent =
+        WebNavigationBrowserAgent::FromBrowser(test_browser_.get());
     mediator_.actionFactory =
         [[BrowserActionFactory alloc] initWithBrowser:test_browser_.get()
                                              scenario:kTestMenuScenario];
diff --git a/ios/chrome/browser/ui/webui/policy/policy_ui.mm b/ios/chrome/browser/ui/webui/policy/policy_ui.mm
index d5d09126..cb90f228 100644
--- a/ios/chrome/browser/ui/webui/policy/policy_ui.mm
+++ b/ios/chrome/browser/ui/webui/policy/policy_ui.mm
@@ -56,6 +56,7 @@
       {"labelIsOffHoursActive", IDS_POLICY_LABEL_IS_OFFHOURS_ACTIVE},
       {"labelPoliciesPush", IDS_POLICY_LABEL_PUSH_POLICIES},
       {"labelPrecedence", IDS_POLICY_LABEL_PRECEDENCE},
+      {"labelProfileId", IDS_POLICY_LABEL_PROFILE_ID},
       {"labelRefreshInterval", IDS_POLICY_LABEL_REFRESH_INTERVAL},
       {"labelStatus", IDS_POLICY_LABEL_STATUS},
       {"labelTimeSinceLastFetchAttempt",
diff --git a/ios/chrome/browser/web/BUILD.gn b/ios/chrome/browser/web/BUILD.gn
index 92a032e..425af51 100644
--- a/ios/chrome/browser/web/BUILD.gn
+++ b/ios/chrome/browser/web/BUILD.gn
@@ -54,6 +54,7 @@
     "//ios/chrome/browser/feature_engagement",
     "//ios/chrome/browser/infobars",
     "//ios/chrome/browser/infobars:public",
+    "//ios/chrome/browser/lens",
     "//ios/chrome/browser/main:public",
     "//ios/chrome/browser/ntp",
     "//ios/chrome/browser/ui/commands",
@@ -166,6 +167,7 @@
     "//ios/chrome/app:app_internal",
     "//ios/chrome/app/application_delegate:application_delegate_internal",
     "//ios/chrome/browser/browser_state:test_support",
+    "//ios/chrome/browser/lens",
     "//ios/chrome/browser/main",
     "//ios/chrome/browser/main:test_support",
     "//ios/chrome/browser/ntp",
@@ -175,6 +177,7 @@
     "//ios/chrome/browser/snapshots",
     "//ios/chrome/browser/ui/util",
     "//ios/chrome/browser/url:constants",
+    "//ios/chrome/browser/web",
     "//ios/chrome/browser/web:feature_flags",
     "//ios/chrome/browser/web_state_list",
     "//ios/chrome/browser/web_state_list:agents",
diff --git a/ios/chrome/browser/web/web_navigation_browser_agent.h b/ios/chrome/browser/web/web_navigation_browser_agent.h
index 839d8286..61e8997 100644
--- a/ios/chrome/browser/web/web_navigation_browser_agent.h
+++ b/ios/chrome/browser/web/web_navigation_browser_agent.h
@@ -34,10 +34,14 @@
   // All of the following methods will silently no-op (or return false) if there
   // is no active web state in the assoicated browser's WebStateList.
 
+  // True if the given `web_state` can navigate back.
+  bool CanGoBack(const web::WebState* web_state);
   // True if it is possible to navigate back.
   bool CanGoBack();
   // Navigates back.
   void GoBack();
+  // True if the given `web_state` can navigate forward.
+  bool CanGoForward(const web::WebState* web_state);
   // True if it is possible to navigate forward.
   bool CanGoForward();
   // Navigates forward.
diff --git a/ios/chrome/browser/web/web_navigation_browser_agent.mm b/ios/chrome/browser/web/web_navigation_browser_agent.mm
index 6172a3a..9ee702c 100644
--- a/ios/chrome/browser/web/web_navigation_browser_agent.mm
+++ b/ios/chrome/browser/web/web_navigation_browser_agent.mm
@@ -7,6 +7,10 @@
 #import "components/feature_engagement/public/event_constants.h"
 #import "components/feature_engagement/public/tracker.h"
 #import "ios/chrome/browser/feature_engagement/tracker_factory.h"
+#import "ios/chrome/browser/lens/lens_browser_agent.h"
+#import "ios/chrome/browser/main/browser.h"
+#import "ios/chrome/browser/ui/commands/command_dispatcher.h"
+#import "ios/chrome/browser/ui/commands/lens_commands.h"
 #import "ios/chrome/browser/web/web_navigation_ntp_delegate.h"
 #import "ios/chrome/browser/web/web_navigation_util.h"
 #import "ios/chrome/browser/web_state_list/web_state_list.h"
@@ -25,44 +29,81 @@
 
 WebNavigationBrowserAgent::~WebNavigationBrowserAgent() {}
 
+bool WebNavigationBrowserAgent::CanGoBack(const web::WebState* web_state) {
+  if (!web_state) {
+    return false;
+  }
+
+  if (web_state->GetNavigationManager()->CanGoBack()) {
+    return true;
+  }
+
+  const LensBrowserAgent* lens_browser_agent =
+      LensBrowserAgent::FromBrowser(browser_);
+  if (!lens_browser_agent) {
+    return false;
+  }
+
+  return lens_browser_agent->CanGoBackToLensViewFinder();
+}
+
 bool WebNavigationBrowserAgent::CanGoBack() {
-  return web_state_list_->GetActiveWebState() &&
-         web_state_list_->GetActiveWebState()
-             ->GetNavigationManager()
-             ->CanGoBack();
+  const web::WebState* active_web_state = web_state_list_->GetActiveWebState();
+  return WebNavigationBrowserAgent::CanGoBack(active_web_state);
+}
+
+bool WebNavigationBrowserAgent::CanGoForward(const web::WebState* web_state) {
+  return web_state && web_state->GetNavigationManager()->CanGoForward();
 }
 
 bool WebNavigationBrowserAgent::CanGoForward() {
-  return web_state_list_->GetActiveWebState() &&
-         web_state_list_->GetActiveWebState()
-             ->GetNavigationManager()
-             ->CanGoForward();
+  const web::WebState* active_web_state = web_state_list_->GetActiveWebState();
+  return WebNavigationBrowserAgent::CanGoForward(active_web_state);
 }
 
 void WebNavigationBrowserAgent::GoBack() {
-  if (web_state_list_->GetActiveWebState())
-    web_navigation_util::GoBack(web_state_list_->GetActiveWebState());
+  web::WebState* active_web_state = web_state_list_->GetActiveWebState();
+  if (!active_web_state) {
+    return;
+  }
+
+  if (active_web_state->GetNavigationManager()->CanGoBack()) {
+    web_navigation_util::GoBack(active_web_state);
+    return;
+  }
+
+  // We are at the bottom of the navigation stack. Check to see if Lens back
+  // navigation should bring the user back to the camera.
+  const LensBrowserAgent* lens_browser_agent =
+      LensBrowserAgent::FromBrowser(browser_);
+  if (lens_browser_agent) {
+    lens_browser_agent->GoBackToLensViewFinder();
+  }
 }
+
 void WebNavigationBrowserAgent::GoForward() {
-  if (web_state_list_->GetActiveWebState())
-    web_navigation_util::GoForward(web_state_list_->GetActiveWebState());
+  web::WebState* active_web_state = web_state_list_->GetActiveWebState();
+  if (active_web_state)
+    web_navigation_util::GoForward(active_web_state);
 }
 
 void WebNavigationBrowserAgent::StopLoading() {
-  if (web_state_list_->GetActiveWebState())
-    web_state_list_->GetActiveWebState()->Stop();
+  web::WebState* active_web_state = web_state_list_->GetActiveWebState();
+  if (active_web_state)
+    active_web_state->Stop();
 }
 
 void WebNavigationBrowserAgent::Reload() {
-  if (!web_state_list_->GetActiveWebState())
+  web::WebState* active_web_state = web_state_list_->GetActiveWebState();
+  if (!active_web_state)
     return;
 
   if (delegate_.NTPActiveForCurrentWebState) {
-    [delegate_ reloadNTPForWebState:web_state_list_->GetActiveWebState()];
+    [delegate_ reloadNTPForWebState:active_web_state];
   } else {
     // `check_for_repost` is true because the reload is explicitly initiated
     // by the user.
-    web_state_list_->GetActiveWebState()->GetNavigationManager()->Reload(
+    active_web_state->GetNavigationManager()->Reload(
         web::ReloadType::NORMAL, true /* check_for_repost */);
   }
 }
diff --git a/ios/chrome/browser/web/web_navigation_browser_agent_unittest.mm b/ios/chrome/browser/web/web_navigation_browser_agent_unittest.mm
index 3d2e875..fc8f8d24 100644
--- a/ios/chrome/browser/web/web_navigation_browser_agent_unittest.mm
+++ b/ios/chrome/browser/web/web_navigation_browser_agent_unittest.mm
@@ -5,6 +5,7 @@
 #import "ios/chrome/browser/web/web_navigation_browser_agent.h"
 
 #import "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
+#import "ios/chrome/browser/lens/lens_browser_agent.h"
 #import "ios/chrome/browser/main/browser.h"
 #import "ios/chrome/browser/main/test_browser.h"
 #import "ios/chrome/browser/web/web_navigation_ntp_delegate.h"
@@ -56,6 +57,7 @@
     browser_state_ = TestChromeBrowserState::Builder().Build();
     browser_ = std::make_unique<TestBrowser>(browser_state_.get());
     delegate_ = [[FakeNTPDelegate alloc] init];
+    LensBrowserAgent::CreateForBrowser(browser_.get());
     WebNavigationBrowserAgent::CreateForBrowser(browser_.get());
     agent_ = WebNavigationBrowserAgent::FromBrowser(browser_.get());
     agent_->SetDelegate(delegate_);
diff --git a/ios/chrome/test/providers/lens/test_lens.mm b/ios/chrome/test/providers/lens/test_lens.mm
index a268445..c736b3f2 100644
--- a/ios/chrome/test/providers/lens/test_lens.mm
+++ b/ios/chrome/test/providers/lens/test_lens.mm
@@ -49,6 +49,10 @@
   return false;
 }
 
+absl::optional<LensEntrypoint> GetLensEntryPointFromURL(const GURL& url) {
+  return absl::nullopt;
+}
+
 web::NavigationManager::WebLoadParams GenerateLensLoadParamsForImage(
     UIImage* image,
     LensEntrypoint entry_point,
diff --git a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1 b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
index 01c98f19c..89213d5 100644
--- a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
@@ -1 +1 @@
-cbea3493c3774f4b594fc211e36ced218b38ca84
\ No newline at end of file
+997a5beb33a8e83c09b39712180e890ff02b359a
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1 b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
index b5fda2c5..29260a1 100644
--- a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
@@ -1 +1 @@
-49c6e323dfe4469f5ee72d2d59cfe5b52e08cb95
\ No newline at end of file
+8b419238ee65422d55855bcd3933f3a227045070
\ No newline at end of file
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 71d44b6..45e155e9 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 @@
-3738ac1db1a86a831582b7d4b9775ab8392f0a3d
\ No newline at end of file
+ff118e39f07f18020c8f60de85cae4b933f89eb7
\ 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 3c509ff..2180bf2 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 @@
-f95200479668ef3e22390e987576c9cc2153bdad
\ No newline at end of file
+54c1b98462c8b44a97c03bcf3850bb1329e5029d
\ 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 50a1973..6e1af0e 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 @@
-f01f108a818d629cb0c3b44a08cca2f00081d2b7
\ No newline at end of file
+295f34910b160ccf8caf15b801ebb5a8e5dba0f7
\ 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 0374a21b..3c267ae 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 @@
-176d97349bbc2c62537036bc9ce0b3312223127e
\ No newline at end of file
+63239915aa5059d10efe22b312d9c40cfd3fbb34
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
index c307429..a5385fa 100644
--- a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-50ff5cfc34a47f200349d60dd590056e9bd63e89
\ No newline at end of file
+a1ccf0dabed13ec71301c719a8ccfa4cf466f5bb
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
index e7e0ea91..5549013 100644
--- a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-cb92c621c736005f66f46ed19a188733322c1b3d
\ No newline at end of file
+65ffad4c7e4f485a9f1b68affe14499c7519df0f
\ 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 b4f5bfe..19a666c 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 @@
-db85956da3a376f7be3c60f5376e3bd783e72caf
\ No newline at end of file
+b6e8a718854ee023e180ecb1749093598ec8f7c1
\ 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 0e7af100..ce010af0 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 @@
-da9712fa9767e0f79c8a0d7aff93f32dbdf84b46
\ No newline at end of file
+17265817754bd422944d50df309373eee4e0be78
\ 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 a0e2acb..d22df79 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 @@
-b8eb5fc640de47d57acb5d55e2911d1014f72a7b
\ No newline at end of file
+ce7e0faa409004f090fb9cd5f919f361383a1c7c
\ 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 60c273c7..91b6d54c 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 @@
-048276e9952c4546ef330ced70c37c17e4f3efc8
\ No newline at end of file
+778eac85d7f0bc3222f56d8cca21a4b76ab4258e
\ No newline at end of file
diff --git a/ios/public/provider/chrome/browser/lens/BUILD.gn b/ios/public/provider/chrome/browser/lens/BUILD.gn
index eaf1029..559d05c 100644
--- a/ios/public/provider/chrome/browser/lens/BUILD.gn
+++ b/ios/public/provider/chrome/browser/lens/BUILD.gn
@@ -15,4 +15,5 @@
     "//base",
     "//ios/web/public/navigation:navigation",
   ]
+  public_deps = [ "//third_party/abseil-cpp:absl" ]
 }
diff --git a/ios/public/provider/chrome/browser/lens/lens_api.h b/ios/public/provider/chrome/browser/lens/lens_api.h
index 821893b..6ea6609 100644
--- a/ios/public/provider/chrome/browser/lens/lens_api.h
+++ b/ios/public/provider/chrome/browser/lens/lens_api.h
@@ -9,6 +9,7 @@
 
 #import "base/callback.h"
 #import "ios/web/public/navigation/navigation_manager.h"
+#import "third_party/abseil-cpp/absl/types/optional.h"
 
 @class LensConfiguration;
 @class UIViewController;
@@ -60,18 +61,21 @@
 // Returns whether Lens is supported for the current build.
 bool IsLensSupported();
 
-// Returns whether or not the url represents a Lens Web results page.
+// Returns whether or not `url` represents a Lens Web results page.
 bool IsLensWebResultsURL(const GURL& url);
 
+// Returns the Lens entry point for `url` if it is a Lens Web results page.
+absl::optional<LensEntrypoint> GetLensEntryPointFromURL(const GURL& url);
+
 // Generates web load params for a Lens image search for the given
-// 'image' and 'entry_point'.
+// `image` and `entry_point`.
 web::NavigationManager::WebLoadParams GenerateLensLoadParamsForImage(
     UIImage* image,
     LensEntrypoint entry_point,
     bool is_incognito);
 
 // Generates web load params for a Lens image search for the given
-// 'image' and 'entry_point'. `completion` will be run on the main
+// `image` and `entry_point`. `completion` will be run on the main
 // thread.
 void GenerateLensLoadParamsForImageAsync(UIImage* image,
                                          LensEntrypoint entry_point,
diff --git a/media/base/mac/video_frame_mac.cc b/media/base/mac/video_frame_mac.cc
index 062b0bc..5c543a9 100644
--- a/media/base/mac/video_frame_mac.cc
+++ b/media/base/mac/video_frame_mac.cc
@@ -32,6 +32,23 @@
   reinterpret_cast<const VideoFrame*>(frame_ref)->Release();
 }
 
+// Current list of acceptable CVPixelFormat mappings. If we start supporting
+// RGB frame encoding we'll need to extend this list.
+bool IsAcceptableCvPixelFormat(VideoPixelFormat format, OSType cv_format) {
+  if (format == PIXEL_FORMAT_I420) {
+    return cv_format == kCVPixelFormatType_420YpCbCr8Planar ||
+           cv_format == kCVPixelFormatType_420YpCbCr8PlanarFullRange;
+  }
+  if (format == PIXEL_FORMAT_NV12) {
+    return cv_format == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange ||
+           cv_format == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange;
+  }
+  if (format == PIXEL_FORMAT_NV12A) {
+    return cv_format == kCVPixelFormatType_420YpCbCr8VideoRange_8A_TriPlanar;
+  }
+  return false;
+}
+
 }  // namespace
 
 MEDIA_EXPORT base::ScopedCFTypeRef<CVPixelBufferRef>
@@ -39,6 +56,7 @@
   base::ScopedCFTypeRef<CVPixelBufferRef> pixel_buffer;
   if (!frame)
     return pixel_buffer;
+
   const gfx::Rect& visible_rect = frame->visible_rect();
   bool crop_needed = visible_rect != gfx::Rect(frame->coded_size());
 
@@ -46,6 +64,11 @@
     // If the frame is backed by a pixel buffer, just return that buffer.
     if (frame->CvPixelBuffer()) {
       pixel_buffer.reset(frame->CvPixelBuffer(), base::scoped_policy::RETAIN);
+      if (!IsAcceptableCvPixelFormat(
+              frame->format(), CVPixelBufferGetPixelFormatType(pixel_buffer))) {
+        DLOG(ERROR) << "Dropping CVPixelBuffer w/ incorrect format.";
+        pixel_buffer.reset();
+      }
       return pixel_buffer;
     }
 
@@ -62,6 +85,12 @@
                         << cv_return;
             pixel_buffer.reset();
           }
+          if (!IsAcceptableCvPixelFormat(
+                  frame->format(),
+                  CVPixelBufferGetPixelFormatType(pixel_buffer))) {
+            DLOG(ERROR) << "Dropping CVPixelBuffer w/ incorrect format.";
+            pixel_buffer.reset();
+          }
           return pixel_buffer;
         }
       }
@@ -91,10 +120,12 @@
   } else if (video_frame_format == PIXEL_FORMAT_NV12A) {
     cv_format = kCVPixelFormatType_420YpCbCr8VideoRange_8A_TriPlanar;
   } else {
-    DLOG(ERROR) << " unsupported frame format: " << video_frame_format;
+    DLOG(ERROR) << "Unsupported frame format: " << video_frame_format;
     return pixel_buffer;
   }
 
+  DCHECK(IsAcceptableCvPixelFormat(video_frame_format, cv_format));
+
   int num_planes = VideoFrame::NumPlanes(video_frame_format);
   DCHECK_LE(num_planes, kMaxPlanes);
 
diff --git a/media/gpu/chromeos/oop_video_decoder.cc b/media/gpu/chromeos/oop_video_decoder.cc
index 2b939e3b..87e339b5 100644
--- a/media/gpu/chromeos/oop_video_decoder.cc
+++ b/media/gpu/chromeos/oop_video_decoder.cc
@@ -74,6 +74,14 @@
 
 namespace media {
 
+namespace {
+
+// Size of the timestamp cache. We don't want the cache to grow without bounds.
+// The maximum size is chosen to be the same as in the VaapiVideoDecoder.
+constexpr size_t kTimestampCacheSize = 128;
+
+}  // namespace
+
 // static
 std::unique_ptr<VideoDecoderMixin> OOPVideoDecoder::Create(
     mojo::PendingRemote<stable::mojom::StableVideoDecoder>
@@ -98,6 +106,7 @@
     : VideoDecoderMixin(std::move(media_log),
                         std::move(decoder_task_runner),
                         std::move(client)),
+      fake_timestamp_to_real_timestamp_cache_(kTimestampCacheSize),
       remote_decoder_(std::move(pending_remote_decoder)),
       weak_this_factory_(this) {
   VLOGF(2);
@@ -181,22 +190,37 @@
       pending_remote_stable_cdm_context;
   if (config.is_encrypted()) {
 #if BUILDFLAG(IS_CHROMEOS)
-    if (!cdm_context || !cdm_context->GetChromeOsCdmContext()) {
-      std::move(init_cb).Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
-      return;
-    }
-    mojo::PendingReceiver<stable::mojom::StableCdmContext> cdm_receiver;
-    pending_remote_stable_cdm_context =
-        cdm_receiver.InitWithNewPipeAndPassRemote();
-    mojo::MakeSelfOwnedReceiver(
-        std::make_unique<chromeos::StableCdmContextImpl>(cdm_context),
-        std::move(cdm_receiver));
+    // There's logic in MojoVideoDecoderService::Initialize() to ensure that the
+    // CDM doesn't change across Initialize() calls. We rely on this assumption
+    // to ensure that creating a single StableCdmContextImpl that survives
+    // re-initializations is correct: the remote decoder requires a bound
+    // |pending_remote_stable_cdm_context| only for the first Initialize() call
+    // that sets up encryption.
+    DCHECK(!stable_cdm_context_ ||
+           cdm_context == stable_cdm_context_->cdm_context());
+    if (!stable_cdm_context_) {
+      if (!cdm_context || !cdm_context->GetChromeOsCdmContext()) {
+        std::move(init_cb).Run(
+            DecoderStatus::Codes::kUnsupportedEncryptionMode);
+        return;
+      }
+      stable_cdm_context_ =
+          std::make_unique<chromeos::StableCdmContextImpl>(cdm_context);
+      stable_cdm_context_receiver_ =
+          std::make_unique<mojo::Receiver<stable::mojom::StableCdmContext>>(
+              stable_cdm_context_.get(), pending_remote_stable_cdm_context
+                                             .InitWithNewPipeAndPassReceiver());
+
+      // base::Unretained() is safe because |this| owns the mojo::Receiver.
+      stable_cdm_context_receiver_->set_disconnect_handler(
+          base::BindOnce(&OOPVideoDecoder::Stop, base::Unretained(this)));
 #if BUILDFLAG(USE_VAAPI)
-    // We need to signal that for AMD we will do transcryption on the GPU side.
-    // Then on the other end we just make transcryption a no-op.
-    needs_transcryption_ = (VaapiWrapper::GetImplementationType() ==
-                            VAImplementation::kMesaGallium);
+      // We need to signal that for AMD we will do transcryption on the GPU
+      // side. Then on the other end we just make transcryption a no-op.
+      needs_transcryption_ = (VaapiWrapper::GetImplementationType() ==
+                              VAImplementation::kMesaGallium);
 #endif  // BUILDFLAG(USE_VAAPI)
+    }
 #else
     std::move(init_cb).Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
     return;
@@ -256,14 +280,32 @@
         base::BindOnce(std::move(decode_cb), DecoderStatus::Codes::kFailed));
     return;
   }
-  const uint64_t decode_id = decode_counter_++;
 
+  CHECK(buffer);
+  if (!buffer->end_of_stream()) {
+    const base::TimeDelta next_fake_timestamp =
+        current_fake_timestamp_ + base::Microseconds(1u);
+    if (next_fake_timestamp == current_fake_timestamp_) {
+      // We've reached the maximum base::TimeDelta.
+      decoder_task_runner_->PostTask(
+          FROM_HERE,
+          base::BindOnce(std::move(decode_cb), DecoderStatus::Codes::kFailed));
+      return;
+    }
+    current_fake_timestamp_ = next_fake_timestamp;
+    DCHECK(
+        fake_timestamp_to_real_timestamp_cache_.Peek(current_fake_timestamp_) ==
+        fake_timestamp_to_real_timestamp_cache_.end());
+    fake_timestamp_to_real_timestamp_cache_.Put(current_fake_timestamp_,
+                                                buffer->timestamp());
+    buffer->set_timestamp(current_fake_timestamp_);
+  }
+
+  const uint64_t decode_id = decode_counter_++;
   pending_decodes_.insert({decode_id, std::move(decode_cb)});
 
   const bool is_flushing = buffer->end_of_stream();
 
-  CHECK(buffer);
-
   mojom::DecoderBufferPtr mojo_buffer =
       mojo_decoder_buffer_writer_->WriteDecoderBuffer(buffer);
   if (!mojo_buffer) {
@@ -293,12 +335,20 @@
     return;
   }
 
-  // Check that the |decode_cb| corresponding to the flush is not called until
-  // the decode callback has been called for each pending decode.
-  if (is_flushing && pending_decodes_.size() != 1) {
-    VLOGF(2) << "Received a flush callback while having pending decodes";
-    Stop();
-    return;
+  if (is_flushing) {
+    // Check that the |decode_cb| corresponding to the flush is not called until
+    // the decode callback has been called for each pending decode.
+    if (pending_decodes_.size() != 1) {
+      VLOGF(2) << "Received a flush callback while having pending decodes";
+      Stop();
+      return;
+    }
+
+    // After a flush is completed, we shouldn't receive decoded frames
+    // corresponding to Decode() calls that came in prior to the flush. The
+    // clearing of the cache together with the validation in
+    // OnVideoFrameDecoded() should guarantee this.
+    fake_timestamp_to_real_timestamp_cache_.Clear();
   }
 
   auto it = pending_decodes_.begin();
@@ -359,6 +409,12 @@
   remote_decoder_.reset();
   mojo_decoder_buffer_writer_.reset();
   stable_video_frame_handle_releaser_remote_.reset();
+  fake_timestamp_to_real_timestamp_cache_.Clear();
+
+#if BUILDFLAG(IS_CHROMEOS)
+  stable_cdm_context_receiver_.reset();
+  stable_cdm_context_.reset();
+#endif  // BUILDFLAG(IS_CHROMEOS)
 
   if (init_cb_)
     std::move(init_cb_).Run(DecoderStatus::Codes::kFailed);
@@ -440,6 +496,16 @@
     return;
   }
 
+  base::TimeDelta timestamp = frame->timestamp();
+  auto it = fake_timestamp_to_real_timestamp_cache_.Get(timestamp);
+  if (it == fake_timestamp_to_real_timestamp_cache_.end()) {
+    // The remote decoder is misbehaving.
+    VLOGF(2) << "Received an unexpected decoded frame";
+    Stop();
+    return;
+  }
+  frame->set_timestamp(it->second);
+
   // The destruction observer will be called after the client releases the
   // video frame. BindToCurrentLoop() is used to make sure that the WeakPtr
   // is dereferenced on the correct sequence.
diff --git a/media/gpu/chromeos/oop_video_decoder.h b/media/gpu/chromeos/oop_video_decoder.h
index 7ec4bddd7..e85d28a7 100644
--- a/media/gpu/chromeos/oop_video_decoder.h
+++ b/media/gpu/chromeos/oop_video_decoder.h
@@ -5,14 +5,22 @@
 #ifndef MEDIA_GPU_CHROMEOS_OOP_VIDEO_DECODER_H_
 #define MEDIA_GPU_CHROMEOS_OOP_VIDEO_DECODER_H_
 
+#include "base/containers/lru_cache.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
 #include "media/base/media_log.h"
 #include "media/gpu/chromeos/video_decoder_pipeline.h"
 #include "media/mojo/mojom/stable/stable_video_decoder.mojom.h"
 #include "mojo/public/cpp/bindings/associated_receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
 
+#if BUILDFLAG(IS_CHROMEOS)
+namespace chromeos {
+class StableCdmContextImpl;
+}  // namespace chromeos
+#endif  // BUILDFLAG(IS_CHROMEOS)
+
 namespace media {
 
 class MojoDecoderBufferWriter;
@@ -92,6 +100,28 @@
   std::map<uint64_t, DecodeCB> pending_decodes_
       GUARDED_BY_CONTEXT(sequence_checker_);
 
+  // |fake_timestamp_to_real_timestamp_cache_| allows us to associate Decode()
+  // calls with decoded frames. On each non-flush Decode() call, we generate a
+  // fake timestamp (tracked in |current_fake_timestamp_|) and we map that to
+  // the DecoderBuffer's timestamp. When a decoded frame is received, we look up
+  // its timestamp in |fake_timestamp_to_real_timestamp_cache_| and update it to
+  // the real timestamp. This logic allows us to do a couple of things:
+  //
+  // 1) Not trust the timestamps that come from the remote decoder (the trust
+  //    model is that the remote decoder is untrusted).
+  //
+  // 2) Guarantee the following requirement mandated by the
+  //    VideoDecoder::Decode() API: "If |buffer| is an EOS buffer then the
+  //    decoder must be flushed, i.e. |output_cb| must be called for each frame
+  //    pending in the queue and |decode_cb| must be called after that." We can
+  //    do this by clearing the cache when a flush has been reported to be
+  //    completed by the remote decoder.
+  base::TimeDelta current_fake_timestamp_
+      GUARDED_BY_CONTEXT(sequence_checker_) = base::Microseconds(0u);
+  base::LRUCache<base::TimeDelta, base::TimeDelta>
+      fake_timestamp_to_real_timestamp_cache_
+          GUARDED_BY_CONTEXT(sequence_checker_);
+
   base::OnceClosure reset_cb_ GUARDED_BY_CONTEXT(sequence_checker_);
 
   mojo::AssociatedReceiver<stable::mojom::VideoDecoderClient> client_receiver_
@@ -100,6 +130,13 @@
   mojo::Receiver<stable::mojom::MediaLog> stable_media_log_receiver_
       GUARDED_BY_CONTEXT(sequence_checker_){this};
 
+#if BUILDFLAG(IS_CHROMEOS)
+  std::unique_ptr<chromeos::StableCdmContextImpl> stable_cdm_context_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+  std::unique_ptr<mojo::Receiver<stable::mojom::StableCdmContext>>
+      stable_cdm_context_receiver_ GUARDED_BY_CONTEXT(sequence_checker_);
+#endif  // BUILDFLAG(IS_CHROMEOS)
+
   VideoDecoderType decoder_type_ GUARDED_BY_CONTEXT(sequence_checker_) =
       VideoDecoderType::kUnknown;
 
diff --git a/media/gpu/chromeos/video_decoder_pipeline.cc b/media/gpu/chromeos/video_decoder_pipeline.cc
index 00e6424..82980c5 100644
--- a/media/gpu/chromeos/video_decoder_pipeline.cc
+++ b/media/gpu/chromeos/video_decoder_pipeline.cc
@@ -383,11 +383,6 @@
     std::move(init_cb).Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
     return;
   }
-  if (cdm_context && !allow_encrypted_content_for_testing_) {
-    VLOGF(1) << "cdm_context is not supported.";
-    std::move(init_cb).Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
-    return;
-  }
 #endif  // !BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA)
 
   // Make sure that the configuration requested is supported by the driver,
diff --git a/media/gpu/mac/vt_video_encode_accelerator_mac.cc b/media/gpu/mac/vt_video_encode_accelerator_mac.cc
index 7f24ffa..b522f40 100644
--- a/media/gpu/mac/vt_video_encode_accelerator_mac.cc
+++ b/media/gpu/mac/vt_video_encode_accelerator_mac.cc
@@ -19,6 +19,7 @@
 #include "base/task/thread_pool.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
+#include "build/build_config.h"
 #include "media/base/bitrate.h"
 #include "media/base/bitstream_buffer.h"
 #include "media/base/mac/video_frame_mac.h"
@@ -231,8 +232,16 @@
 
   for (const auto& supported_profile : kSupportedProfiles) {
     if (VideoCodecProfileToVideoCodec(supported_profile) == VideoCodec::kH264) {
-      profile.profile = supported_profile;
-      profiles.push_back(profile);
+#if defined(ARCH_CPU_X86_FAMILY)
+      for (const auto& min_resolution : {gfx::Size(640, 1), gfx::Size(1, 480)})
+#else
+      const auto min_resolution = gfx::Size();
+#endif
+      {
+        profile.min_resolution = min_resolution;
+        profile.profile = supported_profile;
+        profiles.push_back(profile);
+      }
     }
   }
   return profiles;
@@ -321,6 +330,12 @@
   bitstream_buffer_size_ = config.input_visible_size.GetArea();
   require_low_delay_ = config.require_low_delay;
 
+  if (codec_ == VideoCodec::kH264 || codec_ == VideoCodec::kHEVC) {
+    required_encoder_type_ = config.required_encoder_type;
+  } else {
+    DLOG(ERROR) << "Software encoder selection is only allowed for H264/H265.";
+  }
+
   if (config.HasTemporalLayer())
     num_temporal_layers_ = config.spatial_layers.front().num_of_temporal_layers;
 
@@ -335,6 +350,20 @@
     return false;
   }
 
+  // Report whether hardware decode is being used.
+  bool using_hardware = false;
+  base::ScopedCFTypeRef<CFBooleanRef> cf_using_hardware;
+  if (VTSessionCopyProperty(
+          compression_session_,
+          kVTCompressionPropertyKey_UsingHardwareAcceleratedVideoEncoder,
+          kCFAllocatorDefault, cf_using_hardware.InitializeInto()) == 0) {
+    using_hardware = CFBooleanGetValue(cf_using_hardware);
+  }
+  if (!using_hardware) {
+    MEDIA_LOG(INFO, media_log.get())
+        << "VideoToolbox selected a software encoder.";
+  }
+
   client_task_runner_->PostTask(
       FROM_HERE, base::BindOnce(&Client::RequireBitstreamBuffers, client_,
                                 kNumInputBuffers, input_visible_size_,
@@ -711,7 +740,15 @@
 
   std::vector<CFTypeRef> encoder_keys{
       kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncoder};
-  std::vector<CFTypeRef> encoder_values{kCFBooleanTrue};
+  std::vector<CFTypeRef> encoder_values{required_encoder_type_ ==
+                                                Config::EncoderType::kHardware
+                                            ? kCFBooleanTrue
+                                            : kCFBooleanFalse};
+  if (required_encoder_type_ == Config::EncoderType::kSoftware) {
+    encoder_keys.push_back(
+        kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder);
+    encoder_values.push_back(kCFBooleanFalse);
+  }
 
   if (__builtin_available(macOS LOW_LATENCY_FLAG_AVAILABLE_VER, *)) {
     // Remove the validation once HEVC SVC mode is supported on macOS.
@@ -748,7 +785,7 @@
     // we'll clear it without calling CFRelease() because it can be unsafe
     // to call on a not fully created session.
     (void)compression_session_.release();
-    DVLOG(1) << " VTCompressionSessionCreate failed: " << status;
+    OSSTATUS_DLOG(ERROR, status) << " VTCompressionSessionCreate failed: ";
     return false;
   }
   DVLOG(3) << " VTCompressionSession created with input size="
diff --git a/media/gpu/mac/vt_video_encode_accelerator_mac.h b/media/gpu/mac/vt_video_encode_accelerator_mac.h
index e9cac9081c..5cf8b322 100644
--- a/media/gpu/mac/vt_video_encode_accelerator_mac.h
+++ b/media/gpu/mac/vt_video_encode_accelerator_mac.h
@@ -144,6 +144,9 @@
   // Context: https://crbug.com/1195177 https://crbug.com/webrtc/7304
   bool require_low_delay_ = true;
 
+  // Used to control selection of OS software encoders.
+  Config::EncoderType required_encoder_type_ = Config::EncoderType::kHardware;
+
   // Bitstream buffers ready to be used to return encoded output as a FIFO.
   base::circular_deque<std::unique_ptr<BitstreamBufferRef>>
       bitstream_buffer_queue_;
diff --git a/media/mojo/mojom/stable/stable_video_decoder.mojom b/media/mojo/mojom/stable/stable_video_decoder.mojom
index dcfb1a7..b08667a 100644
--- a/media/mojo/mojom/stable/stable_video_decoder.mojom
+++ b/media/mojo/mojom/stable/stable_video_decoder.mojom
@@ -124,6 +124,12 @@
   // Configure (or reconfigure) the decoder. This must be called before decoding
   // any frames, and must not be called while there are pending Initialize(),
   // Decode(), or Reset() requests.
+  //
+  // |cdm_context| is required for the first Initialize() call that sets up
+  // encryption and is ignored on subsequent calls.
+  //
+  // TODO(b/195769334): consider passing |cdm_context| in Construct() instead of
+  // Initialize().
   Initialize@2(VideoDecoderConfig config, bool low_delay,
                pending_remote<StableCdmContext>? cdm_context)
       => (Status status,
diff --git a/media/mojo/mojom/video_encode_accelerator.mojom b/media/mojo/mojom/video_encode_accelerator.mojom
index 69d8478..4eccfbc 100644
--- a/media/mojo/mojom/video_encode_accelerator.mojom
+++ b/media/mojo/mojom/video_encode_accelerator.mojom
@@ -129,6 +129,13 @@
     kGpuMemoryBuffer,
   };
 
+  // See media::VideoEncodeAccelerator::Config::EncoderType
+  enum EncoderType {
+    kHardware,
+    kSoftware,
+    kNoPreference,
+  };
+
   VideoPixelFormat input_format;
   gfx.mojom.Size input_visible_size;
   VideoCodecProfile output_profile;
@@ -146,6 +153,7 @@
   array<SpatialLayer> spatial_layers;
   InterLayerPredMode inter_layer_pred;
   bool require_low_delay;
+  EncoderType required_encoder_type;
 };
 
 interface VideoEncodeAccelerator {
diff --git a/media/mojo/mojom/video_encode_accelerator_mojom_traits.cc b/media/mojo/mojom/video_encode_accelerator_mojom_traits.cc
index 9d35f3d5..75c98e5 100644
--- a/media/mojo/mojom/video_encode_accelerator_mojom_traits.cc
+++ b/media/mojo/mojom/video_encode_accelerator_mojom_traits.cc
@@ -334,6 +334,45 @@
 }
 
 // static
+media::mojom::VideoEncodeAcceleratorConfig_EncoderType
+EnumTraits<media::mojom::VideoEncodeAcceleratorConfig_EncoderType,
+           media::VideoEncodeAccelerator::Config::EncoderType>::
+    ToMojom(media::VideoEncodeAccelerator::Config::EncoderType input) {
+  switch (input) {
+    case media::VideoEncodeAccelerator::Config::EncoderType::kHardware:
+      return media::mojom::VideoEncodeAcceleratorConfig_EncoderType::kHardware;
+    case media::VideoEncodeAccelerator::Config::EncoderType::kSoftware:
+      return media::mojom::VideoEncodeAcceleratorConfig_EncoderType::kSoftware;
+    case media::VideoEncodeAccelerator::Config::EncoderType::kNoPreference:
+      return media::mojom::VideoEncodeAcceleratorConfig_EncoderType::
+          kNoPreference;
+  }
+  NOTREACHED();
+  return media::mojom::VideoEncodeAcceleratorConfig_EncoderType::kHardware;
+}
+
+// static
+bool EnumTraits<media::mojom::VideoEncodeAcceleratorConfig_EncoderType,
+                media::VideoEncodeAccelerator::Config::EncoderType>::
+    FromMojom(media::mojom::VideoEncodeAcceleratorConfig_EncoderType input,
+              media::VideoEncodeAccelerator::Config::EncoderType* output) {
+  switch (input) {
+    case media::mojom::VideoEncodeAcceleratorConfig_EncoderType::kHardware:
+      *output = media::VideoEncodeAccelerator::Config::EncoderType::kHardware;
+      return true;
+    case media::mojom::VideoEncodeAcceleratorConfig_EncoderType::kSoftware:
+      *output = media::VideoEncodeAccelerator::Config::EncoderType::kSoftware;
+      return true;
+    case media::mojom::VideoEncodeAcceleratorConfig_EncoderType::kNoPreference:
+      *output =
+          media::VideoEncodeAccelerator::Config::EncoderType::kNoPreference;
+      return true;
+  }
+  NOTREACHED();
+  return false;
+}
+
+// static
 media::mojom::VideoEncodeAcceleratorConfig_ContentType
 EnumTraits<media::mojom::VideoEncodeAcceleratorConfig_ContentType,
            media::VideoEncodeAccelerator::Config::ContentType>::
@@ -528,12 +567,17 @@
   if (!input.ReadInterLayerPred(&inter_layer_pred))
     return false;
 
+  media::VideoEncodeAccelerator::Config::EncoderType required_encoder_type;
+  if (!input.ReadRequiredEncoderType(&required_encoder_type))
+    return false;
+
   *output = media::VideoEncodeAccelerator::Config(
       input_format, input_visible_size, output_profile, bitrate,
       initial_framerate, gop_length, h264_output_level, is_constrained_h264,
       storage_type, content_type, spatial_layers, inter_layer_pred);
 
   output->require_low_delay = input.require_low_delay();
+  output->required_encoder_type = required_encoder_type;
 
   return true;
 }
diff --git a/media/mojo/mojom/video_encode_accelerator_mojom_traits.h b/media/mojo/mojom/video_encode_accelerator_mojom_traits.h
index 23bcaa3..37ae5e0 100644
--- a/media/mojo/mojom/video_encode_accelerator_mojom_traits.h
+++ b/media/mojo/mojom/video_encode_accelerator_mojom_traits.h
@@ -342,6 +342,17 @@
 };
 
 template <>
+struct EnumTraits<media::mojom::VideoEncodeAcceleratorConfig_EncoderType,
+                  media::VideoEncodeAccelerator::Config::EncoderType> {
+  static media::mojom::VideoEncodeAcceleratorConfig_EncoderType ToMojom(
+      media::VideoEncodeAccelerator::Config::EncoderType input);
+
+  static bool FromMojom(
+      media::mojom::VideoEncodeAcceleratorConfig_EncoderType,
+      media::VideoEncodeAccelerator::Config::EncoderType* output);
+};
+
+template <>
 struct EnumTraits<media::mojom::VideoEncodeAcceleratorConfig_InterLayerPredMode,
                   media::VideoEncodeAccelerator::Config::InterLayerPredMode> {
   static media::mojom::VideoEncodeAcceleratorConfig_InterLayerPredMode ToMojom(
@@ -519,6 +530,11 @@
     return input.require_low_delay;
   }
 
+  static media::VideoEncodeAccelerator::Config::EncoderType
+  required_encoder_type(const media::VideoEncodeAccelerator::Config& input) {
+    return input.required_encoder_type;
+  }
+
   static bool Read(media::mojom::VideoEncodeAcceleratorConfigDataView input,
                    media::VideoEncodeAccelerator::Config* output);
 };
diff --git a/media/mojo/services/stable_video_decoder_service.cc b/media/mojo/services/stable_video_decoder_service.cc
index 799ab77..36dbb875 100644
--- a/media/mojo/services/stable_video_decoder_service.cc
+++ b/media/mojo/services/stable_video_decoder_service.cc
@@ -95,29 +95,21 @@
 
   // The |config| should have been validated at deserialization time.
   DCHECK(config.IsValidConfig());
-
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  // This can change across initializations, so reset this state.
-  if (cdm_id_) {
-    cdm_service_context_->UnregisterRemoteCdmContext(cdm_id_.value());
-    cdm_id_.reset();
-  }
-  remote_cdm_context_.reset();
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
-
   if (config.is_encrypted()) {
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-    if (!cdm_context) {
-      std::move(callback).Run(DecoderStatus::Codes::kMissingCDM,
-                              /*needs_bitstream_conversion=*/false,
-                              /*max_decode_requests=*/1,
-                              VideoDecoderType::kUnknown);
-      return;
+    if (!cdm_id_) {
+      if (!cdm_context) {
+        std::move(callback).Run(DecoderStatus::Codes::kMissingCDM,
+                                /*needs_bitstream_conversion=*/false,
+                                /*max_decode_requests=*/1,
+                                VideoDecoderType::kUnknown);
+        return;
+      }
+      remote_cdm_context_ = base::WrapRefCounted(
+          new chromeos::RemoteCdmContext(std::move(cdm_context)));
+      cdm_id_ = cdm_service_context_->RegisterRemoteCdmContext(
+          remote_cdm_context_.get());
     }
-    remote_cdm_context_ = base::WrapRefCounted(
-        new chromeos::RemoteCdmContext(std::move(cdm_context)));
-    cdm_id_ = cdm_service_context_->RegisterRemoteCdmContext(
-        remote_cdm_context_.get());
 #else
     std::move(callback).Run(DecoderStatus::Codes::kUnsupportedConfig,
                             /*needs_bitstream_conversion=*/false,
diff --git a/media/video/video_encode_accelerator.h b/media/video/video_encode_accelerator.h
index a8938ac..2b3c011 100644
--- a/media/video/video_encode_accelerator.h
+++ b/media/video/video_encode_accelerator.h
@@ -217,6 +217,10 @@
     // kGpuMemoryBuffer if a video frame has a GpuMemoryBuffer.
     enum class StorageType { kShmem, kGpuMemoryBuffer };
 
+    // Used to require a certain encoder type is selected. The default is that
+    // hardware is required.
+    enum class EncoderType { kHardware, kSoftware, kNoPreference };
+
     struct MEDIA_EXPORT SpatialLayer {
       // The encoder dimension of the spatial layer.
       int32_t width = 0;
@@ -314,6 +318,10 @@
     // This flag forces the encoder to use low latency mode, suitable for
     // RTC use cases.
     bool require_low_delay = true;
+
+    // Indicates what type of encoder is required. Useful when OS software
+    // encoders may be present and/or superior to built-in encoders.
+    EncoderType required_encoder_type = EncoderType::kHardware;
   };
 
   // Interface for clients that use VideoEncodeAccelerator. These callbacks will
diff --git a/media/video/video_encode_accelerator_adapter.cc b/media/video/video_encode_accelerator_adapter.cc
index 6726071..61cf5e4 100644
--- a/media/video/video_encode_accelerator_adapter.cc
+++ b/media/video/video_encode_accelerator_adapter.cc
@@ -87,7 +87,8 @@
     const VideoEncoder::Options& opts,
     VideoPixelFormat format,
     VideoFrame::StorageType storage_type,
-    VideoEncodeAccelerator::SupportedRateControlMode supported_rc_modes) {
+    VideoEncodeAccelerator::SupportedRateControlMode supported_rc_modes,
+    VideoEncodeAccelerator::Config::EncoderType required_encoder_type) {
   absl::optional<uint32_t> initial_framerate;
   if (opts.framerate.has_value())
     initial_framerate = static_cast<uint32_t>(opts.framerate.value());
@@ -125,6 +126,7 @@
 
   config.require_low_delay =
       opts.latency_mode == VideoEncoder::LatencyMode::Realtime;
+  config.required_encoder_type = required_encoder_type;
 
   const bool is_rgb =
       format == PIXEL_FORMAT_XBGR || format == PIXEL_FORMAT_XRGB ||
@@ -300,12 +302,14 @@
 VideoEncodeAcceleratorAdapter::VideoEncodeAcceleratorAdapter(
     GpuVideoAcceleratorFactories* gpu_factories,
     std::unique_ptr<MediaLog> media_log,
-    scoped_refptr<base::SequencedTaskRunner> callback_task_runner)
+    scoped_refptr<base::SequencedTaskRunner> callback_task_runner,
+    VideoEncodeAccelerator::Config::EncoderType required_encoder_type)
     : output_pool_(base::MakeRefCounted<base::UnsafeSharedMemoryPool>()),
       gpu_factories_(gpu_factories),
       media_log_(std::move(media_log)),
       accelerator_task_runner_(gpu_factories_->GetTaskRunner()),
-      callback_task_runner_(std::move(callback_task_runner)) {
+      callback_task_runner_(std::move(callback_task_runner)),
+      required_encoder_type_(required_encoder_type) {
   DETACH_FROM_SEQUENCE(accelerator_sequence_checker_);
 }
 
@@ -451,7 +455,7 @@
 
   auto vea_config =
       SetUpVeaConfig(profile_, options_, format, first_frame->storage_type(),
-                     supported_rc_modes_);
+                     supported_rc_modes_, required_encoder_type_);
 
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
   // Linux/ChromeOS require a special configuration to use dmabuf storage.
diff --git a/media/video/video_encode_accelerator_adapter.h b/media/video/video_encode_accelerator_adapter.h
index d47b124..ead17024 100644
--- a/media/video/video_encode_accelerator_adapter.h
+++ b/media/video/video_encode_accelerator_adapter.h
@@ -49,7 +49,9 @@
   VideoEncodeAcceleratorAdapter(
       GpuVideoAcceleratorFactories* gpu_factories,
       std::unique_ptr<MediaLog> media_log,
-      scoped_refptr<base::SequencedTaskRunner> callback_task_runner);
+      scoped_refptr<base::SequencedTaskRunner> callback_task_runner,
+      VideoEncodeAccelerator::Config::EncoderType required_encoder_type =
+          VideoEncodeAccelerator::Config::EncoderType::kHardware);
   ~VideoEncodeAcceleratorAdapter() override;
 
   enum class InputBufferKind { Any, GpuMemBuf, CpuMemBuf };
@@ -186,6 +188,9 @@
   OutputCB output_cb_;
 
   gfx::Size input_coded_size_;
+
+  VideoEncodeAccelerator::Config::EncoderType required_encoder_type_ =
+      VideoEncodeAccelerator::Config::EncoderType::kHardware;
 };
 
 }  // namespace media
diff --git a/mojo/core/core_ipcz.cc b/mojo/core/core_ipcz.cc
index 28a607a6..1c3888a 100644
--- a/mojo/core/core_ipcz.cc
+++ b/mojo/core/core_ipcz.cc
@@ -410,9 +410,15 @@
       config.byte_capacity = options->capacity_num_bytes;
     }
   }
-  DataPipe::Pair pipe = DataPipe::CreatePair(config);
-  *data_pipe_producer_handle = DataPipe::Box(std::move(pipe.producer));
-  *data_pipe_consumer_handle = DataPipe::Box(std::move(pipe.consumer));
+  absl::optional<DataPipe::Pair> pipe = DataPipe::CreatePair(config);
+  if (!pipe) {
+    // This result implies that we failed to allocate or map a new shared memory
+    // region and therefore have no transfer buffer for the pipe.
+    return MOJO_RESULT_RESOURCE_EXHAUSTED;
+  }
+
+  *data_pipe_producer_handle = DataPipe::Box(std::move(pipe->producer));
+  *data_pipe_consumer_handle = DataPipe::Box(std::move(pipe->consumer));
   return MOJO_RESULT_OK;
 }
 
diff --git a/mojo/core/ipcz_driver/data_pipe.cc b/mojo/core/ipcz_driver/data_pipe.cc
index 6837ce23..94c4157 100644
--- a/mojo/core/ipcz_driver/data_pipe.cc
+++ b/mojo/core/ipcz_driver/data_pipe.cc
@@ -120,17 +120,19 @@
 
 DataPipe::DataPipe(EndpointType endpoint_type,
                    const Config& config,
-                   scoped_refptr<SharedBuffer> buffer)
+                   scoped_refptr<SharedBuffer> buffer,
+                   scoped_refptr<SharedBufferMapping> mapping)
     : endpoint_type_(endpoint_type),
       element_size_(config.element_size),
       buffer_(std::move(buffer)),
-      data_(SharedBufferMapping::Create(buffer_->region())),
+      data_(std::move(mapping)),
       is_peer_closed_(config.is_peer_closed) {
   DCHECK_GT(element_size_, 0u);
   DCHECK_LE(element_size_, std::numeric_limits<uint32_t>::max());
   DCHECK_GT(config.byte_capacity, 0u);
   DCHECK_LE(config.byte_capacity, std::numeric_limits<uint32_t>::max());
   DCHECK_EQ(config.byte_capacity, buffer_->region().GetSize());
+  DCHECK_EQ(config.byte_capacity, data_.capacity());
 }
 
 DataPipe::~DataPipe() {
@@ -138,7 +140,7 @@
 }
 
 // static
-DataPipe::Pair DataPipe::CreatePair(const Config& config) {
+absl::optional<DataPipe::Pair> DataPipe::CreatePair(const Config& config) {
   ScopedIpczHandle producer;
   ScopedIpczHandle consumer;
   const IpczResult result =
@@ -147,16 +149,38 @@
                                ScopedIpczHandle::Receiver(consumer));
   DCHECK_EQ(result, IPCZ_RESULT_OK);
 
-  auto region = base::UnsafeSharedMemoryRegion::Create(config.byte_capacity);
+  base::UnsafeSharedMemoryRegion consumer_region =
+      base::UnsafeSharedMemoryRegion::Create(config.byte_capacity);
+  if (!consumer_region.IsValid()) {
+    return absl::nullopt;
+  }
+
+  base::UnsafeSharedMemoryRegion producer_region = consumer_region.Duplicate();
+  if (!producer_region.IsValid()) {
+    return absl::nullopt;
+  }
+
+  scoped_refptr<SharedBuffer> consumer_buffer =
+      SharedBuffer::MakeForRegion(std::move(consumer_region));
+  scoped_refptr<SharedBuffer> producer_buffer =
+      SharedBuffer::MakeForRegion(std::move(producer_region));
+  auto consumer_mapping =
+      SharedBufferMapping::Create(consumer_buffer->region());
+  auto producer_mapping =
+      SharedBufferMapping::Create(producer_buffer->region());
+  if (!consumer_mapping || !producer_mapping) {
+    return absl::nullopt;
+  }
+
   Pair pair;
   pair.consumer = base::MakeRefCounted<DataPipe>(
-      EndpointType::kConsumer, config,
-      SharedBuffer::MakeForRegion(region.Duplicate()));
+      EndpointType::kConsumer, config, std::move(consumer_buffer),
+      std::move(consumer_mapping));
   pair.consumer->AdoptPortal(std::move(consumer));
 
   pair.producer = base::MakeRefCounted<DataPipe>(
-      EndpointType::kProducer, config,
-      SharedBuffer::MakeForRegion(std::move(region)));
+      EndpointType::kProducer, config, std::move(producer_buffer),
+      std::move(producer_mapping));
   pair.producer->AdoptPortal(std::move(producer));
   return pair;
 }
@@ -497,12 +521,18 @@
     return nullptr;
   }
 
+  scoped_refptr<SharedBufferMapping> mapping =
+      SharedBufferMapping::Create(buffer->region());
+  if (!mapping) {
+    return nullptr;
+  }
+
   auto endpoint = base::MakeRefCounted<DataPipe>(
       header.endpoint_type,
       Config{.element_size = element_size,
              .byte_capacity = buffer_size,
              .is_peer_closed = header.is_peer_closed},
-      std::move(buffer));
+      std::move(buffer), std::move(mapping));
   if (!endpoint->DeserializeRingBuffer(header.ring_buffer_state)) {
     return nullptr;
   }
diff --git a/mojo/core/ipcz_driver/data_pipe.h b/mojo/core/ipcz_driver/data_pipe.h
index 7bf1bcca..64dcb820 100644
--- a/mojo/core/ipcz_driver/data_pipe.h
+++ b/mojo/core/ipcz_driver/data_pipe.h
@@ -80,17 +80,20 @@
 
   // Constructs a partial DataPipe endpoint of type `endpoint_type`, configured
   // according to `config`, and using `buffer` for the underlying transfer
-  // buffer.
+  // buffer. `mapping` must be a valid mapping of all the memory referenced by
+  // `buffer`.
   //
   // This DataPipe is not usable until it's given a portal via AdoptPortal().
   DataPipe(EndpointType endpoint_type,
            const Config& config,
-           scoped_refptr<SharedBuffer> buffer);
+           scoped_refptr<SharedBuffer> buffer,
+           scoped_refptr<SharedBufferMapping> mapping);
 
   static Type object_type() { return kDataPipe; }
 
   // Constructs a new pair of DataPipe endpoints, one for reading and one for
-  // writing.
+  // writing. May fail and return null if the data pipe's shared memory backing
+  // could not be allocated.
   struct Pair {
     Pair();
     Pair(const Pair&);
@@ -100,7 +103,7 @@
     scoped_refptr<DataPipe> consumer;
     scoped_refptr<DataPipe> producer;
   };
-  static Pair CreatePair(const Config& config);
+  static absl::optional<Pair> CreatePair(const Config& config);
 
   bool is_producer() const { return endpoint_type_ == EndpointType::kProducer; }
   bool is_consumer() const { return endpoint_type_ == EndpointType::kConsumer; }
diff --git a/mojo/public/cpp/bindings/tests/default_construct_unittest.cc b/mojo/public/cpp/bindings/tests/default_construct_unittest.cc
index f374708..38256c7 100644
--- a/mojo/public/cpp/bindings/tests/default_construct_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/default_construct_unittest.cc
@@ -40,9 +40,20 @@
   base::RunLoop run_loop;
   remote->TestMethod(TestStruct(42),
                      base::BindLambdaForTesting([&](const TestStruct& out) {
-                       EXPECT_EQ(out.value, 42);
+                       EXPECT_EQ(out.value(), 42);
                        run_loop.Quit();
                      }));
+  run_loop.Run();
+}
+
+// Ensures that a non-typemapped type with a field typemapped to a type without
+// a public default constructor initializes that field using
+// `mojo::DefaultConstructTraits::CreateInstance()` (crbug.com/1385587). Note
+// that the generated Mojo code wouldn't even compile without the accompanying
+// fix, so this test just covers the runtime behavior.
+TEST_F(DefaultConstructTest, TypeWithPrivatelyDefaultConstructibleField) {
+  mojom::TestStructContainer container;
+  EXPECT_EQ(container.test_struct.value(), 0);
 }
 
 }  // namespace mojo::test::default_construct
diff --git a/mojo/public/cpp/bindings/tests/default_construct_unittest.test-mojom b/mojo/public/cpp/bindings/tests/default_construct_unittest.test-mojom
index a528081..cbc49df5 100644
--- a/mojo/public/cpp/bindings/tests/default_construct_unittest.test-mojom
+++ b/mojo/public/cpp/bindings/tests/default_construct_unittest.test-mojom
@@ -10,6 +10,10 @@
   int32 value;
 };
 
+struct TestStructContainer {
+  TestStruct test_struct;
+};
+
 interface TestInterface {
   TestMethod(TestStruct in) => (TestStruct out);
 };
diff --git a/mojo/public/cpp/bindings/tests/default_construct_unittest_mojom_traits.h b/mojo/public/cpp/bindings/tests/default_construct_unittest_mojom_traits.h
index 42cf5e4..244cca23 100644
--- a/mojo/public/cpp/bindings/tests/default_construct_unittest_mojom_traits.h
+++ b/mojo/public/cpp/bindings/tests/default_construct_unittest_mojom_traits.h
@@ -12,20 +12,23 @@
 
 namespace test::default_construct {
 
-// For convenience, the C++ struct is simply defined in the traits header. This
+// For convenience, the C++ class is simply defined in the traits header. This
 // should never be done in non-test code.
-struct TestStruct {
-  explicit TestStruct(int value) : value(value) {}
+class TestStruct {
+ public:
+  explicit TestStruct(int value) : value_(value) {}
 
   TestStruct(const TestStruct&) = default;
   TestStruct& operator=(const TestStruct&) = default;
 
- public:
+  int value() const { return value_; }
+
+ private:
   friend mojo::DefaultConstructTraits;
 
   TestStruct() = default;
 
-  int value = 0;
+  int value_ = 0;
 };
 
 }  // namespace test::default_construct
@@ -34,12 +37,12 @@
 struct StructTraits<test::default_construct::mojom::TestStructDataView,
                     test::default_construct::TestStruct> {
   static int value(const test::default_construct::TestStruct& in) {
-    return in.value;
+    return in.value();
   }
 
   static bool Read(test::default_construct::mojom::TestStructDataView in,
                    test::default_construct::TestStruct* out) {
-    out->value = in.value();
+    *out = test::default_construct::TestStruct(in.value());
     return true;
   }
 };
diff --git a/mojo/public/cpp/system/invitation.cc b/mojo/public/cpp/system/invitation.cc
index aafc8c6..7245722 100644
--- a/mojo/public/cpp/system/invitation.cc
+++ b/mojo/public/cpp/system/invitation.cc
@@ -189,11 +189,12 @@
 // static
 ScopedMessagePipeHandle OutgoingInvitation::SendIsolated(
     PlatformChannelEndpoint channel_endpoint,
-    base::StringPiece connection_name) {
+    base::StringPiece connection_name,
+    base::ProcessHandle target_process) {
   mojo::OutgoingInvitation invitation;
   ScopedMessagePipeHandle pipe =
       invitation.AttachMessagePipe(kIsolatedPipeName);
-  SendInvitation(std::move(invitation.handle_), base::kNullProcessHandle,
+  SendInvitation(std::move(invitation.handle_), target_process,
                  channel_endpoint.TakePlatformHandle(),
                  MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL,
                  MOJO_SEND_INVITATION_FLAG_ISOLATED | invitation.extra_flags_,
@@ -204,11 +205,12 @@
 // static
 ScopedMessagePipeHandle OutgoingInvitation::SendIsolated(
     PlatformChannelServerEndpoint server_endpoint,
-    base::StringPiece connection_name) {
+    base::StringPiece connection_name,
+    base::ProcessHandle target_process) {
   mojo::OutgoingInvitation invitation;
   ScopedMessagePipeHandle pipe =
       invitation.AttachMessagePipe(kIsolatedPipeName);
-  SendInvitation(std::move(invitation.handle_), base::kNullProcessHandle,
+  SendInvitation(std::move(invitation.handle_), target_process,
                  server_endpoint.TakePlatformHandle(),
                  MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL_SERVER,
                  MOJO_SEND_INVITATION_FLAG_ISOLATED | invitation.extra_flags_,
diff --git a/mojo/public/cpp/system/invitation.h b/mojo/public/cpp/system/invitation.h
index 1caf951..b208a4f 100644
--- a/mojo/public/cpp/system/invitation.h
+++ b/mojo/public/cpp/system/invitation.h
@@ -138,7 +138,8 @@
   // connection using the same name will be disconnected.
   static ScopedMessagePipeHandle SendIsolated(
       PlatformChannelEndpoint channel_endpoint,
-      base::StringPiece connection_name = {});
+      base::StringPiece connection_name = {},
+      base::ProcessHandle target_process = base::kNullProcessHandle);
 
   // Similar to above but sends |invitation| via |server_endpoint|, which should
   // correspond to a |PlatformChannelServerEndpoint| taken from a
@@ -148,7 +149,8 @@
   // connection using the same name will be disconnected.
   static ScopedMessagePipeHandle SendIsolated(
       PlatformChannelServerEndpoint server_endpoint,
-      base::StringPiece connection_name = {});
+      base::StringPiece connection_name = {},
+      base::ProcessHandle target_process = base::kNullProcessHandle);
 
  private:
   MojoSendInvitationFlags extra_flags_ = MOJO_SEND_INVITATION_FLAG_NONE;
diff --git a/mojo/public/cpp/system/tests/invitation_unittest.cc b/mojo/public/cpp/system/tests/invitation_unittest.cc
index 9d31eb2..af4d14a 100644
--- a/mojo/public/cpp/system/tests/invitation_unittest.cc
+++ b/mojo/public/cpp/system/tests/invitation_unittest.cc
@@ -144,8 +144,8 @@
         } else {
           DCHECK(primordial_pipes);
           DCHECK_EQ(num_primordial_pipes, 1u);
-          primordial_pipes[0] =
-              OutgoingInvitation::SendIsolated(std::move(channel_endpoint));
+          primordial_pipes[0] = OutgoingInvitation::SendIsolated(
+              std::move(channel_endpoint), {}, child_process_.Handle());
         }
         break;
 #if !BUILDFLAG(IS_FUCHSIA)
@@ -158,8 +158,8 @@
         } else {
           DCHECK(primordial_pipes);
           DCHECK_EQ(num_primordial_pipes, 1u);
-          primordial_pipes[0] =
-              OutgoingInvitation::SendIsolated(std::move(server_endpoint));
+          primordial_pipes[0] = OutgoingInvitation::SendIsolated(
+              std::move(server_endpoint), {}, child_process_.Handle());
         }
         break;
 #endif  // !BUILDFLAG(IS_FUCHSIA)
diff --git a/mojo/public/tools/bindings/BUILD.gn b/mojo/public/tools/bindings/BUILD.gn
index 9b2e82f..bd68ac6 100644
--- a/mojo/public/tools/bindings/BUILD.gn
+++ b/mojo/public/tools/bindings/BUILD.gn
@@ -87,8 +87,6 @@
     "$mojom_generator_root/generators/mojolpm_templates/mojolpm_macros.tmpl",
     "$mojom_generator_root/generators/mojolpm_templates/mojolpm_to_proto_macros.tmpl",
     "$mojom_generator_root/generators/mojolpm_templates/mojolpm_traits_specialization_macros.tmpl",
-    "$mojom_generator_root/generators/ts_templates/module_definition.tmpl",
-    "$mojom_generator_root/generators/ts_templates/mojom.tmpl",
   ]
   script = mojom_generator_script
 
@@ -98,7 +96,6 @@
     "$target_gen_dir/java_templates.zip",
     "$target_gen_dir/mojolpm_templates.zip",
     "$target_gen_dir/js_templates.zip",
-    "$target_gen_dir/ts_templates.zip",
   ]
   args = [
     "-o",
diff --git a/mojo/public/tools/bindings/compile_typescript.py b/mojo/public/tools/bindings/compile_typescript.py
deleted file mode 100644
index 9bf71b9..0000000
--- a/mojo/public/tools/bindings/compile_typescript.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# Copyright 2019 The Chromium Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import os
-import sys
-import argparse
-
-# Module 'node' has no 'RunNode' member (no-member)
-# pylint: disable=no-member
-
-_HERE_PATH = os.path.dirname(__file__)
-_SRC_PATH = os.path.normpath(os.path.join(_HERE_PATH, '..', '..', '..', '..'))
-
-sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'node'))
-import node
-import node_modules
-
-def main(argv):
-  parser = argparse.ArgumentParser()
-  parser.add_argument('--tsconfig_path', required=True)
-  args = parser.parse_args(argv)
-
-  result = node.RunNode([node_modules.PathToTypescript()] +
-                        ['--project', args.tsconfig_path])
-  if len(result) != 0:
-    raise RuntimeError('Failed to compile Typescript: \n%s' % result)
-
-if __name__ == '__main__':
-  main(sys.argv[1:])
diff --git a/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm.cc.tmpl b/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm.cc.tmpl
index c572e2233..5c9cbb2c 100644
--- a/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm.cc.tmpl
+++ b/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm.cc.tmpl
@@ -416,9 +416,9 @@
 {%-           set kind = param.kind %}
 {%-           set param_mojom_type = kind|cpp_wrapper_type(add_same_module_namespaces=true) %}
 {%-           set param_maybe_mojom_type = kind|cpp_wrapper_type(add_same_module_namespaces=true, ignore_nullable=True) %}
-  {{param_mojom_type}} local_{{name}};
+  {{param_mojom_type}} local_{{name}} = mojo::DefaultConstructTraits::CreateInstance<{{param_mojom_type}}>();
 {%-           if kind|is_nullable_kind %}
-  {{param_maybe_mojom_type}} local_maybe_{{name}};
+  {{param_maybe_mojom_type}} local_maybe_{{name}} = mojo::DefaultConstructTraits::CreateInstance<{{param_maybe_mojom_type}}>();
 {%-           endif %}
 {%-         endfor %}
 
diff --git a/mojo/public/tools/bindings/generators/mojom_cpp_generator.py b/mojo/public/tools/bindings/generators/mojom_cpp_generator.py
index 6891f25..f420c26 100644
--- a/mojo/public/tools/bindings/generators/mojom_cpp_generator.py
+++ b/mojo/public/tools/bindings/generators/mojom_cpp_generator.py
@@ -544,6 +544,9 @@
 
   def _DefaultValue(self, field):
     if not field.default:
+      if self._IsTypemappedKind(field.kind):
+        return "mojo::DefaultConstructTraits::CreateInstance<%s>()" % (
+            self._GetCppWrapperType(field.kind))
       return ""
 
     if mojom.IsStructKind(field.kind):
diff --git a/mojo/public/tools/bindings/generators/mojom_ts_generator.py b/mojo/public/tools/bindings/generators/mojom_ts_generator.py
deleted file mode 100644
index 271ecb21..0000000
--- a/mojo/public/tools/bindings/generators/mojom_ts_generator.py
+++ /dev/null
@@ -1,160 +0,0 @@
-# Copyright 2019 The Chromium Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""Generates Typescript source files from a mojom.Module."""
-
-import argparse
-import mojom.generate.generator as generator
-import mojom.generate.module as mojom
-from mojom.generate.template_expander import UseJinja
-import os
-
-GENERATOR_PREFIX = 'ts'
-
-_kind_to_typescript_type = {
-  mojom.BOOL:                  "boolean",
-  mojom.INT8:                  "number",
-  mojom.UINT8:                 "number",
-  mojom.INT16:                 "number",
-  mojom.UINT16:                "number",
-  mojom.INT32:                 "number",
-  mojom.UINT32:                "number",
-  mojom.FLOAT:                 "number",
-  mojom.INT64:                 "bigint",
-  mojom.UINT64:                "bigint",
-  mojom.DOUBLE:                "number",
-  mojom.STRING:                "string",
-  mojom.NULLABLE_STRING:       "string",
-  mojom.HANDLE:                "MojoHandle",
-  mojom.DCPIPE:                "MojoHandle",
-  mojom.DPPIPE:                "MojoHandle",
-  mojom.MSGPIPE:               "MojoHandle",
-  mojom.SHAREDBUFFER:          "MojoHandle",
-  mojom.NULLABLE_HANDLE:       "MojoHandle",
-  mojom.NULLABLE_DCPIPE:       "MojoHandle",
-  mojom.NULLABLE_DPPIPE:       "MojoHandle",
-  mojom.NULLABLE_MSGPIPE:      "MojoHandle",
-  mojom.NULLABLE_SHAREDBUFFER: "MojoHandle",
-}
-
-class TypescriptStylizer(generator.Stylizer):
-  def StylizeModule(self, mojom_namespace):
-    return '.'.join(generator.ToCamel(word, lower_initial=True)
-                        for word in mojom_namespace.split('.'))
-
-  def StylizeField(self, mojom_name):
-    return generator.ToCamel(mojom_name, lower_initial=True)
-
-  def StylizeConstant(self, mojom_name):
-    return generator.ToUpperSnakeCase(mojom_name)
-
-class Generator(generator.Generator):
-  def _GetParameters(self, use_es_modules=False):
-    return {
-        "module": self.module,
-        "use_es_modules": use_es_modules,
-        "enums": self.module.enums,
-        "structs": self.module.structs,
-    }
-
-  @staticmethod
-  def GetTemplatePrefix():
-    return "ts_templates"
-
-  def GetFilters(self):
-    ts_filters = {
-        "typescript_type_with_nullability": self._TypescriptTypeWithNullability,
-        "constant_value": self._ConstantValue,
-        "relative_path": self._RelativePath,
-    }
-    return ts_filters
-
-  @UseJinja("mojom.tmpl")
-  def _GenerateBindings(self):
-    return self._GetParameters()
-
-  @UseJinja("mojom.tmpl")
-  def _GenerateESModulesBindings(self):
-    return self._GetParameters(use_es_modules=True)
-
-  def GenerateFiles(self, unparsed_args):
-    if self.variant:
-      raise Exception("Variants not supported in JavaScript bindings.")
-
-    parser = argparse.ArgumentParser()
-    parser.add_argument('--ts_use_es_modules',
-                        action="store_true",
-                        help="Generate bindings that use ES Modules.")
-    args = parser.parse_args(unparsed_args)
-
-    self.module.Stylize(TypescriptStylizer())
-
-    self._SetUniqueAliasesForImports()
-
-    if args.ts_use_es_modules == True:
-      self.Write(self._GenerateESModulesBindings(),
-                 "%s-lite.m.ts" % self.module.path)
-    else:
-      self.Write(self._GenerateBindings(), "%s-lite.ts" % self.module.path)
-
-  # Function that sets unique alias names for all imported modules based on
-  # their namespace. For example, for two imported modules with namespace
-  # "mojo.foo", we would generate two aliases "mojoFoo" and "mojoFoo$0".
-  def _SetUniqueAliasesForImports(self):
-    used_aliases = set()
-    for each_import in self.module.imports:
-      # Per the style guide module aliases start with lower case.
-      simple_alias = generator.ToCamel(identifier=each_import.namespace,
-                                       lower_initial=True,
-                                       delimiter='.')
-      unique_alias = simple_alias
-      counter = 0
-      while unique_alias in used_aliases:
-        unique_alias = '%s$%d' % (simple_alias, counter)
-        counter += 1
-
-      used_aliases.add(unique_alias)
-      each_import.unique_alias = unique_alias
-
-  def _TypescriptType(self, kind, use_es_modules):
-    if kind in mojom.PRIMITIVES:
-      return _kind_to_typescript_type[kind]
-
-    if mojom.IsStructKind(kind):
-      if kind.module and kind.module.path != self.module.path:
-        namespace = (kind.module.unique_alias
-                     if use_es_modules else kind.module.namespace)
-        return '.'.join([namespace, kind.name])
-
-      return kind.name
-
-    raise Exception("Type is not supported yet.")
-
-  def _TypescriptTypeWithNullability(self, kind, use_es_modules):
-    return (self._TypescriptType(kind, use_es_modules) +
-            (" | null" if mojom.IsNullableKind(kind) else ""))
-
-  def _ConstantValue(self, constant):
-    value = constant.value
-    if isinstance(value, (mojom.EnumValue, mojom.NamedValue)):
-      # TODO(crbug.com/1008761): Support EnumValue and NamedValue.
-      raise Exception("Constant value not supported yet. %s" % value)
-
-    if isinstance(value, mojom.BuiltinValue):
-      if value.value == "double.INFINITY" or value.value == "float.INFINITY":
-        return "Infinity";
-      if value.value == "double.NEGATIVE_INFINITY" or \
-         value.value == "float.NEGATIVE_INFINITY":
-        return "-Infinity";
-      if value.value == "double.NAN" or value.value == "float.NAN":
-        return "NaN";
-      raise Exception("Unknown BuiltinValue: %s" % value.value)
-
-    if constant.kind == mojom.INT64 or constant.kind == mojom.UINT64:
-      return "BigInt('%s')" % value
-
-    return value
-
-  def _RelativePath(self, path):
-    return os.path.relpath(path, os.path.dirname(self.module.path))
diff --git a/mojo/public/tools/bindings/generators/ts_templates/module_definition.tmpl b/mojo/public/tools/bindings/generators/ts_templates/module_definition.tmpl
deleted file mode 100644
index a00f9a03..0000000
--- a/mojo/public/tools/bindings/generators/ts_templates/module_definition.tmpl
+++ /dev/null
@@ -1,36 +0,0 @@
-{%- if not use_es_modules -%}
-namespace {{module.namespace}} {
-{% endif %}
-{#--- Constants #}
-{%- for constant in module.constants %}
-export const {{constant.name}}: {{constant.kind|typescript_type_with_nullability(use_es_modules)}} =
-  {{constant|constant_value}};
-{%- endfor %}
-
-{#--- Enums #}
-{%- for enum in enums %}
-export enum {{enum.name}} {
-{%- for field in enum.fields %}
-  {{field.name}} = {{field.numeric_value}},
-{%- endfor %}
-{%- if enum.min_value is not none %}
-  MIN_VALUE = {{enum.min_value}},
-{%- endif %}
-{%- if enum.max_value is not none %}
-  MAX_VALUE = {{enum.max_value}},
-{%- endif %}
-}
-{% endfor %}
-
-{#--- Structs #}
-{%- for struct in structs %}
-export interface {{struct.name}} {
-{%- for packed_field in struct.packed.packed_fields %}
-    {{packed_field.field.name}}: {{packed_field.field.kind|typescript_type_with_nullability(use_es_modules)}};
-{%- endfor %}
-}
-{% endfor %}
-
-{%- if not use_es_modules %}
-} // namespace {{module.namespace}}
-{% endif %}
diff --git a/mojo/public/tools/bindings/generators/ts_templates/mojom.tmpl b/mojo/public/tools/bindings/generators/ts_templates/mojom.tmpl
deleted file mode 100644
index ff1232b5..0000000
--- a/mojo/public/tools/bindings/generators/ts_templates/mojom.tmpl
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-{%- for import in module.imports %}
-{%- if use_es_modules %}
-{#
- #  We add a ".js" file extension because otherwise the compiled JS would import
- #  "-lite.m" which doesn't work on browsers unless served with a
- #  "text/javascript" MIME type.
- #}
-import * as {{import.unique_alias}} from '{{import.path|relative_path}}-lite.m.js';
-{% else %}
-/// <reference path="{{import.path|relative_path}}-lite.ts" />
-{% endif %}
-{%- endfor %}
-
-{% include "module_definition.tmpl" %}
diff --git a/mojo/public/tools/bindings/mojom.gni b/mojo/public/tools/bindings/mojom.gni
index 80fc005..5afce408 100644
--- a/mojo/public/tools/bindings/mojom.gni
+++ b/mojo/public/tools/bindings/mojom.gni
@@ -110,7 +110,6 @@
       "$mojom_generator_root/generators/mojom_java_generator.py",
       "$mojom_generator_root/generators/mojom_js_generator.py",
       "$mojom_generator_root/generators/mojom_mojolpm_generator.py",
-      "$mojom_generator_root/generators/mojom_ts_generator.py",
       "$mojom_generator_script",
     ]
 
@@ -1721,7 +1720,7 @@
 
   use_typescript_for_target =
       enable_typescript_bindings && defined(invoker.use_typescript_sources) &&
-      invoker.use_typescript_sources
+      invoker.use_typescript_sources && defined(invoker.webui_module_path)
 
   if (!use_typescript_for_target && defined(invoker.use_typescript_sources)) {
     not_needed(invoker, [ "use_typescript_sources" ])
@@ -1954,39 +1953,14 @@
   }
   if ((generate_fuzzing || !defined(invoker.cpp_only) || !invoker.cpp_only) &&
       use_typescript_for_target) {
-    generator_js_target_names = []
-    source_filelist = []
-    foreach(source, sources_list) {
-      source_filelist += [ rebase_path(source, root_build_dir) ]
-    }
-
-    dependency_types = [
-      {
-        name = "regular"
-        ts_extension = ".ts"
-        js_extension = ".js"
-      },
-      {
-        name = "es_modules"
-        ts_extension = ".m.ts"
-        js_extension = ".m.js"
-      },
-    ]
-
-    foreach(dependency_type, dependency_types) {
-      ts_outputs = []
-      js_outputs = []
-
-      foreach(base_path, output_file_base_paths) {
-        ts_outputs +=
-            [ "$root_gen_dir/$base_path-lite${dependency_type.ts_extension}" ]
-        js_outputs +=
-            [ "$root_gen_dir/$base_path-lite${dependency_type.js_extension}" ]
+    if (sources_list != []) {
+      source_filelist = []
+      foreach(source, sources_list) {
+        source_filelist += [ rebase_path(source, root_build_dir) ]
       }
 
       # Generate Typescript bindings.
-      generator_ts_target_name =
-          "${target_name}_${dependency_type.name}__ts__generator"
+      generator_ts_target_name = "${target_name}_ts__generator"
 
       action(generator_ts_target_name) {
         script = mojom_generator_script
@@ -1997,7 +1971,10 @@
           "//mojo/public/tools/bindings:precompile_templates",
         ]
 
-        outputs = ts_outputs
+        outputs = []
+        foreach(base_path, output_file_base_paths) {
+          outputs += [ "$root_gen_dir/mojom-webui/$base_path-webui.ts" ]
+        }
         args = common_generator_args
         response_file_contents = source_filelist
 
@@ -2007,78 +1984,23 @@
           "typescript",
         ]
 
-        if (dependency_type.name == "es_modules") {
-          args += [ "--ts_use_es_modules" ]
+        if (!defined(invoker.scramble_message_ids) ||
+            invoker.scramble_message_ids) {
+          inputs += message_scrambling_inputs
+          args += message_scrambling_args
         }
 
-        # TODO(crbug.com/1007587): Support scramble_message_ids.
+        # TODO(crbug.com/1007587): Support scramble_message_ids if above is
+        # insufficient.
         # TODO(crbug.com/1007591): Support generate_fuzzing.
       }
-
-      # Create tsconfig.json for the generated Typescript.
-      tsconfig_filename =
-          "$target_gen_dir/$target_name-${dependency_type.name}-tsconfig.json"
-      tsconfig = {
-      }
-      tsconfig.compilerOptions = {
-        composite = true
-        target = "es6"
-        module = "es6"
-        lib = [
-          "es6",
-          "esnext.bigint",
-        ]
-        strict = true
-      }
-      tsconfig.files = []
-      foreach(base_path, output_file_base_paths) {
-        tsconfig.files += [ rebase_path(
-                "$root_gen_dir/$base_path-lite${dependency_type.ts_extension}",
-                target_gen_dir,
-                root_gen_dir) ]
-      }
-      tsconfig.references = []
-
-      # Get tsconfigs for deps.
-      foreach(d, all_deps) {
-        dep_target_gen_dir = rebase_path(get_label_info(d, "target_gen_dir"))
-        dep_name = get_label_info(d, "name")
-        reference = {
-        }
-        reference.path = "$dep_target_gen_dir/$dep_name-${dependency_type.name}-tsconfig.json"
-        tsconfig.references += [ reference ]
-      }
-      write_file(tsconfig_filename, tsconfig, "json")
-
-      # Compile previously generated Typescript to Javascript.
-      generator_js_target_name =
-          "${target_name}_${dependency_type.name}__js__generator"
-      generator_js_target_names += [ generator_js_target_name ]
-
-      action(generator_js_target_name) {
-        script = "$mojom_generator_root/compile_typescript.py"
-        sources = ts_outputs
-        outputs = js_outputs
-        public_deps = [ ":$generator_ts_target_name" ]
-        foreach(d, all_deps) {
-          full_name = get_label_info(d, "label_no_toolchain")
-          public_deps +=
-              [ "${full_name}_${dependency_type.name}__js__generator" ]
-        }
-
-        absolute_tsconfig_path =
-            rebase_path(tsconfig_filename, "", target_gen_dir)
-        args = [ "--tsconfig_path=$absolute_tsconfig_path" ]
-      }
     }
 
-    js_target_name = target_name + "_js"
-    group(js_target_name) {
+    ts_target_name = target_name + "_ts"
+    group(ts_target_name) {
       public_deps = []
       if (sources_list != []) {
-        foreach(generator_js_target_name, generator_js_target_names) {
-          public_deps += [ ":$generator_js_target_name" ]
-        }
+        public_deps += [ ":$generator_ts_target_name" ]
       }
 
       foreach(d, all_deps) {
@@ -2087,17 +2009,11 @@
       }
     }
 
-    group(js_data_deps_target_name) {
-      data = js_outputs
-      deps = []
-      foreach(generator_js_target_name, generator_js_target_names) {
-        deps += [ ":$generator_js_target_name" ]
-      }
-      data_deps = []
-      foreach(d, all_deps) {
-        full_name = get_label_info(d, "label_no_toolchain")
-        data_deps += [ "${full_name}_js_data_deps" ]
-      }
+    # Use |js_target_name| to track dependencies since TS targets may depend on
+    # either TS or JS targets.
+    js_target_name = target_name + "_js"
+    group(js_target_name) {
+      public_deps = [ ":$ts_target_name" ]
     }
   }
 }
diff --git a/mojo/public/tools/bindings/mojom_bindings_generator.py b/mojo/public/tools/bindings/mojom_bindings_generator.py
index 5a5cace..e0ecc221 100755
--- a/mojo/public/tools/bindings/mojom_bindings_generator.py
+++ b/mojo/public/tools/bindings/mojom_bindings_generator.py
@@ -54,7 +54,6 @@
     "javascript": "mojom_js_generator",
     "java": "mojom_java_generator",
     "mojolpm": "mojom_mojolpm_generator",
-    "typescript": "mojom_ts_generator",
 }
 
 _BUILTIN_CHECKS = {
diff --git a/net/cert/cert_verify_proc_unittest.cc b/net/cert/cert_verify_proc_unittest.cc
index 17503fd..4590f051 100644
--- a/net/cert/cert_verify_proc_unittest.cc
+++ b/net/cert/cert_verify_proc_unittest.cc
@@ -4312,6 +4312,24 @@
   EXPECT_THAT(Verify(), IsOk());
 }
 
+TEST_P(CertVerifyProcConstraintsTest, BasicConstraintsNotPresentRoot) {
+  chain_[3]->EraseExtension(der::Input(kBasicConstraintsOid));
+
+  if (VerifyProcTypeIsBuiltin() || VerifyProcTypeIsMacAtMostOS10_14() ||
+      verify_proc_type() == CERT_VERIFY_PROC_ANDROID ||
+      verify_proc_type() == CERT_VERIFY_PROC_WIN) {
+    EXPECT_THAT(Verify(), IsOk());
+  } else {
+    EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID));
+  }
+}
+
+TEST_P(CertVerifyProcConstraintsTest, BasicConstraintsNotPresentIntermediate) {
+  chain_[2]->EraseExtension(der::Input(kBasicConstraintsOid));
+
+  EXPECT_THAT(Verify(), IsError(ExpectedIntermediateConstraintError()));
+}
+
 TEST_P(CertVerifyProcConstraintsTest, NameConstraintsNotMatchingRoot) {
   chain_[3]->SetNameConstraintsDnsNames(/*permitted_dns_names=*/{"example.org"},
                                         /*excluded_dns_names=*/{});
diff --git a/net/websockets/websocket_frame.h b/net/websockets/websocket_frame.h
index 0499275a..3d293ea8 100644
--- a/net/websockets/websocket_frame.h
+++ b/net/websockets/websocket_frame.h
@@ -50,13 +50,10 @@
            opcode == kOpCodePong;
   }
 
-  // These values must be a compile-time constant. "enum hack" is used here
-  // to make MSVC happy.
-  enum {
-    kBaseHeaderSize = 2,
-    kMaximumExtendedLengthSize = 8,
-    kMaskingKeyLength = 4
-  };
+  // These values must be compile-time constants.
+  static constexpr size_t kBaseHeaderSize = 2;
+  static constexpr size_t kMaximumExtendedLengthSize = 8;
+  static constexpr size_t kMaskingKeyLength = 4;
 
   // Contains four-byte data representing "masking key" of WebSocket frames.
   struct WebSocketMaskingKey {
diff --git a/printing/metafile_skia.cc b/printing/metafile_skia.cc
index 2150153..ecbd282 100644
--- a/printing/metafile_skia.cc
+++ b/printing/metafile_skia.cc
@@ -32,6 +32,7 @@
 // Note that headers in third_party/skia/src are fragile.  This is
 // an experimental, fragile, and diagnostic-only document type.
 #include "third_party/skia/src/utils/SkMultiPictureDocument.h"
+#include "ui/gfx/geometry/size_conversions.h"
 #include "ui/gfx/geometry/skia_conversions.h"
 
 #if BUILDFLAG(IS_MAC)
@@ -79,7 +80,7 @@
 };
 
 struct MetafileSkiaData {
-  cc::PaintRecorder recorder;  // Current recording
+  cc::InspectablePaintRecorder recorder;  // Current recording
 
   std::vector<Page> pages;
   std::unique_ptr<SkStreamAsset> data_stream;
@@ -147,8 +148,7 @@
 
   float inverse_scale = 1.0 / scale_factor;
   cc::PaintCanvas* canvas = data_->recorder.beginRecording(
-      inverse_scale * physical_page_size.width(),
-      inverse_scale * physical_page_size.height());
+      gfx::ScaleToCeiledSize(physical_page_size, inverse_scale));
   // Recording canvas is owned by the `data_->recorder`.  No ref() necessary.
   if (content_area != gfx::Rect(page_size) ||
       page_orientation != mojom::PageOrientation::kUpright) {
@@ -189,11 +189,11 @@
 
   sk_sp<cc::PaintRecord> pic = data_->recorder.finishRecordingAsPicture();
   if (data_->scale_factor != 1.0f) {
-    cc::PaintCanvas* canvas = data_->recorder.beginRecording(
-        data_->size.width(), data_->size.height());
+    cc::PaintRecorder recorder;
+    cc::PaintCanvas* canvas = recorder.beginRecording();
     canvas->scale(data_->scale_factor, data_->scale_factor);
     canvas->drawPicture(pic);
-    pic = data_->recorder.finishRecordingAsPicture();
+    pic = recorder.finishRecordingAsPicture();
   }
   data_->pages.emplace_back(data_->size, std::move(pic));
   return true;
diff --git a/remoting/host/chromoting_host_services_client.cc b/remoting/host/chromoting_host_services_client.cc
index 3e13f0a..ac780c3 100644
--- a/remoting/host/chromoting_host_services_client.cc
+++ b/remoting/host/chromoting_host_services_client.cc
@@ -4,8 +4,8 @@
 
 #include "remoting/host/chromoting_host_services_client.h"
 
-#include "base/bind.h"
 #include "base/environment.h"
+#include "base/functional/bind.h"
 #include "base/notreached.h"
 #include "base/sequence_checker.h"
 #include "build/build_config.h"
@@ -26,6 +26,19 @@
 
 bool g_initialized = false;
 
+mojo::PendingRemote<mojom::ChromotingHostServices> ConnectToServer(
+    mojo::IsolatedConnection& connection) {
+  auto server_name = GetChromotingHostServicesServerName();
+  auto endpoint = mojo::NamedPlatformChannel::ConnectToServer(server_name);
+  if (!endpoint.is_valid()) {
+    LOG(WARNING) << "Cannot connect to IPC through server name " << server_name
+                 << ". Endpoint is invalid.";
+    return {};
+  }
+  return mojo::PendingRemote<mojom::ChromotingHostServices>(
+      connection.Connect(std::move(endpoint)), /* version= */ 0);
+}
+
 }  // namespace
 
 #if BUILDFLAG(IS_LINUX)
@@ -38,15 +51,16 @@
 
 ChromotingHostServicesClient::ChromotingHostServicesClient()
     : ChromotingHostServicesClient(base::Environment::Create(),
-                                   GetChromotingHostServicesServerName()) {
+                                   base::BindRepeating(&ConnectToServer)) {
   DCHECK(g_initialized)
       << "ChromotingHostServicesClient::Initialize() has not been called.";
 }
 
 ChromotingHostServicesClient::ChromotingHostServicesClient(
     std::unique_ptr<base::Environment> environment,
-    const mojo::NamedPlatformChannel::ServerName& server_name)
-    : environment_(std::move(environment)), server_name_(server_name) {}
+    ConnectToServerCallback connect_to_server)
+    : environment_(std::move(environment)),
+      connect_to_server_(std::move(connect_to_server)) {}
 
 ChromotingHostServicesClient::~ChromotingHostServicesClient() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -86,15 +100,8 @@
     return true;
   }
 
-  auto endpoint = mojo::NamedPlatformChannel::ConnectToServer(server_name_);
-  if (!endpoint.is_valid()) {
-    LOG(WARNING) << "Cannot connect to IPC through server name " << server_name_
-                 << ". Endpoint is invalid.";
-    return false;
-  }
   connection_ = std::make_unique<mojo::IsolatedConnection>();
-  mojo::PendingRemote<mojom::ChromotingHostServices> pending_remote(
-      connection_->Connect(std::move(endpoint)), /* version= */ 0);
+  auto pending_remote = connect_to_server_.Run(*connection_);
   if (!pending_remote.is_valid()) {
     LOG(WARNING) << "Invalid message pipe.";
     connection_.reset();
diff --git a/remoting/host/chromoting_host_services_client.h b/remoting/host/chromoting_host_services_client.h
index 13e4e79..9b28e59a 100644
--- a/remoting/host/chromoting_host_services_client.h
+++ b/remoting/host/chromoting_host_services_client.h
@@ -12,7 +12,6 @@
 #include "base/thread_annotations.h"
 #include "build/build_config.h"
 #include "mojo/public/cpp/bindings/remote.h"
-#include "mojo/public/cpp/platform/named_platform_channel.h"
 #include "remoting/host/chromoting_host_services_provider.h"
 #include "remoting/host/mojom/chromoting_host_services.mojom.h"
 
@@ -55,14 +54,16 @@
  private:
   friend class ChromotingHostServicesClientTest;
 
+  using ConnectToServerCallback = base::RepeatingCallback<mojo::PendingRemote<
+      mojom::ChromotingHostServices>(mojo::IsolatedConnection&)>;
+
 #if BUILDFLAG(IS_LINUX)
   static constexpr char kChromeRemoteDesktopSessionEnvVar[] =
       "CHROME_REMOTE_DESKTOP_SESSION";
 #endif
 
-  ChromotingHostServicesClient(
-      std::unique_ptr<base::Environment> environment,
-      const mojo::NamedPlatformChannel::ServerName& server_name);
+  ChromotingHostServicesClient(std::unique_ptr<base::Environment> environment,
+                               ConnectToServerCallback connect_to_server);
 
   // Attempts to connect to the IPC server if the connection has not been
   // established. Returns a boolean indicating whether there is a valid IPC
@@ -77,7 +78,7 @@
   SEQUENCE_CHECKER(sequence_checker_);
 
   std::unique_ptr<base::Environment> environment_;
-  mojo::NamedPlatformChannel::ServerName server_name_;
+  ConnectToServerCallback connect_to_server_;
   std::unique_ptr<mojo::IsolatedConnection> connection_
       GUARDED_BY_CONTEXT(sequence_checker_);
   mojo::Remote<mojom::ChromotingHostServices> remote_
diff --git a/remoting/host/chromoting_host_services_client_unittest.cc b/remoting/host/chromoting_host_services_client_unittest.cc
index fec77aad..6e7e7b72 100644
--- a/remoting/host/chromoting_host_services_client_unittest.cc
+++ b/remoting/host/chromoting_host_services_client_unittest.cc
@@ -16,9 +16,10 @@
 #include "base/run_loop.h"
 #include "base/test/task_environment.h"
 #include "build/build_config.h"
-#include "components/named_mojo_ipc_server/named_mojo_ipc_server.h"
-#include "components/named_mojo_ipc_server/named_mojo_ipc_test_util.h"
-#include "remoting/host/chromoting_host.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
+#include "mojo/public/cpp/system/isolated_connection.h"
 #include "remoting/host/mojo_caller_security_checker.h"
 #include "remoting/host/mojom/chromoting_host_services.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -39,44 +40,32 @@
 
  protected:
   void SetChromeRemoteDesktopSessionEnvVar(bool is_crd_session);
-  void WaitForServerEndpointCreated();
   void WaitForSessionServicesBound();
   void SetRemoteDisconnectCallback(base::OnceClosure callback);
 
   base::test::TaskEnvironment task_environment_;
   raw_ptr<base::Environment> environment_;
+  bool is_server_started_ = true;
   std::unique_ptr<ChromotingHostServicesClient> client_;
-  std::unique_ptr<
-      named_mojo_ipc_server::NamedMojoIpcServer<mojom::ChromotingHostServices>>
-      ipc_server_;
+  mojo::ReceiverSet<mojom::ChromotingHostServices> host_services_receivers_;
   std::vector<mojo::PendingReceiver<mojom::ChromotingSessionServices>>
-      receivers_;
+      session_services_receivers_;
 
  private:
-  void OnServerEndpointCreated();
-
-  // Used to block the thread until the server has created the endpoint.
-  std::unique_ptr<base::RunLoop> on_server_endpoint_created_run_loop_;
+  mojo::PendingRemote<mojom::ChromotingHostServices> ConnectToServer(
+      mojo::IsolatedConnection& connection);
 
   // Used to block the thread until a session services bind request is received.
   std::unique_ptr<base::RunLoop> session_services_bound_run_loop_;
 };
 
 ChromotingHostServicesClientTest::ChromotingHostServicesClientTest() {
-  auto test_server_name =
-      named_mojo_ipc_server::test::GenerateRandomServerName();
   auto environment = base::Environment::Create();
   environment_ = environment.get();
   client_ = base::WrapUnique(new ChromotingHostServicesClient(
-      std::move(environment), test_server_name));
-  ipc_server_ = std::make_unique<
-      named_mojo_ipc_server::NamedMojoIpcServer<mojom::ChromotingHostServices>>(
-      test_server_name, this, base::BindRepeating(&IsTrustedMojoEndpoint));
-  ipc_server_->set_on_server_endpoint_created_callback_for_testing(
-      base::BindRepeating(
-          &ChromotingHostServicesClientTest::OnServerEndpointCreated,
-          base::Unretained(this)));
-  on_server_endpoint_created_run_loop_ = std::make_unique<base::RunLoop>();
+      std::move(environment),
+      base::BindRepeating(&ChromotingHostServicesClientTest::ConnectToServer,
+                          base::Unretained(this))));
   session_services_bound_run_loop_ = std::make_unique<base::RunLoop>();
   SetChromeRemoteDesktopSessionEnvVar(true);
 }
@@ -85,12 +74,11 @@
 
 void ChromotingHostServicesClientTest::BindSessionServices(
     mojo::PendingReceiver<mojom::ChromotingSessionServices> receiver) {
-  receivers_.push_back(std::move(receiver));
+  session_services_receivers_.push_back(std::move(receiver));
   session_services_bound_run_loop_->Quit();
 }
 
 void ChromotingHostServicesClientTest::TearDown() {
-  ipc_server_->StopServer();
   task_environment_.RunUntilIdle();
 }
 
@@ -108,11 +96,6 @@
   // No-op on other platforms.
 }
 
-void ChromotingHostServicesClientTest::WaitForServerEndpointCreated() {
-  on_server_endpoint_created_run_loop_->Run();
-  on_server_endpoint_created_run_loop_ = std::make_unique<base::RunLoop>();
-}
-
 void ChromotingHostServicesClientTest::WaitForSessionServicesBound() {
   session_services_bound_run_loop_->Run();
   session_services_bound_run_loop_ = std::make_unique<base::RunLoop>();
@@ -123,13 +106,21 @@
   client_->on_session_disconnected_callback_for_testing_ = std::move(callback);
 }
 
-void ChromotingHostServicesClientTest::OnServerEndpointCreated() {
-  on_server_endpoint_created_run_loop_->Quit();
+mojo::PendingRemote<mojom::ChromotingHostServices>
+ChromotingHostServicesClientTest::ConnectToServer(
+    mojo::IsolatedConnection& connection) {
+  if (!is_server_started_) {
+    return mojo::PendingRemote<mojom::ChromotingHostServices>();
+  }
+  mojo::PendingReceiver<mojom::ChromotingHostServices> pending_receiver;
+  auto pending_remote = pending_receiver.InitWithNewPipeAndPassRemote();
+  host_services_receivers_.Add(this, std::move(pending_receiver));
+  return pending_remote;
 }
 
 TEST_F(ChromotingHostServicesClientTest,
        ServerNotRunning_GetSessionServicesReturnsNull) {
-  ipc_server_->StopServer();
+  is_server_started_ = false;
   ASSERT_EQ(client_->GetSessionServices(), nullptr);
 }
 
@@ -138,8 +129,6 @@
 TEST_F(ChromotingHostServicesClientTest,
        NotInRemoteDesktopSession_GetSessionServicesReturnsNull) {
   SetChromeRemoteDesktopSessionEnvVar(false);
-  ipc_server_->StartServer();
-  WaitForServerEndpointCreated();
   ASSERT_EQ(client_->GetSessionServices(), nullptr);
 }
 
@@ -147,40 +136,30 @@
 
 TEST_F(ChromotingHostServicesClientTest,
        CallGetSessionServicesTwice_SamePointerReturned) {
-  ipc_server_->StartServer();
-  WaitForServerEndpointCreated();
   auto* session_services = client_->GetSessionServices();
   ASSERT_NE(session_services, nullptr);
+  ASSERT_EQ(host_services_receivers_.size(), 1u);
   WaitForSessionServicesBound();
-  ASSERT_EQ(receivers_.size(), 1u);
+  ASSERT_EQ(session_services_receivers_.size(), 1u);
   ASSERT_EQ(client_->GetSessionServices(), session_services);
+  ASSERT_EQ(host_services_receivers_.size(), 1u);
+  ASSERT_EQ(session_services_receivers_.size(), 1u);
 }
 
 TEST_F(ChromotingHostServicesClientTest,
        ServerClosesReceiverAndClientReconnects) {
-  ipc_server_->StartServer();
-  WaitForServerEndpointCreated();
   ASSERT_NE(client_->GetSessionServices(), nullptr);
   WaitForSessionServicesBound();
-  ASSERT_EQ(receivers_.size(), 1u);
+  ASSERT_EQ(session_services_receivers_.size(), 1u);
 
   base::RunLoop remote_disconnect_run_loop;
   SetRemoteDisconnectCallback(remote_disconnect_run_loop.QuitClosure());
-  receivers_.clear();
+  session_services_receivers_.clear();
   remote_disconnect_run_loop.Run();
 
   ASSERT_NE(client_->GetSessionServices(), nullptr);
   WaitForSessionServicesBound();
-  ASSERT_EQ(receivers_.size(), 1u);
+  ASSERT_EQ(session_services_receivers_.size(), 1u);
 }
 
-// Ideally we should also verify that the client can reconnect after the server
-// is restarted. This doesn't seem to work with single process unit test though,
-// since mojo for some reason can't cleanly tear down a connection when both
-// ends of the platform channel are from the same process. This issue doesn't
-// seem to happen in the real world, where platform channels are always used
-// between two processes.
-// TODO(yuweih): Consider adding a test to verify this by spawning a separate
-// server process.
-
 }  // namespace remoting
diff --git a/sandbox/policy/win/sandbox_win.cc b/sandbox/policy/win/sandbox_win.cc
index f42acfc5..88ba3bd 100644
--- a/sandbox/policy/win/sandbox_win.cc
+++ b/sandbox/policy/win/sandbox_win.cc
@@ -276,50 +276,6 @@
   return base::StringPrintf(L"\\Sessions\\%lu%ls", s_session_id, object);
 }
 
-// Checks if the sandbox can be let to run without a job object assigned.
-// Returns true if the job object has to be applied to the sandbox and false
-// otherwise.
-bool ShouldSetJobLevel(bool allow_no_sandbox_job) {
-  // Windows 8 allows nested jobs so we don't need to check if we are in other
-  // job.
-  if (base::win::GetVersion() >= base::win::Version::WIN8)
-    return true;
-
-  BOOL in_job = true;
-  // Either there is no job yet associated so we must add our job,
-  if (!::IsProcessInJob(::GetCurrentProcess(), NULL, &in_job))
-    NOTREACHED() << "IsProcessInJob failed. " << GetLastError();
-  if (!in_job)
-    return true;
-
-  // ...or there is a job but the JOB_OBJECT_LIMIT_BREAKAWAY_OK limit is set.
-  JOBOBJECT_EXTENDED_LIMIT_INFORMATION job_info = {};
-  if (!::QueryInformationJobObject(NULL, JobObjectExtendedLimitInformation,
-                                   &job_info, sizeof(job_info), NULL)) {
-    NOTREACHED() << "QueryInformationJobObject failed. " << GetLastError();
-    return true;
-  }
-  if (job_info.BasicLimitInformation.LimitFlags & JOB_OBJECT_LIMIT_BREAKAWAY_OK)
-    return true;
-
-  // Lastly in place of the flag which was supposed to be used only for running
-  // Chrome in remote sessions we do this check explicitly here.
-  // According to MS this flag can be false for a remote session only on Windows
-  // Server 2012 and newer so if we do the check last we should be on the safe
-  // side. See: https://msdn.microsoft.com/en-us/library/aa380798.aspx.
-  if (!::GetSystemMetrics(SM_REMOTESESSION)) {
-    // TODO(pastarmovj): Even though the number are low, this flag is still
-    // necessary in some limited set of cases. Remove it once Windows 7 is no
-    // longer supported together with the rest of the checks in this function.
-    return !allow_no_sandbox_job;
-  }
-
-  // Allow running without the sandbox in this case. This slightly reduces the
-  // ability of the sandbox to protect its children from spawning new processes
-  // or preventing them from shutting down Windows or accessing the clipboard.
-  return false;
-}
-
 // Adds the generic config rules to a sandbox TargetConfig.
 ResultCode AddGenericConfig(sandbox::TargetConfig* config) {
   DCHECK(!config->IsConfigured());
@@ -406,9 +362,7 @@
 
   config->SetLockdownDefaultDacl();
 
-  // Win8+ adds a device DeviceApi that we don't need.
-  if (base::win::GetVersion() >= base::win::Version::WIN8)
-    result = config->AddKernelObjectToClose(L"File", L"\\Device\\DeviceApi");
+  result = config->AddKernelObjectToClose(L"File", L"\\Device\\DeviceApi");
   if (result != SBOX_ALL_OK)
     return result;
 
@@ -863,22 +817,13 @@
   // on process shutdown, in which case TerminateProcess can fail. See
   // https://crbug.com/820996.
   if (delegate->ShouldUnsandboxedRunInJob()) {
-    BOOL in_job = true;
-    // Prior to Windows 8 nested jobs aren't possible.
-    if (base::win::GetVersion() >= base::win::Version::WIN8 ||
-        (::IsProcessInJob(::GetCurrentProcess(), nullptr, &in_job) &&
-         !in_job)) {
-      static Job* job_object = nullptr;
-      if (!job_object) {
-        job_object = new Job;
-        DWORD result = job_object->Init(JobLevel::kUnprotected, nullptr, 0, 0);
-        if (result != ERROR_SUCCESS) {
-          job_object = nullptr;
-          return SBOX_ERROR_CANNOT_INIT_JOB;
-        }
-      }
-      options.job_handle = job_object->GetHandle();
+    static base::NoDestructor<Job> job_object;
+    if (!job_object->IsValid()) {
+      DWORD result = job_object->Init(JobLevel::kUnprotected, 0, 0);
+      if (result != ERROR_SUCCESS)
+        return SBOX_ERROR_CANNOT_INIT_JOB;
     }
+    options.job_handle = job_object->GetHandle();
   }
 
   // Chromium binaries are marked as CET Compatible but some processes
@@ -919,8 +864,6 @@
                                    uint32_t ui_exceptions,
                                    TargetConfig* config) {
   DCHECK(!config->IsConfigured());
-  if (!ShouldSetJobLevel(config->GetAllowNoSandboxJob()))
-    return config->SetJobLevel(JobLevel::kNone, 0);
 
   ResultCode ret = config->SetJobLevel(job_level, ui_exceptions);
   if (ret != SBOX_ALL_OK)
@@ -962,10 +905,6 @@
 ResultCode SandboxWin::AddWin32kLockdownPolicy(TargetConfig* config) {
   DCHECK(!config->IsConfigured());
 #if !defined(NACL_WIN64)
-  // Win32k Lockdown is supported on Windows 8+.
-  if (base::win::GetVersion() < base::win::Version::WIN8)
-    return SBOX_ALL_OK;
-
   MitigationFlags flags = config->GetProcessMitigations();
   // Check not enabling twice. Should not happen.
   DCHECK_EQ(0U, flags & MITIGATION_WIN32K_DISABLE);
diff --git a/sandbox/win/src/broker_services.cc b/sandbox/win/src/broker_services.cc
index 16a7c3db..58c24c1 100644
--- a/sandbox/win/src/broker_services.cc
+++ b/sandbox/win/src/broker_services.cc
@@ -546,11 +546,8 @@
   if (container)
     startup_info->SetAppContainer(container);
 
-  // On Win10, jobs are associated via startup_info.
-  if (base::win::GetVersion() >= base::win::Version::WIN10 &&
-      policy_base->HasJob()) {
+  if (policy_base->HasJob())
     startup_info->AddJobToAssociate(policy_base->GetJobHandle());
-  }
 
   if (!startup_info->BuildStartupInformation())
     return SBOX_ERROR_PROC_THREAD_ATTRIBUTES;
@@ -566,8 +563,8 @@
     }
   }
   std::unique_ptr<TargetProcess> target = std::make_unique<TargetProcess>(
-      std::move(initial_token), std::move(lockdown_token),
-      policy_base->GetJobHandle(), thread_pool_, imp_caps);
+      std::move(initial_token), std::move(lockdown_token), thread_pool_,
+      imp_caps);
 
   result = target->Create(exe_path, command_line, std::move(startup_info),
                           &process_info, last_error);
@@ -612,6 +609,13 @@
     JobTracker* tracker =
         new JobTracker(std::move(policy_base), process_info.process_id());
 
+    // Verify that the process is actually in the specified job. This should
+    // only fail if something has gone wrong during process creation.
+    BOOL in_job;
+    CHECK(
+        job_handle &&
+        ::IsProcessInJob(process_info.process_handle(), job_handle, &in_job) &&
+        in_job);
     // Post the tracker to the tracking thread, then associate the job with
     // the tracker. The worker thread takes ownership of these objects.
     CHECK(::PostQueuedCompletionStatus(
diff --git a/sandbox/win/src/broker_services.h b/sandbox/win/src/broker_services.h
index 88c4e3ba..d12355f 100644
--- a/sandbox/win/src/broker_services.h
+++ b/sandbox/win/src/broker_services.h
@@ -18,7 +18,6 @@
 #include "base/win/scoped_handle.h"
 #include "sandbox/win/src/alternate_desktop.h"
 #include "sandbox/win/src/crosscall_server.h"
-#include "sandbox/win/src/job.h"
 #include "sandbox/win/src/sandbox.h"
 #include "sandbox/win/src/sandbox_policy_base.h"
 #include "sandbox/win/src/sharedmem_ipc_server.h"
diff --git a/sandbox/win/src/handle_closer.cc b/sandbox/win/src/handle_closer.cc
index d4f5ecf..9336964 100644
--- a/sandbox/win/src/handle_closer.cc
+++ b/sandbox/win/src/handle_closer.cc
@@ -11,7 +11,6 @@
 #include "base/check_op.h"
 #include "base/memory/free_deleter.h"
 #include "base/ranges/algorithm.h"
-#include "base/win/windows_version.h"
 #include "sandbox/win/src/win_utils.h"
 
 namespace {
diff --git a/sandbox/win/src/handle_closer_agent.cc b/sandbox/win/src/handle_closer_agent.cc
index 7c31bed..b66c169 100644
--- a/sandbox/win/src/handle_closer_agent.cc
+++ b/sandbox/win/src/handle_closer_agent.cc
@@ -10,7 +10,6 @@
 #include "base/logging.h"
 #include "base/win/static_constants.h"
 #include "base/win/win_util.h"
-#include "base/win/windows_version.h"
 #include "sandbox/win/src/win_utils.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
@@ -151,11 +150,6 @@
   // If the accurate handle enumeration fails then fallback to the old brute
   // force approach. This should only happen on Windows 7 and 8.0.
   absl::optional<ProcessHandleMap> handle_map = GetCurrentProcessHandles();
-  if (!handle_map) {
-    DCHECK(base::win::GetVersion() < base::win::Version::WIN8_1);
-    handle_map = GetCurrentProcessHandlesWin7();
-  }
-
   if (!handle_map)
     return false;
 
diff --git a/sandbox/win/src/job.cc b/sandbox/win/src/job.cc
index 36c28b202..d5e2307 100644
--- a/sandbox/win/src/job.cc
+++ b/sandbox/win/src/job.cc
@@ -14,20 +14,17 @@
 
 namespace sandbox {
 
-Job::Job() : job_handle_(nullptr) {}
-
-Job::~Job() {}
+Job::Job() = default;
+Job::~Job() = default;
 
 DWORD Job::Init(JobLevel security_level,
-                const wchar_t* job_name,
                 DWORD ui_exceptions,
                 size_t memory_limit) {
-  if (job_handle_.IsValid())
+  if (job_handle_.is_valid())
     return ERROR_ALREADY_INITIALIZED;
 
-  job_handle_.Set(::CreateJobObject(nullptr,  // No security attribute
-                                    job_name));
-  if (!job_handle_.IsValid())
+  job_handle_.Set(::CreateJobObject(nullptr, nullptr));
+  if (!job_handle_.is_valid())
     return ::GetLastError();
 
   JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = {};
@@ -90,42 +87,20 @@
 }
 
 bool Job::IsValid() {
-  return job_handle_.IsValid();
+  return job_handle_.is_valid();
 }
 
 HANDLE Job::GetHandle() {
-  return job_handle_.Get();
-}
-
-DWORD Job::UserHandleGrantAccess(HANDLE handle) {
-  if (!job_handle_.IsValid())
-    return ERROR_NO_DATA;
-
-  if (!::UserHandleGrantAccess(handle, job_handle_.Get(),
-                               true)) {  // Access allowed.
-    return ::GetLastError();
-  }
-
-  return ERROR_SUCCESS;
-}
-
-DWORD Job::AssignProcessToJob(HANDLE process_handle) {
-  if (!job_handle_.IsValid())
-    return ERROR_NO_DATA;
-
-  if (!::AssignProcessToJobObject(job_handle_.Get(), process_handle))
-    return ::GetLastError();
-
-  return ERROR_SUCCESS;
+  return job_handle_.get();
 }
 
 DWORD Job::SetActiveProcessLimit(DWORD processes) {
   JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = {};
 
-  if (!job_handle_.IsValid())
+  if (!job_handle_.is_valid())
     return ERROR_NO_DATA;
 
-  if (!::QueryInformationJobObject(job_handle_.Get(),
+  if (!::QueryInformationJobObject(job_handle_.get(),
                                    JobObjectExtendedLimitInformation, &jeli,
                                    sizeof(jeli), nullptr)) {
     return ::GetLastError();
@@ -133,7 +108,7 @@
   jeli.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_ACTIVE_PROCESS;
   jeli.BasicLimitInformation.ActiveProcessLimit = processes;
 
-  if (!::SetInformationJobObject(job_handle_.Get(),
+  if (!::SetInformationJobObject(job_handle_.get(),
                                  JobObjectExtendedLimitInformation, &jeli,
                                  sizeof(jeli))) {
     return ::GetLastError();
diff --git a/sandbox/win/src/job.h b/sandbox/win/src/job.h
index e4739cf..31037e1b 100644
--- a/sandbox/win/src/job.h
+++ b/sandbox/win/src/job.h
@@ -15,8 +15,7 @@
 // Handles the creation of job objects based on a security profile.
 // Sample usage:
 //   Job job;
-//   job.Init(JobLevel::kLockdown, nullptr);  //no job name
-//   job.AssignProcessToJob(process_handle);
+//   job.Init(JobLevel::kLockdown, 0, 0);
 class Job {
  public:
   Job();
@@ -28,29 +27,12 @@
 
   // Initializes and creates the job object. The security of the job is based
   // on the security_level parameter.
-  // job_name can be nullptr if the job is unnamed.
   // If the chosen profile has too many ui restrictions, you can disable some
   // by specifying them in the ui_exceptions parameters.
   // If the function succeeds, the return value is ERROR_SUCCESS. If the
   // function fails, the return value is the win32 error code corresponding to
   // the error.
-  DWORD Init(JobLevel security_level,
-             const wchar_t* job_name,
-             DWORD ui_exceptions,
-             size_t memory_limit);
-
-  // Assigns the process referenced by process_handle to the job.
-  // If the function succeeds, the return value is ERROR_SUCCESS. If the
-  // function fails, the return value is the win32 error code corresponding to
-  // the error.
-  DWORD AssignProcessToJob(HANDLE process_handle);
-
-  // Grants access to "handle" to the job. All processes in the job can
-  // subsequently recognize and use the handle.
-  // If the function succeeds, the return value is ERROR_SUCCESS. If the
-  // function fails, the return value is the win32 error code corresponding to
-  // the error.
-  DWORD UserHandleGrantAccess(HANDLE handle);
+  DWORD Init(JobLevel security_level, DWORD ui_exceptions, size_t memory_limit);
 
   // True if the job has been initialized and has a valid handle.
   bool IsValid();
diff --git a/sandbox/win/src/job_unittest.cc b/sandbox/win/src/job_unittest.cc
index 78a42df..6214270 100644
--- a/sandbox/win/src/job_unittest.cc
+++ b/sandbox/win/src/job_unittest.cc
@@ -14,26 +14,14 @@
 
 // Tests the creation and destruction of the job.
 TEST(JobTest, TestCreation) {
-  // Scope the creation of Job.
-  {
-    // Create the job.
-    Job job;
-    ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS),
-              job.Init(JobLevel::kLockdown, L"my_test_job_name", 0, 0));
-
-    // check if the job exists.
-    HANDLE job_handle =
-        ::OpenJobObjectW(GENERIC_ALL, false, L"my_test_job_name");
-    ASSERT_TRUE(job_handle);
-
-    if (job_handle)
-      CloseHandle(job_handle);
-  }
-
-  // Check if the job is destroyed when the object goes out of scope.
-  HANDLE job_handle = ::OpenJobObjectW(GENERIC_ALL, false, L"my_test_job_name");
-  ASSERT_TRUE(!job_handle);
-  ASSERT_EQ(static_cast<DWORD>(ERROR_FILE_NOT_FOUND), ::GetLastError());
+  // Create the job.
+  Job job;
+  ASSERT_FALSE(job.IsValid());
+  ASSERT_EQ(nullptr, job.GetHandle());
+  ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS),
+            job.Init(JobLevel::kLockdown, 0, 0));
+  EXPECT_TRUE(job.IsValid());
+  EXPECT_NE(nullptr, job.GetHandle());
 }
 
 // Tests the ui exceptions
@@ -43,12 +31,12 @@
   {
     // Create the job.
     Job job;
-    ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS),
-              job.Init(JobLevel::kLockdown, L"my_test_job_name",
-                       JOB_OBJECT_UILIMIT_READCLIPBOARD, 0));
+    ASSERT_EQ(
+        static_cast<DWORD>(ERROR_SUCCESS),
+        job.Init(JobLevel::kLockdown, JOB_OBJECT_UILIMIT_READCLIPBOARD, 0));
 
     job_handle = job.GetHandle();
-    ASSERT_TRUE(job_handle != INVALID_HANDLE_VALUE);
+    ASSERT_NE(nullptr, job_handle);
 
     JOBOBJECT_BASIC_UI_RESTRICTIONS jbur = {0};
     DWORD size = sizeof(jbur);
@@ -63,10 +51,10 @@
     // Create the job.
     Job job;
     ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS),
-              job.Init(JobLevel::kLockdown, L"my_test_job_name", 0, 0));
+              job.Init(JobLevel::kLockdown, 0, 0));
 
     job_handle = job.GetHandle();
-    ASSERT_TRUE(job_handle != INVALID_HANDLE_VALUE);
+    ASSERT_NE(nullptr, job_handle);
 
     JOBOBJECT_BASIC_UI_RESTRICTIONS jbur = {0};
     DWORD size = sizeof(jbur);
@@ -83,78 +71,33 @@
   // Create the job.
   Job job;
   ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS),
-            job.Init(JobLevel::kLockdown, L"my_test_job_name", 0, 0));
+            job.Init(JobLevel::kLockdown, 0, 0));
   ASSERT_EQ(static_cast<DWORD>(ERROR_ALREADY_INITIALIZED),
-            job.Init(JobLevel::kLockdown, L"test", 0, 0));
-}
-
-// Tests the error case when we use a method and the object is not yet
-// initialized.
-TEST(JobTest, NoInit) {
-  Job job;
-  ASSERT_EQ(static_cast<DWORD>(ERROR_NO_DATA),
-            job.UserHandleGrantAccess(nullptr));
-  ASSERT_EQ(static_cast<DWORD>(ERROR_NO_DATA), job.AssignProcessToJob(nullptr));
-  ASSERT_FALSE(job.GetHandle() == INVALID_HANDLE_VALUE);
+            job.Init(JobLevel::kLockdown, 0, 0));
 }
 
 // Tests the initialization of the job with different security levels.
 TEST(JobTest, SecurityLevel) {
   Job job_lockdown;
   ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS),
-            job_lockdown.Init(JobLevel::kLockdown, L"job_lockdown", 0, 0));
+            job_lockdown.Init(JobLevel::kLockdown, 0, 0));
 
   Job job_limited_user;
-  ASSERT_EQ(
-      static_cast<DWORD>(ERROR_SUCCESS),
-      job_limited_user.Init(JobLevel::kLimitedUser, L"job_limited_user", 0, 0));
+  ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS),
+            job_limited_user.Init(JobLevel::kLimitedUser, 0, 0));
 
   Job job_interactive;
-  ASSERT_EQ(
-      static_cast<DWORD>(ERROR_SUCCESS),
-      job_interactive.Init(JobLevel::kInteractive, L"job_interactive", 0, 0));
+  ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS),
+            job_interactive.Init(JobLevel::kInteractive, 0, 0));
 
   Job job_unprotected;
-  ASSERT_EQ(
-      static_cast<DWORD>(ERROR_SUCCESS),
-      job_unprotected.Init(JobLevel::kUnprotected, L"job_unprotected", 0, 0));
+  ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS),
+            job_unprotected.Init(JobLevel::kUnprotected, 0, 0));
 
   // JobLevel::kNone means we run without a job object so Init should fail.
   Job job_none;
   ASSERT_EQ(static_cast<DWORD>(ERROR_BAD_ARGUMENTS),
-            job_none.Init(JobLevel::kNone, L"job_none", 0, 0));
-}
-
-// Tests the method "AssignProcessToJob".
-TEST(JobTest, ProcessInJob) {
-  // Create the job.
-  Job job;
-  ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS),
-            job.Init(JobLevel::kUnprotected, L"job_test_process", 0, 0));
-
-  wchar_t notepad[] = L"notepad";
-  STARTUPINFO si = {sizeof(si)};
-  PROCESS_INFORMATION temp_process_info = {};
-  ASSERT_TRUE(::CreateProcess(nullptr, notepad, nullptr, nullptr, false, 0,
-                              nullptr, nullptr, &si, &temp_process_info));
-  base::win::ScopedProcessInformation pi(temp_process_info);
-  ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS),
-            job.AssignProcessToJob(pi.process_handle()));
-
-  // Get the job handle.
-  HANDLE job_handle = job.GetHandle();
-
-  // Check if the process is in the job.
-  JOBOBJECT_BASIC_PROCESS_ID_LIST jbpidl = {0};
-  DWORD size = sizeof(jbpidl);
-  EXPECT_TRUE(::QueryInformationJobObject(
-      job_handle, JobObjectBasicProcessIdList, &jbpidl, size, &size));
-
-  EXPECT_EQ(1u, jbpidl.NumberOfAssignedProcesses);
-  EXPECT_EQ(1u, jbpidl.NumberOfProcessIdsInList);
-  EXPECT_EQ(pi.process_id(), jbpidl.ProcessIdList[0]);
-
-  EXPECT_TRUE(::TerminateProcess(pi.process_handle(), 0));
+            job_none.Init(JobLevel::kNone, 0, 0));
 }
 
 }  // namespace sandbox
diff --git a/sandbox/win/src/policy_engine_opcodes.cc b/sandbox/win/src/policy_engine_opcodes.cc
index ad1bb1d..b9b856a 100644
--- a/sandbox/win/src/policy_engine_opcodes.cc
+++ b/sandbox/win/src/policy_engine_opcodes.cc
@@ -262,6 +262,10 @@
   const wchar_t* source_str = nullptr;
   if (!param->Get(&source_str))
     return EVAL_ERROR;
+  // Assume we won't want to match when a nullptr parameter is passed to the
+  // hooked function.
+  if (!source_str)
+    return EVAL_FALSE;
 
   int start_position = 0;
   int match_len = 0;
diff --git a/sandbox/win/src/policy_engine_unittest.cc b/sandbox/win/src/policy_engine_unittest.cc
index badc1bb..b7e369a8 100644
--- a/sandbox/win/src/policy_engine_unittest.cc
+++ b/sandbox/win/src/policy_engine_unittest.cc
@@ -93,6 +93,11 @@
   EXPECT_EQ(POLICY_MATCH, pr);
   EXPECT_EQ(FAKE_ACCESS_DENIED, pol_ev.GetAction());
 
+  // Cope ok with nullptr string fields.
+  filename = nullptr;
+  pr = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params));
+  EXPECT_EQ(NO_POLICY_MATCH, pr);
+
   delete[] reinterpret_cast<char*>(policy);
 }
 
diff --git a/sandbox/win/src/process_mitigations.cc b/sandbox/win/src/process_mitigations.cc
index 192a804..def08306 100644
--- a/sandbox/win/src/process_mitigations.cc
+++ b/sandbox/win/src/process_mitigations.cc
@@ -36,39 +36,22 @@
 
 namespace {
 
-// API defined in libloaderapi.h >= Win8.
-using SetDefaultDllDirectoriesFunction = decltype(&SetDefaultDllDirectories);
-
-// APIs defined in processthreadsapi.h >= Win8.
-using SetProcessMitigationPolicyFunction =
-    decltype(&SetProcessMitigationPolicy);
-using GetProcessMitigationPolicyFunction =
-    decltype(&GetProcessMitigationPolicy);
-using SetThreadInformationFunction = decltype(&SetThreadInformation);
-
 // Returns a two-element array of mitigation flags supported on this machine.
-// - This function is only useful on >= base::win::Version::WIN8.
 const ULONG64* GetSupportedMitigations() {
   static ULONG64 mitigations[2] = {};
 
   // This static variable will only be initialized once.
   if (!mitigations[0] && !mitigations[1]) {
-    GetProcessMitigationPolicyFunction get_process_mitigation_policy =
-        reinterpret_cast<GetProcessMitigationPolicyFunction>(::GetProcAddress(
-            ::GetModuleHandleA("kernel32.dll"), "GetProcessMitigationPolicy"));
-    if (get_process_mitigation_policy) {
-      // NOTE: the two-element-sized input array is only supported on >= Win10
-      // RS2.
-      //       If an earlier version, the second element will be left 0.
-      size_t mits_size =
-          (base::win::GetVersion() >= base::win::Version::WIN10_RS2)
-              ? (sizeof(mitigations[0]) * 2)
-              : sizeof(mitigations[0]);
-      if (!get_process_mitigation_policy(::GetCurrentProcess(),
-                                         ProcessMitigationOptionsMask,
-                                         &mitigations, mits_size)) {
-        NOTREACHED();
-      }
+    // NOTE: the two-element-sized input array is only supported on >= Win10
+    // RS2. If an earlier version, the second element will be left 0.
+    size_t mits_size =
+        (base::win::GetVersion() >= base::win::Version::WIN10_RS2)
+            ? (sizeof(mitigations[0]) * 2)
+            : sizeof(mitigations[0]);
+    if (!::GetProcessMitigationPolicy(::GetCurrentProcess(),
+                                      ProcessMitigationOptionsMask,
+                                      &mitigations, mits_size)) {
+      NOTREACHED();
     }
   }
 
@@ -105,14 +88,7 @@
 bool SetProcessMitigationPolicyInternal(PROCESS_MITIGATION_POLICY policy,
                                         PVOID lpBuffer,
                                         SIZE_T dwLength) {
-  HMODULE module = ::GetModuleHandleA("kernel32.dll");
-  SetProcessMitigationPolicyFunction set_process_mitigation_policy_function =
-      reinterpret_cast<SetProcessMitigationPolicyFunction>(
-          ::GetProcAddress(module, "SetProcessMitigationPolicy"));
-  if (!set_process_mitigation_policy_function)
-    return false;
-
-  PCHECK(set_process_mitigation_policy_function(policy, lpBuffer, dwLength))
+  PCHECK(::SetProcessMitigationPolicy(policy, lpBuffer, dwLength))
       << "SetProcessMitigationPolicy failed with Policy: " << policy;
 
   return true;
@@ -129,28 +105,20 @@
   base::win::Version version = base::win::GetVersion();
 
   if (flags & MITIGATION_DLL_SEARCH_ORDER) {
-    HMODULE module = ::GetModuleHandleA("kernel32.dll");
-    SetDefaultDllDirectoriesFunction set_default_dll_directories =
-        reinterpret_cast<SetDefaultDllDirectoriesFunction>(
-            ::GetProcAddress(module, "SetDefaultDllDirectories"));
-
-    // Check for SetDefaultDllDirectories since it requires KB2533623.
-    if (set_default_dll_directories) {
 #if defined(COMPONENT_BUILD)
-      const DWORD directory_flags = LOAD_LIBRARY_SEARCH_DEFAULT_DIRS;
+    const DWORD directory_flags = LOAD_LIBRARY_SEARCH_DEFAULT_DIRS;
 #else
-      // In a non-component build, all DLLs will be loaded manually, or via
-      // manifest definition, so these flags can be stronger. This prevents DLL
-      // planting in the application directory.
-      const DWORD directory_flags =
-          LOAD_LIBRARY_SEARCH_SYSTEM32 | LOAD_LIBRARY_SEARCH_USER_DIRS;
+    // In a non-component build, all DLLs will be loaded manually, or via
+    // manifest definition, so these flags can be stronger. This prevents DLL
+    // planting in the application directory.
+    const DWORD directory_flags =
+        LOAD_LIBRARY_SEARCH_SYSTEM32 | LOAD_LIBRARY_SEARCH_USER_DIRS;
 #endif
-      if (!set_default_dll_directories(directory_flags)) {
-        return false;
-      }
-
-      applied_flags |= MITIGATION_DLL_SEARCH_ORDER;
+    if (!::SetDefaultDllDirectories(directory_flags)) {
+      return false;
     }
+
+    applied_flags |= MITIGATION_DLL_SEARCH_ORDER;
   }
 
   // Set the heap to terminate on corruption
@@ -186,10 +154,6 @@
   }
 #endif
 
-  // This is all we can do in Win7 and below.
-  if (version < base::win::Version::WIN8)
-    return true;
-
   // Enable ASLR policies.
   if (flags & MITIGATION_RELOCATE_IMAGE) {
     PROCESS_MITIGATION_ASLR_POLICY policy = {};
@@ -253,9 +217,6 @@
     applied_flags |= MITIGATION_EXTENSION_POINT_DISABLE;
   }
 
-  if (version < base::win::Version::WIN8_1)
-    return true;
-
   // Enable dynamic code policies.
   if (!IsRunning32bitEmulatedOnArm64() &&
       (flags & MITIGATION_DYNAMIC_CODE_DISABLE)) {
@@ -272,9 +233,6 @@
     applied_flags |= MITIGATION_DYNAMIC_CODE_DISABLE;
   }
 
-  if (version < base::win::Version::WIN10)
-    return true;
-
   // Enable font policies.
   if (flags & MITIGATION_NONSYSTEM_FONT_DISABLE) {
     PROCESS_MITIGATION_FONT_DISABLE_POLICY policy = {};
@@ -389,29 +347,16 @@
   if (!CanSetMitigationsPerThread(flags))
     return false;
 
-  base::win::Version version = base::win::GetVersion();
-
-  if (version < base::win::Version::WIN10_RS1)
+  if (base::win::GetVersion() < base::win::Version::WIN10_RS1)
     return true;
 
   // Enable dynamic code per-thread policies.
   if (flags & MITIGATION_DYNAMIC_CODE_OPT_OUT_THIS_THREAD) {
     DWORD thread_policy = THREAD_DYNAMIC_CODE_ALLOW;
 
-    // NOTE: SetThreadInformation API only exists on >= Win8.  Dynamically
-    //       get function handle.
-    base::ScopedNativeLibrary dll(base::FilePath(L"kernel32.dll"));
-    if (!dll.is_valid())
-      return false;
-    SetThreadInformationFunction set_thread_info_function =
-        reinterpret_cast<SetThreadInformationFunction>(
-            dll.GetFunctionPointer("SetThreadInformation"));
-    if (!set_thread_info_function)
-      return false;
-
     // NOTE: Must use the pseudo-handle here, a thread HANDLE won't work.
-    if (!set_thread_info_function(::GetCurrentThread(), ThreadDynamicCodePolicy,
-                                  &thread_policy, sizeof(thread_policy))) {
+    if (!::SetThreadInformation(::GetCurrentThread(), ThreadDynamicCodePolicy,
+                                &thread_policy, sizeof(thread_policy))) {
       return false;
     }
   }
@@ -432,14 +377,8 @@
   *policy_value_1 = 0;
   *policy_value_2 = 0;
 
-#if defined(_WIN64)
+#if defined(_WIN64) || defined(_M_IX86)
   *size = sizeof(*policy_flags);
-#elif defined(_M_IX86)
-  // A 64-bit flags attribute is illegal on 32-bit Win 7.
-  if (version < base::win::Version::WIN8)
-    *size = sizeof(DWORD);
-  else
-    *size = sizeof(*policy_flags);
 #else
 #error This platform is not supported.
 #endif
@@ -457,73 +396,53 @@
     *policy_value_1 |= PROCESS_CREATION_MITIGATION_POLICY_SEHOP_ENABLE;
 #endif
 
-  // Win 7
-  if (version < base::win::Version::WIN8)
-    return;
-
-  // Everything >= Win8, do not return before the end of the function where
-  // the final policy bitmap is sanity checked against what is supported on this
-  // machine.  The API required to do so is only available since Win8.
-
-  // Mitigations >= Win8:
-  //----------------------------------------------------------------------------
-  if (version >= base::win::Version::WIN8) {
-    if (flags & MITIGATION_RELOCATE_IMAGE) {
+  if (flags & MITIGATION_RELOCATE_IMAGE) {
+    *policy_value_1 |=
+        PROCESS_CREATION_MITIGATION_POLICY_FORCE_RELOCATE_IMAGES_ALWAYS_ON;
+    if (flags & MITIGATION_RELOCATE_IMAGE_REQUIRED) {
       *policy_value_1 |=
-          PROCESS_CREATION_MITIGATION_POLICY_FORCE_RELOCATE_IMAGES_ALWAYS_ON;
-      if (flags & MITIGATION_RELOCATE_IMAGE_REQUIRED) {
-        *policy_value_1 |=
-            PROCESS_CREATION_MITIGATION_POLICY_FORCE_RELOCATE_IMAGES_ALWAYS_ON_REQ_RELOCS;
-      }
-    }
-
-    if (flags & MITIGATION_HEAP_TERMINATE) {
-      *policy_value_1 |=
-          PROCESS_CREATION_MITIGATION_POLICY_HEAP_TERMINATE_ALWAYS_ON;
-    }
-
-    if (flags & MITIGATION_BOTTOM_UP_ASLR) {
-      *policy_value_1 |=
-          PROCESS_CREATION_MITIGATION_POLICY_BOTTOM_UP_ASLR_ALWAYS_ON;
-    }
-
-    if (flags & MITIGATION_HIGH_ENTROPY_ASLR) {
-      *policy_value_1 |=
-          PROCESS_CREATION_MITIGATION_POLICY_HIGH_ENTROPY_ASLR_ALWAYS_ON;
-    }
-
-    if (flags & MITIGATION_STRICT_HANDLE_CHECKS) {
-      *policy_value_1 |=
-          PROCESS_CREATION_MITIGATION_POLICY_STRICT_HANDLE_CHECKS_ALWAYS_ON;
-    }
-
-    if (flags & MITIGATION_WIN32K_DISABLE) {
-      *policy_value_1 |=
-          PROCESS_CREATION_MITIGATION_POLICY_WIN32K_SYSTEM_CALL_DISABLE_ALWAYS_ON;
-    }
-
-    if (flags & MITIGATION_EXTENSION_POINT_DISABLE) {
-      *policy_value_1 |=
-          PROCESS_CREATION_MITIGATION_POLICY_EXTENSION_POINT_DISABLE_ALWAYS_ON;
+          PROCESS_CREATION_MITIGATION_POLICY_FORCE_RELOCATE_IMAGES_ALWAYS_ON_REQ_RELOCS;
     }
   }
 
-  // Mitigations >= Win8.1:
-  //----------------------------------------------------------------------------
-  if (version >= base::win::Version::WIN8_1) {
-    if (flags & MITIGATION_DYNAMIC_CODE_DISABLE) {
-      *policy_value_1 |=
-          PROCESS_CREATION_MITIGATION_POLICY_PROHIBIT_DYNAMIC_CODE_ALWAYS_ON;
-    }
+  if (flags & MITIGATION_HEAP_TERMINATE) {
+    *policy_value_1 |=
+        PROCESS_CREATION_MITIGATION_POLICY_HEAP_TERMINATE_ALWAYS_ON;
   }
 
-  // Mitigations >= Win10:
-  //----------------------------------------------------------------------------
-  if (version >= base::win::Version::WIN10) {
-    if (flags & MITIGATION_NONSYSTEM_FONT_DISABLE) {
-      *policy_value_1 |=
-          PROCESS_CREATION_MITIGATION_POLICY_FONT_DISABLE_ALWAYS_ON;
-    }
+  if (flags & MITIGATION_BOTTOM_UP_ASLR) {
+    *policy_value_1 |=
+        PROCESS_CREATION_MITIGATION_POLICY_BOTTOM_UP_ASLR_ALWAYS_ON;
+  }
+
+  if (flags & MITIGATION_HIGH_ENTROPY_ASLR) {
+    *policy_value_1 |=
+        PROCESS_CREATION_MITIGATION_POLICY_HIGH_ENTROPY_ASLR_ALWAYS_ON;
+  }
+
+  if (flags & MITIGATION_STRICT_HANDLE_CHECKS) {
+    *policy_value_1 |=
+        PROCESS_CREATION_MITIGATION_POLICY_STRICT_HANDLE_CHECKS_ALWAYS_ON;
+  }
+
+  if (flags & MITIGATION_WIN32K_DISABLE) {
+    *policy_value_1 |=
+        PROCESS_CREATION_MITIGATION_POLICY_WIN32K_SYSTEM_CALL_DISABLE_ALWAYS_ON;
+  }
+
+  if (flags & MITIGATION_EXTENSION_POINT_DISABLE) {
+    *policy_value_1 |=
+        PROCESS_CREATION_MITIGATION_POLICY_EXTENSION_POINT_DISABLE_ALWAYS_ON;
+  }
+
+  if (flags & MITIGATION_DYNAMIC_CODE_DISABLE) {
+    *policy_value_1 |=
+        PROCESS_CREATION_MITIGATION_POLICY_PROHIBIT_DYNAMIC_CODE_ALWAYS_ON;
+  }
+
+  if (flags & MITIGATION_NONSYSTEM_FONT_DISABLE) {
+    *policy_value_1 |=
+        PROCESS_CREATION_MITIGATION_POLICY_FONT_DISABLE_ALWAYS_ON;
   }
 
   // Mitigations >= Win10 TH2:
@@ -625,15 +544,6 @@
 }
 
 MitigationFlags FilterPostStartupProcessMitigations(MitigationFlags flags) {
-  base::win::Version version = base::win::GetVersion();
-
-  // Windows 7.
-  if (version < base::win::Version::WIN8) {
-    return flags & (MITIGATION_BOTTOM_UP_ASLR | MITIGATION_DLL_SEARCH_ORDER |
-                    MITIGATION_HEAP_TERMINATE);
-  }
-
-  // Windows 8 and above.
   return flags & (MITIGATION_BOTTOM_UP_ASLR | MITIGATION_DLL_SEARCH_ORDER);
 }
 
diff --git a/sandbox/win/src/process_mitigations_deathtest.cc b/sandbox/win/src/process_mitigations_deathtest.cc
index 1b08dfe..bf29a26 100644
--- a/sandbox/win/src/process_mitigations_deathtest.cc
+++ b/sandbox/win/src/process_mitigations_deathtest.cc
@@ -58,9 +58,6 @@
 }
 
 TEST(ProcessMitigationsDeathTest, CheckRatchetDownOrderMatters) {
-  if (base::win::GetVersion() < base::win::Version::WIN8)
-    GTEST_SKIP() << "Skipping test due to unsupported Windows version";
-
   std::wstring test_command = L"CheckDeath ";
   test_command += base::NumberToWString(kRatchetDown);
   test_command += L" ";
@@ -73,9 +70,6 @@
 }
 
 TEST(ProcessMitigationsDeathTest, CheckRatchetDownAndLockdownExclusive) {
-  if (base::win::GetVersion() < base::win::Version::WIN8)
-    GTEST_SKIP() << "Skipping test due to unsupported Windows version";
-
   std::wstring test_command = L"CheckDeath ";
   test_command += base::NumberToWString(kRatchetDown);
   test_command += L" ";
@@ -88,9 +82,6 @@
 }
 
 TEST(ProcessMitigationsDeathTest, CheckRatchetDownAndLockdownExclusive2) {
-  if (base::win::GetVersion() < base::win::Version::WIN8)
-    GTEST_SKIP() << "Skipping test due to unsupported Windows version";
-
   std::wstring test_command = L"CheckDeath ";
   test_command += base::NumberToWString(kLockdown);
   test_command += L" ";
@@ -103,9 +94,6 @@
 }
 
 TEST(ProcessMitigationsDeathTest, CheckSetStartAndLockdownExclusive) {
-  if (base::win::GetVersion() < base::win::Version::WIN8)
-    GTEST_SKIP() << "Skipping test due to unsupported Windows version";
-
   std::wstring test_command = L"CheckDeath ";
   test_command += base::NumberToWString(kLockdown);
   test_command += L" ";
@@ -118,9 +106,6 @@
 }
 
 TEST(ProcessMitigationsDeathTest, CheckSetStartAndLockdownExclusive2) {
-  if (base::win::GetVersion() < base::win::Version::WIN8)
-    GTEST_SKIP() << "Skipping test due to unsupported Windows version";
-
   std::wstring test_command = L"CheckDeath ";
   test_command += base::NumberToWString(kSetStart);
   test_command += L" ";
diff --git a/sandbox/win/src/process_mitigations_dyncode_unittest.cc b/sandbox/win/src/process_mitigations_dyncode_unittest.cc
index bbd17f6..5664a7b 100644
--- a/sandbox/win/src/process_mitigations_dyncode_unittest.cc
+++ b/sandbox/win/src/process_mitigations_dyncode_unittest.cc
@@ -413,9 +413,6 @@
 // This test validates that setting the MITIGATION_DYNAMIC_CODE_DISABLE
 // mitigation enables the setting on a process.
 TEST(ProcessMitigationsTest, CheckWin81DynamicCodePolicySuccess) {
-  if (base::win::GetVersion() < base::win::Version::WIN8_1)
-    return;
-
 // TODO(crbug.com/805414): Windows ASan hotpatching requires dynamic code.
 #if !defined(ADDRESS_SANITIZER)
   std::wstring test_command = L"CheckPolicy ";
@@ -453,9 +450,6 @@
 // This test validates that we can meddle with dynamic code if the
 // MITIGATION_DYNAMIC_CODE_DISABLE mitigation is NOT set.
 TEST(ProcessMitigationsTest, CheckWin81DynamicCode_BaseCase) {
-  if (base::win::GetVersion() < base::win::Version::WIN8_1)
-    return;
-
   ScopedTestMutex mutex(hooking_dll::g_hooking_dll_mutex);
 
   // Expect success, no mitigation.
@@ -467,9 +461,6 @@
 // This test validates that setting the MITIGATION_DYNAMIC_CODE_DISABLE
 // mitigation prevents meddling with dynamic code.
 TEST(ProcessMitigationsTest, CheckWin81DynamicCode_TestMitigation) {
-  if (base::win::GetVersion() < base::win::Version::WIN8_1)
-    return;
-
   ScopedTestMutex mutex(hooking_dll::g_hooking_dll_mutex);
 
   // Expect failure, with mitigation.
diff --git a/sandbox/win/src/process_mitigations_extensionpoints_unittest.cc b/sandbox/win/src/process_mitigations_extensionpoints_unittest.cc
index 83bdaba8..6b0634f3 100644
--- a/sandbox/win/src/process_mitigations_extensionpoints_unittest.cc
+++ b/sandbox/win/src/process_mitigations_extensionpoints_unittest.cc
@@ -364,9 +364,6 @@
 // This test validates that setting the MITIGATION_EXTENSION_POINT_DISABLE
 // mitigation enables the setting on a process.
 TEST(ProcessMitigationsTest, CheckWin8ExtensionPointPolicySuccess) {
-  if (base::win::GetVersion() < base::win::Version::WIN8)
-    return;
-
   std::wstring test_command = L"CheckPolicy ";
   test_command += std::to_wstring(sandbox::TESTPOLICY_EXTENSIONPOINT);
 
@@ -400,9 +397,6 @@
 // MANUAL testing only.
 TEST(ProcessMitigationsTest,
      DISABLED_CheckWin8ExtensionPoint_GlobalHook_Success) {
-  if (base::win::GetVersion() < base::win::Version::WIN8)
-    return;
-
   ScopedTestMutex mutex(g_extension_point_test_mutex);
 
   TestWin8ExtensionPointHookWrapper(true /* is_success_test */,
@@ -415,9 +409,6 @@
 // MANUAL testing only.
 TEST(ProcessMitigationsTest,
      DISABLED_CheckWin8ExtensionPoint_GlobalHook_Failure) {
-  if (base::win::GetVersion() < base::win::Version::WIN8)
-    return;
-
   ScopedTestMutex mutex(g_extension_point_test_mutex);
 
   TestWin8ExtensionPointHookWrapper(false /* is_success_test */,
@@ -429,9 +420,6 @@
 //
 // MANUAL testing only.
 TEST(ProcessMitigationsTest, DISABLED_CheckWin8ExtensionPoint_Hook_Success) {
-  if (base::win::GetVersion() < base::win::Version::WIN8)
-    return;
-
   ScopedTestMutex mutex(g_extension_point_test_mutex);
 
   TestWin8ExtensionPointHookWrapper(true /* is_success_test */,
@@ -446,9 +434,6 @@
 //
 // MANUAL testing only.
 TEST(ProcessMitigationsTest, DISABLED_CheckWin8ExtensionPoint_Hook_Failure) {
-  if (base::win::GetVersion() < base::win::Version::WIN8)
-    return;
-
   ScopedTestMutex mutex(g_extension_point_test_mutex);
 
   TestWin8ExtensionPointHookWrapper(false /* is_success_test */,
@@ -461,9 +446,6 @@
 // MANUAL testing only.
 // Must run this test as admin/elevated.
 TEST(ProcessMitigationsTest, DISABLED_CheckWin8ExtensionPoint_AppInit_Success) {
-  if (base::win::GetVersion() < base::win::Version::WIN8)
-    return;
-
   ScopedTestMutex mutex(g_extension_point_test_mutex);
 
   TestWin8ExtensionPointAppInitWrapper(true /* is_success_test */);
@@ -475,9 +457,6 @@
 // MANUAL testing only.
 // Must run this test as admin/elevated.
 TEST(ProcessMitigationsTest, DISABLED_CheckWin8ExtensionPoint_AppInit_Failure) {
-  if (base::win::GetVersion() < base::win::Version::WIN8)
-    return;
-
   ScopedTestMutex mutex(g_extension_point_test_mutex);
 
   TestWin8ExtensionPointAppInitWrapper(false /* is_success_test */);
diff --git a/sandbox/win/src/process_mitigations_unittest.cc b/sandbox/win/src/process_mitigations_unittest.cc
index cd47d13..b5609f5 100644
--- a/sandbox/win/src/process_mitigations_unittest.cc
+++ b/sandbox/win/src/process_mitigations_unittest.cc
@@ -659,28 +659,6 @@
 
 //------------------------------------------------------------------------------
 // DEP (MITIGATION_DEP and MITIGATION_DEP_NO_ATL_THUNK)
-// Win7 x86
-//------------------------------------------------------------------------------
-
-#if !defined(_WIN64)
-// DEP is always enabled on 64-bit.  Only test on x86.
-TEST(ProcessMitigationsTest, CheckDepWin7) {
-  if (base::win::GetVersion() > base::win::Version::WIN7)
-    return;
-
-  TestRunner runner;
-  sandbox::TargetConfig* config = runner.GetPolicy()->GetConfig();
-
-  EXPECT_EQ(config->SetProcessMitigations(MITIGATION_DEP |
-                                          MITIGATION_DEP_NO_ATL_THUNK |
-                                          MITIGATION_SEHOP),
-            SBOX_ALL_OK);
-  EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"CheckDep"));
-}
-#endif  // !defined(_WIN64)
-
-//------------------------------------------------------------------------------
-// DEP (MITIGATION_DEP and MITIGATION_DEP_NO_ATL_THUNK)
 // >= Win8 x86
 //------------------------------------------------------------------------------
 
@@ -690,9 +668,6 @@
 // This test validates that setting the MITIGATION_DEP*
 // mitigations enables the setting on a process.
 TEST(ProcessMitigationsTest, CheckDepWin8PolicySuccess) {
-  if (base::win::GetVersion() < base::win::Version::WIN8)
-    return;
-
   std::wstring test_command = L"CheckPolicy ";
   test_command += std::to_wstring(TESTPOLICY_DEP);
 
@@ -731,9 +706,6 @@
 //------------------------------------------------------------------------------
 
 TEST(ProcessMitigationsTest, CheckWin8AslrPolicySuccess) {
-  if (base::win::GetVersion() < base::win::Version::WIN8)
-    return;
-
   std::wstring test_command = L"CheckPolicy ";
   test_command += std::to_wstring(TESTPOLICY_ASLR);
 
@@ -759,9 +731,6 @@
 //------------------------------------------------------------------------------
 
 TEST(ProcessMitigationsTest, CheckWin8StrictHandlePolicySuccess) {
-  if (base::win::GetVersion() < base::win::Version::WIN8)
-    return;
-
   std::wstring test_command = L"CheckPolicy ";
   test_command += std::to_wstring(TESTPOLICY_STRICTHANDLE);
 
@@ -786,9 +755,6 @@
 // This test validates that setting the MITIGATION_NON_SYSTEM_FONTS_DISABLE
 // mitigation enables the setting on a process.
 TEST(ProcessMitigationsTest, CheckWin10NonSystemFontLockDownPolicySuccess) {
-  if (base::win::GetVersion() < base::win::Version::WIN10)
-    return;
-
   std::wstring test_command = L"CheckPolicy ";
   test_command += std::to_wstring(TESTPOLICY_NONSYSFONT);
 
@@ -817,18 +783,12 @@
 // This test validates that we can load a non-system font if the
 // MITIGATION_NON_SYSTEM_FONTS_DISABLE mitigation is NOT set.
 TEST(ProcessMitigationsTest, CheckWin10NonSystemFontLockDownLoadSuccess) {
-  if (base::win::GetVersion() < base::win::Version::WIN10)
-    return;
-
   TestWin10NonSystemFont(true /* is_success_test */);
 }
 
 // This test validates that setting the MITIGATION_NON_SYSTEM_FONTS_DISABLE
 // mitigation prevents the loading of a non-system font.
 TEST(ProcessMitigationsTest, CheckWin10NonSystemFontLockDownLoadFailure) {
-  if (base::win::GetVersion() < base::win::Version::WIN10)
-    return;
-
   TestWin10NonSystemFont(false /* is_success_test */);
 }
 
@@ -1436,9 +1396,6 @@
 // This test validates setting a pre-startup mitigation and a post startup
 // mitigation on the same windows policy works in release and crashes in debug.
 TEST(ProcessMitigationsTest, SetPreAndPostStartupSamePolicy_ProcessDep) {
-  if (base::win::GetVersion() < base::win::Version::WIN8)
-    return;
-
   std::wstring test_command = L"CheckPolicy ";
   test_command += base::NumberToWString(TESTPOLICY_DEP);
 
@@ -1461,9 +1418,6 @@
 // This test validates setting a pre-startup mitigation and a post startup
 // mitigation on the same windows policy works in release and crashes in debug.
 TEST(ProcessMitigationsTest, SetPreAndPostStartupSamePolicy_ASLR) {
-  if (base::win::GetVersion() < base::win::Version::WIN8)
-    return;
-
   std::wstring test_command = L"CheckPolicy ";
   test_command += base::NumberToWString(TESTPOLICY_ASLR);
 
diff --git a/sandbox/win/src/process_mitigations_win32k_unittest.cc b/sandbox/win/src/process_mitigations_win32k_unittest.cc
index 18e37e3c..fdf54df1 100644
--- a/sandbox/win/src/process_mitigations_win32k_unittest.cc
+++ b/sandbox/win/src/process_mitigations_win32k_unittest.cc
@@ -24,9 +24,6 @@
 // the target process causes the launch to fail in process initialization.
 // The test process itself links against user32/gdi32.
 TEST(ProcessMitigationsWin32kTest, CheckWin8LockDownFailure) {
-  if (base::win::GetVersion() < base::win::Version::WIN8)
-    return;
-
   std::wstring test_policy_command = L"CheckPolicy ";
   test_policy_command += std::to_wstring(TESTPOLICY_WIN32K);
 
@@ -44,9 +41,6 @@
 // The test process itself links against user32/gdi32.
 
 TEST(ProcessMitigationsWin32kTest, CheckWin8LockDownSuccess) {
-  if (base::win::GetVersion() < base::win::Version::WIN8)
-    return;
-
   std::wstring test_policy_command = L"CheckPolicy ";
   test_policy_command += std::to_wstring(TESTPOLICY_WIN32K);
 
diff --git a/sandbox/win/src/restricted_token_utils.cc b/sandbox/win/src/restricted_token_utils.cc
index 8bd608c..c02c0931 100644
--- a/sandbox/win/src/restricted_token_utils.cc
+++ b/sandbox/win/src/restricted_token_utils.cc
@@ -14,7 +14,6 @@
 #include "base/notreached.h"
 #include "base/win/scoped_handle.h"
 #include "base/win/windows_version.h"
-#include "sandbox/win/src/job.h"
 #include "sandbox/win/src/restricted_token.h"
 #include "sandbox/win/src/sandbox_nt_util.h"
 #include "sandbox/win/src/sandbox_utils.h"
diff --git a/sandbox/win/src/sandbox_policy_base.cc b/sandbox/win/src/sandbox_policy_base.cc
index 58c69fde9..c93d421 100644
--- a/sandbox/win/src/sandbox_policy_base.cc
+++ b/sandbox/win/src/sandbox_policy_base.cc
@@ -493,8 +493,8 @@
     return SBOX_ALL_OK;
 
   // Create the Windows job object.
-  DWORD result = job_.Init(config()->GetJobLevel(), nullptr,
-                           config()->ui_exceptions(), config()->memory_limit());
+  DWORD result = job_.Init(config()->GetJobLevel(), config()->ui_exceptions(),
+                           config()->memory_limit());
   if (ERROR_SUCCESS != result)
     return SBOX_ERROR_CANNOT_INIT_JOB;
 
diff --git a/sandbox/win/src/startup_information_helper.h b/sandbox/win/src/startup_information_helper.h
index 2f0f9a55..20ebc76 100644
--- a/sandbox/win/src/startup_information_helper.h
+++ b/sandbox/win/src/startup_information_helper.h
@@ -44,8 +44,7 @@
   //        PROC_THREAD_ATTRIBUTE_ALL_APPLICATION_PACKAGES_POLICY
   // based on |container|. |container| should be valid.
   void SetAppContainer(scoped_refptr<AppContainer> container);
-  // Creates PROC_THREAD_ATTRIBUTE_JOB_LIST with |job_handle|. Not valid before
-  // Windows 10.
+  // Creates PROC_THREAD_ATTRIBUTE_JOB_LIST with |job_handle|.
   void AddJobToAssociate(HANDLE job_handle);
 
   // Will one or more jobs be associated via the wrapped StartupInformation.
diff --git a/sandbox/win/src/target_process.cc b/sandbox/win/src/target_process.cc
index e457a595..a0e608a 100644
--- a/sandbox/win/src/target_process.cc
+++ b/sandbox/win/src/target_process.cc
@@ -83,15 +83,11 @@
 TargetProcess::TargetProcess(
     base::win::ScopedHandle initial_token,
     base::win::ScopedHandle lockdown_token,
-    HANDLE job,
     ThreadPool* thread_pool,
     const std::vector<base::win::Sid>& impersonation_capabilities)
-    // This object owns everything initialized here except thread_pool and
-    // the job_ handle. The Job handle is closed by BrokerServices and results
-    // eventually in a call to our dtor.
+    // This object owns everything initialized here except thread_pool.
     : lockdown_token_(std::move(lockdown_token)),
       initial_token_(std::move(initial_token)),
-      job_(job),
       thread_pool_(thread_pool),
       base_address_(nullptr),
       impersonation_capabilities_(
@@ -139,12 +135,6 @@
   if (startup_info->has_extended_startup_info())
     flags |= EXTENDED_STARTUPINFO_PRESENT;
 
-  if (job_ && base::win::GetVersion() < base::win::Version::WIN8) {
-    // Windows 8 implements nested jobs, but for older systems we need to
-    // break out of any job we're in to enforce our restrictions.
-    flags |= CREATE_BREAKAWAY_FROM_JOB;
-  }
-
   bool inherit_handles = startup_info_helper->ShouldInheritHandles();
   PROCESS_INFORMATION temp_process_info = {};
   if (!::CreateProcessAsUserW(lockdown_token_.Get(), exe_path, cmd_line.get(),
@@ -160,17 +150,6 @@
   }
   base::win::ScopedProcessInformation process_info(temp_process_info);
 
-  if (job_ && !startup_info_helper->HasJobsToAssociate()) {
-    DCHECK(base::win::GetVersion() < base::win::Version::WIN10);
-    // Assign the suspended target to the windows job object. On Win 10
-    // this happens through PROC_THREAD_ATTRIBUTE_JOB_LIST.
-    if (!::AssignProcessToJobObject(job_, process_info.process_handle())) {
-      *win_error = ::GetLastError();
-      ::TerminateProcess(process_info.process_handle(), 0);
-      return SBOX_ERROR_ASSIGN_PROCESS_TO_JOB_OBJECT;
-    }
-  }
-
   if (initial_token_.IsValid()) {
     HANDLE impersonation_token = initial_token_.Get();
     base::win::ScopedHandle app_container_token;
@@ -378,7 +357,7 @@
     HANDLE process,
     HMODULE base_address) {
   auto target = std::make_unique<TargetProcess>(
-      base::win::ScopedHandle(), base::win::ScopedHandle(), nullptr, nullptr,
+      base::win::ScopedHandle(), base::win::ScopedHandle(), nullptr,
       std::vector<base::win::Sid>());
   PROCESS_INFORMATION process_info = {};
   process_info.hProcess = process;
diff --git a/sandbox/win/src/target_process.h b/sandbox/win/src/target_process.h
index fcaec33..db08820 100644
--- a/sandbox/win/src/target_process.h
+++ b/sandbox/win/src/target_process.h
@@ -36,7 +36,6 @@
   // The constructor takes ownership of |initial_token| and |lockdown_token|
   TargetProcess(base::win::ScopedHandle initial_token,
                 base::win::ScopedHandle lockdown_token,
-                HANDLE job,
                 ThreadPool* thread_pool,
                 const std::vector<base::win::Sid>& impersonation_capabilities);
 
@@ -71,9 +70,6 @@
   // Returns the handle to the target process.
   HANDLE Process() const { return sandbox_process_info_.process_handle(); }
 
-  // Returns the handle to the job object that the target process belongs to.
-  HANDLE Job() const { return job_; }
-
   // Returns the address of the target main exe. This is used by the
   // interceptions framework.
   HMODULE MainModule() const {
@@ -112,10 +108,6 @@
   base::win::ScopedHandle initial_token_;
   // Kernel handle to the shared memory used by the IPC server.
   base::win::ScopedHandle shared_section_;
-  // Job object containing the target process. This is used during
-  // process creation prior to Windows 10 and to identify the process in
-  // broker_services.cc.
-  HANDLE job_;
   // Reference to the IPC subsystem.
   std::unique_ptr<SharedMemIPCServer> ipc_server_;
   // Provides the threads used by the IPC. This class does not own this pointer.
diff --git a/sandbox/win/src/win_utils.cc b/sandbox/win/src/win_utils.cc
index 03b7178..0ef8ad47 100644
--- a/sandbox/win/src/win_utils.cc
+++ b/sandbox/win/src/win_utils.cc
@@ -573,36 +573,6 @@
   return handle_map;
 }
 
-absl::optional<ProcessHandleMap> GetCurrentProcessHandlesWin7() {
-  DWORD handle_count = UINT_MAX;
-  const int kInvalidHandleThreshold = 100;
-  const size_t kHandleOffset = 4;  // Handles are always a multiple of 4.
-
-  if (!::GetProcessHandleCount(::GetCurrentProcess(), &handle_count))
-    return absl::nullopt;
-  ProcessHandleMap handle_map;
-
-  uint32_t handle_value = 0;
-  int invalid_count = 0;
-
-  // Keep incrementing until we hit the number of handles reported by
-  // GetProcessHandleCount(). If we hit a very long sequence of invalid
-  // handles we assume that we've run past the end of the table.
-  while (handle_count && invalid_count < kInvalidHandleThreshold) {
-    handle_value += kHandleOffset;
-    HANDLE handle = base::win::Uint32ToHandle(handle_value);
-    std::wstring type_name;
-    if (!GetTypeNameFromHandle(handle, &type_name)) {
-      ++invalid_count;
-      continue;
-    }
-
-    --handle_count;
-    handle_map[type_name].push_back(handle);
-  }
-  return handle_map;
-}
-
 }  // namespace sandbox
 
 void ResolveNTFunctionPtr(const char* name, void* ptr) {
diff --git a/sandbox/win/src/win_utils.h b/sandbox/win/src/win_utils.h
index fbc9822..fafc6b7 100644
--- a/sandbox/win/src/win_utils.h
+++ b/sandbox/win/src/win_utils.h
@@ -123,11 +123,6 @@
 // change between the return of the list and when you use them.
 absl::optional<ProcessHandleMap> GetCurrentProcessHandles();
 
-// Fallback function for GetCurrentProcessHandles. Should only be needed on
-// Windows 7 which doesn't support the API to query all process handles. This
-// uses a brute force method to get the process handles.
-absl::optional<ProcessHandleMap> GetCurrentProcessHandlesWin7();
-
 }  // namespace sandbox
 
 // Resolves a function name in NTDLL to a function pointer. The second parameter
diff --git a/sandbox/win/src/win_utils_unittest.cc b/sandbox/win/src/win_utils_unittest.cc
index d136f61..785a7b5a 100644
--- a/sandbox/win/src/win_utils_unittest.cc
+++ b/sandbox/win/src/win_utils_unittest.cc
@@ -326,13 +326,7 @@
 }
 
 TEST(WinUtils, GetCurrentProcessHandles) {
-  if (base::win::GetVersion() < base::win::Version::WIN8) {
-    ASSERT_FALSE(GetCurrentProcessHandles());
-    EXPECT_EQ(DWORD{ERROR_INVALID_PARAMETER}, ::GetLastError());
-  } else {
-    TestCurrentProcessHandles(GetCurrentProcessHandles);
-  }
-  TestCurrentProcessHandles(GetCurrentProcessHandlesWin7);
+  TestCurrentProcessHandles(GetCurrentProcessHandles);
 }
 
 }  // namespace sandbox
diff --git a/services/accessibility/BUILD.gn b/services/accessibility/BUILD.gn
index 0988306f..a0e2a2eb 100644
--- a/services/accessibility/BUILD.gn
+++ b/services/accessibility/BUILD.gn
@@ -12,17 +12,13 @@
 }
 
 source_set("lib") {
-  sources = [
-    "automation_impl.cc",
-    "automation_impl.h",
-  ]
   public_deps = [
     "//services/accessibility/public/mojom",
     "//ui/accessibility:ax_base",
   ]
 
   if (is_chromeos_ash) {
-    sources += [
+    sources = [
       "accessibility_service_cros.cc",
       "accessibility_service_cros.h",
       "assistive_technology_controller_impl.cc",
@@ -41,9 +37,11 @@
       "//v8:external_startup_data",
     ]
   } else {
-    sources += [
+    sources = [
       "accessibility_service_chrome.cc",
       "accessibility_service_chrome.h",
+      "automation_impl.cc",
+      "automation_impl.h",
     ]
   }
 }
@@ -52,8 +50,8 @@
   testonly = true
 
   sources = [
-    "fake_automation_client.cc",
-    "fake_automation_client.h",
+    "fake_service_client.cc",
+    "fake_service_client.h",
   ]
 
   deps = [ ":lib" ]
diff --git a/services/accessibility/accessibility_service_chrome.cc b/services/accessibility/accessibility_service_chrome.cc
index 3b1a4a8..f651d237 100644
--- a/services/accessibility/accessibility_service_chrome.cc
+++ b/services/accessibility/accessibility_service_chrome.cc
@@ -17,11 +17,12 @@
 
 AccessibilityServiceChrome::~AccessibilityServiceChrome() = default;
 
-void AccessibilityServiceChrome::BindAutomation(
-    mojo::PendingRemote<mojom::AutomationClient> automation_client_remote,
-    mojo::PendingReceiver<mojom::Automation> automation_receiver) {
-  automation_->Bind(std::move(automation_client_remote),
-                    std::move(automation_receiver));
+void AccessibilityServiceChrome::BindAccessibilityServiceClient(
+    mojo::PendingRemote<mojom::AccessibilityServiceClient>
+        accessibility_client_remote) {
+  DCHECK(!accessibility_service_client_remote_.is_bound());
+  accessibility_service_client_remote_.Bind(
+      std::move(accessibility_client_remote));
 }
 
 }  // namespace ax
diff --git a/services/accessibility/accessibility_service_chrome.h b/services/accessibility/accessibility_service_chrome.h
index 8cd7f9da..1b81404 100644
--- a/services/accessibility/accessibility_service_chrome.h
+++ b/services/accessibility/accessibility_service_chrome.h
@@ -8,6 +8,7 @@
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
 #include "services/accessibility/public/mojom/accessibility_service.mojom.h"
 
 namespace ax {
@@ -25,13 +26,15 @@
 
  private:
   // mojom::AccessibilityService:
-  void BindAutomation(
-      mojo::PendingRemote<mojom::AutomationClient> accessibility_client_remote,
-      mojo::PendingReceiver<mojom::Automation> automation_receiver) override;
+  void BindAccessibilityServiceClient(
+      mojo::PendingRemote<mojom::AccessibilityServiceClient>
+          accessibility_client_remote) override;
 
   std::unique_ptr<AutomationImpl> automation_;
 
   mojo::Receiver<mojom::AccessibilityService> receiver_;
+  mojo::Remote<mojom::AccessibilityServiceClient>
+      accessibility_service_client_remote_;
 
   base::WeakPtrFactory<AccessibilityServiceChrome> weak_ptr_factory_{this};
 };
diff --git a/services/accessibility/accessibility_service_chrome_unittest.cc b/services/accessibility/accessibility_service_chrome_unittest.cc
index 9957dfb5..38bed16 100644
--- a/services/accessibility/accessibility_service_chrome_unittest.cc
+++ b/services/accessibility/accessibility_service_chrome_unittest.cc
@@ -5,21 +5,21 @@
 #include "services/accessibility/accessibility_service_chrome.h"
 #include "base/test/task_environment.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
-#include "services/accessibility/fake_automation_client.h"
+#include "services/accessibility/fake_service_client.h"
 #include "services/accessibility/public/mojom/accessibility_service.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace ax {
 
-TEST(AccessibilityServiceTest, BindsAutomation) {
+TEST(AccessibilityServiceTest, BindsAccessibilityServiceClient) {
   base::test::SingleThreadTaskEnvironment task_environment;
   mojo::PendingReceiver<mojom::AccessibilityService> receiver;
   std::unique_ptr<AccessibilityServiceChrome> service =
       std::make_unique<AccessibilityServiceChrome>(std::move(receiver));
 
-  FakeAutomationClient client(service.get());
-  client.BindToAutomation();
-  EXPECT_TRUE(client.IsBound());
+  FakeServiceClient client(service.get());
+  client.BindAccessibilityServiceClientForTest();
+  EXPECT_TRUE(client.AccessibilityServiceClientIsBound());
 }
 
 }  // namespace ax
diff --git a/services/accessibility/accessibility_service_cros.cc b/services/accessibility/accessibility_service_cros.cc
index b8ec461..41a6d8f 100644
--- a/services/accessibility/accessibility_service_cros.cc
+++ b/services/accessibility/accessibility_service_cros.cc
@@ -5,7 +5,6 @@
 #include "services/accessibility/accessibility_service_cros.h"
 
 #include <memory>
-#include "services/accessibility/automation_impl.h"
 
 #include "services/accessibility/assistive_technology_controller_impl.h"
 
@@ -15,16 +14,15 @@
     mojo::PendingReceiver<mojom::AccessibilityService> receiver)
     : receiver_(this, std::move(receiver)) {
   at_controller_ = std::make_unique<AssistiveTechnologyControllerImpl>();
-  automation_ = std::make_unique<AutomationImpl>();
 }
 
 AccessibilityServiceCros::~AccessibilityServiceCros() = default;
 
-void AccessibilityServiceCros::BindAutomation(
-    mojo::PendingRemote<mojom::AutomationClient> automation_client_remote,
-    mojo::PendingReceiver<mojom::Automation> automation_receiver) {
-  automation_->Bind(std::move(automation_client_remote),
-                    std::move(automation_receiver));
+void AccessibilityServiceCros::BindAccessibilityServiceClient(
+    mojo::PendingRemote<mojom::AccessibilityServiceClient>
+        accessibility_client_remote) {
+  at_controller_->BindAccessibilityServiceClient(
+      std::move(accessibility_client_remote));
 }
 
 void AccessibilityServiceCros::BindAssistiveTechnologyController(
@@ -32,9 +30,7 @@
         at_at_controller_receiver,
     const std::vector<mojom::AssistiveTechnologyType>& enabled_features) {
   at_controller_->Bind(std::move(at_at_controller_receiver));
-  for (auto feature : enabled_features) {
-    at_controller_->EnableAssistiveTechnology(feature, /*enabled=*/true);
-  }
+  at_controller_->EnableAssistiveTechnology(enabled_features);
 }
 
 }  // namespace ax
diff --git a/services/accessibility/accessibility_service_cros.h b/services/accessibility/accessibility_service_cros.h
index d62e0fa5..d35b43f 100644
--- a/services/accessibility/accessibility_service_cros.h
+++ b/services/accessibility/accessibility_service_cros.h
@@ -11,8 +11,6 @@
 #include "services/accessibility/public/mojom/accessibility_service.mojom.h"
 
 namespace ax {
-
-class AutomationImpl;
 class AssistiveTechnologyControllerImpl;
 
 // Implementation of the Accessibility Service for Chrome OS.
@@ -26,11 +24,12 @@
 
  private:
   friend class AccessibilityServiceCrosTest;
+  friend class AssistiveTechnologyControllerTest;
 
   // mojom::AccessibilityService:
-  void BindAutomation(
-      mojo::PendingRemote<mojom::AutomationClient> accessibility_client_remote,
-      mojo::PendingReceiver<mojom::Automation> automation_receiver) override;
+  void BindAccessibilityServiceClient(
+      mojo::PendingRemote<mojom::AccessibilityServiceClient>
+          accessibility_client_remote) override;
   void BindAssistiveTechnologyController(
       mojo::PendingReceiver<mojom::AssistiveTechnologyController>
           at_controller_receiver,
@@ -39,8 +38,6 @@
 
   std::unique_ptr<AssistiveTechnologyControllerImpl> at_controller_;
 
-  std::unique_ptr<AutomationImpl> automation_;
-
   mojo::Receiver<mojom::AccessibilityService> receiver_;
 
   base::WeakPtrFactory<AccessibilityServiceCros> weak_ptr_factory_{this};
diff --git a/services/accessibility/accessibility_service_cros_unittest.cc b/services/accessibility/accessibility_service_cros_unittest.cc
index 6d45b7b..9203367 100644
--- a/services/accessibility/accessibility_service_cros_unittest.cc
+++ b/services/accessibility/accessibility_service_cros_unittest.cc
@@ -3,13 +3,14 @@
 // found in the LICENSE file.
 
 #include "services/accessibility/accessibility_service_cros.h"
+
 #include "base/functional/bind.h"
 #include "base/test/task_environment.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/accessibility/assistive_technology_controller_impl.h"
-#include "services/accessibility/fake_automation_client.h"
+#include "services/accessibility/fake_service_client.h"
 #include "services/accessibility/public/mojom/accessibility_service.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -64,14 +65,14 @@
   base::test::TaskEnvironment task_environment_;
 };
 
-TEST_F(AccessibilityServiceCrosTest, BindsAutomation) {
+TEST_F(AccessibilityServiceCrosTest, BindsAccessibilityServiceClient) {
   mojo::PendingReceiver<mojom::AccessibilityService> receiver;
   std::unique_ptr<AccessibilityServiceCros> service =
       std::make_unique<AccessibilityServiceCros>(std::move(receiver));
 
-  FakeAutomationClient client(service.get());
-  client.BindToAutomation();
-  EXPECT_TRUE(client.IsBound());
+  FakeServiceClient client(service.get());
+  client.BindAccessibilityServiceClientForTest();
+  EXPECT_TRUE(client.AccessibilityServiceClientIsBound());
 }
 
 TEST_F(AccessibilityServiceCrosTest,
diff --git a/services/accessibility/assistive_technology_controller_impl.cc b/services/accessibility/assistive_technology_controller_impl.cc
index c6e99a8..c5b57a3 100644
--- a/services/accessibility/assistive_technology_controller_impl.cc
+++ b/services/accessibility/assistive_technology_controller_impl.cc
@@ -8,6 +8,7 @@
 
 #include "services/accessibility/automation_impl.h"
 #include "services/accessibility/features/v8_manager.h"
+#include "services/accessibility/public/mojom/accessibility_service.mojom.h"
 
 namespace ax {
 
@@ -24,24 +25,35 @@
   at_controller_receiver_.Bind(std::move(at_controller_receiver));
 }
 
+void AssistiveTechnologyControllerImpl::BindAccessibilityServiceClient(
+    mojo::PendingRemote<mojom::AccessibilityServiceClient>
+        accessibility_client_remote) {
+  DCHECK(!accessibility_service_client_remote_.is_bound());
+  accessibility_service_client_remote_.Bind(
+      std::move(accessibility_client_remote));
+}
+
 void AssistiveTechnologyControllerImpl::BindAutomation(
     mojo::PendingRemote<mojom::Automation> automation,
     mojo::PendingReceiver<mojom::AutomationClient> automation_client) {
-  if (automation_bound_closure_for_test_) {
-    std::move(automation_bound_closure_for_test_).Run();
-  }
-  // TODO(crbug.com/1355633): Bind to Automation in the embedding OS
-  // after updating the mojom. See go/chromeos-atp-v8-design.
+  accessibility_service_client_remote_->BindAutomation(
+      std::move(automation), std::move(automation_client));
 }
 
 void AssistiveTechnologyControllerImpl::EnableAssistiveTechnology(
-    mojom::AssistiveTechnologyType type,
-    bool enabled) {
-  auto it = enabled_ATs_.find(type);
-  if (enabled && it == enabled_ATs_.end()) {
-    enabled_ATs_[type] = GetOrMakeV8Manager(type);
-  } else if (!enabled && it != enabled_ATs_.end()) {
-    enabled_ATs_.erase(type);
+    const std::vector<mojom::AssistiveTechnologyType>& enabled_features) {
+  for (int i = static_cast<int>(mojom::AssistiveTechnologyType::kMinValue);
+       i <= static_cast<int>(mojom::AssistiveTechnologyType::kMaxValue); i++) {
+    mojom::AssistiveTechnologyType type =
+        static_cast<mojom::AssistiveTechnologyType>(i);
+    bool enabled = std::find(enabled_features.begin(), enabled_features.end(),
+                             type) != enabled_features.end();
+    auto it = enabled_ATs_.find(type);
+    if (enabled && it == enabled_ATs_.end()) {
+      enabled_ATs_[type] = GetOrMakeV8Manager(type);
+    } else if (!enabled && it != enabled_ATs_.end()) {
+      enabled_ATs_.erase(type);
+    }
   }
 }
 
@@ -50,11 +62,6 @@
   return enabled_ATs_.find(type) != enabled_ATs_.end();
 }
 
-void AssistiveTechnologyControllerImpl::SetAutomationBoundClosureForTest(
-    base::OnceClosure closure) {
-  automation_bound_closure_for_test_ = std::move(closure);
-}
-
 void AssistiveTechnologyControllerImpl::RunScriptForTest(
     mojom::AssistiveTechnologyType type,
     const std::string& script,
diff --git a/services/accessibility/assistive_technology_controller_impl.h b/services/accessibility/assistive_technology_controller_impl.h
index 6324de36..de9111f 100644
--- a/services/accessibility/assistive_technology_controller_impl.h
+++ b/services/accessibility/assistive_technology_controller_impl.h
@@ -29,25 +29,27 @@
   AssistiveTechnologyControllerImpl& operator=(
       const AssistiveTechnologyControllerImpl&) = delete;
 
-  // Called by the AccessibilityService.
+  // Called by the AccessibilityServiceCros.
   void Bind(mojo::PendingReceiver<mojom::AssistiveTechnologyController>
                 at_controller_receiver);
+  void BindAccessibilityServiceClient(
+      mojo::PendingRemote<mojom::AccessibilityServiceClient>
+          accessibility_client_remote);
 
-  // Called by the Automation implementation within a V8 isolate to request
-  // binding to the OS automation and automation client.
+  // Called by AutomationInternalBindings owned by a V8 instance
+  // to request binding of Automation and AutomationClient in the OS.
   void BindAutomation(
       mojo::PendingRemote<mojom::Automation> automation,
       mojo::PendingReceiver<mojom::AutomationClient> automation_client);
 
-  // TODO(crbug.com/1355633): Override this method from
   // mojom::AssistiveTechnologyController:
-  void EnableAssistiveTechnology(mojom::AssistiveTechnologyType type,
-                                 bool enabled);
+  void EnableAssistiveTechnology(
+      const std::vector<mojom::AssistiveTechnologyType>& enabled_features)
+      override;
 
   bool IsFeatureEnabled(mojom::AssistiveTechnologyType type) const;
 
   // Methods for testing.
-  void SetAutomationBoundClosureForTest(base::OnceClosure closure);
   void RunScriptForTest(mojom::AssistiveTechnologyType type,
                         const std::string& script,
                         base::OnceClosure on_complete);
@@ -65,13 +67,14 @@
   // AccessibilityServiceCros).
   bool v8_initialized_ = false;
 
-  // For testing.
-  base::OnceClosure automation_bound_closure_for_test_;
-
   // This class is a receiver for mojom::AssistiveTechnologyController.
   mojo::Receiver<mojom::AssistiveTechnologyController> at_controller_receiver_{
       this};
 
+  // The remote to the Accessibility Service Client in the OS.
+  mojo::Remote<mojom::AccessibilityServiceClient>
+      accessibility_service_client_remote_;
+
   base::WeakPtrFactory<AssistiveTechnologyControllerImpl> weak_ptr_factory_{
       this};
 };
diff --git a/services/accessibility/assistive_technology_controller_impl_unittest.cc b/services/accessibility/assistive_technology_controller_impl_unittest.cc
index c1f7fba..f605ec8 100644
--- a/services/accessibility/assistive_technology_controller_impl_unittest.cc
+++ b/services/accessibility/assistive_technology_controller_impl_unittest.cc
@@ -3,7 +3,10 @@
 // found in the LICENSE file.
 
 #include "services/accessibility/assistive_technology_controller_impl.h"
+
 #include "base/test/task_environment.h"
+#include "services/accessibility/accessibility_service_cros.h"
+#include "services/accessibility/fake_service_client.h"
 #include "services/accessibility/public/mojom/accessibility_service.mojom-shared.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -18,38 +21,63 @@
       const AssistiveTechnologyControllerTest&) = delete;
   ~AssistiveTechnologyControllerTest() override = default;
 
+  void SetUp() override {
+    mojo::PendingReceiver<mojom::AccessibilityService> receiver;
+    service_ = std::make_unique<AccessibilityServiceCros>(std::move(receiver));
+    at_controller_ = service_->at_controller_.get();
+
+    client_ = std::make_unique<FakeServiceClient>(service_.get());
+    client_->BindAccessibilityServiceClientForTest();
+    EXPECT_TRUE(client_->AccessibilityServiceClientIsBound());
+  }
+
+ protected:
+  AssistiveTechnologyControllerImpl* at_controller_ = nullptr;
+  std::unique_ptr<FakeServiceClient> client_;
+
  private:
+  std::unique_ptr<AccessibilityServiceCros> service_;
   base::test::TaskEnvironment task_environment_;
 };
 
 // Disabling disabled features is a no-op.
 TEST_F(AssistiveTechnologyControllerTest, DisablesDisabledFeatures) {
-  AssistiveTechnologyControllerImpl at_controller;
   // Features begin disabled at construction.
   for (int i = static_cast<int>(mojom::AssistiveTechnologyType::kMinValue);
-       i < static_cast<int>(mojom::AssistiveTechnologyType::kMaxValue); i++) {
+       i <= static_cast<int>(mojom::AssistiveTechnologyType::kMaxValue); i++) {
     mojom::AssistiveTechnologyType type =
         static_cast<mojom::AssistiveTechnologyType>(i);
-    EXPECT_FALSE(at_controller.IsFeatureEnabled(type));
+    EXPECT_FALSE(at_controller_->IsFeatureEnabled(type));
   }
+  std::vector<mojom::AssistiveTechnologyType> empty_features;
+  at_controller_->EnableAssistiveTechnology(empty_features);
   // I have disabled your features. Pray I do not disable them further.
   for (int i = static_cast<int>(mojom::AssistiveTechnologyType::kMinValue);
-       i < static_cast<int>(mojom::AssistiveTechnologyType::kMaxValue); i++) {
+       i <= static_cast<int>(mojom::AssistiveTechnologyType::kMaxValue); i++) {
     mojom::AssistiveTechnologyType type =
         static_cast<mojom::AssistiveTechnologyType>(i);
-    at_controller.EnableAssistiveTechnology(type, /*enabled=*/false);
-    EXPECT_FALSE(at_controller.IsFeatureEnabled(type));
+    EXPECT_FALSE(at_controller_->IsFeatureEnabled(type));
   }
 }
 
 // Enables one feature several times in a row to ensure it doesn't cause issues.
 TEST_F(AssistiveTechnologyControllerTest, EnablesEnabledFeatures) {
   AssistiveTechnologyControllerImpl at_controller;
-  for (int i = 0; i < 3; i++) {
-    at_controller.EnableAssistiveTechnology(
-        mojom::AssistiveTechnologyType::kDictation, /*enabled=*/true);
-    EXPECT_TRUE(at_controller.IsFeatureEnabled(
-        mojom::AssistiveTechnologyType::kDictation));
+  std::vector<mojom::AssistiveTechnologyType> enabled_features;
+
+  // For each feature, try and enable it three times in a row.
+  for (int i = static_cast<int>(mojom::AssistiveTechnologyType::kMinValue);
+       i <= static_cast<int>(mojom::AssistiveTechnologyType::kMaxValue); i++) {
+    mojom::AssistiveTechnologyType type =
+        static_cast<mojom::AssistiveTechnologyType>(i);
+    enabled_features.emplace_back(type);
+    for (int j = 0; j < 3; j++) {
+      at_controller_->EnableAssistiveTechnology(enabled_features);
+      EXPECT_TRUE(at_controller_->IsFeatureEnabled(type));
+    }
+    enabled_features.clear();
+    at_controller_->EnableAssistiveTechnology(enabled_features);
+    EXPECT_FALSE(at_controller_->IsFeatureEnabled(type));
   }
 }
 
@@ -57,20 +85,23 @@
 TEST_F(AssistiveTechnologyControllerTest, EnableAndDisableAllFeatures) {
   AssistiveTechnologyControllerImpl at_controller;
   // Turn everything on.
+  std::vector<mojom::AssistiveTechnologyType> enabled_features;
   for (int i = static_cast<int>(mojom::AssistiveTechnologyType::kMinValue);
-       i < static_cast<int>(mojom::AssistiveTechnologyType::kMaxValue); i++) {
+       i <= static_cast<int>(mojom::AssistiveTechnologyType::kMaxValue); i++) {
     mojom::AssistiveTechnologyType type =
         static_cast<mojom::AssistiveTechnologyType>(i);
-    at_controller.EnableAssistiveTechnology(type, /*enabled=*/true);
-    EXPECT_TRUE(at_controller.IsFeatureEnabled(type));
+    enabled_features.emplace_back(type);
+    at_controller_->EnableAssistiveTechnology(enabled_features);
+    EXPECT_TRUE(at_controller_->IsFeatureEnabled(type));
   }
   // Turn everything off.
-  for (int i = static_cast<int>(mojom::AssistiveTechnologyType::kMinValue);
-       i < static_cast<int>(mojom::AssistiveTechnologyType::kMaxValue); i++) {
+  for (int i = static_cast<int>(mojom::AssistiveTechnologyType::kMaxValue);
+       i >= static_cast<int>(mojom::AssistiveTechnologyType::kMinValue); i--) {
     mojom::AssistiveTechnologyType type =
         static_cast<mojom::AssistiveTechnologyType>(i);
-    at_controller.EnableAssistiveTechnology(type, /*enabled=*/false);
-    EXPECT_FALSE(at_controller.IsFeatureEnabled(type));
+    enabled_features.pop_back();
+    at_controller_->EnableAssistiveTechnology(enabled_features);
+    EXPECT_FALSE(at_controller_->IsFeatureEnabled(type));
   }
 }
 
@@ -78,10 +109,10 @@
        BindsAutomationMojomAfterEnablingFeature) {
   AssistiveTechnologyControllerImpl at_controller;
   base::RunLoop automation_bound_runner_;
-  at_controller.SetAutomationBoundClosureForTest(
-      automation_bound_runner_.QuitClosure());
-  at_controller.EnableAssistiveTechnology(
-      mojom::AssistiveTechnologyType::kMagnifier, /*enabled=*/true);
+  client_->SetAutomationBoundClosure(automation_bound_runner_.QuitClosure());
+  std::vector<mojom::AssistiveTechnologyType> enabled_features;
+  enabled_features.emplace_back(mojom::AssistiveTechnologyType::kMagnifier);
+  at_controller_->EnableAssistiveTechnology(enabled_features);
   automation_bound_runner_.Run();
   // TODO(crbug.com/1355633): After adding mojom to bind automation, we can
   // start passing a11y events to V8 here.
@@ -90,19 +121,20 @@
 TEST_F(AssistiveTechnologyControllerTest,
        BindsAutomationV8AfterEnablingFeature) {
   AssistiveTechnologyControllerImpl at_controller;
-  at_controller.EnableAssistiveTechnology(
-      mojom::AssistiveTechnologyType::kChromeVox, /*enabled=*/true);
+  std::vector<mojom::AssistiveTechnologyType> enabled_features;
+  enabled_features.emplace_back(mojom::AssistiveTechnologyType::kChromeVox);
+  at_controller_->EnableAssistiveTechnology(enabled_features);
   base::RunLoop script_waiter;
   // This script will not compile if chrome.automation.GetFocus() is not found
   // in V8, causing the test to crash.
-  // TODO(crbug.com/1355633): After adding mojom to bind automation, we can
+  // TODO(crbug.com/1355633): After adding mojom for automation, we can
   // start passing a11y events to V8 and then ensuring calling these methods
   // changes the underlying accessibility info.
   std::string script = R"JS(
     chrome.automation.GetFocus();
   )JS";
-  at_controller.RunScriptForTest(mojom::AssistiveTechnologyType::kChromeVox,
-                                 script, script_waiter.QuitClosure());
+  at_controller_->RunScriptForTest(mojom::AssistiveTechnologyType::kChromeVox,
+                                   script, script_waiter.QuitClosure());
   script_waiter.Run();
 }
 
diff --git a/services/accessibility/automation_impl.h b/services/accessibility/automation_impl.h
index 66004b0..7d8bee2 100644
--- a/services/accessibility/automation_impl.h
+++ b/services/accessibility/automation_impl.h
@@ -18,8 +18,7 @@
 
 namespace ax {
 
-// Implementation of Automation in the Accessibility service process for Chrome
-// OS. This will construct accessibility trees in V8.
+// Implementation of Automation in the Accessibility service process for Chrome.
 class AutomationImpl : public mojom::Automation {
  public:
   AutomationImpl();
diff --git a/services/accessibility/fake_automation_client.cc b/services/accessibility/fake_automation_client.cc
deleted file mode 100644
index a860001..0000000
--- a/services/accessibility/fake_automation_client.cc
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "services/accessibility/fake_automation_client.h"
-
-namespace ax {
-FakeAutomationClient::FakeAutomationClient(mojom::AccessibilityService* service)
-    : service_(service) {}
-
-FakeAutomationClient::~FakeAutomationClient() = default;
-
-void FakeAutomationClient::BindToAutomation() {
-  service_->BindAutomation(
-      automation_client_receiver_.BindNewPipeAndPassRemote(),
-      automation_.BindNewPipeAndPassReceiver());
-}
-
-bool FakeAutomationClient::IsBound() {
-  return automation_.is_bound() && automation_client_receiver_.is_bound();
-}
-
-}  // namespace ax
diff --git a/services/accessibility/fake_automation_client.h b/services/accessibility/fake_automation_client.h
deleted file mode 100644
index 8984e0f..0000000
--- a/services/accessibility/fake_automation_client.h
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef SERVICES_ACCESSIBILITY_FAKE_AUTOMATION_CLIENT_H_
-#define SERVICES_ACCESSIBILITY_FAKE_AUTOMATION_CLIENT_H_
-
-#include "mojo/public/cpp/bindings/receiver.h"
-#include "mojo/public/cpp/bindings/remote.h"
-#include "services/accessibility/public/mojom/accessibility_service.mojom.h"
-
-namespace ax {
-
-// A fake Automation Client for use in tests.
-// TODO(crbug.com/1355633) This can be extended to allow for passing events into
-// the service once the mojom is landed.
-class FakeAutomationClient : public mojom::AutomationClient {
- public:
-  explicit FakeAutomationClient(mojom::AccessibilityService* service);
-  FakeAutomationClient(const FakeAutomationClient& other) = delete;
-  FakeAutomationClient& operator=(const FakeAutomationClient&) = delete;
-  ~FakeAutomationClient() override;
-
-  // Methods for testing.
-  void BindToAutomation();
-  bool IsBound();
-
- private:
-  mojom::AccessibilityService* service_;
-
-  mojo::Remote<mojom::Automation> automation_;
-  mojo::Receiver<mojom::AutomationClient> automation_client_receiver_{this};
-};
-
-}  // namespace ax
-
-#endif  // SERVICES_ACCESSIBILITY_FAKE_AUTOMATION_CLIENT_H_
diff --git a/services/accessibility/fake_service_client.cc b/services/accessibility/fake_service_client.cc
new file mode 100644
index 0000000..947ebcef
--- /dev/null
+++ b/services/accessibility/fake_service_client.cc
@@ -0,0 +1,40 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/accessibility/fake_service_client.h"
+
+namespace ax {
+FakeServiceClient::FakeServiceClient(mojom::AccessibilityService* service)
+    : service_(service) {}
+
+FakeServiceClient::~FakeServiceClient() = default;
+
+void FakeServiceClient::BindAutomation(
+    mojo::PendingRemote<ax::mojom::Automation> automation,
+    mojo::PendingReceiver<ax::mojom::AutomationClient> automation_client) {
+  automation_client_receivers_.Add(this, std::move(automation_client));
+  automation_remotes_.Add(std::move(automation));
+  if (automation_bound_closure_) {
+    std::move(automation_bound_closure_).Run();
+  }
+}
+
+void FakeServiceClient::BindAccessibilityServiceClientForTest() {
+  service_->BindAccessibilityServiceClient(
+      a11y_client_receiver_.BindNewPipeAndPassRemote());
+}
+
+void FakeServiceClient::SetAutomationBoundClosure(base::OnceClosure closure) {
+  automation_bound_closure_ = std::move(closure);
+}
+
+bool FakeServiceClient::AutomationIsBound() {
+  return automation_client_receivers_.size() && automation_remotes_.size();
+}
+
+bool FakeServiceClient::AccessibilityServiceClientIsBound() {
+  return a11y_client_receiver_.is_bound();
+}
+
+}  // namespace ax
diff --git a/services/accessibility/fake_service_client.h b/services/accessibility/fake_service_client.h
new file mode 100644
index 0000000..0039c8d
--- /dev/null
+++ b/services/accessibility/fake_service_client.h
@@ -0,0 +1,51 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_ACCESSIBILITY_FAKE_SERVICE_CLIENT_H_
+#define SERVICES_ACCESSIBILITY_FAKE_SERVICE_CLIENT_H_
+
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
+#include "mojo/public/cpp/bindings/remote_set.h"
+#include "services/accessibility/public/mojom/accessibility_service.mojom.h"
+
+namespace ax {
+
+// A fake AccessibilityServiceClient and AutomationClient for use in tests.
+// This allows tests to mock out the OS side of the mojom pipes.
+// TODO(crbug.com/1355633) This can be extended to allow for passing events into
+// the service once the mojom is landed.
+class FakeServiceClient : public mojom::AccessibilityServiceClient,
+                          public mojom::AutomationClient {
+ public:
+  explicit FakeServiceClient(mojom::AccessibilityService* service);
+  FakeServiceClient(const FakeServiceClient& other) = delete;
+  FakeServiceClient& operator=(const FakeServiceClient&) = delete;
+  ~FakeServiceClient() override;
+
+  // ax::mojom::AccessibilityServiceClient:
+  void BindAutomation(mojo::PendingRemote<ax::mojom::Automation> automation,
+                      mojo::PendingReceiver<ax::mojom::AutomationClient>
+                          automation_client) override;
+
+  // Methods for testing.
+  void BindAccessibilityServiceClientForTest();
+  void SetAutomationBoundClosure(base::OnceClosure closure);
+  bool AccessibilityServiceClientIsBound();
+  bool AutomationIsBound();
+
+ private:
+  mojom::AccessibilityService* service_;
+
+  base::OnceClosure automation_bound_closure_;
+
+  mojo::Receiver<mojom::AccessibilityServiceClient> a11y_client_receiver_{this};
+
+  mojo::RemoteSet<mojom::Automation> automation_remotes_;
+  mojo::ReceiverSet<mojom::AutomationClient> automation_client_receivers_;
+};
+
+}  // namespace ax
+
+#endif  // SERVICES_ACCESSIBILITY_FAKE_SERVICE_CLIENT_H_
diff --git a/services/accessibility/public/mojom/accessibility_service.mojom b/services/accessibility/public/mojom/accessibility_service.mojom
index 413e5361..459c47c 100644
--- a/services/accessibility/public/mojom/accessibility_service.mojom
+++ b/services/accessibility/public/mojom/accessibility_service.mojom
@@ -14,8 +14,8 @@
 
 // Main interface a client uses to send accessibility tree updates and
 // results, implemented in the Accessibility service. Clients may
-// be the OS browser process, renderers, or other accessibility tree
-// sources.
+// be the main OS browser process, renderers, or other accessibility tree
+// sources or aggregators.
 interface Automation {
   // // Forwards an accessibility tree destroyed event from any accessibility
   // // tree from client to the accessibility service.
@@ -40,15 +40,22 @@
   //   ax.mojom.AXTreeID tree_id,
   //   int32 node_id,
   //   ax.mojom.AXRelativeBounds bounds);
+
+  // // Dispatches the result of a query for text location.
+  // // Currently supported by ARC++ in response to
+  // // ax::mojom::Action::kGetTextLocation.
+  //  DispatchGetTextLocationResult(
+  //    ax.mojom.ActionData data,
+  //    gfx.mojom.Rect rect);
 };
 
-// Implemented by e.g. Chrome OS Ash, Chrome browser process, renderers,
-// or other tree sources.
-// Used by the accessibility service to enable accessibility and perform
+// Implemented outside of the service, e.g. Chrome OS Ash, Chrome browser
+// process, renderers, or other tree sources.
+// Called by the accessibility service to enable accessibility and perform
 // actions. For example the accessibility service might want to do a
 // 'click' because a screen reader requested the default action. Then the
 // accessibility service would use AutomationClient::PerformAction to pass
-// that down to the client.
+// that back to the client.
 interface AutomationClient {
   // // Enables automation for the client. This will result in the client
   // // repeatedly calling DispatchAccessibilityEvents() on the Automation
@@ -72,7 +79,7 @@
   // PerformAction(ax.mojom.AXActionData action_data);
 };
 
-// Features which are implemented by the accessibility service.
+// All the features which are implemented by the accessibility service.
 [EnableIf=is_chromeos_ash]
 enum AssistiveTechnologyType {
   kChromeVox,
@@ -87,8 +94,9 @@
 // The caller is the client OS, for example, Chrome OS Ash.
 [EnableIf=is_chromeos_ash]
 interface AssistiveTechnologyController {
-  // // Turns on or off an accessibility feature in the service.
-  // EnableAssistiveTechnology(AssistiveTechnologyType type, bool enabled);
+  // Updates the service with the current list of enabled features.
+  // Any features not in this list are considered disabled.
+  EnableAssistiveTechnology(array<AssistiveTechnologyType> enabled_features);
 };
 
 // AccessibilityService aggregates accessibility information from
@@ -97,20 +105,38 @@
 // hosts accessibility features in a V8 runtime.
 // TODO(crbug.com/1355633): The Accessibility Service will need to run in a
 // sandboxed process that allows V8 execution and access to read local
-// Javascript files in a known directory.
+// Javascript files in a known directory in Chrome OS and Fushcia.
 [ServiceSandbox=sandbox.mojom.Sandbox.kService]
 interface AccessibilityService {
-  // Binds a new Automation hosted in the service process to a client.
-  BindAutomation(
-    pending_remote<AutomationClient> automation_client,
-    pending_receiver<Automation> automation);
+  // Binds a AccessibilityServiceClient implemented in the main OS process to
+  // the service.
+  BindAccessibilityServiceClient(
+    pending_remote<AccessibilityServiceClient> accessibility_service_client);
 
-  // Binds an AssistiveTechnologyController hosted in the service process,
-  // allowing the client to control which Assistive Technologies are active.
-  // Callers may pass in a list of initially enabled features; features not
-  // in this list are assumed disabled.
+  // Binds an AssistiveTechnologyController implemented in in the service
+  // process, allowing the client to control which Assistive Technologies are
+  // active. Callers may pass in a list of initially enabled features; features
+  // not in this list are assumed disabled.
   [EnableIf=is_chromeos_ash]
   BindAssistiveTechnologyController(
     pending_receiver<AssistiveTechnologyController> at_controller,
     array<AssistiveTechnologyType> enabled_features);
 };
+
+// Implemented by the main OS process, e.g. Chrome OS Ash on Chrome OS. Allows
+// the AccessibilityService to bind APIs on-demand when needed by AT features,
+// and to bind once or once-per-feature. May be called more than once.
+// On Chrome OS, for example, when ChromeVox is turned on the service will use
+// this interface to bind text-to-speech and Automation APIs, and when Dictation
+// is turned on, the service will bind speech recognition and Automation APIs.
+// TODO(crbug.com/1355633): Other APIs needed in Chrome OS and Fuchsia should be
+// bound here.
+interface AccessibilityServiceClient {
+  // Binds an Automation implemented in the service process to a client
+  // implemented in the main OS process.
+  // In Chrome OS this may be called once per feature to provide
+  // automation connections to each V8 isolate.
+  BindAutomation(
+    pending_remote<Automation> automation,
+    pending_receiver<AutomationClient> automation_client);
+};
diff --git a/services/data_decoder/public/cpp/data_decoder.cc b/services/data_decoder/public/cpp/data_decoder.cc
index 435d94b..812aff9b 100644
--- a/services/data_decoder/public/cpp/data_decoder.cc
+++ b/services/data_decoder/public/cpp/data_decoder.cc
@@ -130,7 +130,7 @@
 }
 #endif
 
-#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(BUILD_RUST_JSON_PARSER)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(BUILD_RUST_JSON_READER)
 
 void ParsingComplete(scoped_refptr<DataDecoder::CancellationFlag> is_cancelled,
                      DataDecoder::ValueParseCallback callback,
@@ -184,7 +184,7 @@
 
 void DataDecoder::ParseJson(const std::string& json,
                             ValueParseCallback callback) {
-#if BUILDFLAG(BUILD_RUST_JSON_PARSER)
+#if BUILDFLAG(BUILD_RUST_JSON_READER)
   // Parses JSON directly in the calling process using the memory-safe
   // Rust parser.
   base::ThreadPool::PostTaskAndReplyWithResult(
diff --git a/services/data_decoder/public/cpp/data_decoder_unittest.cc b/services/data_decoder/public/cpp/data_decoder_unittest.cc
index b300037..244b11e1 100644
--- a/services/data_decoder/public/cpp/data_decoder_unittest.cc
+++ b/services/data_decoder/public/cpp/data_decoder_unittest.cc
@@ -66,7 +66,7 @@
   EXPECT_EQ(2u, service().receivers().size());
 }
 
-#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(BUILD_RUST_JSON_PARSER)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(BUILD_RUST_JSON_READER)
 
 class DataDecoderMultiThreadTest : public testing::Test {
  protected:
@@ -98,6 +98,6 @@
   EXPECT_EQ(122.416294033786585, list[0].GetDouble());
 }
 
-#endif  // BUILDFLAG(IS_ANDROID) || BUILDFLAG(BUILD_RUST_JSON_PARSER)
+#endif  // BUILDFLAG(IS_ANDROID) || BUILDFLAG(BUILD_RUST_JSON_READER)
 
 }  // namespace data_decoder
diff --git a/services/network/udp_socket_unittest.cc b/services/network/udp_socket_unittest.cc
index 0e52a54..801dc13a 100644
--- a/services/network/udp_socket_unittest.cc
+++ b/services/network/udp_socket_unittest.cc
@@ -673,15 +673,14 @@
   EXPECT_EQ(std::vector<uint8_t>(), result.data.value());
 }
 
-#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_FUCHSIA)
+#if BUILDFLAG(IS_ANDROID)
 // Some Android devices do not support multicast socket.
 // The ones supporting multicast need WifiManager.MulticastLock to enable it.
 // https://developer.android.com/reference/android/net/wifi/WifiManager.MulticastLock.html
-// TODO(crbug.com/1255191): Fails on Fuchsia running with run-test-component.
 #define MAYBE_JoinMulticastGroup DISABLED_JoinMulticastGroup
 #else
 #define MAYBE_JoinMulticastGroup JoinMulticastGroup
-#endif  // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_FUCHSIA)
+#endif  // BUILDFLAG(IS_ANDROID)
 TEST_F(UDPSocketTest, MAYBE_JoinMulticastGroup) {
   const char kGroup[] = "237.132.100.17";
 
diff --git a/skia/config/SkUserConfig.h b/skia/config/SkUserConfig.h
index 8248d8a..0c63a28 100644
--- a/skia/config/SkUserConfig.h
+++ b/skia/config/SkUserConfig.h
@@ -214,8 +214,6 @@
 
 #define SK_SUPPORT_LEGACY_DRAWLOOPER
 
-#define SK_SUPPORT_LEGACY_DITHER
-
 #define SK_USE_LEGACY_MIPMAP_BUILDER
 
 ///////////////////////// Imported from BUILD.gn and skia_common.gypi
diff --git a/testing/buildbot/chromium.angle.json b/testing/buildbot/chromium.angle.json
index 708cd3d3..b298ac0 100644
--- a/testing/buildbot/chromium.angle.json
+++ b/testing/buildbot/chromium.angle.json
@@ -93,7 +93,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "named_caches": [
@@ -151,7 +151,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "named_caches": [
@@ -895,7 +895,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -920,7 +920,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -963,7 +963,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -1003,7 +1003,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -1044,7 +1044,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -1084,7 +1084,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -1124,7 +1124,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -1164,7 +1164,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index 46c13ea..b0c0d5c4 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -243,7 +243,6 @@
               }
             ]
           },
-          "quickrun_shards": 8,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 7
         },
@@ -1123,6 +1122,117 @@
           "--passthrough",
           "-v",
           "--stable-jobs",
+          "--extra-browser-args=--log-level=0 --js-flags=--expose-gc --use-gl=angle --use-angle=gles --use-cmd-decoder=passthrough --force_high_performance_gpu",
+          "--webgl-conformance-version=2.0.1",
+          "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl2_conformance_tests_output.json",
+          "--jobs=1",
+          "--remote=127.0.0.1",
+          "--remote-ssh-port=9222",
+          "--magic-vm-cache=magic_cros_vm_cache"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "webgl2_conformance_gles_passthrough_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86",
+              "kvm": "1",
+              "os": "Ubuntu-18.04",
+              "pool": "chromium.tests"
+            }
+          ],
+          "idempotent": false,
+          "named_caches": [
+            {
+              "name": "cros_vm",
+              "path": "magic_cros_vm_cache"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "cros_vm"
+              }
+            ]
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 20
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "webgl_conformance",
+          "--show-stdout",
+          "--browser=cros-chrome",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
+          "--extra-browser-args=--log-level=0 --js-flags=--expose-gc --use-gl=angle --use-angle=gles --use-cmd-decoder=passthrough --force_high_performance_gpu",
+          "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl_conformance_tests_output.json",
+          "--jobs=1",
+          "--remote=127.0.0.1",
+          "--remote-ssh-port=9222",
+          "--magic-vm-cache=magic_cros_vm_cache"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "webgl_conformance_gles_passthrough_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86",
+              "kvm": "1",
+              "os": "Ubuntu-18.04",
+              "pool": "chromium.tests"
+            }
+          ],
+          "idempotent": false,
+          "named_caches": [
+            {
+              "name": "cros_vm",
+              "path": "magic_cros_vm_cache"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "cros_vm"
+              }
+            ]
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 20
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "webgl_conformance",
+          "--show-stdout",
+          "--browser=cros-chrome",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
           "--extra-browser-args=--log-level=0 --js-flags=--expose-gc --force_high_performance_gpu --disable-features=BackgroundVideoPauseOptimization --disable-background-media-suspend --disable-renderer-backgrounding --disable-background-timer-throttling",
           "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl_conformance_tests_output.json",
           "--jobs=1",
@@ -3610,7 +3720,6 @@
               "os": "Ubuntu-18.04"
             }
           ],
-          "quickrun_shards": 40,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 60
         },
@@ -5254,7 +5363,6 @@
               "os": "Ubuntu-18.04"
             }
           ],
-          "quickrun_shards": 20,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 20
         },
@@ -5875,9 +5983,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5423.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5424.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5423.0",
+        "description": "Run with ash-chrome version 110.0.5424.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -5889,8 +5997,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5423.0",
-              "revision": "version:110.0.5423.0"
+              "location": "lacros_version_skew_tests_v110.0.5424.0",
+              "revision": "version:110.0.5424.0"
             }
           ],
           "dimension_sets": [
@@ -6041,9 +6149,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5423.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5424.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5423.0",
+        "description": "Run with ash-chrome version 110.0.5424.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -6055,8 +6163,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5423.0",
-              "revision": "version:110.0.5423.0"
+              "location": "lacros_version_skew_tests_v110.0.5424.0",
+              "revision": "version:110.0.5424.0"
             }
           ],
           "dimension_sets": [
@@ -6192,9 +6300,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5423.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5424.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5423.0",
+        "description": "Run with ash-chrome version 110.0.5424.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -6206,8 +6314,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5423.0",
-              "revision": "version:110.0.5423.0"
+              "location": "lacros_version_skew_tests_v110.0.5424.0",
+              "revision": "version:110.0.5424.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/chromium.dawn.json b/testing/buildbot/chromium.dawn.json
index 91ef666ea..b98a6c5 100644
--- a/testing/buildbot/chromium.dawn.json
+++ b/testing/buildbot/chromium.dawn.json
@@ -2596,7 +2596,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -2623,7 +2623,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -2650,7 +2650,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -2677,7 +2677,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -2703,7 +2703,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -2727,7 +2727,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -2758,7 +2758,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -2782,7 +2782,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -2821,7 +2821,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -2859,7 +2859,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -2895,7 +2895,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -2934,7 +2934,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -2973,7 +2973,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -3011,7 +3011,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -3049,7 +3049,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -3090,7 +3090,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -4624,7 +4624,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -4651,7 +4651,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -4678,7 +4678,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -4705,7 +4705,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -4731,7 +4731,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -4755,7 +4755,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -4786,7 +4786,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -4810,7 +4810,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -4849,7 +4849,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -4887,7 +4887,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -4923,7 +4923,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -4962,7 +4962,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -5001,7 +5001,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -5039,7 +5039,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -5077,7 +5077,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -5118,7 +5118,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index eb0d80ce..d7f5fe14 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -18872,6 +18872,117 @@
           "--passthrough",
           "-v",
           "--stable-jobs",
+          "--extra-browser-args=--log-level=0 --js-flags=--expose-gc --use-gl=angle --use-angle=gles --use-cmd-decoder=passthrough --force_high_performance_gpu",
+          "--webgl-conformance-version=2.0.1",
+          "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl2_conformance_tests_output.json",
+          "--jobs=1",
+          "--remote=127.0.0.1",
+          "--remote-ssh-port=9222",
+          "--magic-vm-cache=magic_cros_vm_cache"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "webgl2_conformance_gles_passthrough_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86",
+              "kvm": "1",
+              "os": "Ubuntu-18.04",
+              "pool": "chromium.tests"
+            }
+          ],
+          "idempotent": false,
+          "named_caches": [
+            {
+              "name": "cros_vm",
+              "path": "magic_cros_vm_cache"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "cros_vm"
+              }
+            ]
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 20
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "webgl_conformance",
+          "--show-stdout",
+          "--browser=cros-chrome",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
+          "--extra-browser-args=--log-level=0 --js-flags=--expose-gc --use-gl=angle --use-angle=gles --use-cmd-decoder=passthrough --force_high_performance_gpu",
+          "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl_conformance_tests_output.json",
+          "--jobs=1",
+          "--remote=127.0.0.1",
+          "--remote-ssh-port=9222",
+          "--magic-vm-cache=magic_cros_vm_cache"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "webgl_conformance_gles_passthrough_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86",
+              "kvm": "1",
+              "os": "Ubuntu-18.04",
+              "pool": "chromium.tests"
+            }
+          ],
+          "idempotent": false,
+          "named_caches": [
+            {
+              "name": "cros_vm",
+              "path": "magic_cros_vm_cache"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "cros_vm"
+              }
+            ]
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 20
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "webgl_conformance",
+          "--show-stdout",
+          "--browser=cros-chrome",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
           "--extra-browser-args=--log-level=0 --js-flags=--expose-gc --force_high_performance_gpu",
           "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl_conformance_tests_output.json",
           "--jobs=1",
@@ -82459,804 +82570,6 @@
       }
     ]
   },
-  "linux-blink-v8-sandbox-future-rel": {
-    "gtest_tests": [
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "absl_hardening_tests",
-        "test_id_prefix": "ninja://third_party/abseil-cpp:absl_hardening_tests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "angle_unittests",
-        "test_id_prefix": "ninja://third_party/angle/src/tests:angle_unittests/",
-        "use_isolated_scripts_api": true
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "base_unittests",
-        "test_id_prefix": "ninja://base:base_unittests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "blink_common_unittests",
-        "test_id_prefix": "ninja://third_party/blink/common:blink_common_unittests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "blink_heap_unittests",
-        "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "blink_platform_unittests",
-        "test_id_prefix": "ninja://third_party/blink/renderer/platform:blink_platform_unittests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "webkit_unit_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "blink_unittests",
-        "test_id_prefix": "ninja://third_party/blink/renderer/controller:blink_unittests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "boringssl_crypto_tests",
-        "test_id_prefix": "ninja://third_party/boringssl:boringssl_crypto_tests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "boringssl_ssl_tests",
-        "test_id_prefix": "ninja://third_party/boringssl:boringssl_ssl_tests/"
-      },
-      {
-        "args": [
-          "--gtest_filter=-*UsingRealWebcam*"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "capture_unittests",
-        "test_id_prefix": "ninja://media/capture:capture_unittests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "cast_unittests",
-        "test_id_prefix": "ninja://media/cast:cast_unittests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "components_browsertests",
-        "test_id_prefix": "ninja://components:components_browsertests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "components_unittests",
-        "test_id_prefix": "ninja://components:components_unittests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 8
-        },
-        "test": "content_browsertests",
-        "test_id_prefix": "ninja://content/test:content_browsertests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "content_unittests",
-        "test_id_prefix": "ninja://content/test:content_unittests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "crashpad_tests",
-        "test_id_prefix": "ninja://third_party/crashpad/crashpad:crashpad_tests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "crypto_unittests",
-        "test_id_prefix": "ninja://crypto:crypto_unittests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "events_unittests",
-        "test_id_prefix": "ninja://ui/events:events_unittests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "gcm_unit_tests",
-        "test_id_prefix": "ninja://google_apis/gcm:gcm_unit_tests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "gin_unittests",
-        "test_id_prefix": "ninja://gin:gin_unittests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "google_apis_unittests",
-        "test_id_prefix": "ninja://google_apis:google_apis_unittests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "gpu_unittests",
-        "test_id_prefix": "ninja://gpu:gpu_unittests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "gwp_asan_unittests",
-        "test_id_prefix": "ninja://components/gwp_asan:gwp_asan_unittests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "ipc_tests",
-        "test_id_prefix": "ninja://ipc:ipc_tests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "latency_unittests",
-        "test_id_prefix": "ninja://ui/latency:latency_unittests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "libjingle_xmpp_unittests",
-        "test_id_prefix": "ninja://third_party/libjingle_xmpp:libjingle_xmpp_unittests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "liburlpattern_unittests",
-        "test_id_prefix": "ninja://third_party/liburlpattern:liburlpattern_unittests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "media_unittests",
-        "test_id_prefix": "ninja://media:media_unittests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "midi_unittests",
-        "test_id_prefix": "ninja://media/midi:midi_unittests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "mojo_unittests",
-        "test_id_prefix": "ninja://mojo:mojo_unittests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "net_unittests",
-        "test_id_prefix": "ninja://net:net_unittests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "perfetto_unittests",
-        "test_id_prefix": "ninja://third_party/perfetto:perfetto_unittests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "services_unittests",
-        "test_id_prefix": "ninja://services:services_unittests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "shell_dialogs_unittests",
-        "test_id_prefix": "ninja://ui/shell_dialogs:shell_dialogs_unittests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "skia_unittests",
-        "test_id_prefix": "ninja://skia:skia_unittests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "sql_unittests",
-        "test_id_prefix": "ninja://sql:sql_unittests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "storage_unittests",
-        "test_id_prefix": "ninja://storage:storage_unittests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "ui_base_unittests",
-        "test_id_prefix": "ninja://ui/base:ui_base_unittests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "ui_touch_selection_unittests",
-        "test_id_prefix": "ninja://ui/touch_selection:ui_touch_selection_unittests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "url_unittests",
-        "test_id_prefix": "ninja://url:url_unittests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "wtf_unittests",
-        "test_id_prefix": "ninja://third_party/blink/renderer/platform/wtf:wtf_unittests/"
-      },
-      {
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "zlib_unittests",
-        "test_id_prefix": "ninja://third_party/zlib:zlib_unittests/"
-      }
-    ],
-    "isolated_scripts": [
-      {
-        "args": [
-          "--num-retries=3",
-          "--write-run-histories-to=${ISOLATED_OUTDIR}/run_histories.json",
-          "--git-revision=${got_revision}"
-        ],
-        "check_flakiness_for_new_tests": false,
-        "isolate_name": "blink_web_tests",
-        "merge": {
-          "args": [
-            "--verbose"
-          ],
-          "script": "//third_party/blink/tools/merge_web_test_results.py"
-        },
-        "name": "blink_web_tests",
-        "precommit_args": [
-          "--gerrit-issue=${patch_issue}",
-          "--gerrit-patchset=${patch_set}",
-          "--buildbucket-id=${buildbucket_build_id}"
-        ],
-        "resultdb": {
-          "enable": true
-        },
-        "results_handler": "layout tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 5
-        },
-        "test_id_prefix": "ninja://:blink_web_tests/"
-      },
-      {
-        "args": [
-          "--num-retries=3",
-          "--write-run-histories-to=${ISOLATED_OUTDIR}/run_histories.json",
-          "--git-revision=${got_revision}"
-        ],
-        "check_flakiness_for_new_tests": false,
-        "isolate_name": "blink_wpt_tests",
-        "merge": {
-          "args": [
-            "--verbose"
-          ],
-          "script": "//third_party/blink/tools/merge_web_test_results.py"
-        },
-        "name": "blink_wpt_tests",
-        "precommit_args": [
-          "--gerrit-issue=${patch_issue}",
-          "--gerrit-patchset=${patch_set}",
-          "--buildbucket-id=${buildbucket_build_id}"
-        ],
-        "resultdb": {
-          "enable": true
-        },
-        "results_handler": "layout tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 7
-        },
-        "test_id_prefix": "ninja://:blink_wpt_tests/"
-      }
-    ]
-  },
   "linux-blink-wpt-reset-rel": {
     "isolated_scripts": [
       {
@@ -88083,9 +87396,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5423.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5424.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5423.0",
+        "description": "Run with ash-chrome version 110.0.5424.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -88097,8 +87410,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5423.0",
-              "revision": "version:110.0.5423.0"
+              "location": "lacros_version_skew_tests_v110.0.5424.0",
+              "revision": "version:110.0.5424.0"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -88219,9 +87532,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5423.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5424.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5423.0",
+        "description": "Run with ash-chrome version 110.0.5424.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -88233,8 +87546,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5423.0",
-              "revision": "version:110.0.5423.0"
+              "location": "lacros_version_skew_tests_v110.0.5424.0",
+              "revision": "version:110.0.5424.0"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -88345,9 +87658,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5423.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5424.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5423.0",
+        "description": "Run with ash-chrome version 110.0.5424.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -88359,8 +87672,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5423.0",
-              "revision": "version:110.0.5423.0"
+              "location": "lacros_version_skew_tests_v110.0.5424.0",
+              "revision": "version:110.0.5424.0"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -89700,9 +89013,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5423.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5424.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5423.0",
+        "description": "Run with ash-chrome version 110.0.5424.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -89713,8 +89026,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5423.0",
-              "revision": "version:110.0.5423.0"
+              "location": "lacros_version_skew_tests_v110.0.5424.0",
+              "revision": "version:110.0.5424.0"
             }
           ],
           "dimension_sets": [
@@ -89866,9 +89179,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5423.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5424.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5423.0",
+        "description": "Run with ash-chrome version 110.0.5424.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -89879,8 +89192,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5423.0",
-              "revision": "version:110.0.5423.0"
+              "location": "lacros_version_skew_tests_v110.0.5424.0",
+              "revision": "version:110.0.5424.0"
             }
           ],
           "dimension_sets": [
@@ -90017,9 +89330,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5423.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5424.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5423.0",
+        "description": "Run with ash-chrome version 110.0.5424.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -90030,8 +89343,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5423.0",
-              "revision": "version:110.0.5423.0"
+              "location": "lacros_version_skew_tests_v110.0.5424.0",
+              "revision": "version:110.0.5424.0"
             }
           ],
           "dimension_sets": [
@@ -91552,9 +90865,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5423.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5424.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5423.0",
+        "description": "Run with ash-chrome version 110.0.5424.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -91565,8 +90878,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5423.0",
-              "revision": "version:110.0.5423.0"
+              "location": "lacros_version_skew_tests_v110.0.5424.0",
+              "revision": "version:110.0.5424.0"
             }
           ],
           "dimension_sets": [
@@ -91718,9 +91031,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5423.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5424.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5423.0",
+        "description": "Run with ash-chrome version 110.0.5424.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -91731,8 +91044,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5423.0",
-              "revision": "version:110.0.5423.0"
+              "location": "lacros_version_skew_tests_v110.0.5424.0",
+              "revision": "version:110.0.5424.0"
             }
           ],
           "dimension_sets": [
@@ -91869,9 +91182,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5423.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5424.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5423.0",
+        "description": "Run with ash-chrome version 110.0.5424.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -91882,8 +91195,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5423.0",
-              "revision": "version:110.0.5423.0"
+              "location": "lacros_version_skew_tests_v110.0.5424.0",
+              "revision": "version:110.0.5424.0"
             }
           ],
           "dimension_sets": [
@@ -92681,9 +91994,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5423.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5424.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5423.0",
+        "description": "Run with ash-chrome version 110.0.5424.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -92694,8 +92007,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5423.0",
-              "revision": "version:110.0.5423.0"
+              "location": "lacros_version_skew_tests_v110.0.5424.0",
+              "revision": "version:110.0.5424.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/chromium.gpu.fyi.json b/testing/buildbot/chromium.gpu.fyi.json
index 30ff4d2..7193944b 100644
--- a/testing/buildbot/chromium.gpu.fyi.json
+++ b/testing/buildbot/chromium.gpu.fyi.json
@@ -8618,7 +8618,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8647,7 +8647,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8674,7 +8674,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -8699,7 +8699,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8723,7 +8723,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8744,7 +8744,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8769,7 +8769,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -8808,7 +8808,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -8845,7 +8845,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -8882,7 +8882,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -8923,7 +8923,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -8969,7 +8969,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -9006,7 +9006,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -9052,7 +9052,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -9090,7 +9090,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -9127,7 +9127,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -9164,7 +9164,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -9204,7 +9204,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -9244,7 +9244,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -9284,7 +9284,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -9309,7 +9309,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -9337,7 +9337,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -9362,7 +9362,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -9386,7 +9386,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -9407,7 +9407,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -9432,7 +9432,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -9471,7 +9471,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -9508,7 +9508,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -9545,7 +9545,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -9586,7 +9586,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -9632,7 +9632,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -9678,7 +9678,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -9716,7 +9716,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -9753,7 +9753,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -9792,7 +9792,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -11486,7 +11486,7 @@
               "cpu": "arm64",
               "display_attached": "1",
               "mac_model": "Macmini9,1",
-              "os": "Mac-12.4|Mac-12.5",
+              "os": "Mac-12.5",
               "pool": "chromium.tests"
             }
           ],
@@ -11516,7 +11516,7 @@
               "cpu": "arm64",
               "display_attached": "1",
               "mac_model": "Macmini9,1",
-              "os": "Mac-12.4|Mac-12.5",
+              "os": "Mac-12.5",
               "pool": "chromium.tests"
             }
           ],
@@ -11544,7 +11544,7 @@
               "cpu": "arm64",
               "display_attached": "1",
               "mac_model": "Macmini9,1",
-              "os": "Mac-12.4|Mac-12.5",
+              "os": "Mac-12.5",
               "pool": "chromium.tests"
             }
           ],
@@ -11570,7 +11570,7 @@
               "cpu": "arm64",
               "display_attached": "1",
               "mac_model": "Macmini9,1",
-              "os": "Mac-12.4|Mac-12.5",
+              "os": "Mac-12.5",
               "pool": "chromium.tests"
             }
           ],
@@ -11595,7 +11595,7 @@
               "cpu": "arm64",
               "display_attached": "1",
               "mac_model": "Macmini9,1",
-              "os": "Mac-12.4|Mac-12.5",
+              "os": "Mac-12.5",
               "pool": "chromium.tests"
             }
           ],
@@ -11617,7 +11617,7 @@
               "cpu": "arm64",
               "display_attached": "1",
               "mac_model": "Macmini9,1",
-              "os": "Mac-12.4|Mac-12.5",
+              "os": "Mac-12.5",
               "pool": "chromium.tests"
             }
           ],
@@ -11643,7 +11643,7 @@
               "cpu": "arm64",
               "display_attached": "1",
               "mac_model": "Macmini9,1",
-              "os": "Mac-12.4|Mac-12.5",
+              "os": "Mac-12.5",
               "pool": "chromium.tests"
             }
           ],
@@ -11683,7 +11683,7 @@
               "cpu": "arm64",
               "display_attached": "1",
               "mac_model": "Macmini9,1",
-              "os": "Mac-12.4|Mac-12.5",
+              "os": "Mac-12.5",
               "pool": "chromium.tests"
             }
           ],
@@ -11721,7 +11721,7 @@
               "cpu": "arm64",
               "display_attached": "1",
               "mac_model": "Macmini9,1",
-              "os": "Mac-12.4|Mac-12.5",
+              "os": "Mac-12.5",
               "pool": "chromium.tests"
             }
           ],
@@ -11759,7 +11759,7 @@
               "cpu": "arm64",
               "display_attached": "1",
               "mac_model": "Macmini9,1",
-              "os": "Mac-12.4|Mac-12.5",
+              "os": "Mac-12.5",
               "pool": "chromium.tests"
             }
           ],
@@ -11801,7 +11801,7 @@
               "cpu": "arm64",
               "display_attached": "1",
               "mac_model": "Macmini9,1",
-              "os": "Mac-12.4|Mac-12.5",
+              "os": "Mac-12.5",
               "pool": "chromium.tests"
             }
           ],
@@ -11848,7 +11848,7 @@
               "cpu": "arm64",
               "display_attached": "1",
               "mac_model": "Macmini9,1",
-              "os": "Mac-12.4|Mac-12.5",
+              "os": "Mac-12.5",
               "pool": "chromium.tests"
             }
           ],
@@ -11886,7 +11886,7 @@
               "cpu": "arm64",
               "display_attached": "1",
               "mac_model": "Macmini9,1",
-              "os": "Mac-12.4|Mac-12.5",
+              "os": "Mac-12.5",
               "pool": "chromium.tests"
             }
           ],
@@ -11933,7 +11933,7 @@
               "cpu": "arm64",
               "display_attached": "1",
               "mac_model": "Macmini9,1",
-              "os": "Mac-12.4|Mac-12.5",
+              "os": "Mac-12.5",
               "pool": "chromium.tests"
             }
           ],
@@ -11972,7 +11972,7 @@
               "cpu": "arm64",
               "display_attached": "1",
               "mac_model": "Macmini9,1",
-              "os": "Mac-12.4|Mac-12.5",
+              "os": "Mac-12.5",
               "pool": "chromium.tests"
             }
           ],
@@ -12010,7 +12010,7 @@
               "cpu": "arm64",
               "display_attached": "1",
               "mac_model": "Macmini9,1",
-              "os": "Mac-12.4|Mac-12.5",
+              "os": "Mac-12.5",
               "pool": "chromium.tests"
             }
           ],
@@ -12048,7 +12048,7 @@
               "cpu": "arm64",
               "display_attached": "1",
               "mac_model": "Macmini9,1",
-              "os": "Mac-12.4|Mac-12.5",
+              "os": "Mac-12.5",
               "pool": "chromium.tests"
             }
           ],
@@ -12089,7 +12089,7 @@
               "cpu": "arm64",
               "display_attached": "1",
               "mac_model": "Macmini9,1",
-              "os": "Mac-12.4|Mac-12.5",
+              "os": "Mac-12.5",
               "pool": "chromium.tests"
             }
           ],
@@ -12131,7 +12131,7 @@
               "cpu": "arm64",
               "display_attached": "1",
               "mac_model": "Macmini9,1",
-              "os": "Mac-12.4|Mac-12.5",
+              "os": "Mac-12.5",
               "pool": "chromium.tests"
             }
           ],
@@ -12172,7 +12172,7 @@
               "cpu": "arm64",
               "display_attached": "1",
               "mac_model": "Macmini9,1",
-              "os": "Mac-12.4|Mac-12.5",
+              "os": "Mac-12.5",
               "pool": "chromium.tests"
             }
           ],
@@ -12213,7 +12213,7 @@
               "cpu": "arm64",
               "display_attached": "1",
               "mac_model": "Macmini9,1",
-              "os": "Mac-12.4|Mac-12.5",
+              "os": "Mac-12.5",
               "pool": "chromium.tests"
             }
           ],
@@ -12254,7 +12254,7 @@
               "cpu": "arm64",
               "display_attached": "1",
               "mac_model": "Macmini9,1",
-              "os": "Mac-12.4|Mac-12.5",
+              "os": "Mac-12.5",
               "pool": "chromium.tests"
             }
           ],
@@ -12280,7 +12280,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -12309,7 +12309,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -12336,7 +12336,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -12361,7 +12361,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -12385,7 +12385,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -12406,7 +12406,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -12431,7 +12431,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -12470,7 +12470,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -12507,7 +12507,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -12544,7 +12544,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -12585,7 +12585,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -12631,7 +12631,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -12668,7 +12668,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -12714,7 +12714,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -12752,7 +12752,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -12789,7 +12789,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -12826,7 +12826,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -12866,7 +12866,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -12908,7 +12908,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -12948,7 +12948,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -12989,7 +12989,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -13029,7 +13029,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -17073,10 +17073,299 @@
     ]
   },
   "Win10 FYI x64 Exp Release (Intel HD 630)": {
+    "gtest_tests": [
+      {
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:9bc5-31.0.101.2111",
+              "os": "Windows-10",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "angle_unittests",
+        "test_id_prefix": "ninja://third_party/angle/src/tests:angle_unittests/",
+        "use_isolated_scripts_api": true
+      },
+      {
+        "args": [
+          "--enable-gpu",
+          "--test-launcher-bot-mode",
+          "--test-launcher-jobs=1",
+          "--gtest_filter=TabCaptureApiPixelTest.EndToEnd*"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "tab_capture_end2end_tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:9bc5-31.0.101.2111",
+              "os": "Windows-10",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "browser_tests",
+        "test_id_prefix": "ninja://chrome/test:browser_tests/"
+      },
+      {
+        "args": [
+          "--use-cmd-decoder=passthrough",
+          "--use-gl=angle",
+          "--use-gpu-in-tests"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "gl_tests_passthrough",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:9bc5-31.0.101.2111",
+              "os": "Windows-10",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 2
+        },
+        "test": "gl_tests",
+        "test_id_prefix": "ninja://gpu:gl_tests/"
+      },
+      {
+        "args": [
+          "--use-gpu-in-tests",
+          "--test-launcher-filter-file=../../testing/buildbot/filters/win.intel.gl_unittests.filter"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:9bc5-31.0.101.2111",
+              "os": "Windows-10",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gl_unittests",
+        "test_id_prefix": "ninja://ui/gl:gl_unittests/"
+      },
+      {
+        "args": [
+          "--use-gpu-in-tests"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:9bc5-31.0.101.2111",
+              "os": "Windows-10",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gles2_conform_test",
+        "test_id_prefix": "ninja://gpu/gles2_conform_support:gles2_conform_test/"
+      },
+      {
+        "args": [
+          "--use-gpu-in-tests",
+          "--use-angle=d3d9"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "gles2_conform_d3d9_test",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:9bc5-31.0.101.2111",
+              "os": "Windows-10",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gles2_conform_test",
+        "test_id_prefix": "ninja://gpu/gles2_conform_support:gles2_conform_test/"
+      },
+      {
+        "args": [
+          "--use-gpu-in-tests",
+          "--use-angle=gl",
+          "--disable-gpu-sandbox"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "gles2_conform_gl_test",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:9bc5-31.0.101.2111",
+              "os": "Windows-10",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gles2_conform_test",
+        "test_id_prefix": "ninja://gpu/gles2_conform_support:gles2_conform_test/"
+      },
+      {
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:9bc5-31.0.101.2111",
+              "os": "Windows-10",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gpu_unittests",
+        "test_id_prefix": "ninja://gpu:gpu_unittests/"
+      },
+      {
+        "args": [
+          "--use-gpu-in-tests"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:9bc5-31.0.101.2111",
+              "os": "Windows-10",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "vulkan_tests",
+        "test_id_prefix": "ninja://gpu/vulkan:vulkan_tests/"
+      },
+      {
+        "args": [
+          "--ignore-runtime-requirements=*"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "xr_browser_tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:9bc5-31.0.101.2111",
+              "os": "Windows-10",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "xr_browser_tests",
+        "test_id_prefix": "ninja://chrome/test:xr_browser_tests/"
+      }
+    ],
     "isolated_scripts": [
       {
         "args": [
-          "noop_sleep",
+          "context_lost",
+          "--show-stdout",
+          "--browser=release_x64",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=passthrough --use-gl=angle"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "context_lost_passthrough_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:9bc5-31.0.101.2111",
+              "os": "Windows-10",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "gpu_process",
           "--show-stdout",
           "--browser=release_x64",
           "--passthrough",
@@ -17089,14 +17378,18 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "noop_sleep_tests",
+        "name": "gpu_process_launch_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
         "should_retry_with_patch": false,
         "swarming": {
           "can_use_on_swarming_builders": true,
           "containment_type": "AUTO",
           "dimension_sets": [
             {
-              "gpu": "8086:5912-26.20.100.8141|8086:3e92-26.20.100.8141",
+              "gpu": "8086:9bc5-31.0.101.2111",
               "os": "Windows-10",
               "pool": "chromium.tests.gpu"
             }
@@ -17106,6 +17399,485 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "hardware_accelerated_feature",
+          "--show-stdout",
+          "--browser=release_x64",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "hardware_accelerated_feature_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:9bc5-31.0.101.2111",
+              "os": "Windows-10",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "info_collection",
+          "--show-stdout",
+          "--browser=release_x64",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --force_high_performance_gpu",
+          "--expected-vendor-id",
+          "8086",
+          "--expected-device-id",
+          "9bc5"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "info_collection_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:9bc5-31.0.101.2111",
+              "os": "Windows-10",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "maps",
+          "--show-stdout",
+          "--browser=release_x64",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=passthrough --use-gl=angle",
+          "--dont-restore-color-profile-after-test",
+          "--test-machine-name",
+          "${buildername}",
+          "--git-revision=${got_revision}"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "maps_pixel_passthrough_test",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:9bc5-31.0.101.2111",
+              "os": "Windows-10",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "mediapipe",
+          "--show-stdout",
+          "--browser=release_x64",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --force_higher_performance_gpu --use-cmd-decoder=passthrough --use-gl=angle"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "mediapipe_passthrough_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:9bc5-31.0.101.2111",
+              "os": "Windows-10",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "pixel",
+          "--show-stdout",
+          "--browser=release_x64",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=passthrough --use-gl=angle",
+          "--dont-restore-color-profile-after-test",
+          "--test-machine-name",
+          "${buildername}",
+          "--git-revision=${got_revision}"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "pixel_skia_gold_passthrough_test",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:9bc5-31.0.101.2111",
+              "os": "Windows-10",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "screenshot_sync",
+          "--show-stdout",
+          "--browser=release_x64",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=passthrough --use-gl=angle",
+          "--dont-restore-color-profile-after-test"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "screenshot_sync_passthrough_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:9bc5-31.0.101.2111",
+              "os": "Windows-10",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "trace_test",
+          "--show-stdout",
+          "--browser=release_x64",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "trace_test",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:9bc5-31.0.101.2111",
+              "os": "Windows-10",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "webcodecs",
+          "--show-stdout",
+          "--browser=release_x64",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "webcodecs_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:9bc5-31.0.101.2111",
+              "os": "Windows-10",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "webgl_conformance",
+          "--show-stdout",
+          "--browser=release_x64",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-gl=angle --use-angle=d3d11 --use-cmd-decoder=passthrough --force_high_performance_gpu",
+          "--webgl-conformance-version=2.0.1",
+          "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl2_conformance_tests_output.json",
+          "--jobs=2"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "webgl2_conformance_d3d11_passthrough_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:9bc5-31.0.101.2111",
+              "os": "Windows-10",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 20
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "webgl_conformance",
+          "--show-stdout",
+          "--browser=release_x64",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-gl=angle --use-angle=d3d11 --use-cmd-decoder=passthrough --force_high_performance_gpu",
+          "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl_conformance_tests_output.json",
+          "--jobs=2"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "webgl_conformance_d3d11_passthrough_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:9bc5-31.0.101.2111",
+              "os": "Windows-10",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 2
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "webgl_conformance",
+          "--show-stdout",
+          "--browser=release_x64",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-gl=angle --use-angle=d3d9 --use-cmd-decoder=passthrough --force_high_performance_gpu",
+          "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl_conformance_tests_output.json",
+          "--jobs=2"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "webgl_conformance_d3d9_passthrough_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:9bc5-31.0.101.2111",
+              "os": "Windows-10",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 2
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "webgl_conformance",
+          "--show-stdout",
+          "--browser=release_x64",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=vulkan --use-cmd-decoder=passthrough --force_high_performance_gpu",
+          "--jobs=2"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "webgl_conformance_vulkan_passthrough_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "gpu": "8086:9bc5-31.0.101.2111",
+              "os": "Windows-10",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "expiration": 21600,
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 2
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
       }
     ]
   },
diff --git a/testing/buildbot/chromium.gpu.json b/testing/buildbot/chromium.gpu.json
index b7f0daa..515287d4 100644
--- a/testing/buildbot/chromium.gpu.json
+++ b/testing/buildbot/chromium.gpu.json
@@ -1247,7 +1247,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -1275,7 +1275,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -1300,7 +1300,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -1339,7 +1339,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -1376,7 +1376,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -1413,7 +1413,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -1454,7 +1454,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -1500,7 +1500,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -1546,7 +1546,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -1584,7 +1584,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -1621,7 +1621,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -1660,7 +1660,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -1686,7 +1686,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -1715,7 +1715,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -1742,7 +1742,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -1767,7 +1767,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -1806,7 +1806,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -1843,7 +1843,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -1880,7 +1880,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -1921,7 +1921,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -1967,7 +1967,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -2013,7 +2013,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -2051,7 +2051,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -2088,7 +2088,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -2127,7 +2127,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
diff --git a/testing/buildbot/chromium.linux.json b/testing/buildbot/chromium.linux.json
index 86072c7..aa719748 100644
--- a/testing/buildbot/chromium.linux.json
+++ b/testing/buildbot/chromium.linux.json
@@ -2152,7 +2152,6 @@
               "os": "Ubuntu-18.04"
             }
           ],
-          "quickrun_shards": 15,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 20
         },
@@ -3545,7 +3544,6 @@
               "os": "Ubuntu-18.04"
             }
           ],
-          "quickrun_shards": 7,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 8
         },
@@ -3584,7 +3582,6 @@
               "os": "Ubuntu-18.04"
             }
           ],
-          "quickrun_shards": 11,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 10
         },
diff --git a/testing/buildbot/chromium.mac.json b/testing/buildbot/chromium.mac.json
index 6d189d34..e8b5d32 100644
--- a/testing/buildbot/chromium.mac.json
+++ b/testing/buildbot/chromium.mac.json
@@ -5816,7 +5816,6 @@
               "os": "Mac-11|Mac-10.16"
             }
           ],
-          "quickrun_shards": 10,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 12
         },
@@ -6973,7 +6972,6 @@
               "os": "Mac-11|Mac-10.16"
             }
           ],
-          "quickrun_shards": 10,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 12
         },
@@ -7012,7 +7010,6 @@
               "os": "Mac-11|Mac-10.16"
             }
           ],
-          "quickrun_shards": 16,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 18
         },
@@ -7735,7 +7732,6 @@
               "os": "Mac-12"
             }
           ],
-          "quickrun_shards": 10,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 12
         },
@@ -8891,8 +8887,6 @@
               "os": "Mac-12"
             }
           ],
-          "inverse_quickrun_shards": 24,
-          "quickrun_shards": 10,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 12
         },
@@ -8932,8 +8926,6 @@
               "os": "Mac-12"
             }
           ],
-          "inverse_quickrun_shards": 36,
-          "quickrun_shards": 16,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 18
         },
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json
index cbbbf8c..bddee2b 100644
--- a/testing/buildbot/chromium.memory.json
+++ b/testing/buildbot/chromium.memory.json
@@ -282,7 +282,6 @@
               "os": "Ubuntu-18.04"
             }
           ],
-          "quickrun_shards": 80,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 40
         },
@@ -486,7 +485,6 @@
               "os": "Ubuntu-18.04"
             }
           ],
-          "quickrun_shards": 24,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 24
         },
@@ -936,7 +934,6 @@
               "os": "Ubuntu-18.04"
             }
           ],
-          "quickrun_shards": 12,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 12
         },
@@ -18562,10 +18559,10 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5423.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5424.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always"
         ],
-        "description": "Run with ash-chrome version 110.0.5423.0",
+        "description": "Run with ash-chrome version 110.0.5424.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -18577,8 +18574,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5423.0",
-              "revision": "version:110.0.5423.0"
+              "location": "lacros_version_skew_tests_v110.0.5424.0",
+              "revision": "version:110.0.5424.0"
             }
           ],
           "dimension_sets": [
@@ -18736,10 +18733,10 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5423.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5424.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always"
         ],
-        "description": "Run with ash-chrome version 110.0.5423.0",
+        "description": "Run with ash-chrome version 110.0.5424.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -18751,8 +18748,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5423.0",
-              "revision": "version:110.0.5423.0"
+              "location": "lacros_version_skew_tests_v110.0.5424.0",
+              "revision": "version:110.0.5424.0"
             }
           ],
           "dimension_sets": [
@@ -18892,10 +18889,10 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5423.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5424.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always"
         ],
-        "description": "Run with ash-chrome version 110.0.5423.0",
+        "description": "Run with ash-chrome version 110.0.5424.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -18907,8 +18904,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5423.0",
-              "revision": "version:110.0.5423.0"
+              "location": "lacros_version_skew_tests_v110.0.5424.0",
+              "revision": "version:110.0.5424.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/chromium.swangle.json b/testing/buildbot/chromium.swangle.json
index 2df80c6..f03c2f3 100644
--- a/testing/buildbot/chromium.swangle.json
+++ b/testing/buildbot/chromium.swangle.json
@@ -844,7 +844,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
diff --git a/testing/buildbot/chromium.win.json b/testing/buildbot/chromium.win.json
index a048699..9bb369f 100644
--- a/testing/buildbot/chromium.win.json
+++ b/testing/buildbot/chromium.win.json
@@ -2336,7 +2336,6 @@
               "os": "Windows-10-19042"
             }
           ],
-          "quickrun_shards": 40,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 55
         },
@@ -3100,7 +3099,6 @@
               "os": "Windows-10-19042"
             }
           ],
-          "quickrun_shards": 8,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 9
         },
@@ -3705,7 +3703,6 @@
               "os": "Windows-10-19042"
             }
           ],
-          "quickrun_shards": 3,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 3
         },
diff --git a/testing/buildbot/client.v8.fyi.json b/testing/buildbot/client.v8.fyi.json
index 1647809..5cde772 100644
--- a/testing/buildbot/client.v8.fyi.json
+++ b/testing/buildbot/client.v8.fyi.json
@@ -1081,7 +1081,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -1117,7 +1117,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -1153,7 +1153,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -1198,7 +1198,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -1243,7 +1243,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -1280,7 +1280,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -1316,7 +1316,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -1355,7 +1355,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -1394,7 +1394,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
diff --git a/testing/buildbot/filters/BUILD.gn b/testing/buildbot/filters/BUILD.gn
index a82dd26..34df6b3 100644
--- a/testing/buildbot/filters/BUILD.gn
+++ b/testing/buildbot/filters/BUILD.gn
@@ -270,8 +270,10 @@
 source_set("gl_unittests_filters") {
   testonly = true
 
-  data =
-      [ "//testing/buildbot/filters/android.emulator_12.gl_unittests.filter" ]
+  data = [
+    "//testing/buildbot/filters/android.emulator_12.gl_unittests.filter",
+    "//testing/buildbot/filters/win.intel.gl_unittests.filter",
+  ]
 }
 
 source_set("interactive_ui_tests_filters") {
diff --git a/testing/buildbot/filters/android.emulator.gl_tests.filter b/testing/buildbot/filters/android.emulator.gl_tests.filter
index 315b3c9e..14abeb9 100644
--- a/testing/buildbot/filters/android.emulator.gl_tests.filter
+++ b/testing/buildbot/filters/android.emulator.gl_tests.filter
@@ -3,3 +3,6 @@
 -SetAggressivelyFreeResourcesTest.*
 -GLBGRAMipMapTest.*
 -GpuFenceTest.*
+
+# BGRA_8888 support is poor in Android emulator.
+-GLTextureImageBackingFactoryWithReadbackTest.ReadbackToMemory/BGRA_8888
diff --git a/testing/buildbot/filters/android.emulator_11.gl_tests.filter b/testing/buildbot/filters/android.emulator_11.gl_tests.filter
index 416a201e..5b6847f 100644
--- a/testing/buildbot/filters/android.emulator_11.gl_tests.filter
+++ b/testing/buildbot/filters/android.emulator_11.gl_tests.filter
@@ -15,3 +15,6 @@
 -AHardwareBufferImageBackingFactoryTest.GLSkiaGL
 -AHardwareBufferImageBackingFactoryTest.InitialData
 -AHardwareBufferImageBackingFactoryTest.Overlay
+
+# BGRA_8888 support is poor in Android emulator.
+-GLTextureImageBackingFactoryWithReadbackTest.ReadbackToMemory/BGRA_8888
diff --git a/testing/buildbot/filters/android.emulator_12.gl_tests.filter b/testing/buildbot/filters/android.emulator_12.gl_tests.filter
index 23ed8e03..cda88c95 100644
--- a/testing/buildbot/filters/android.emulator_12.gl_tests.filter
+++ b/testing/buildbot/filters/android.emulator_12.gl_tests.filter
@@ -10,3 +10,6 @@
 
 # crbug.com/1271745
 -ES3MapBufferRangeTest.CopyBufferSubData
+
+# BGRA_8888 support is poor in Android emulator.
+-GLTextureImageBackingFactoryWithReadbackTest.ReadbackToMemory/BGRA_8888
diff --git a/testing/buildbot/filters/win.intel.gl_unittests.filter b/testing/buildbot/filters/win.intel.gl_unittests.filter
new file mode 100644
index 0000000..4764a92
--- /dev/null
+++ b/testing/buildbot/filters/win.intel.gl_unittests.filter
@@ -0,0 +1,5 @@
+# crbug.com/1376203
+-DelegatedInkPointRendererGpuTest.DrawPointsAsTheyArrive
+-DelegatedInkPointRendererGpuTest.MaximumPointerIds
+-DelegatedInkPointRendererGpuTest.MultiplePointerIds
+-DelegatedInkPointRendererGpuTest.StoreAndRemovePointsAndTokens
diff --git a/testing/buildbot/mixins.pyl b/testing/buildbot/mixins.pyl
index d88bb72f..7ff4bdc 100644
--- a/testing/buildbot/mixins.pyl
+++ b/testing/buildbot/mixins.pyl
@@ -849,7 +849,7 @@
       'dimensions': {
         'cpu': 'arm64',
         'mac_model': 'Macmini9,1',
-        'os': 'Mac-12.4|Mac-12.5',
+        'os': 'Mac-12.5',
         'pool': 'chromium.tests',
         'display_attached': '1',
       },
@@ -886,7 +886,7 @@
       'dimensions': {
         'cpu': 'x86-64',
         'gpu': '8086:3e9b',
-        'os': 'Mac-12.4|Mac-12.5',
+        'os': 'Mac-12.5',
         'display_attached': '1',
       },
     },
@@ -1286,15 +1286,6 @@
       },
     },
   },
-  'win10_intel_hd_630_experimental': {
-    'swarming': {
-      'dimensions': {
-        'gpu': '8086:5912-26.20.100.8141|8086:3e92-26.20.100.8141',
-        'os': 'Windows-10',
-        'pool': 'chromium.tests.gpu',
-      },
-    },
-  },
   'win10_intel_hd_630_stable': {
     'swarming': {
       'dimensions': {
@@ -1318,6 +1309,15 @@
       ],
     },
   },
+  'win10_intel_uhd_630_experimental': {
+    'swarming': {
+      'dimensions': {
+        'gpu': '8086:9bc5-31.0.101.2111',
+        'os': 'Windows-10',
+        'pool': 'chromium.tests.gpu',
+      },
+    },
+  },
   'win10_nvidia_gtx_1660_experimental': {
     'swarming': {
       'dimensions': {
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index e3d0ad5..1f3695b 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -241,7 +241,6 @@
           "--additional-env-var=LLVM_PROFILE_FILE=${ISOLATED_OUTDIR}/profraw/default-%2m.profraw",
         ],
         'swarming': {
-          'quickrun_shards': 7,
           'shards': 8,
         },
       },
@@ -281,7 +280,6 @@
             },
           ],
           'shards': 12,
-          'quickrun_shards': 10,
         },
       },
       'Mac12 Tests': {
@@ -292,8 +290,6 @@
             },
           ],
           'shards': 12,
-          'quickrun_shards': 10,
-          'inverse_quickrun_shards': 24,
         },
       },
       'Mac12 Tests (dbg)': {
@@ -569,7 +565,6 @@
           "--additional-env-var=LLVM_PROFILE_FILE=${ISOLATED_OUTDIR}/profraw/default-%2m.profraw",
         ],
         'swarming': {
-          'quickrun_shards': 11,
           'shards': 10,
         },
       },
@@ -609,7 +604,6 @@
             },
           ],
           'shards': 18,
-          'quickrun_shards': 16,
         },
       },
       'Mac12 Tests': {
@@ -623,8 +617,6 @@
             },
           ],
           'shards': 18,
-          'quickrun_shards': 16,
-          'inverse_quickrun_shards': 36,
         },
       },
       'Mac12 Tests (dbg)': {
@@ -909,7 +901,6 @@
         # crbug.com/1257927
         'swarming': {
           'shards': 40,
-          'quickrun_shards': 80
         },
       },
       'Linux ASan Tests (sandboxed)': {
@@ -958,7 +949,6 @@
       'Linux Tests': {
         'swarming': {
           'shards': 20,
-          'quickrun_shards': 15,
         },
       },
       # https://crbug.com/1084469
@@ -1048,7 +1038,6 @@
           # This is for slow test execution that often becomes a critical path of
           # swarming jobs. crbug.com/868114
           'shards': 55,
-          'quickrun_shards': 40,
         }
       },
       'Win11 Tests x64': {
@@ -1093,7 +1082,6 @@
       'linux-chromeos-rel': {
         'swarming': {
           'shards': 60,
-          'quickrun_shards': 40,
           'dimension_sets': [
             {
               'kvm': '1',
@@ -1118,7 +1106,6 @@
       'linux-lacros-tester-rel': {
         'swarming': {
           'shards': 20,
-          'quickrun_shards': 20,
         },
       },
       'mac-code-coverage': {
@@ -1229,7 +1216,6 @@
     'modifications': {
       'chromeos-amd64-generic-rel': {
         'swarming': {
-          'quickrun_shards': 8,
           'shards': 7,
         },
       },
@@ -1663,7 +1649,6 @@
       'Linux ASan LSan Tests (1)': {
         'swarming': {
           'shards': 24,
-          'quickrun_shards': 24,
         },
       },
       'Linux ASan Tests (sandboxed)': {
@@ -1682,13 +1667,11 @@
       'Mac11 Tests': {
         'swarming': {
           'shards': 12,
-          'quickrun_shards': 10,
         },
       },
       'Mac12 Tests': {
         'swarming': {
           'shards': 12,
-          'quickrun_shards': 10,
         },
       },
       'Mac12 Tests (dbg)': { # https://crbug.com/1279504
@@ -2323,6 +2306,11 @@
       'Linux MSan Focal',
     ],
     'modifications': {
+      'Win10 FYI x64 Exp Release (Intel HD 630)': {
+        'args': [
+          '--test-launcher-filter-file=../../testing/buildbot/filters/win.intel.gl_unittests.filter',
+        ],
+      },
       'android-12-x64-rel': {
         'args': [
           '--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_12.gl_unittests.filter',
@@ -2487,7 +2475,6 @@
         # These are slow on the ASan trybot for some reason, crbug.com/1257927
         'swarming': {
           'shards': 12,
-          'quickrun_shards': 12,
         },
       },
       'Linux Chromium OS ASan LSan Tests (1)': {
@@ -2548,7 +2535,6 @@
       'Win10 Tests x64': {
         'swarming': {
           'shards': 9,
-          'quickrun_shards': 8,
         },
       },
       # temporary, https://crbug.com/818832
@@ -3186,6 +3172,11 @@
       },
     },
   },
+  'power_measurement_test': {
+    'remove_from': [
+      'Win10 FYI x64 Exp Release (Intel HD 630)',  # https://crbug.com/1376082
+    ],
+  },
   'sandbox_linux_unittests': {
     'modifications': {
       'android-12-x64-rel': {
@@ -3362,7 +3353,6 @@
       'Win10 Tests x64': {
         'swarming': {
           'shards': 3,
-          'quickrun_shards': 3,
         },
       },
       'Win10 Tests x64 (dbg)': {
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index 97f58db..f4a22b3a 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -3524,6 +3524,9 @@
           '--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl_conformance_tests_output.json',
           '$$MAGIC_SUBSTITUTION_GPUParallelJobs',
         ],
+        'chromeos_args': [
+          '$$MAGIC_SUBSTITUTION_ChromeOSTelemetryRemote',
+        ],
         'android_args': [
           '$$MAGIC_SUBSTITUTION_GPUTelemetryNoRootForUnrootedDevices',
         ],
@@ -3533,6 +3536,9 @@
         'swarming': {
           'shards': 6,
         },
+        'chromeos_swarming': {
+          'shards': 20,
+        },
       },
     },
 
@@ -5977,6 +5983,8 @@
     ],
 
     'gpu_chromeos_telemetry_tests': [
+      'gpu_webgl_conformance_gles_passthrough_telemetry_tests',
+      'gpu_webgl2_conformance_gles_passthrough_telemetry_tests',
       'gpu_webgl_conformance_telemetry_tests',
     ],
 
diff --git a/testing/buildbot/tryserver.chromium.mac.json b/testing/buildbot/tryserver.chromium.mac.json
index ab9f61e..5ab6c71 100644
--- a/testing/buildbot/tryserver.chromium.mac.json
+++ b/testing/buildbot/tryserver.chromium.mac.json
@@ -46,7 +46,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -94,7 +94,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -148,7 +148,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -187,7 +187,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -224,7 +224,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -261,7 +261,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -345,7 +345,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -391,7 +391,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -467,7 +467,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -513,7 +513,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -551,7 +551,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -627,7 +627,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -703,7 +703,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -743,7 +743,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -783,7 +783,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -823,7 +823,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
@@ -863,7 +863,7 @@
               "cpu": "x86-64",
               "display_attached": "1",
               "gpu": "8086:3e9b",
-              "os": "Mac-12.4|Mac-12.5"
+              "os": "Mac-12.5"
             }
           ],
           "idempotent": false,
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index 0e42f3c9..35e81fbc 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -22,16 +22,16 @@
   },
   'LACROS_VERSION_SKEW_CANARY': {
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5423.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5424.0/test_ash_chrome',
     ],
-    'description': 'Run with ash-chrome version 110.0.5423.0',
+    'description': 'Run with ash-chrome version 110.0.5424.0',
     'identifier': 'Lacros version skew testing ash canary',
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v110.0.5423.0',
-          'revision': 'version:110.0.5423.0',
+          'location': 'lacros_version_skew_tests_v110.0.5424.0',
+          'revision': 'version:110.0.5424.0',
         },
       ],
     },
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index 95c8b93..11a7f89 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -3334,15 +3334,6 @@
           'isolated_scripts': 'chromium_webkit_isolated_scripts',
         },
       },
-      'linux-blink-v8-sandbox-future-rel': {
-        'mixins': [
-          'linux-bionic',
-        ],
-        'test_suites': {
-          'gtest_tests': 'chromium_gtests',
-          'isolated_scripts': 'chromium_webkit_isolated_scripts',
-        },
-      },
       'linux-blink-wpt-reset-rel': {
         'mixins': [
           'linux-bionic',
@@ -4593,14 +4584,15 @@
         'browser_config': 'release_x64',
         'mixins': [
           'limited_capacity_bot',
-          'win10_intel_hd_630_experimental',
+          'win10_intel_uhd_630_experimental',
         ],
         # When the experimental driver is identical to the stable driver, this
         # should be running the gpu_noop_sleep_telemetry_test. Otherwise, it
         # should be running the same test_suites as
         # 'Win10 FYI x64 Release (Intel HD 630)'
         'test_suites': {
-          'gpu_telemetry_tests': 'gpu_noop_sleep_telemetry_test',
+          'gtest_tests': 'gpu_fyi_win_gtests',
+          'gpu_telemetry_tests': 'gpu_fyi_win_intel_release_telemetry_tests',
         },
       },
       'Win10 FYI x64 Exp Release (NVIDIA)': {
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index df747742..9652c2c 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -1180,28 +1180,6 @@
             ]
         }
     ],
-    "AutofillEnforceDelaysInStrikeDatabase": [
-        {
-            "platforms": [
-                "android",
-                "chromeos",
-                "chromeos_lacros",
-                "fuchsia",
-                "ios",
-                "linux",
-                "mac",
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "AutofillEnforceDelaysInStrikeDatabase"
-                    ]
-                }
-            ]
-        }
-    ],
     "AutofillFillAndImportFromMoreFields": [
         {
             "platforms": [
@@ -2791,104 +2769,7 @@
             ],
             "experiments": [
                 {
-                    "name": "Dogfood",
-                    "params": {
-                        "allow_bookmark_type_swapping": "true",
-                        "autodismiss_enabled": "true",
-                        "bookmark_compact_visuals_enabled": "true",
-                        "bookmark_in_app_menu": "true",
-                        "bookmark_visuals_enabled": "true",
-                        "enable_persisted_tab_data_maintenance": "true",
-                        "enable_price_notification": "true",
-                        "enable_price_tracking": "true",
-                        "price_tracking_with_optimization_guide": "true",
-                        "use_root_bookmark_as_default": "true"
-                    },
-                    "enable_features": [
-                        "BookmarksImprovedSaveFlow",
-                        "BookmarksRefresh",
-                        "CommercePriceTracking",
-                        "OptimizationGuidePushNotifications",
-                        "ReadLater",
-                        "ShoppingList"
-                    ]
-                },
-                {
-                    "name": "Default_1__M100_C_v5",
-                    "params": {
-                        "use_root_bookmark_as_default": "true"
-                    },
-                    "enable_features": [
-                        "ReadLater"
-                    ]
-                },
-                {
-                    "name": "ReadLater_CCT_non_US__NoCustomTab_M100_C_v5",
-                    "params": {
-                        "use_cct": "false",
-                        "use_root_bookmark_as_default": "true"
-                    },
-                    "enable_features": [
-                        "ReadLater"
-                    ]
-                },
-                {
-                    "name": "ReadLater_SaveFlow_non_US__SemiIntegratedRedux_M100_C_v5",
-                    "params": {
-                        "allow_bookmark_type_swapping": "true",
-                        "autodismiss_enabled": "true",
-                        "use_root_bookmark_as_default": "true"
-                    },
-                    "enable_features": [
-                        "BookmarksImprovedSaveFlow",
-                        "ReadLater"
-                    ]
-                },
-                {
-                    "name": "Default_1__M100_A_v5",
-                    "params": {
-                        "enable_persisted_tab_data_maintenance": "true",
-                        "enable_price_tracking": "true",
-                        "price_tracking_with_optimization_guide": "true",
-                        "use_root_bookmark_as_default": "true"
-                    },
-                    "enable_features": [
-                        "CommercePriceTracking",
-                        "ReadLater"
-                    ]
-                },
-                {
-                    "name": "ReadLater_CCT_US__NoCustomTab_M100_A_v5",
-                    "params": {
-                        "enable_persisted_tab_data_maintenance": "true",
-                        "enable_price_tracking": "true",
-                        "price_tracking_with_optimization_guide": "true",
-                        "use_cct": "false",
-                        "use_root_bookmark_as_default": "true"
-                    },
-                    "enable_features": [
-                        "CommercePriceTracking",
-                        "ReadLater"
-                    ]
-                },
-                {
-                    "name": "ReadLater_SaveFlow_US__SemiIntegratedRedux_M100_A_v5",
-                    "params": {
-                        "allow_bookmark_type_swapping": "true",
-                        "autodismiss_enabled": "true",
-                        "enable_persisted_tab_data_maintenance": "true",
-                        "enable_price_tracking": "true",
-                        "price_tracking_with_optimization_guide": "true",
-                        "use_root_bookmark_as_default": "true"
-                    },
-                    "enable_features": [
-                        "BookmarksImprovedSaveFlow",
-                        "CommercePriceTracking",
-                        "ReadLater"
-                    ]
-                },
-                {
-                    "name": "WallE_US__ShoppingCompact_M100_A_v5",
+                    "name": "Shopping_Launch",
                     "params": {
                         "autodismiss_enabled": "true",
                         "bookmark_compact_visuals_enabled": "true",
@@ -2897,75 +2778,13 @@
                         "enable_persisted_tab_data_maintenance": "true",
                         "enable_price_notification": "true",
                         "enable_price_tracking": "true",
-                        "price_tracking_with_optimization_guide": "true",
-                        "use_root_bookmark_as_default": "true"
+                        "price_tracking_with_optimization_guide": "true"
                     },
                     "enable_features": [
                         "BookmarksImprovedSaveFlow",
                         "BookmarksRefresh",
                         "CommercePriceTracking",
                         "OptimizationGuidePushNotifications",
-                        "ReadLater",
-                        "ShoppingList"
-                    ]
-                },
-                {
-                    "name": "ReadLater_SaveFlow_non_US__SemiIntegrated_NoCCT_M100_C_v7",
-                    "params": {
-                        "allow_bookmark_type_swapping": "true",
-                        "autodismiss_enabled": "true",
-                        "use_cct": "false",
-                        "use_root_bookmark_as_default": "true"
-                    },
-                    "enable_features": [
-                        "BookmarksImprovedSaveFlow",
-                        "ReadLater"
-                    ]
-                },
-                {
-                    "name": "WallE_US__ShoppingCompact_WithRL_M100_A_v7",
-                    "params": {
-                        "allow_bookmark_type_swapping": "true",
-                        "autodismiss_enabled": "true",
-                        "bookmark_compact_visuals_enabled": "true",
-                        "bookmark_in_app_menu": "true",
-                        "bookmark_visuals_enabled": "true",
-                        "enable_persisted_tab_data_maintenance": "true",
-                        "enable_price_notification": "true",
-                        "enable_price_tracking": "true",
-                        "price_tracking_with_optimization_guide": "true",
-                        "use_root_bookmark_as_default": "true"
-                    },
-                    "enable_features": [
-                        "BookmarksImprovedSaveFlow",
-                        "BookmarksRefresh",
-                        "CommercePriceTracking",
-                        "OptimizationGuidePushNotifications",
-                        "ReadLater",
-                        "ShoppingList"
-                    ]
-                },
-                {
-                    "name": "WallE_US__ShoppingCompact_WithRL_NoCCT_M100_A_v7",
-                    "params": {
-                        "allow_bookmark_type_swapping": "true",
-                        "autodismiss_enabled": "true",
-                        "bookmark_compact_visuals_enabled": "true",
-                        "bookmark_in_app_menu": "true",
-                        "bookmark_visuals_enabled": "true",
-                        "enable_persisted_tab_data_maintenance": "true",
-                        "enable_price_notification": "true",
-                        "enable_price_tracking": "true",
-                        "price_tracking_with_optimization_guide": "true",
-                        "use_cct": "false",
-                        "use_root_bookmark_as_default": "true"
-                    },
-                    "enable_features": [
-                        "BookmarksImprovedSaveFlow",
-                        "BookmarksRefresh",
-                        "CommercePriceTracking",
-                        "OptimizationGuidePushNotifications",
-                        "ReadLater",
                         "ShoppingList"
                     ]
                 }
@@ -4186,6 +4005,21 @@
             ]
         }
     ],
+    "DiscoFeedEndpoint": [
+        {
+            "platforms": [
+                "android"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "DiscoFeedEndpoint"
+                    ]
+                }
+            ]
+        }
+    ],
     "DiscountConsentV2": [
         {
             "platforms": [
@@ -5922,24 +5756,6 @@
             ]
         }
     ],
-    "IOSStaticResourceServing": [
-        {
-            "platforms": [
-                "ios"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled_SRS",
-                    "params": {
-                        "DiscoverFeedSRSReconstructedTemplatesEnabled": "true"
-                    },
-                    "enable_features": [
-                        "EnableDiscoverFeedStaticResourceServing"
-                    ]
-                }
-            ]
-        }
-    ],
     "IOSTFLiteLanguageDetection": [
         {
             "platforms": [
@@ -9340,6 +9156,25 @@
             ]
         }
     ],
+    "ReadLaterUSOnly": [
+        {
+            "platforms": [
+                "android"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "params": {
+                        "allow_bookmark_type_swapping": "true",
+                        "use_cct": "false"
+                    },
+                    "enable_features": [
+                        "ReadLater"
+                    ]
+                }
+            ]
+        }
+    ],
     "RecordPermissionExpirationTimestamps": [
         {
             "platforms": [
diff --git a/third_party/blink/renderer/build/scripts/core/css/css_properties.py b/third_party/blink/renderer/build/scripts/core/css/css_properties.py
index 26f53d2..287cbd2 100755
--- a/third_party/blink/renderer/build/scripts/core/css/css_properties.py
+++ b/third_party/blink/renderer/build/scripts/core/css/css_properties.py
@@ -84,6 +84,10 @@
         'mutable requires field_template:monotonic_flag [%s]' % name
     assert not prop.in_origin_trial or prop.runtime_flag,\
         'Property participates in origin trial, but has no runtime flag'
+    custom_functions = set(prop.computed_style_custom_functions)
+    protected_functions = set(set(prop.computed_style_protected_functions))
+    assert not custom_functions.intersection(protected_functions), \
+        'Functions must be specified as either protected or custom, not both [%s]' % name
 
 # Determines whether or not style builders (i.e. Apply functions)
 # should be generated for the given property.
@@ -157,7 +161,6 @@
         """True if the property is unconditionally web-exposed."""
         return not self.is_internal and not self.runtime_flag
 
-
 def generate_property_field(default):
     # Must use 'default_factory' rather than 'default' for list/dict.
     # https://docs.python.org/3/library/dataclasses.html#dataclasses.field
diff --git a/third_party/blink/renderer/build/scripts/core/style/computed_style_fields.py b/third_party/blink/renderer/build/scripts/core/style/computed_style_fields.py
index 4f00645..a53084e 100644
--- a/third_party/blink/renderer/build/scripts/core/style/computed_style_fields.py
+++ b/third_party/blink/renderer/build/scripts/core/style/computed_style_fields.py
@@ -140,7 +140,8 @@
                  type_name, wrapper_pointer_name, field_template, size,
                  default_value, custom_copy, custom_compare, mutable,
                  getter_method_name, setter_method_name, initial_method_name,
-                 computed_style_custom_functions, **kwargs):
+                 computed_style_custom_functions,
+                 computed_style_protected_functions, **kwargs):
         name_source = NameStyleConverter(name_for_methods)
         self.name = name_source.to_class_data_member()
         self.writable = writable
@@ -168,7 +169,14 @@
         self.initial_method_name = initial_method_name
         self.resetter_method_name = name_source.to_function_name(
             prefix='reset')
+        self.internal_resetter_method_name = NameStyleConverter(
+            self.resetter_method_name).to_function_name(suffix='internal')
         self.computed_style_custom_functions = computed_style_custom_functions
+        self.computed_style_protected_functions = computed_style_protected_functions
+        self.getter_visibility = self.get_visibility('getter')
+        self.setter_visibility = self.get_visibility('setter')
+        self.resetter_visibility = self.get_visibility('resetter')
+
         # Only bitfields have sizes.
         self.is_bit_field = self.size is not None
 
@@ -193,3 +201,10 @@
                 suffix=suffix)
         assert len(kwargs) == 0, \
             'Unexpected arguments provided to Field: ' + str(kwargs)
+
+    def get_visibility(self, function):
+        if function in self.computed_style_protected_functions:
+            return 'protected'
+        if function in self.computed_style_custom_functions:
+            return 'protected'
+        return 'public'
diff --git a/third_party/blink/renderer/build/scripts/core/style/make_computed_style_base.py b/third_party/blink/renderer/build/scripts/core/style/make_computed_style_base.py
index d3570fe..76dcfd5 100755
--- a/third_party/blink/renderer/build/scripts/core/style/make_computed_style_base.py
+++ b/third_party/blink/renderer/build/scripts/core/style/make_computed_style_base.py
@@ -336,6 +336,8 @@
         initial_method_name=property_.initial,
         computed_style_custom_functions=property_.
         computed_style_custom_functions,
+        computed_style_protected_functions=property_.
+        computed_style_protected_functions,
     )
 
 
@@ -366,6 +368,8 @@
         initial_method_name=name_source.to_function_name(prefix='initial'),
         computed_style_custom_functions=property_.
         computed_style_custom_functions,
+        computed_style_protected_functions=property_.
+        computed_style_protected_functions,
     )
 
 
@@ -596,6 +600,7 @@
             self._css_properties.longhands, json5_file_paths[5],
             self.default_parameters)
         self._properties = properties + self._css_properties.extra_fields
+        self._longhands = [p for p in properties if p.is_longhand]
 
         self._generated_enums = _create_enums(self._properties)
 
@@ -622,7 +627,7 @@
         # We create separate groups/fields for generating ComputedStyle-
         # BuilderBase. The only difference between these fields and the regular
         # fields, is that the builder fields have the "builder" flag set, which
-        # us used to weak the code generation in the field templates.
+        # is used to tweak the code generation in the field templates.
         #
         # TODO(crbug.com/1377295): When the builder is fully deployed, we no
         #                          longer need two groups.
@@ -650,6 +655,7 @@
         return {
             'input_files': self._input_files,
             'properties': self._properties,
+            'longhands': self._longhands,
             'enums': self._generated_enums,
             'include_paths': self._include_paths,
             'computed_style': self._root_group,
diff --git a/third_party/blink/renderer/build/scripts/core/style/templates/computed_style_base.h.tmpl b/third_party/blink/renderer/build/scripts/core/style/templates/computed_style_base.h.tmpl
index 7384e9e..cdbc6ba 100644
--- a/third_party/blink/renderer/build/scripts/core/style/templates/computed_style_base.h.tmpl
+++ b/third_party/blink/renderer/build/scripts/core/style/templates/computed_style_base.h.tmpl
@@ -43,6 +43,13 @@
 // Forward declaration for diff functions.
 class ComputedStyle;
 
+// Forward declaration of friends:
+{% for property in longhands %}
+{% if property.computed_style_custom_functions or property.computed_style_protected_functions %}
+namespace {{property.namespace}} { class {{property.classname}}; }
+{% endif %}
+{% endfor %}
+
 // The generated portion of ComputedStyle. For more info, see the header comment
 // in ComputedStyle.h.
 //
@@ -80,6 +87,14 @@
 // 'external' field has an extra setter that takes an rvalue reference. A list
 // of the available templates can be found in css_properties.json5.
 class ComputedStyleBase {
+  // Properties with protected accessors must be friends because
+  // Longhand::Apply* functions typically need the "raw" computed value:
+  {% for property in longhands %}
+  {% if property.computed_style_custom_functions or property.computed_style_protected_functions %}
+  friend class {{property.namespace}}::{{property.classname}};
+  {% endif %}
+  {% endfor %}
+
  public:
   inline bool IndependentInheritedEqual(const ComputedStyleBase& o) const {
     return (
diff --git a/third_party/blink/renderer/build/scripts/templates/fields/base.tmpl b/third_party/blink/renderer/build/scripts/templates/fields/base.tmpl
index 9357a7c..9ae3448 100644
--- a/third_party/blink/renderer/build/scripts/templates/fields/base.tmpl
+++ b/third_party/blink/renderer/build/scripts/templates/fields/base.tmpl
@@ -1,65 +1,85 @@
-{% from 'templates/fields/field.tmpl' import encode, decode, const_ref, nonconst_ref, rvalue_ref, getter_expression, setter_expression, set_if_changed, move_if_changed %}
+{% from 'templates/fields/field.tmpl' import encode, decode, const_ref, nonconst_ref, rvalue_ref, getter_expression, setter_expression, set_if_changed, move_if_changed, getter_method_name, setter_method_name, resetter_method_name, mutable_method_name %}
 
-{% macro decl_getter_method(field) -%}
-{% if 'getter' not in field.computed_style_custom_functions %}
-{{const_ref(field)}} {{field.getter_method_name}}() const {
+{% macro decl_getter_method(field, visibility) -%}
+{% if field.getter_visibility == visibility %}
+{{const_ref(field)}} {{getter_method_name(field)}}() const {
   return {{decode(field, getter_expression(field))}};
 }
 {% endif %}
 {%- endmacro %}
 
-{% macro decl_setter_method(field) -%}
-{% if 'setter' not in field.computed_style_custom_functions and field.writable %}
-void {{field.setter_method_name}}({{const_ref(field)}} v) {
+{% macro decl_setter_method(field, visibility) -%}
+{% if field.setter_visibility == visibility and field.writable %}
+void {{setter_method_name(field)}}({{const_ref(field)}} v) {
   {{set_if_changed(field, encode(field, "v"))|indent(2)}}
 }
 {% endif %}
 {%- endmacro %}
 
-{% macro decl_move_method(field) -%}
-{% if 'setter' not in field.computed_style_custom_functions and field.writable %}
-void {{field.setter_method_name}}({{rvalue_ref(field)}} v) {
+{% macro decl_move_method(field, visibility) -%}
+{% if field.setter_visibility == visibility and field.writable %}
+void {{setter_method_name(field)}}({{rvalue_ref(field)}} v) {
   {{move_if_changed(field, encode(field, "v"))|indent(2)}}
 }
 {% endif %}
 {%- endmacro %}
 
-{% macro decl_resetter_method(field) -%}
-{% if 'resetter' not in field.computed_style_custom_functions and field.writable %}
-inline void {{field.resetter_method_name}}() {
+{% macro decl_resetter_method(field, visibility) -%}
+{% if field.resetter_visibility == visibility and field.writable %}
+inline void {{resetter_method_name(field)}}() {
   {{setter_expression(field)}} = {{encode(field, field.default_value)}};
 }
 {% endif %}
 {%- endmacro %}
 
+{#
+  Mutable*-functions always have protected visibility, and always carry the
+  Internal suffix.
+#}
 {% macro decl_mutable_method(field) -%}
-{% if 'mutable' not in field.computed_style_custom_functions and field.writable %}
-{{nonconst_ref(field)}} {{field.internal_mutable_method_name}}() {
+{% if field.writable %}
+{{nonconst_ref(field)}} {{mutable_method_name(field)}}() {
   return {{decode(field, setter_expression(field))}};
 }
 {% endif %}
 {%- endmacro %}
 
-{% macro decl_internal_getter_method(field) -%}
-{% if 'getter' in field.computed_style_custom_functions %}
-{{const_ref(field)}} {{field.internal_getter_method_name}}() const {
-  return {{decode(field, getter_expression(field))}};
-}
-{% endif %}
+{# public #}
+
+{% macro decl_public_getter_method(field) -%}
+{{decl_getter_method(field, 'public')}}
 {%- endmacro %}
 
-{% macro decl_internal_setter_method(field) -%}
-{% if 'setter' in field.computed_style_custom_functions and field.writable %}
-void {{field.internal_setter_method_name}}({{const_ref(field)}} v) {
-  {{set_if_changed(field, encode(field, "v"))|indent(2)}}
-}
-{% endif %}
+{% macro decl_public_setter_method(field) -%}
+{{decl_setter_method(field, 'public')}}
 {%- endmacro %}
 
-{% macro decl_internal_move_method(field) -%}
-{% if 'setter' in field.computed_style_custom_functions and field.writable %}
-void {{field.internal_setter_method_name}}({{rvalue_ref(field)}} v) {
-  {{move_if_changed(field, encode(field, "v"))|indent(2)}}
-}
-{% endif %}
+{% macro decl_public_move_method(field) -%}
+{{decl_move_method(field, 'public')}}
+{%- endmacro %}
+
+{% macro decl_public_resetter_method(field) -%}
+{{decl_resetter_method(field, 'public')}}
+{%- endmacro %}
+
+{# protected #}
+
+{% macro decl_protected_getter_method(field) -%}
+{{decl_getter_method(field, 'protected')}}
+{%- endmacro %}
+
+{% macro decl_protected_setter_method(field) -%}
+{{decl_setter_method(field, 'protected')}}
+{%- endmacro %}
+
+{% macro decl_protected_move_method(field) -%}
+{{decl_move_method(field, 'protected')}}
+{%- endmacro %}
+
+{% macro decl_protected_resetter_method(field) -%}
+{{decl_resetter_method(field, 'protected')}}
+{%- endmacro %}
+
+{% macro decl_protected_mutable_method(field) -%}
+{{decl_mutable_method(field)}}
 {%- endmacro %}
diff --git a/third_party/blink/renderer/build/scripts/templates/fields/external.tmpl b/third_party/blink/renderer/build/scripts/templates/fields/external.tmpl
index 6a6494b..8e90148 100644
--- a/third_party/blink/renderer/build/scripts/templates/fields/external.tmpl
+++ b/third_party/blink/renderer/build/scripts/templates/fields/external.tmpl
@@ -2,17 +2,18 @@
 {% from 'templates/fields/field.tmpl' import getter_expression, setter_expression %}
 
 {% macro decl_public_methods(field) %}
-{{base.decl_getter_method(field)}}
-{{base.decl_setter_method(field)}}
-{{base.decl_move_method(field)}}
-{{base.decl_resetter_method(field)}}
+{{base.decl_public_getter_method(field)}}
+{{base.decl_public_setter_method(field)}}
+{{base.decl_public_move_method(field)}}
+{{base.decl_public_resetter_method(field)}}
 {% endmacro %}
 
 {% macro decl_protected_methods(field) -%}
-{{base.decl_internal_getter_method(field)}}
+{{base.decl_protected_getter_method(field)}}
 {% if not field.wrapper_pointer_name %}
-{{base.decl_internal_setter_method(field)}}
+{{base.decl_protected_setter_method(field)}}
 {% endif %}
-{{base.decl_internal_move_method(field)}}
-{{base.decl_mutable_method(field)}}
+{{base.decl_protected_move_method(field)}}
+{{base.decl_protected_resetter_method(field)}}
+{{base.decl_protected_mutable_method(field)}}
 {%- endmacro %}
diff --git a/third_party/blink/renderer/build/scripts/templates/fields/field.tmpl b/third_party/blink/renderer/build/scripts/templates/fields/field.tmpl
index 2dc4559..b0b7242 100644
--- a/third_party/blink/renderer/build/scripts/templates/fields/field.tmpl
+++ b/third_party/blink/renderer/build/scripts/templates/fields/field.tmpl
@@ -16,6 +16,34 @@
 {%- endif %}
 {% endmacro %}
 
+{% macro getter_method_name(field) -%}
+{% if 'getter' not in field.computed_style_custom_functions -%}
+{{field.getter_method_name}}
+{%- else -%}
+{{field.internal_getter_method_name}}
+{%- endif %}
+{%- endmacro %}
+
+{% macro setter_method_name(field) -%}
+{% if 'setter' not in field.computed_style_custom_functions -%}
+{{field.setter_method_name}}
+{%- else -%}
+{{field.internal_setter_method_name}}
+{%- endif %}
+{%- endmacro %}
+
+{% macro resetter_method_name(field) -%}
+{% if 'resetter' not in field.computed_style_custom_functions -%}
+{{field.resetter_method_name}}
+{%- else -%}
+{{field.internal_resetter_method_name}}
+{%- endif %}
+{%- endmacro %}
+
+{% macro mutable_method_name(field) -%}
+{{field.internal_mutable_method_name}}
+{%- endmacro %}
+
 {% macro accessor_expression_prefix(field) %}
 {% if field.builder -%}
 style_->
diff --git a/third_party/blink/renderer/build/scripts/templates/fields/keyword.tmpl b/third_party/blink/renderer/build/scripts/templates/fields/keyword.tmpl
index 5023dc7..53c18db1 100644
--- a/third_party/blink/renderer/build/scripts/templates/fields/keyword.tmpl
+++ b/third_party/blink/renderer/build/scripts/templates/fields/keyword.tmpl
@@ -1,13 +1,14 @@
 {% import 'templates/fields/base.tmpl' as base %}
 
 {% macro decl_public_methods(field) -%}
-{{base.decl_getter_method(field)}}
-{{base.decl_setter_method(field)}}
-{{base.decl_resetter_method(field)}}
+{{base.decl_public_getter_method(field)}}
+{{base.decl_public_setter_method(field)}}
+{{base.decl_public_resetter_method(field)}}
 {%- endmacro %}
 
 {% macro decl_protected_methods(field) -%}
-{{base.decl_internal_getter_method(field)}}
-{{base.decl_internal_setter_method(field)}}
-{{base.decl_mutable_method(field)}}
+{{base.decl_protected_getter_method(field)}}
+{{base.decl_protected_setter_method(field)}}
+{{base.decl_protected_resetter_method(field)}}
+{{base.decl_protected_mutable_method(field)}}
 {%- endmacro %}
diff --git a/third_party/blink/renderer/build/scripts/templates/fields/monotonic_flag.tmpl b/third_party/blink/renderer/build/scripts/templates/fields/monotonic_flag.tmpl
index 1aeae00b..768dbd35 100644
--- a/third_party/blink/renderer/build/scripts/templates/fields/monotonic_flag.tmpl
+++ b/third_party/blink/renderer/build/scripts/templates/fields/monotonic_flag.tmpl
@@ -3,7 +3,7 @@
 {% from 'templates/macros.tmpl' import print_if %}
 
 {% macro decl_public_methods(field) %}
-{{base.decl_getter_method(field)}}
+{{base.decl_public_getter_method(field)}}
 {% if field.writable or field.mutable %}
 void {{field.setter_method_name}}() {{print_if(field.mutable, "const ")}}{
   {{set_if_changed(field, encode(field, "true"))|indent(2)}}
@@ -12,5 +12,5 @@
 {% endmacro %}
 
 {% macro decl_protected_methods(field) -%}
-{{base.decl_internal_setter_method(field)}}
+{{base.decl_protected_setter_method(field)}}
 {%- endmacro %}
diff --git a/third_party/blink/renderer/build/scripts/templates/fields/primitive.tmpl b/third_party/blink/renderer/build/scripts/templates/fields/primitive.tmpl
index 5023dc7..53c18db1 100644
--- a/third_party/blink/renderer/build/scripts/templates/fields/primitive.tmpl
+++ b/third_party/blink/renderer/build/scripts/templates/fields/primitive.tmpl
@@ -1,13 +1,14 @@
 {% import 'templates/fields/base.tmpl' as base %}
 
 {% macro decl_public_methods(field) -%}
-{{base.decl_getter_method(field)}}
-{{base.decl_setter_method(field)}}
-{{base.decl_resetter_method(field)}}
+{{base.decl_public_getter_method(field)}}
+{{base.decl_public_setter_method(field)}}
+{{base.decl_public_resetter_method(field)}}
 {%- endmacro %}
 
 {% macro decl_protected_methods(field) -%}
-{{base.decl_internal_getter_method(field)}}
-{{base.decl_internal_setter_method(field)}}
-{{base.decl_mutable_method(field)}}
+{{base.decl_protected_getter_method(field)}}
+{{base.decl_protected_setter_method(field)}}
+{{base.decl_protected_resetter_method(field)}}
+{{base.decl_protected_mutable_method(field)}}
 {%- endmacro %}
diff --git a/third_party/blink/renderer/core/animation/color_property_functions.cc b/third_party/blink/renderer/core/animation/color_property_functions.cc
index b8e9b1b..e4f8fa30 100644
--- a/third_party/blink/renderer/core/animation/color_property_functions.cc
+++ b/third_party/blink/renderer/core/animation/color_property_functions.cc
@@ -37,7 +37,7 @@
         return nullptr;
       return style.CaretColor().ToStyleColor();
     case CSSPropertyID::kColor:
-      return style.GetColor();
+      return style.Color();
     case CSSPropertyID::kOutlineColor:
       return style.OutlineColor();
     case CSSPropertyID::kColumnRuleColor:
diff --git a/third_party/blink/renderer/core/animation/css_interpolation_environment.h b/third_party/blink/renderer/core/animation/css_interpolation_environment.h
index 40f8a26..6ab04fcf 100644
--- a/third_party/blink/renderer/core/animation/css_interpolation_environment.h
+++ b/third_party/blink/renderer/core/animation/css_interpolation_environment.h
@@ -16,19 +16,23 @@
 
 class CSSInterpolationEnvironment : public InterpolationEnvironment {
  public:
-  explicit CSSInterpolationEnvironment(const InterpolationTypesMap& map,
-                                       StyleResolverState& state,
-                                       StyleCascade* cascade,
-                                       CascadeResolver* cascade_resolver)
+  CSSInterpolationEnvironment(const InterpolationTypesMap& map,
+                              StyleResolverState& state,
+                              StyleCascade* cascade,
+                              CascadeResolver* cascade_resolver)
       : InterpolationEnvironment(map),
         state_(&state),
-        style_(state.Style()),
+        base_style_(state.StyleBuilder().GetBaseComputedStyle()),
         cascade_(cascade),
         cascade_resolver_(cascade_resolver) {}
 
-  explicit CSSInterpolationEnvironment(const InterpolationTypesMap& map,
-                                       const ComputedStyle& style)
-      : InterpolationEnvironment(map), style_(&style) {}
+  CSSInterpolationEnvironment(const InterpolationTypesMap& map,
+                              StyleResolverState& state)
+      : InterpolationEnvironment(map), state_(&state) {}
+
+  CSSInterpolationEnvironment(const InterpolationTypesMap& map,
+                              const ComputedStyle& base_style)
+      : InterpolationEnvironment(map), base_style_(&base_style) {}
 
   bool IsCSS() const final { return true; }
 
@@ -41,9 +45,9 @@
     return *state_;
   }
 
-  const ComputedStyle& Style() const {
-    DCHECK(style_);
-    return *style_;
+  const ComputedStyle& BaseStyle() const {
+    DCHECK(base_style_);
+    return *base_style_;
   }
 
   // TODO(crbug.com/985023): This effective violates const.
@@ -51,7 +55,7 @@
 
  private:
   StyleResolverState* state_ = nullptr;
-  const ComputedStyle* style_ = nullptr;
+  const ComputedStyle* base_style_ = nullptr;
   StyleCascade* cascade_ = nullptr;
   CascadeResolver* cascade_resolver_ = nullptr;
 };
diff --git a/third_party/blink/renderer/core/animation/css_interpolation_type.cc b/third_party/blink/renderer/core/animation/css_interpolation_type.cc
index f807704..242dc2ce 100644
--- a/third_party/blink/renderer/core/animation/css_interpolation_type.cc
+++ b/third_party/blink/renderer/core/animation/css_interpolation_type.cc
@@ -297,7 +297,7 @@
 InterpolationValue CSSInterpolationType::MaybeConvertUnderlyingValue(
     const InterpolationEnvironment& environment) const {
   const ComputedStyle& style =
-      To<CSSInterpolationEnvironment>(environment).Style();
+      To<CSSInterpolationEnvironment>(environment).BaseStyle();
   if (!GetProperty().IsCSSCustomProperty()) {
     return MaybeConvertStandardPropertyUnderlyingValue(style);
   }
diff --git a/third_party/blink/renderer/core/animation/css_var_cycle_interpolation_type.cc b/third_party/blink/renderer/core/animation/css_var_cycle_interpolation_type.cc
index 04a8237..45576fa 100644
--- a/third_party/blink/renderer/core/animation/css_var_cycle_interpolation_type.cc
+++ b/third_party/blink/renderer/core/animation/css_var_cycle_interpolation_type.cc
@@ -110,7 +110,7 @@
 InterpolationValue CSSVarCycleInterpolationType::MaybeConvertUnderlyingValue(
     const InterpolationEnvironment& environment) const {
   const ComputedStyle& style =
-      To<CSSInterpolationEnvironment>(environment).Style();
+      To<CSSInterpolationEnvironment>(environment).BaseStyle();
   DCHECK(!style.GetVariableData(GetProperty().CustomPropertyName()) ||
          !style.GetVariableData(GetProperty().CustomPropertyName())
               ->NeedsVariableResolution());
diff --git a/third_party/blink/renderer/core/animation/transition_interpolation.cc b/third_party/blink/renderer/core/animation/transition_interpolation.cc
index 36a1906..41db7e2 100644
--- a/third_party/blink/renderer/core/animation/transition_interpolation.cc
+++ b/third_party/blink/renderer/core/animation/transition_interpolation.cc
@@ -34,7 +34,7 @@
 void TransitionInterpolation::Apply(StyleResolverState& state) const {
   CSSInterpolationTypesMap map(state.GetDocument().GetPropertyRegistry(),
                                state.GetDocument());
-  CSSInterpolationEnvironment environment(map, state, nullptr, nullptr);
+  CSSInterpolationEnvironment environment(map, state);
   type_.Apply(CurrentInterpolableValue(), CurrentNonInterpolableValue(),
               environment);
 }
diff --git a/third_party/blink/renderer/core/animation/transition_keyframe.cc b/third_party/blink/renderer/core/animation/transition_keyframe.cc
index a54c260b..121088c 100644
--- a/third_party/blink/renderer/core/animation/transition_keyframe.cc
+++ b/third_party/blink/renderer/core/animation/transition_keyframe.cc
@@ -47,7 +47,7 @@
   StyleResolverState state(document, *element);
   state.SetStyle(document.GetStyleResolver().CreateComputedStyle());
   CSSInterpolationTypesMap map(document.GetPropertyRegistry(), document);
-  CSSInterpolationEnvironment environment(map, state, nullptr, nullptr);
+  CSSInterpolationEnvironment environment(map, state);
   value_->GetType().Apply(value_->GetInterpolableValue(),
                           value_->GetNonInterpolableValue(), environment);
 
diff --git a/third_party/blink/renderer/core/css/css_properties.json5 b/third_party/blink/renderer/core/css/css_properties.json5
index 2484c99..9572a27d 100644
--- a/third_party/blink/renderer/core/css/css_properties.json5
+++ b/third_party/blink/renderer/core/css/css_properties.json5
@@ -354,13 +354,39 @@
     type_name: {
     },
 
-    // - custom_function: Any function specified in the list is not
-    // automatically generated in ComputedStyle. Use this when a generated
-    // function is not correct.
+    // - computed_style_protected_functions
+    //
+    // Any function specified in the list will be generated with protected
+    // visibility. This is useful if the default-generated getter function is
+    // typically not what clients want to use.
+    //
+    // For example, the Clear getter is protected to force clients to take
+    // TextDirection into account.
+    computed_style_protected_functions: {
+      default: [],
+      valid_type: "list",
+      valid_values: ["getter", "setter", "resetter"],
+    },
+
+    // - computed_style_custom_functions
+    //
+    // Any function specified in the list will be generated with protected
+    // visibility and an "Internal" suffix. A custom accessor (with the suffix-
+    // less name) must be manually provided on ComputedStyle. This is useful for
+    // e.g. properties that have special behavior that affects the computed
+    // value of the property.
+    //
+    // For example, the computed value of border-left-width magically becomes
+    // zero if border-left-style is none or hidden. The generated code can not
+    // express this, hence a custom one is specified.
+    //
+    // Any custom function automatically gets protected visiblity, and therefore
+    // it is not valid to specify a function as both custom and explicitly
+    // protected (using computed_style_protected_functions).
     computed_style_custom_functions: {
       default: [],
       valid_type: "list",
-      valid_values: ["initial", "getter", "setter", "reset", "mutable"],
+      valid_values: ["initial", "getter", "setter", "resetter"],
     },
 
     // - converter: "ConvertRadius"
@@ -928,7 +954,7 @@
       include_paths: ["third_party/blink/renderer/core/css/style_color.h"],
       default_value: "StyleColor(Color::kBlack)",
       type_name: "StyleColor",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       style_builder_custom_functions: ["initial", "inherit", "value"],
       priority: "High",
       keywords: ["currentcolor"],
@@ -1413,7 +1439,7 @@
       field_template: "external",
       include_paths: ["third_party/blink/renderer/core/css/style_auto_color.h"],
       type_name: "StyleAutoColor",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       keywords: ["auto", "currentcolor"],
       typedom_types: ["Keyword"],
       converter: "ConvertStyleAutoColor",
@@ -1593,7 +1619,7 @@
       include_paths: ["third_party/blink/renderer/core/css/style_color.h"],
       default_value: "StyleColor(Color::kTransparent)",
       type_name: "StyleColor",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       keywords: ["currentcolor"],
       typedom_types: ["Keyword"],
       converter: "ConvertStyleColor",
@@ -1736,7 +1762,7 @@
       include_paths: ["third_party/blink/renderer/core/css/style_color.h"],
       default_value: "StyleColor::CurrentColor()",
       type_name: "StyleColor",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       keywords: ["currentcolor"],
       typedom_types: ["Keyword"],
       converter: "ConvertStyleColor",
@@ -1916,7 +1942,7 @@
       include_paths: ["third_party/blink/renderer/core/css/style_color.h"],
       default_value: "StyleColor::CurrentColor()",
       type_name: "StyleColor",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       keywords: ["currentcolor"],
       typedom_types: ["Keyword"],
       converter: "ConvertStyleColor",
@@ -1979,7 +2005,7 @@
       include_paths: ["third_party/blink/renderer/core/css/style_color.h"],
       default_value: "StyleColor::CurrentColor()",
       type_name: "StyleColor",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       keywords: ["currentcolor"],
       typedom_types: ["Keyword"],
       converter: "ConvertStyleColor",
@@ -2042,7 +2068,7 @@
       include_paths: ["third_party/blink/renderer/core/css/style_color.h"],
       default_value: "StyleColor::CurrentColor()",
       type_name: "StyleColor",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       keywords: ["currentcolor"],
       typedom_types: ["Keyword"],
       converter: "ConvertStyleColor",
@@ -2245,7 +2271,7 @@
       include_paths: ["third_party/blink/renderer/core/css/style_auto_color.h"],
       default_value: "StyleAutoColor::AutoColor()",
       type_name: "StyleAutoColor",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       converter: "ConvertStyleAutoColor",
       keywords: ["auto", "currentcolor"],
       typedom_types: ["Keyword"],
@@ -2255,7 +2281,7 @@
       name: "clear",
       property_methods: ["CSSValueFromComputedStyleInternal"],
       field_template: "keyword",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       keywords: ["none", "left", "right", "both", "inline-start", "inline-end"],
       typedom_types: ["Keyword"],
       default_value: "none",
@@ -2679,7 +2705,7 @@
       name: "float",
       property_methods: ["CSSValueFromComputedStyleInternal"],
       field_template: "keyword",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       keywords: ["none", "left", "right", "inline-start", "inline-end"],
       typedom_types: ["Keyword"],
       default_value: "none",
@@ -3502,7 +3528,7 @@
       include_paths: ["third_party/blink/renderer/core/css/style_color.h"],
       default_value: "StyleColor::CurrentColor()",
       type_name: "StyleColor",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       keywords: ["currentcolor"],
       typedom_types: ["Keyword"],
       converter: "ConvertStyleColor",
@@ -3826,7 +3852,7 @@
     {
       name: "pointer-events",
       property_methods: ["CSSValueFromComputedStyleInternal"],
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       independent: true,
       inherited: true,
       field_template: "keyword",
@@ -3897,7 +3923,7 @@
       property_methods: ["CSSValueFromComputedStyleInternal"],
       field_group: "*",
       field_template: "keyword",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       style_builder_custom_functions: ["value"],
       keywords: ["none", "both", "horizontal", "vertical", "block", "inline"],
       typedom_types: ["Keyword"],
@@ -4348,7 +4374,7 @@
       include_paths: ["third_party/blink/renderer/core/css/style_color.h"],
       default_value: "StyleColor(Color::kBlack)",
       type_name: "StyleColor",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       style_builder_template: "color",
       style_builder_template_args: {
         initial_color: "ComputedStyleInitialValues::InitialStopColor",
@@ -4566,7 +4592,7 @@
       include_paths: ["third_party/blink/renderer/core/css/style_color.h"],
       default_value: "StyleColor::CurrentColor()",
       type_name: "StyleColor",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       keywords: ["currentcolor"],
       typedom_types: ["Keyword"],
       converter: "ConvertStyleColor",
@@ -5077,7 +5103,7 @@
       field_size: 5,
       field_template: "primitive",
       include_paths: ["third_party/blink/renderer/platform/theme_types.h"],
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       default_value: "kNoControlPart",
       type_name: "ControlPart",
     },
@@ -5254,7 +5280,7 @@
       include_paths: ["third_party/blink/renderer/core/css/style_color.h"],
       default_value: "StyleColor::CurrentColor()",
       type_name: "StyleColor",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       keywords: ["currentcolor"],
       typedom_types: ["Keyword"],
       converter: "ConvertStyleColor",
@@ -5576,7 +5602,7 @@
       include_paths: ["third_party/blink/renderer/core/css/style_color.h"],
       default_value: "StyleColor::CurrentColor()",
       type_name: "StyleColor",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       keywords: ["currentcolor"],
       typedom_types: ["Keyword"],
       converter: "ConvertStyleColor",
@@ -5613,7 +5639,7 @@
       include_paths: ["third_party/blink/renderer/core/css/style_color.h"],
       default_value: "StyleColor::CurrentColor()",
       type_name: "StyleColor",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       converter: "ConvertStyleColor",
       style_builder_template: "color",
       valid_for_highlight_legacy: true,
@@ -5638,7 +5664,7 @@
       include_paths: ["third_party/blink/renderer/core/css/style_color.h"],
       default_value: "StyleColor::CurrentColor()",
       type_name: "StyleColor",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       converter: "ConvertStyleColor",
       style_builder_template: "color",
       valid_for_highlight_legacy: true,
@@ -5698,7 +5724,7 @@
     {
       name: "-webkit-user-modify",
       property_methods: ["CSSValueFromComputedStyleInternal"],
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       inherited: true,
       field_group: "*",
       field_template: "keyword",
@@ -5708,7 +5734,7 @@
     {
       name: "user-select",
       property_methods: ["CSSValueFromComputedStyleInternal"],
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       inherited: true,
       field_group: "*",
       field_template: "keyword",
@@ -7042,7 +7068,7 @@
       include_paths: ["third_party/blink/renderer/core/css/style_color.h"],
       default_value: "StyleColor(Color::kBlack)",
       type_name: "StyleColor",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       style_builder_custom_functions: ["initial", "inherit", "value"],
       priority: "High",
       valid_for_first_letter: true,
@@ -7064,7 +7090,7 @@
       default_value: "StyleAutoColor::AutoColor()",
       type_name: "StyleAutoColor",
       converter: "ConvertStyleAutoColor",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       style_builder_template: "visited_color",
       style_builder_template_args: {
         initial_color: "StyleAutoColor::AutoColor",
@@ -7080,7 +7106,7 @@
       include_paths: ["third_party/blink/renderer/core/css/style_color.h"],
       default_value: "StyleColor::CurrentColor()",
       type_name: "StyleColor",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       converter: "ConvertStyleColor",
       style_builder_template: "visited_color",
     },
@@ -7093,7 +7119,7 @@
       include_paths: ["third_party/blink/renderer/core/css/style_color.h"],
       default_value: "StyleColor(Color::kTransparent)",
       type_name: "StyleColor",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       converter: "ConvertStyleColor",
       style_builder_template: "visited_color",
       style_builder_template_args: {
@@ -7115,7 +7141,7 @@
       include_paths: ["third_party/blink/renderer/core/css/style_color.h"],
       default_value: "StyleColor::CurrentColor()",
       type_name: "StyleColor",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       converter: "ConvertStyleColor",
       style_builder_template: "visited_color",
       valid_for_first_letter: true,
@@ -7133,7 +7159,7 @@
       include_paths: ["third_party/blink/renderer/core/css/style_color.h"],
       default_value: "StyleColor::CurrentColor()",
       type_name: "StyleColor",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       converter: "ConvertStyleColor",
       style_builder_template: "visited_color",
       valid_for_first_letter: true,
@@ -7151,7 +7177,7 @@
       include_paths: ["third_party/blink/renderer/core/css/style_color.h"],
       default_value: "StyleColor::CurrentColor()",
       type_name: "StyleColor",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       converter: "ConvertStyleColor",
       style_builder_template: "visited_color",
       valid_for_first_letter: true,
@@ -7169,7 +7195,7 @@
       include_paths: ["third_party/blink/renderer/core/css/style_color.h"],
       default_value: "StyleColor::CurrentColor()",
       type_name: "StyleColor",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       converter: "ConvertStyleColor",
       style_builder_template: "visited_color",
       valid_for_first_letter: true,
@@ -7246,7 +7272,7 @@
       include_paths: ["third_party/blink/renderer/core/css/style_color.h"],
       default_value: "StyleColor::CurrentColor()",
       type_name: "StyleColor",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       converter: "ConvertStyleColor",
       style_builder_template: "visited_color",
       valid_for_cue: true,
@@ -7279,7 +7305,7 @@
       include_paths: ["third_party/blink/renderer/core/css/style_color.h"],
       default_value: "StyleColor::CurrentColor()",
       type_name: "StyleColor",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       converter: "ConvertStyleColor",
       style_builder_template: "visited_color",
       valid_for_first_letter: true,
@@ -7298,7 +7324,7 @@
       include_paths: ["third_party/blink/renderer/core/css/style_color.h"],
       default_value: "StyleColor::CurrentColor()",
       type_name: "StyleColor",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       converter: "ConvertStyleColor",
       style_builder_template: "visited_color",
       valid_for_highlight_legacy: true,
@@ -7314,7 +7340,7 @@
       include_paths: ["third_party/blink/renderer/core/css/style_color.h"],
       default_value: "StyleColor::CurrentColor()",
       type_name: "StyleColor",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       converter: "ConvertStyleColor",
       style_builder_template: "visited_color",
       valid_for_highlight_legacy: true,
@@ -7330,7 +7356,7 @@
       include_paths: ["third_party/blink/renderer/core/css/style_color.h"],
       default_value: "StyleColor::CurrentColor()",
       type_name: "StyleColor",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       converter: "ConvertStyleColor",
       style_builder_template: "visited_color",
       valid_for_highlight_legacy: true,
@@ -7347,7 +7373,7 @@
                       "third_party/blink/renderer/core/css/style_color.h"],
       default_value: "StyleColor(CSSValueID::kCanvas)",
       type_name: "StyleColor",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       converter: "ConvertStyleColor",
       style_builder_template: "color",
       style_builder_template_args: {
@@ -7365,7 +7391,7 @@
       include_paths: ["third_party/blink/renderer/core/css/style_color.h"],
       default_value: "StyleColor::CurrentColor()",
       type_name: "StyleColor",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       converter: "ConvertStyleColor",
       style_builder_template: "color",
       valid_for_first_letter: true,
@@ -7380,7 +7406,7 @@
                       "third_party/blink/renderer/core/css/style_color.h"],
       default_value: "StyleColor(CSSValueID::kCanvastext)",
       type_name: "StyleColor",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       style_builder_custom_functions: ["initial", "inherit", "value"],
       valid_for_first_letter: true,
       valid_for_first_line: true,
@@ -7395,7 +7421,7 @@
       include_paths: ["third_party/blink/renderer/core/css/style_color.h"],
       default_value: "StyleColor::CurrentColor()",
       type_name: "StyleColor",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       converter: "ConvertStyleColor",
       style_builder_template: "color",
       valid_for_cue: true,
@@ -7411,7 +7437,7 @@
                       "third_party/blink/renderer/core/css/style_color.h"],
       default_value: "StyleColor(CSSValueID::kCanvastext)",
       type_name: "StyleColor",
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
       style_builder_custom_functions: ["initial", "inherit", "value"],
       valid_for_first_letter: true,
       valid_for_first_line: true,
diff --git a/third_party/blink/renderer/core/css/css_property_equality.cc b/third_party/blink/renderer/core/css/css_property_equality.cc
index ddd793b..952d2bf 100644
--- a/third_party/blink/renderer/core/css/css_property_equality.cc
+++ b/third_party/blink/renderer/core/css/css_property_equality.cc
@@ -138,7 +138,7 @@
     case CSSPropertyID::kClip:
       return a.Clip() == b.Clip();
     case CSSPropertyID::kColor:
-      return a.GetColor() == b.GetColor() &&
+      return a.Color() == b.Color() &&
              a.InternalVisitedColor() == b.InternalVisitedColor();
     case CSSPropertyID::kFill:
       return a.FillPaint().EqualTypeOrColor(b.FillPaint()) &&
diff --git a/third_party/blink/renderer/core/css/properties/computed_style_utils.cc b/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
index ae57967..944322b 100644
--- a/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
+++ b/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
@@ -3328,7 +3328,7 @@
           style, style.CaretColor(), CSSValuePhase::kComputedValue);
     case CSSPropertyID::kColor:
       return ComputedStyleUtils::CurrentColorOrValidColor(
-          style, style.GetColor(), CSSValuePhase::kComputedValue);
+          style, style.Color(), CSSValuePhase::kComputedValue);
     case CSSPropertyID::kMinHeight: {
       if (style.MinHeight().IsAuto())
         return CSSIdentifierValue::Create(CSSValueID::kAuto);
diff --git a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
index 1a0408b..1ca466f 100644
--- a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
+++ b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
@@ -1547,7 +1547,7 @@
                                                  const ComputedStyle& style,
                                                  bool* is_current_color) const {
   DCHECK(!visited_link);
-  if (style.ShouldForceColor(style.GetColor())) {
+  if (style.ShouldForceColor(style.Color())) {
     return To<Longhand>(GetCSSPropertyInternalForcedColor())
         .ColorIncludingFallback(false, style, is_current_color);
   }
@@ -1558,7 +1558,7 @@
     const ComputedStyle& style,
     const LayoutObject*,
     bool allow_visited_style) const {
-  if (style.ShouldForceColor(style.GetColor())) {
+  if (style.ShouldForceColor(style.Color())) {
     return GetCSSPropertyInternalForcedColor().CSSValueFromComputedStyle(
         style, nullptr, allow_visited_style);
   }
@@ -1580,7 +1580,7 @@
     builder.SetColor(StyleColor(
         state.ParentStyle()->VisitedDependentColor(GetCSSPropertyColor())));
   } else {
-    builder.SetColor(state.ParentStyle()->GetColor());
+    builder.SetColor(state.ParentStyle()->Color());
   }
   builder.SetColorIsInherited(true);
   builder.SetColorIsCurrentColor(state.ParentStyle()->ColorIsCurrentColor());
@@ -1596,7 +1596,7 @@
     builder.SetColorIsCurrentColor(true);
     if (state.UsesHighlightPseudoInheritance() &&
         state.OriginatingElementStyle())
-      builder.SetColor(state.OriginatingElementStyle()->GetColor());
+      builder.SetColor(state.OriginatingElementStyle()->Color());
     return;
   }
   if (value.IsInitialColorValue()) {
@@ -3244,7 +3244,7 @@
     builder.SetInternalVisitedColor(StyleColor(
         state.ParentStyle()->VisitedDependentColor(GetCSSPropertyColor())));
   } else {
-    builder.SetInternalVisitedColor(state.ParentStyle()->GetColor());
+    builder.SetInternalVisitedColor(state.ParentStyle()->Color());
   }
   builder.SetInternalVisitedColorIsCurrentColor(
       state.ParentStyle()->InternalVisitedColorIsCurrentColor());
diff --git a/third_party/blink/renderer/core/css/resolver/style_adjuster.cc b/third_party/blink/renderer/core/css/resolver/style_adjuster.cc
index 206e69e..eec6b37 100644
--- a/third_party/blink/renderer/core/css/resolver/style_adjuster.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_adjuster.cc
@@ -893,7 +893,7 @@
       state.OriginatingElementStyle()) {
     const ComputedStyle* originating_style = state.OriginatingElementStyle();
     if (style.ColorIsCurrentColor())
-      builder.SetColor(originating_style->GetColor());
+      builder.SetColor(originating_style->Color());
     if (style.InternalVisitedColorIsCurrentColor()) {
       builder.SetInternalVisitedColor(
           originating_style->InternalVisitedColor());
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver.cc b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
index 0c8552ec..911dc12 100644
--- a/third_party/blink/renderer/core/css/resolver/style_resolver.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
@@ -1749,11 +1749,11 @@
 
   CSSAnimations::CalculateCompositorAnimationUpdate(
       state.AnimationUpdate(), *animating_element, element,
-      *state.Style()->GetBaseComputedStyle(), state.ParentStyle(),
+      *state.StyleBuilder().GetBaseComputedStyle(), state.ParentStyle(),
       WasViewportResized(), state.AffectsCompositorSnapshots());
   CSSAnimations::SnapshotCompositorKeyframes(
       *animating_element, state.AnimationUpdate(),
-      *state.Style()->GetBaseComputedStyle(), state.ParentStyle());
+      *state.StyleBuilder().GetBaseComputedStyle(), state.ParentStyle());
   CSSAnimations::UpdateAnimationFlags(
       *animating_element, state.AnimationUpdate(), state.StyleBuilder());
 
@@ -2051,8 +2051,10 @@
   STACK_UNINITIALIZED StyleCascade cascade(state);
 
   ApplyBaseStyle(&element, style_recalc_context, style_request, state, cascade);
-  ApplyInterpolations(state, cascade, interpolations);
+  state.StyleBuilder().SetBaseData(StyleBaseData::Create(
+      state.StyleBuilder().CloneStyle(), cascade.GetImportantSet()));
 
+  ApplyInterpolations(state, cascade, interpolations);
   return state.TakeStyle();
 }
 
@@ -2086,6 +2088,8 @@
     state.SetLayoutParentStyle(state.ParentStyle());
   }
 
+  state.StyleBuilder().SetBaseData(StyleBaseData::Create(&base_style, nullptr));
+
   // TODO(crbug.com/1098937): Include active CSS animations in a separate
   // interpolations map and add each map at the appropriate CascadeOrigin.
   ApplyInterpolations(state, cascade, transition_interpolations);
diff --git a/third_party/blink/renderer/core/editing/visible_units_test.cc b/third_party/blink/renderer/core/editing/visible_units_test.cc
index 2a02cad..c4429c2 100644
--- a/third_party/blink/renderer/core/editing/visible_units_test.cc
+++ b/third_party/blink/renderer/core/editing/visible_units_test.cc
@@ -915,6 +915,155 @@
   EXPECT_EQ("<p>ab\u200B\u200B|cd</p>",
             TestSnapBackward("<p>ab\u200B\u200B|cd</p>"));
 }
+TEST_F(VisibleUnitsTest, SnapForwardWithImg) {
+  SetBodyContent("<img>");
+  const auto& body = *GetDocument().body();
+  const auto& img = *GetDocument().QuerySelector("img");
+
+  EXPECT_EQ(Position::BeforeNode(img),
+            MostForwardCaretPosition(Position::FirstPositionInNode(body)));
+  EXPECT_EQ(Position::BeforeNode(img),
+            MostForwardCaretPosition(Position(body, 0)));
+  EXPECT_EQ(Position::BeforeNode(img),
+            MostForwardCaretPosition(Position::BeforeNode(img)));
+  EXPECT_EQ(Position::BeforeNode(img),
+            MostForwardCaretPosition(Position(img, 0)));
+  EXPECT_EQ(Position::AfterNode(img),
+            MostForwardCaretPosition(Position::LastPositionInNode(img)));
+  EXPECT_EQ(Position::AfterNode(img),
+            MostForwardCaretPosition(Position::AfterNode(img)));
+}
+
+TEST_F(VisibleUnitsTest, SnapForwardWithInput) {
+  SetBodyContent("<input>");
+  const auto& body = *GetDocument().body();
+  const auto& input = *GetDocument().QuerySelector("input");
+
+  EXPECT_EQ(Position::BeforeNode(input),
+            MostForwardCaretPosition(Position::FirstPositionInNode(body)));
+  EXPECT_EQ(Position::BeforeNode(input),
+            MostForwardCaretPosition(Position(body, 0)));
+  EXPECT_EQ(Position::BeforeNode(input),
+            MostForwardCaretPosition(Position::BeforeNode(input)));
+  EXPECT_EQ(Position::BeforeNode(input),
+            MostForwardCaretPosition(Position::FirstPositionInNode(input)));
+  EXPECT_EQ(Position::BeforeNode(input),
+            MostForwardCaretPosition(Position(input, 0)));
+  EXPECT_EQ(Position::AfterNode(input),
+            MostForwardCaretPosition(Position::LastPositionInNode(input)));
+  EXPECT_EQ(Position::AfterNode(input),
+            MostForwardCaretPosition(Position::AfterNode(input)));
+}
+
+TEST_F(VisibleUnitsTest, SnapForwardWithSelect) {
+  SetBodyContent(
+      "<select><option>1</option><option>2</option><option>3</option></"
+      "select>");
+  const auto& body = *GetDocument().body();
+  const auto& select = *GetDocument().QuerySelector("select");
+
+  EXPECT_EQ(Position::BeforeNode(select),
+            MostForwardCaretPosition(Position(body, 0)));
+  EXPECT_EQ(Position::BeforeNode(select),
+            MostForwardCaretPosition(Position::FirstPositionInNode(body)));
+  EXPECT_EQ(Position::BeforeNode(select),
+            MostForwardCaretPosition(Position::BeforeNode(select)));
+  EXPECT_EQ(Position::BeforeNode(select),
+            MostForwardCaretPosition(Position::FirstPositionInNode(select)));
+  EXPECT_EQ(Position::BeforeNode(select),
+            MostForwardCaretPosition(Position(select, 0)));
+
+  // The internal version of `MostForwardCaretPosition()` is called with
+  // `PositionInFlatTree(slot, 1)` and it scans at end of `<select>` then
+  // returns `PositionInFlatTree(slot, 1)` and converts to
+  // `Position(select, 1)`.
+  EXPECT_EQ(Position(select, 1), MostForwardCaretPosition(Position(select, 1)));
+  EXPECT_EQ(Position(select, 2), MostForwardCaretPosition(Position(select, 2)));
+  EXPECT_EQ(Position::AfterNode(select),
+            MostForwardCaretPosition(Position(select, 3)));
+  EXPECT_EQ(Position::AfterNode(select),
+            MostForwardCaretPosition(Position::LastPositionInNode(select)));
+  EXPECT_EQ(Position::AfterNode(select),
+            MostForwardCaretPosition(Position::AfterNode(select)));
+
+  // Flat tree is
+  //  <select>
+  //    <div>""</div>
+  //    <slot><option>1</option><option>2</option></slot>
+  //  </select>
+  EXPECT_EQ(PositionInFlatTree::BeforeNode(select),
+            MostForwardCaretPosition(PositionInFlatTree(body, 0)));
+  EXPECT_EQ(
+      PositionInFlatTree::BeforeNode(select),
+      MostForwardCaretPosition(PositionInFlatTree::FirstPositionInNode(body)));
+  EXPECT_EQ(PositionInFlatTree::BeforeNode(select),
+            MostForwardCaretPosition(PositionInFlatTree::BeforeNode(select)));
+
+  // Note: `PositionIterator::DeprecatedComputePosition()` returns
+  // `BeforeNode(<select>)` for <select>@n where n is 0 to 3, becase
+  // `EditingIgnoresContent(<select>)` is true.
+  EXPECT_EQ(PositionInFlatTree::BeforeNode(select),
+            MostForwardCaretPosition(
+                PositionInFlatTree::FirstPositionInNode(select)));
+  EXPECT_EQ(PositionInFlatTree::BeforeNode(select),
+            MostForwardCaretPosition(PositionInFlatTree(select, 0)));
+  EXPECT_EQ(PositionInFlatTree::BeforeNode(select),
+            MostForwardCaretPosition(PositionInFlatTree(select, 1)));
+  EXPECT_EQ(PositionInFlatTree::AfterNode(select),
+            MostForwardCaretPosition(PositionInFlatTree(select, 2)));
+
+  EXPECT_EQ(
+      PositionInFlatTree::AfterNode(select),
+      MostForwardCaretPosition(PositionInFlatTree::LastPositionInNode(select)));
+  EXPECT_EQ(PositionInFlatTree::AfterNode(select),
+            MostForwardCaretPosition(PositionInFlatTree::AfterNode(select)));
+}
+
+// From ReplaceSelectionCommandTest.TableAndImages)
+TEST_F(VisibleUnitsTest, SnapForwardWithTableAndImages) {
+  SetBodyContent("<table> <tbody></tbody> </table>");
+  const auto& table = *GetDocument().QuerySelector("table");
+  const auto& body = *GetDocument().body();
+  auto& tbody = *GetDocument().QuerySelector("tbody");
+  auto& img1 = *GetDocument().CreateRawElement(html_names::kImgTag);
+  tbody.AppendChild(&img1);
+  auto& img2 = *GetDocument().CreateRawElement(html_names::kImgTag);
+  tbody.AppendChild(&img2);
+  UpdateAllLifecyclePhasesForTest();
+
+  EXPECT_EQ(Position(body, 0), MostForwardCaretPosition(Position(body, 0)));
+  EXPECT_EQ(Position(body, 0),
+            MostForwardCaretPosition(Position::FirstPositionInNode(body)));
+  EXPECT_EQ(Position(table, 0),
+            MostForwardCaretPosition(Position::BeforeNode(table)));
+  EXPECT_EQ(Position(table, 0),
+            MostForwardCaretPosition(Position::FirstPositionInNode(table)));
+  EXPECT_EQ(Position(table, 0), MostForwardCaretPosition(Position(table, 0)));
+  // The result should be `Position(table, 1)`.
+  EXPECT_EQ(Position(table, 0), MostForwardCaretPosition(Position(table, 1)));
+  EXPECT_EQ(Position::BeforeNode(img1),
+            MostForwardCaretPosition(Position::BeforeNode(tbody)));
+  EXPECT_EQ(Position::BeforeNode(img1),
+            MostForwardCaretPosition(Position(tbody, 0)));
+  EXPECT_EQ(Position::BeforeNode(img1),
+            MostForwardCaretPosition(Position::FirstPositionInNode(tbody)));
+  EXPECT_EQ(Position::BeforeNode(img2),
+            MostForwardCaretPosition(Position(tbody, 1)));
+  EXPECT_EQ(Position::LastPositionInNode(tbody),
+            MostForwardCaretPosition(Position(tbody, 2)));
+  EXPECT_EQ(Position::LastPositionInNode(tbody),
+            MostForwardCaretPosition(Position::LastPositionInNode(tbody)));
+  EXPECT_EQ(Position::LastPositionInNode(tbody),
+            MostForwardCaretPosition(Position::AfterNode(tbody)));
+  // The result should be `Position(table, 2)`.
+  EXPECT_EQ(Position(table, 0), MostForwardCaretPosition(Position(table, 2)));
+  EXPECT_EQ(Position::LastPositionInNode(table),
+            MostForwardCaretPosition(Position(table, 3)));
+  EXPECT_EQ(Position::LastPositionInNode(table),
+            MostForwardCaretPosition(Position::LastPositionInNode(table)));
+  EXPECT_EQ(Position::LastPositionInNode(table),
+            MostForwardCaretPosition(Position::AfterNode(table)));
+}
 
 // http://crbug.com/1134470
 TEST_F(VisibleUnitsTest, SnapForwardWithZeroWidthSpace) {
diff --git a/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.cc b/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.cc
index 3171bdb9..fca6a62a 100644
--- a/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.cc
@@ -17,6 +17,7 @@
 #include "services/metrics/public/cpp/ukm_builders.h"
 #include "services/metrics/public/cpp/ukm_recorder.h"
 #include "third_party/blink/public/common/metrics/document_update_reason.h"
+#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 
@@ -52,7 +53,10 @@
       clock_(clock),
       start_time_(aggregator && aggregator->ShouldMeasureMetric(metric_index)
                       ? clock_->NowTicks()
-                      : base::TimeTicks()) {}
+                      : base::TimeTicks()) {
+  if (aggregator_ && !start_time_.is_null())
+    TRACE_EVENT_BEGIN0("blink", aggregator_->metrics_data()[metric_index].name);
+}
 
 LocalFrameUkmAggregator::ScopedUkmHierarchicalTimer::ScopedUkmHierarchicalTimer(
     ScopedUkmHierarchicalTimer&& other)
@@ -65,10 +69,13 @@
 
 LocalFrameUkmAggregator::ScopedUkmHierarchicalTimer::
     ~ScopedUkmHierarchicalTimer() {
-  if (aggregator_ && base::TimeTicks::IsHighResolution() &&
-      !start_time_.is_null()) {
-    aggregator_->RecordTimerSample(metric_index_, start_time_,
-                                   clock_->NowTicks());
+  if (aggregator_ && !start_time_.is_null()) {
+    if (base::TimeTicks::IsHighResolution()) {
+      aggregator_->RecordTimerSample(metric_index_, start_time_,
+                                     clock_->NowTicks());
+    }
+    TRACE_EVENT_END1("blink", aggregator_->metrics_data()[metric_index_].name,
+                     "preFCP", aggregator_->fcp_state_ == kBeforeFCPSignal);
   }
 }
 
@@ -115,11 +122,14 @@
   main_frame_count = 0;
 }
 
-LocalFrameUkmAggregator::LocalFrameUkmAggregator(int64_t source_id,
-                                                 ukm::UkmRecorder* recorder)
+LocalFrameUkmAggregator::LocalFrameUkmAggregator(
+    int64_t source_id,
+    ukm::UkmRecorder* recorder,
+    bool is_for_main_frame_local_frame_root)
     : source_id_(source_id),
       recorder_(recorder),
-      clock_(base::DefaultTickClock::GetInstance()) {
+      clock_(base::DefaultTickClock::GetInstance()),
+      is_for_main_frame_(is_for_main_frame_local_frame_root) {
   // All of these are assumed to have one entry per sub-metric.
   DCHECK_EQ(std::size(absolute_metric_records_), metrics_data().size());
   DCHECK_EQ(std::size(current_sample_.sub_metrics_counts),
@@ -140,8 +150,6 @@
           "Blink.MainFrame.UpdateTime.AggregatedPreFCP", 1, 10000000, 50);
 
   // Set up the substrings to create the UMA names
-  const char* const uma_preamble = "Blink.";
-  const char* const uma_postscript = ".UpdateTime";
   const char* const uma_prefcp_postscript = ".PreFCP";
   const char* const uma_postfcp_postscript = ".PostFCP";
   const char* const uma_pre_fcp_aggregated_postscript = ".AggregatedPreFCP";
@@ -157,24 +165,20 @@
     absolute_record.reset();
     absolute_record.pre_fcp_aggregate = 0;
     if (metric_data.has_uma) {
-      StringBuilder uma_name;
-      uma_name.Append(uma_preamble);
-      uma_name.Append(metric_data.name);
-      uma_name.Append(uma_postscript);
       StringBuilder pre_fcp_uma_name;
-      pre_fcp_uma_name.Append(uma_name);
+      pre_fcp_uma_name.Append(metric_data.name);
       pre_fcp_uma_name.Append(uma_prefcp_postscript);
       absolute_record.pre_fcp_uma_counter =
           std::make_unique<CustomCountHistogram>(
               pre_fcp_uma_name.ToString().Utf8().c_str(), 1, 10000000, 50);
       StringBuilder post_fcp_uma_name;
-      post_fcp_uma_name.Append(uma_name);
+      post_fcp_uma_name.Append(metric_data.name);
       post_fcp_uma_name.Append(uma_postfcp_postscript);
       absolute_record.post_fcp_uma_counter =
           std::make_unique<CustomCountHistogram>(
               post_fcp_uma_name.ToString().Utf8().c_str(), 1, 10000000, 50);
       StringBuilder aggregated_uma_name;
-      aggregated_uma_name.Append(uma_name);
+      aggregated_uma_name.Append(metric_data.name);
       aggregated_uma_name.Append(uma_pre_fcp_aggregated_postscript);
       absolute_record.uma_aggregate_counter =
           std::make_unique<CustomCountHistogram>(
@@ -190,6 +194,11 @@
 
   base::UmaHistogramBoolean("Blink.LocalFrameRoot.DidReachFirstContentfulPaint",
                             fcp_state_ != kBeforeFCPSignal);
+  if (is_for_main_frame_) {
+    base::UmaHistogramBoolean(
+        "Blink.LocalFrameRoot.DidReachFirstContentfulPaint.MainFrame",
+        fcp_state_ != kBeforeFCPSignal);
+  }
 }
 
 bool LocalFrameUkmAggregator::ShouldMeasureMetric(int64_t metric_id) const {
@@ -310,10 +319,16 @@
   }
 }
 
+void LocalFrameUkmAggregator::BeginForcedLayout() {
+  TRACE_EVENT_BEGIN0("blink", metrics_data()[kForcedStyleAndLayout].name);
+}
+
 void LocalFrameUkmAggregator::RecordForcedLayoutSample(
     DocumentUpdateReason reason,
     base::TimeTicks start,
     base::TimeTicks end) {
+  TRACE_EVENT_END1("blink", metrics_data()[kForcedStyleAndLayout].name,
+                   "preFCP", fcp_state_ == kBeforeFCPSignal);
   int64_t count = (end - start).InMicroseconds();
   bool is_pre_fcp = (fcp_state_ != kHavePassedFCP);
 
diff --git a/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.h b/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.h
index e84ec22..5f0cbbe9 100644
--- a/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.h
+++ b/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.h
@@ -175,35 +175,35 @@
   // Add an entry in this array every time a new metric is added.
   static base::span<const MetricInitializationData> metrics_data() {
     static const MetricInitializationData data[] = {
-        {"CompositingCommit", true},
-        {"CompositingInputs", true},
-        {"ImplCompositorCommit", true},
-        {"IntersectionObservation", true},
-        {"IntersectionObservationInternalCount", true},
-        {"IntersectionObservationJavascriptCount", true},
-        {"Paint", true},
-        {"PrePaint", true},
-        {"Style", true},
-        {"Layout", true},
-        {"HandleInputEvents", true},
-        {"Animate", true},
-        {"UpdateLayers", false},
-        {"WaitForCommit", true},
-        {"DisplayLockIntersectionObserver", true},
-        {"JavascriptIntersectionObserver", true},
-        {"LazyLoadIntersectionObserver", true},
-        {"MediaIntersectionObserver", true},
-        {"AnchorElementMetricsIntersectionObserver", true},
-        {"UpdateViewportIntersection", true},
-        {"ForcedStyleAndLayout", true},
-        {"ContentDocumentUpdate", true},
-        {"HitTestDocumentUpdate", true},
-        {"JavascriptDocumentUpdate", true},
-        {"ScrollDocumentUpdate", true},
-        {"ServiceDocumentUpdate", true},
-        {"UserDrivenDocumentUpdate", true},
-        {"ParseStyleSheet", true},
-        {"Accessibility", true}};
+        {"Blink.CompositingCommit.UpdateTime", true},
+        {"Blink.CompositingInputs.UpdateTime", true},
+        {"Blink.ImplCompositorCommit.UpdateTime", true},
+        {"Blink.IntersectionObservation.UpdateTime", true},
+        {"Blink.IntersectionObservationInternalCount.UpdateTime", true},
+        {"Blink.IntersectionObservationJavascriptCount.UpdateTime", true},
+        {"Blink.Paint.UpdateTime", true},
+        {"Blink.PrePaint.UpdateTime", true},
+        {"Blink.Style.UpdateTime", true},
+        {"Blink.Layout.UpdateTime", true},
+        {"Blink.HandleInputEvents.UpdateTime", true},
+        {"Blink.Animate.UpdateTime", true},
+        {"Blink.UpdateLayers.UpdateTime", false},
+        {"Blink.WaitForCommit.UpdateTime", true},
+        {"Blink.DisplayLockIntersectionObserver.UpdateTime", true},
+        {"Blink.JavascriptIntersectionObserver.UpdateTime", true},
+        {"Blink.LazyLoadIntersectionObserver.UpdateTime", true},
+        {"Blink.MediaIntersectionObserver.UpdateTime", true},
+        {"Blink.AnchorElementMetricsIntersectionObserver.UpdateTime", true},
+        {"Blink.UpdateViewportIntersection.UpdateTime", true},
+        {"Blink.ForcedStyleAndLayout.UpdateTime", true},
+        {"Blink.ContentDocumentUpdate.UpdateTime", true},
+        {"Blink.HitTestDocumentUpdate.UpdateTime", true},
+        {"Blink.JavascriptDocumentUpdate.UpdateTime", true},
+        {"Blink.ScrollDocumentUpdate.UpdateTime", true},
+        {"Blink.ServiceDocumentUpdate.UpdateTime", true},
+        {"Blink.UserDrivenDocumentUpdate.UpdateTime", true},
+        {"Blink.ParseStyleSheet.UpdateTime", true},
+        {"Blink.Accessibility.UpdateTime", true}};
     static_assert(std::size(data) == kCount, "Metrics data mismatch");
     return data;
   }
@@ -259,7 +259,9 @@
     int64_t metric_index_ = -1;
   };
 
-  LocalFrameUkmAggregator(int64_t source_id, ukm::UkmRecorder*);
+  LocalFrameUkmAggregator(int64_t source_id,
+                          ukm::UkmRecorder*,
+                          bool is_for_main_frame_local_frame_root);
   LocalFrameUkmAggregator(const LocalFrameUkmAggregator&) = delete;
   LocalFrameUkmAggregator& operator=(const LocalFrameUkmAggregator&) = delete;
   ~LocalFrameUkmAggregator();
@@ -292,6 +294,9 @@
   // Record a sample for a count-based sub-metric.
   void RecordCountSample(size_t metric_index, int64_t count);
 
+  // Mark the beginning of a forced layout.
+  void BeginForcedLayout();
+
   // Record a ForcedLayout sample. The reason will determine which, if any,
   // additional metrics are reported in order to diagnose the cause of
   // ForcedLayout regressions.
@@ -428,6 +433,9 @@
   // most of the benefit even if we downsample them. This value controls how
   // frequently we collect granular IntersectionObserver metrics.
   size_t intersection_observer_sample_period_ = 10;
+
+  // True if the local frame root that instantiated this is the main frame.
+  bool is_for_main_frame_ = false;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator_test.cc b/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator_test.cc
index 3b114c3..8df4d14 100644
--- a/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator_test.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator_test.cc
@@ -45,7 +45,7 @@
   void ResetAggregator() { aggregator_.reset(); }
   void RestartAggregator() {
     aggregator_ = base::MakeRefCounted<LocalFrameUkmAggregator>(
-        ukm::UkmRecorder::GetNewSourceID(), &recorder_);
+        ukm::UkmRecorder::GetNewSourceID(), &recorder_, true);
     aggregator_->SetTickClockForTesting(test_task_runner_->GetMockTickClock());
   }
 
@@ -54,7 +54,18 @@
   }
 
   std::string GetMetricName(int index) {
-    return LocalFrameUkmAggregator::metrics_data()[index].name;
+    std::string name = LocalFrameUkmAggregator::metrics_data()[index].name;
+
+    // If `name` is an UMA metric of the form Blink.[MetricName].UpdateTime, the
+    // following code extracts out [MetricName] for building up the UKM metric.
+    const char* const uma_postscript = ".UpdateTime";
+    size_t postscript_pos = name.find(uma_postscript);
+    if (postscript_pos) {
+      const char* const uma_preamble = "Blink.";
+      size_t preamble_length = strlen(uma_preamble);
+      name = name.substr(preamble_length, postscript_pos - preamble_length);
+    }
+    return name;
   }
 
   std::string GetBeginMainFrameMetricName(int index) {
@@ -840,6 +851,33 @@
   EXPECT_THAT(histogram_tester.GetAllSamples(
                   "Blink.LocalFrameRoot.DidReachFirstContentfulPaint"),
               BucketsAre(base::Bucket(false, 0), base::Bucket(true, 1)));
+  EXPECT_THAT(
+      histogram_tester.GetAllSamples(
+          "Blink.LocalFrameRoot.DidReachFirstContentfulPaint.MainFrame"),
+      BucketsAre(base::Bucket(false, 0), base::Bucket(true, 1)));
+}
+
+TEST_F(LocalFrameUkmAggregatorSimTest,
+       RemoteDidReachFirstContentfulPaintMetric) {
+  base::HistogramTester histogram_tester;
+
+  InitializeRemote();
+  LocalFrame& local_frame_root = *LocalFrameRoot().GetFrame();
+  ASSERT_FALSE(local_frame_root.IsMainFrame());
+  ASSERT_TRUE(local_frame_root.IsLocalRoot());
+
+  // Simulate the first contentful paint.
+  PaintTiming::From(*local_frame_root.GetDocument()).MarkFirstContentfulPaint();
+
+  local_frame_root.GetDocument()->Shutdown();
+
+  EXPECT_THAT(histogram_tester.GetAllSamples(
+                  "Blink.LocalFrameRoot.DidReachFirstContentfulPaint"),
+              BucketsAre(base::Bucket(false, 0), base::Bucket(true, 1)));
+  EXPECT_THAT(
+      histogram_tester.GetAllSamples(
+          "Blink.LocalFrameRoot.DidReachFirstContentfulPaint.MainFrame"),
+      BucketsAre(base::Bucket(false, 0), base::Bucket(true, 0)));
 }
 
 TEST_F(LocalFrameUkmAggregatorSimTest, DidNotReachFirstContentfulPaintMetric) {
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.cc b/third_party/blink/renderer/core/frame/local_frame_view.cc
index 04d8a1dc..0c97370 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -934,6 +934,7 @@
   if (forced_layout_stack_depth_ > 1)
     return;
   forced_layout_start_time_ = base::TimeTicks::Now();
+  EnsureUkmAggregator().BeginForcedLayout();
 }
 
 void LocalFrameView::DidFinishForcedLayout(DocumentUpdateReason reason) {
@@ -4738,7 +4739,8 @@
   if (!local_root->ukm_aggregator_) {
     local_root->ukm_aggregator_ = base::MakeRefCounted<LocalFrameUkmAggregator>(
         local_root->frame_->GetDocument()->UkmSourceID(),
-        local_root->frame_->GetDocument()->UkmRecorder());
+        local_root->frame_->GetDocument()->UkmRecorder(),
+        local_root->frame_->IsMainFrame());
   }
   return *local_root->ukm_aggregator_;
 }
diff --git a/third_party/blink/renderer/core/frame/web_frame_test.cc b/third_party/blink/renderer/core/frame/web_frame_test.cc
index 7df9683..8480dff 100644
--- a/third_party/blink/renderer/core/frame/web_frame_test.cc
+++ b/third_party/blink/renderer/core/frame/web_frame_test.cc
@@ -13491,8 +13491,7 @@
   print_params.print_content_area.set_size(page_size);
   EXPECT_EQ(1u, frame->PrintBegin(print_params, WebNode()));
   cc::PaintRecorder recorder;
-  frame->PrintPagesForTesting(recorder.beginRecording(SkRect::MakeEmpty()),
-                              page_size, page_size);
+  frame->PrintPagesForTesting(recorder.beginRecording(), page_size, page_size);
   frame->PrintEnd();
 }
 
@@ -13557,8 +13556,8 @@
 
   frame->PrintBegin(print_params, WebNode());
   cc::PaintRecorder recorder;
-  frame->PrintPagesForTesting(recorder.beginRecording(SkRect::MakeEmpty()),
-                              page_size, page_size, pages);
+  frame->PrintPagesForTesting(recorder.beginRecording(), page_size, page_size,
+                              pages);
   frame->PrintEnd();
 
   sk_sp<cc::PaintRecord> paint_record = recorder.finishRecordingAsPicture();
diff --git a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
index ef22f42..e08827e 100644
--- a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
@@ -319,12 +319,11 @@
 
     // The page rect gets scaled and translated, so specify the entire
     // print content area here as the recording rect.
-    gfx::RectF bounds(0, 0, printed_page_height_, printed_page_width_);
     auto* builder = MakeGarbageCollected<PaintRecordBuilder>();
     GraphicsContext& context = builder->Context();
     context.SetPrintingMetafile(canvas->GetPrintingMetafile());
     context.SetPrinting(true);
-    context.BeginRecording(bounds);
+    context.BeginRecording();
     float scale = SpoolPage(context, page_number);
     canvas->drawPicture(context.EndRecording());
     return scale;
@@ -353,7 +352,7 @@
     GraphicsContext& context = builder->Context();
     context.SetPrintingMetafile(canvas->GetPrintingMetafile());
     context.SetPrinting(true);
-    context.BeginRecording(all_pages_rect);
+    context.BeginRecording();
 
     // Fill the whole background by white.
     context.FillRect(all_pages_rect, Color::kWhite, AutoDarkMode::Disabled());
diff --git a/third_party/blink/renderer/core/html/canvas/canvas_async_blob_creator.cc b/third_party/blink/renderer/core/html/canvas/canvas_async_blob_creator.cc
index fc6b95e4..2d4a235b 100644
--- a/third_party/blink/renderer/core/html/canvas/canvas_async_blob_creator.cc
+++ b/third_party/blink/renderer/core/html/canvas/canvas_async_blob_creator.cc
@@ -133,15 +133,6 @@
   }
 }
 
-SkColorType GetColorTypeForConversion(SkColorType color_type) {
-  if (color_type == kRGBA_8888_SkColorType ||
-      color_type == kBGRA_8888_SkColorType) {
-    return color_type;
-  }
-
-  return kN32_SkColorType;
-}
-
 }  // anonymous namespace
 
 CanvasAsyncBlobCreator::CanvasAsyncBlobCreator(
@@ -204,55 +195,9 @@
     skia_image->readPixels(info, pixel, info.minRowBytes(), 0, 0);
   }
 
-  // For kHTMLCanvasToBlobCallback and kOffscreenCanvasConvertToBlobPromise
-  // to-blob function types, we keep the color space of the image and save
-  // it in the info if color management is enabled; otherwise, we color convert
-  // to sRGB and do not tag the image with any color space info.
-  // For kHTMLCanvasConvertToBlobPromise to-blob function type, we color
-  // covnert to the requested color space and pixel format.
-  if (function_type_ != kHTMLCanvasConvertToBlobPromise) {
-    if (skia_image->peekPixels(&src_data_))
-      static_bitmap_image_loaded_ = true;
-  } else {
-    sk_sp<SkColorSpace> blob_color_space =
-        BlobColorSpaceToSkColorSpace(encode_options_->colorSpace());
-    bool needs_color_space_conversion = !ApproximatelyEqualSkColorSpaces(
-        skia_image->refColorSpace(), blob_color_space);
-    if (needs_color_space_conversion && !skia_image->colorSpace()) {
-      skia_image->peekPixels(&src_data_);
-      src_data_.setColorSpace(SkColorSpace::MakeSRGB());
-      skia_image = SkImage::MakeRasterCopy(src_data_);
-      DCHECK(skia_image->colorSpace());
-    }
+  if (skia_image->peekPixels(&src_data_)) {
+    static_bitmap_image_loaded_ = true;
 
-    SkColorType target_color_type =
-        GetColorTypeForConversion(skia_image->colorType());
-    if (encode_options_->pixelFormat() == kRGBA16ImagePixelFormatName)
-      target_color_type = kRGBA_F16_SkColorType;
-    // We can do color space and color type conversion together.
-    if (needs_color_space_conversion) {
-      image_ = UnacceleratedStaticBitmapImage::Create(skia_image);
-      image_ = image_->ConvertToColorSpace(blob_color_space, target_color_type);
-      skia_image = image_->PaintImageForCurrentFrame().GetSwSkImage();
-    } else if (skia_image->colorType() != target_color_type) {
-      size_t data_length = skia_image->width() * skia_image->height() *
-                           SkColorTypeBytesPerPixel(target_color_type);
-      png_data_helper_ = SkData::MakeUninitialized(data_length);
-      SkImageInfo info = SkImageInfo::Make(
-          skia_image->width(), skia_image->height(), target_color_type,
-          skia_image->alphaType(), skia_image->refColorSpace());
-      SkPixmap src_data_f16(info, png_data_helper_->writable_data(),
-                            info.minRowBytes());
-      skia_image->readPixels(src_data_f16, 0, 0);
-      skia_image = SkImage::MakeFromRaster(src_data_f16, nullptr, nullptr);
-      image_ = UnacceleratedStaticBitmapImage::Create(skia_image);
-    }
-
-    if (skia_image->peekPixels(&src_data_))
-      static_bitmap_image_loaded_ = true;
-  }
-
-  if (static_bitmap_image_loaded_) {
     // Ensure that the size of the to-be-encoded-image does not pass the maximum
     // size supported by the encoders.
     int max_dimension = ImageEncoder::MaxDimension(mime_type_);
@@ -321,8 +266,7 @@
 
   if (!use_idle_encoding) {
     if (!IsMainThread()) {
-      DCHECK(function_type_ == kHTMLCanvasConvertToBlobPromise ||
-             function_type_ == kOffscreenCanvasConvertToBlobPromise);
+      DCHECK(function_type_ == kOffscreenCanvasConvertToBlobPromise);
       // When OffscreenCanvas.convertToBlob() occurs on worker thread,
       // we do not need to use background task runner to reduce load on main.
       // So we just directly encode images on the worker thread.
@@ -653,16 +597,6 @@
   visitor->Trace(script_promise_resolver_);
 }
 
-sk_sp<SkColorSpace> CanvasAsyncBlobCreator::BlobColorSpaceToSkColorSpace(
-    String blob_color_space) {
-  skcms_Matrix3x3 gamut = SkNamedGamut::kSRGB;
-  if (blob_color_space == kDisplayP3ImageColorSpaceName)
-    gamut = SkNamedGamut::kDisplayP3;
-  else if (blob_color_space == kRec2020ImageColorSpaceName)
-    gamut = SkNamedGamut::kRec2020;
-  return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, gamut);
-}
-
 bool CanvasAsyncBlobCreator::EncodeImageForConvertToBlobTest() {
   if (!static_bitmap_image_loaded_)
     return false;
diff --git a/third_party/blink/renderer/core/html/canvas/canvas_async_blob_creator.h b/third_party/blink/renderer/core/html/canvas/canvas_async_blob_creator.h
index 84ef707..62e0c5b 100644
--- a/third_party/blink/renderer/core/html/canvas/canvas_async_blob_creator.h
+++ b/third_party/blink/renderer/core/html/canvas/canvas_async_blob_creator.h
@@ -29,13 +29,6 @@
 
 class ExecutionContext;
 
-constexpr const char* kSRGBImageColorSpaceName = "srgb";
-constexpr const char* kRec2020ImageColorSpaceName = "rec2020";
-constexpr const char* kDisplayP3ImageColorSpaceName = "display-p3";
-
-constexpr const char* kRGBA8ImagePixelFormatName = "uint8";
-constexpr const char* kRGBA16ImagePixelFormatName = "uint16";
-
 class CORE_EXPORT CanvasAsyncBlobCreator
     : public GarbageCollected<CanvasAsyncBlobCreator> {
  public:
@@ -52,7 +45,6 @@
   };
   enum ToBlobFunctionType {
     kHTMLCanvasToBlobCallback,
-    kHTMLCanvasConvertToBlobPromise,
     kOffscreenCanvasConvertToBlobPromise
   };
 
@@ -81,9 +73,6 @@
 
   virtual void Trace(Visitor*) const;
 
-  static sk_sp<SkColorSpace> BlobColorSpaceToSkColorSpace(
-      String blob_color_space);
-
   bool EncodeImageForConvertToBlobTest();
   Vector<unsigned char> GetEncodedImageForConvertToBlobTest() {
     return encoded_image_;
diff --git a/third_party/blink/renderer/core/html/canvas/canvas_async_blob_creator_test.cc b/third_party/blink/renderer/core/html/canvas/canvas_async_blob_creator_test.cc
index 71184dd..7fd29b58 100644
--- a/third_party/blink/renderer/core/html/canvas/canvas_async_blob_creator_test.cc
+++ b/third_party/blink/renderer/core/html/canvas/canvas_async_blob_creator_test.cc
@@ -230,111 +230,4 @@
             AsyncBlobCreator()->GetIdleTaskStatus());
 }
 
-static sk_sp<SkImage> DrawAndReturnImage(
-    const std::pair<sk_sp<SkColorSpace>, SkColorType>& color_space_param) {
-  SkPaint transparentRed, transparentGreen, transparentBlue, transparentBlack;
-  transparentRed.setARGB(128, 155, 27, 27);
-  transparentGreen.setARGB(128, 27, 155, 27);
-  transparentBlue.setARGB(128, 27, 27, 155);
-  transparentBlack.setARGB(128, 27, 27, 27);
-
-  SkImageInfo info = SkImageInfo::Make(2, 2, color_space_param.second,
-                                       SkAlphaType::kPremul_SkAlphaType,
-                                       color_space_param.first);
-  sk_sp<SkSurface> surface = SkSurface::MakeRaster(info);
-  surface->getCanvas()->drawRect(SkRect::MakeXYWH(0, 0, 1, 1), transparentRed);
-  surface->getCanvas()->drawRect(SkRect::MakeXYWH(1, 0, 1, 1),
-                                 transparentGreen);
-  surface->getCanvas()->drawRect(SkRect::MakeXYWH(0, 1, 1, 1), transparentBlue);
-  surface->getCanvas()->drawRect(SkRect::MakeXYWH(1, 1, 1, 1),
-                                 transparentBlack);
-  return surface->makeImageSnapshot();
-}
-
-TEST_F(CanvasAsyncBlobCreatorTest, ColorManagedConvertToBlob) {
-  std::list<std::pair<sk_sp<SkColorSpace>, SkColorType>> color_space_params;
-  color_space_params.push_back(std::pair<sk_sp<SkColorSpace>, SkColorType>(
-      SkColorSpace::MakeSRGB(), kN32_SkColorType));
-  color_space_params.push_back(std::pair<sk_sp<SkColorSpace>, SkColorType>(
-      SkColorSpace::MakeSRGBLinear(), kRGBA_F16_SkColorType));
-  color_space_params.push_back(std::pair<sk_sp<SkColorSpace>, SkColorType>(
-      SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear,
-                            SkNamedGamut::kDisplayP3),
-      kRGBA_F16_SkColorType));
-  color_space_params.push_back(std::pair<sk_sp<SkColorSpace>, SkColorType>(
-      SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear, SkNamedGamut::kRec2020),
-      kRGBA_F16_SkColorType));
-  color_space_params.push_back(std::pair<sk_sp<SkColorSpace>, SkColorType>(
-      nullptr, kRGBA_F16_SkColorType));
-  color_space_params.push_back(
-      std::pair<sk_sp<SkColorSpace>, SkColorType>(nullptr, kN32_SkColorType));
-
-  std::list<String> blob_mime_types = {"image/png", "image/webp", "image/jpeg"};
-  std::list<String> blob_color_spaces = {kSRGBImageColorSpaceName,
-                                         kDisplayP3ImageColorSpaceName,
-                                         kRec2020ImageColorSpaceName};
-  std::list<String> blob_pixel_formats = {
-      kRGBA8ImagePixelFormatName,
-      kRGBA16ImagePixelFormatName,
-  };
-
-  // Maximum differences are both observed locally with
-  // kRGBA16ImagePixelFormatName, kSRGBImageColorSpaceName and nil input color
-  // space
-  const unsigned uint8_color_tolerance = 3;
-  const float f16_color_tolerance = 0.015;
-
-  for (auto color_space_param : color_space_params) {
-    for (auto blob_mime_type : blob_mime_types) {
-      for (auto blob_color_space : blob_color_spaces) {
-        for (auto blob_pixel_format : blob_pixel_formats) {
-          // Create the StaticBitmapImage in canvas_color_space
-          sk_sp<SkImage> source_image = DrawAndReturnImage(color_space_param);
-          scoped_refptr<StaticBitmapImage> source_bitmap_image =
-              UnacceleratedStaticBitmapImage::Create(source_image);
-
-          // Prepare encoding options
-          ImageEncodeOptions* options = ImageEncodeOptions::Create();
-          options->setQuality(1);
-          options->setType(blob_mime_type);
-          options->setColorSpace(blob_color_space);
-          options->setPixelFormat(blob_pixel_format);
-
-          // Encode the image using CanvasAsyncBlobCreator
-          auto* async_blob_creator =
-              MakeGarbageCollected<CanvasAsyncBlobCreator>(
-                  source_bitmap_image, options,
-                  CanvasAsyncBlobCreator::ToBlobFunctionType::
-                      kHTMLCanvasConvertToBlobPromise,
-                  base::TimeTicks(), GetFrame().DomWindow(), 0, nullptr);
-          ASSERT_TRUE(async_blob_creator->EncodeImageForConvertToBlobTest());
-
-          sk_sp<SkData> sk_data = SkData::MakeWithCopy(
-              async_blob_creator->GetEncodedImageForConvertToBlobTest().data(),
-              async_blob_creator->GetEncodedImageForConvertToBlobTest().size());
-          sk_sp<SkImage> decoded_img = SkImage::MakeFromEncoded(sk_data);
-
-          sk_sp<SkColorSpace> expected_color_space =
-              CanvasAsyncBlobCreator::BlobColorSpaceToSkColorSpace(
-                  blob_color_space);
-          SkColorType expected_color_type =
-              (blob_pixel_format == kRGBA8ImagePixelFormatName)
-                  ? kN32_SkColorType
-                  : kRGBA_F16_SkColorType;
-          scoped_refptr<StaticBitmapImage> ref_bitmap =
-              source_bitmap_image->ConvertToColorSpace(expected_color_space,
-                                                       expected_color_type);
-          sk_sp<SkImage> ref_image =
-              ref_bitmap->PaintImageForCurrentFrame().GetSwSkImage();
-
-          // Jpeg does not support transparent images.
-          bool compare_alpha = (blob_mime_type != "image/jpeg");
-          ASSERT_TRUE(ColorCorrectionTestUtils::MatchSkImages(
-              ref_image, decoded_img, uint8_color_tolerance,
-              f16_color_tolerance, compare_alpha));
-        }
-      }
-    }
-  }
-}
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.cc b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.cc
index 00c2297..6e2d1a6f 100644
--- a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.cc
+++ b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.cc
@@ -329,9 +329,8 @@
     const ImageEncodeOptions* options,
     ExceptionState& exception_state,
     const CanvasRenderingContext* const context) {
-  WTF::String object_name = "Canvas";
-  if (IsOffscreenCanvas())
-    object_name = "OffscreenCanvas";
+  DCHECK(IsOffscreenCanvas());
+  WTF::String object_name = "OffscreenCanvas";
   std::stringstream error_msg;
 
   if (IsOffscreenCanvas() && IsNeutered()) {
@@ -372,11 +371,7 @@
   if (image_bitmap) {
     auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
     CanvasAsyncBlobCreator::ToBlobFunctionType function_type =
-        CanvasAsyncBlobCreator::kHTMLCanvasConvertToBlobPromise;
-    if (IsOffscreenCanvas()) {
-      function_type =
-          CanvasAsyncBlobCreator::kOffscreenCanvasConvertToBlobPromise;
-    }
+        CanvasAsyncBlobCreator::kOffscreenCanvasConvertToBlobPromise;
     auto* execution_context = ExecutionContext::From(script_state);
     auto* async_creator = MakeGarbageCollected<CanvasAsyncBlobCreator>(
         image_bitmap, options, function_type, start_time, execution_context,
diff --git a/third_party/blink/renderer/core/html/forms/email_input_type.cc b/third_party/blink/renderer/core/html/forms/email_input_type.cc
index d1b959b8..9777b8c 100644
--- a/third_party/blink/renderer/core/html/forms/email_input_type.cc
+++ b/third_party/blink/renderer/core/html/forms/email_input_type.cc
@@ -54,7 +54,10 @@
 
 // RFC5321 says the maximum total length of a domain name is 255 octets.
 const int32_t kMaximumDomainNameLength = 255;
+
 // Use the same option as in url/url_canon_icu.cc
+// TODO(crbug.com/694157): Change the options if UseIDNA2008NonTransitional flag
+// is enabled.
 const int32_t kIdnaConversionOption = UIDNA_CHECK_BIDI;
 
 }  // namespace
diff --git a/third_party/blink/renderer/core/imagebitmap/image_bitmap.cc b/third_party/blink/renderer/core/imagebitmap/image_bitmap.cc
index 051ccf5..6ec1bfe 100644
--- a/third_party/blink/renderer/core/imagebitmap/image_bitmap.cc
+++ b/third_party/blink/renderer/core/imagebitmap/image_bitmap.cc
@@ -928,8 +928,7 @@
   gfx::Rect draw_dst_rect(0, 0, parsed_options.resize_width,
                           parsed_options.resize_height);
   PaintRecorder recorder;
-  cc::PaintCanvas* canvas =
-      recorder.beginRecording(gfx::RectToSkRect(draw_src_rect));
+  cc::PaintCanvas* canvas = recorder.beginRecording();
   if (parsed_options.flip_y) {
     canvas->translate(0, draw_dst_rect.height());
     canvas->scale(1, -1);
diff --git a/third_party/blink/renderer/core/layout/layout_shift_tracker_test.cc b/third_party/blink/renderer/core/layout/layout_shift_tracker_test.cc
index d2071ebe..d56c0bd 100644
--- a/third_party/blink/renderer/core/layout/layout_shift_tracker_test.cc
+++ b/third_party/blink/renderer/core/layout/layout_shift_tracker_test.cc
@@ -1281,4 +1281,43 @@
   EXPECT_TRUE(tracker.NeedsToTrack(*GetLayoutObjectByElementId("hr")));
 }
 
+TEST_F(LayoutShiftTrackerTest, AnimatingTransformCreatesLayoutShiftRoot) {
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      @keyframes move {
+        to { translate: 10px; }
+      }
+      #animation {
+        animation: move 10s infinite;
+        position: absolute;
+        width: 0;
+        height: 0;
+        top: 0;
+      }
+      #child {
+        position: relative;
+        width: 200px;
+        height: 200px;
+        background: blue;
+      }
+    </style>
+    <div id="animation">
+      <div id="child"></div>
+    </div>
+  )HTML");
+
+  EXPECT_FLOAT_EQ(0, GetLayoutShiftTracker().Score());
+
+  GetDocument()
+      .getElementById("animation")
+      ->setAttribute(html_names::kStyleAttr, "top: 400px");
+  // `animation` creates a layout shift root, so `child`'s shift doesn't
+  // include the shift of `animation`. The 2px shift is below the threshold of
+  // reporting a layout shift.
+  GetDocument().getElementById("child")->setAttribute(html_names::kStyleAttr,
+                                                      "top: 2px");
+  UpdateAllLifecyclePhasesForTest();
+  EXPECT_FLOAT_EQ(0, GetLayoutShiftTracker().Score());
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_resource_pattern.cc b/third_party/blink/renderer/core/layout/svg/layout_svg_resource_pattern.cc
index 3f2b68c..cc17bfb 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_resource_pattern.cc
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_resource_pattern.cc
@@ -214,8 +214,7 @@
 
   gfx::RectF bounds(size);
   PaintRecorder paint_recorder;
-  cc::PaintCanvas* canvas =
-      paint_recorder.beginRecording(gfx::RectFToSkRect(bounds));
+  cc::PaintCanvas* canvas = paint_recorder.beginRecording();
 
   auto* pattern_content_element = Attributes().PatternContentElement();
   DCHECK(pattern_content_element);
diff --git a/third_party/blink/renderer/core/paint/document_marker_painter.cc b/third_party/blink/renderer/core/paint/document_marker_painter.cc
index 66af738..fb6362d 100644
--- a/third_party/blink/renderer/core/paint/document_marker_painter.cc
+++ b/third_party/blink/renderer/core/paint/document_marker_painter.cc
@@ -56,9 +56,8 @@
   flags.setStrokeWidth(kMarkerHeight * 1 / 2);
 
   PaintRecorder recorder;
-  recorder.beginRecording(kMarkerWidth, kMarkerHeight);
-  recorder.getRecordingCanvas()->cc::PaintCanvas::drawPath(path.detach(),
-                                                           flags);
+  recorder.beginRecording();
+  recorder.getRecordingCanvas()->drawPath(path.detach(), flags);
 
   return recorder.finishRecordingAsPicture();
 }
@@ -80,7 +79,7 @@
   flags.setAntiAlias(true);
   flags.setColor(color);
   PaintRecorder recorder;
-  recorder.beginRecording(kMarkerWidth, kMarkerHeight);
+  recorder.beginRecording();
   recorder.getRecordingCanvas()->drawOval(SkRect::MakeWH(2 * kR, 2 * kR),
                                           flags);
   return recorder.finishRecordingAsPicture();
diff --git a/third_party/blink/renderer/core/paint/link_highlight_impl.cc b/third_party/blink/renderer/core/paint/link_highlight_impl.cc
index 5218beb..ba6c950 100644
--- a/third_party/blink/renderer/core/paint/link_highlight_impl.cc
+++ b/third_party/blink/renderer/core/paint/link_highlight_impl.cc
@@ -209,8 +209,7 @@
 
   PaintRecorder recorder;
   gfx::Rect record_bounds = PaintableRegion();
-  cc::PaintCanvas* canvas =
-      recorder.beginRecording(record_bounds.width(), record_bounds.height());
+  cc::PaintCanvas* canvas = recorder.beginRecording();
 
   cc::PaintFlags flags;
   flags.setStyle(cc::PaintFlags::kFill_Style);
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
index c530b81a..73e0b60 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
@@ -259,7 +259,6 @@
       void (*compute_matrix)(const ComputedStyle& style,
                              const PhysicalSize& size,
                              gfx::Transform& matrix),
-      CompositingReasons active_animation_reason,
       CompositingReasons compositing_reasons_for_property,
       CompositorElementIdNamespace compositor_namespace,
       bool (ComputedStyle::*running_on_compositor_test)() const,
@@ -1096,7 +1095,6 @@
 static TransformPaintPropertyNode::TransformAndOrigin TransformAndOriginState(
     const LayoutBox& box,
     const PhysicalSize& size,
-    bool has_transform_animation_compositing_reasons,
     void (*compute_matrix)(const ComputedStyle& style,
                            const PhysicalSize& size,
                            gfx::Transform& matrix)) {
@@ -1105,12 +1103,18 @@
   return {matrix, GetTransformOrigin(box, size)};
 }
 
+static bool IsLayoutShiftRootTransform(
+    const TransformPaintPropertyNode& transform) {
+  // This is to keep the layout shift behavior before crrev.com/c/4024030.
+  return transform.HasActiveTransformAnimation() ||
+         !transform.IsIdentityOr2dTranslation();
+}
+
 void FragmentPaintPropertyTreeBuilder::UpdateIndividualTransform(
     bool (*needs_property)(const LayoutObject&, CompositingReasons),
     void (*compute_matrix)(const ComputedStyle& style,
                            const PhysicalSize& size,
                            gfx::Transform& matrix),
-    CompositingReasons active_animation_reason,
     CompositingReasons compositing_reasons_for_property,
     CompositorElementIdNamespace compositor_namespace,
     bool (ComputedStyle::*running_on_compositor_test)() const,
@@ -1152,10 +1156,8 @@
         // to the compositor, in case later animated values will use the origin.
         // See http://crbug.com/937929 for why we are not using
         // style.IsRunningTransformAnimationOnCompositor() etc. here.
-        state.transform_and_origin = TransformAndOriginState(
-            box, size,
-            full_context_.direct_compositing_reasons & active_animation_reason,
-            compute_matrix);
+        state.transform_and_origin =
+            TransformAndOriginState(box, size, compute_matrix);
 
         // TODO(trchen): transform-style should only be respected if a
         // PaintLayer is created. If a node with transform-style: preserve-3d
@@ -1224,7 +1226,7 @@
       // this in the caller.)
       context_.should_flatten_inherited_transform = false;
     }
-    if (transform->IsIdentityOr2dTranslation()) {
+    if (!IsLayoutShiftRootTransform(*transform)) {
       context_.translation_2d_to_layout_shift_root_delta +=
           transform->Get2dTranslation();
     }
@@ -1239,7 +1241,6 @@
         if (style.Translate())
           style.Translate()->Apply(matrix, gfx::SizeF(size));
       },
-      CompositingReason::kActiveTranslateAnimation,
       CompositingReason::kDirectReasonsForTranslateProperty,
       CompositorElementIdNamespace::kTranslateTransform,
       &ComputedStyle::IsRunningTranslateAnimationOnCompositor,
@@ -1256,7 +1257,6 @@
         if (style.Rotate())
           style.Rotate()->Apply(matrix, gfx::SizeF(size));
       },
-      CompositingReason::kActiveRotateAnimation,
       CompositingReason::kDirectReasonsForRotateProperty,
       CompositorElementIdNamespace::kRotateTransform,
       &ComputedStyle::IsRunningRotateAnimationOnCompositor,
@@ -1272,7 +1272,6 @@
         if (style.Scale())
           style.Scale()->Apply(matrix, gfx::SizeF(size));
       },
-      CompositingReason::kActiveScaleAnimation,
       CompositingReason::kDirectReasonsForScaleProperty,
       CompositorElementIdNamespace::kScaleTransform,
       &ComputedStyle::IsRunningScaleAnimationOnCompositor,
@@ -1292,7 +1291,7 @@
             ComputedStyle::kIncludeMotionPath,
             ComputedStyle::kExcludeIndependentTransformProperties);
       },
-      CompositingReason::kNone, CompositingReason::kNone,
+      CompositingReason::kNone,
       // TODO(dbaron): When we support animating offset on the
       // compositor, we need to use an element ID specific to offset.
       // This is currently unused.
@@ -1313,7 +1312,6 @@
             ComputedStyle::kExcludeMotionPath,
             ComputedStyle::kExcludeIndependentTransformProperties);
       },
-      CompositingReason::kActiveTransformAnimation,
       CompositingReasonsForTransformProperty(),
       CompositorElementIdNamespace::kPrimaryTransform,
       &ComputedStyle::IsRunningTransformAnimationOnCompositor,
@@ -3041,7 +3039,7 @@
     return true;
   for (const TransformPaintPropertyNode* transform :
        properties->AllCSSTransformPropertiesOutsideToInside()) {
-    if (transform && !transform->IsIdentityOr2dTranslation())
+    if (transform && IsLayoutShiftRootTransform(*transform))
       return true;
   }
   if (properties->ReplacedContentTransform())
@@ -3232,12 +3230,11 @@
     for (const TransformPaintPropertyNode* transform :
          properties->AllCSSTransformPropertiesOutsideToInside()) {
       if (transform) {
-        if (transform->IsIdentityOr2dTranslation()) {
-          translation2d += transform->Get2dTranslation();
-        } else {
+        if (IsLayoutShiftRootTransform(*transform)) {
           translation2d = gfx::Vector2dF();
           break;
         }
+        translation2d += transform->Get2dTranslation();
       }
     }
     context.translation_2d_to_layout_shift_root_delta -= translation2d;
@@ -4260,7 +4257,7 @@
   auto* properties = fragment_data->PaintProperties();
   auto* transform = properties->Transform();
   auto transform_and_origin = TransformAndOriginState(
-      box, size, transform->HasActiveTransformAnimation(),
+      box, size,
       [](const ComputedStyle& style, const PhysicalSize& size,
          gfx::Transform& matrix) {
         style.ApplyTransform(
diff --git a/third_party/blink/renderer/core/paint/text_decoration_info.cc b/third_party/blink/renderer/core/paint/text_decoration_info.cc
index 5c159c4..bf8506b 100644
--- a/third_party/blink/renderer/core/paint/text_decoration_info.cc
+++ b/third_party/blink/renderer/core/paint/text_decoration_info.cc
@@ -252,14 +252,12 @@
   flags.setStyle(cc::PaintFlags::kStroke_Style);
   flags.setStrokeWidth(params.resolved_thickness);
 
-  // Create a canvas with origin (0,0) and size of the wavy pattern rect.
   PaintRecorder recorder;
-  recorder.beginRecording(pattern_rect.width(), pattern_rect.height());
+  cc::PaintCanvas* canvas = recorder.beginRecording();
 
   // Translate the wavy pattern so that nothing is painted at y<0.
-  cc::RecordPaintCanvas* canvas = recorder.getRecordingCanvas();
   canvas->translate(-pattern_rect.x(), -pattern_rect.y());
-  canvas->cc::PaintCanvas::drawPath(stroke_path.GetSkPath(), flags);
+  canvas->drawPath(stroke_path.GetSkPath(), flags);
 
   return recorder.finishRecordingAsPicture();
 }
diff --git a/third_party/blink/renderer/core/paint/video_painter_test.cc b/third_party/blink/renderer/core/paint/video_painter_test.cc
index f1c265d..315b76e 100644
--- a/third_party/blink/renderer/core/paint/video_painter_test.cc
+++ b/third_party/blink/renderer/core/paint/video_painter_test.cc
@@ -219,8 +219,7 @@
     cc::PaintRecorder recorder;
     paint_preview::PaintPreviewTracker tracker(token, embedding_token,
                                                is_main_frame);
-    cc::PaintCanvas* canvas =
-        recorder.beginRecording(bounds().width(), bounds().height());
+    cc::PaintCanvas* canvas = recorder.beginRecording();
     canvas->SetPaintPreviewTracker(&tracker);
 
     GetLocalMainFrame().CapturePaintPreview(
diff --git a/third_party/blink/renderer/core/scheduler_integration_tests/frame_throttling_test.cc b/third_party/blink/renderer/core/scheduler_integration_tests/frame_throttling_test.cc
index 7f2c581..a496cb8 100644
--- a/third_party/blink/renderer/core/scheduler_integration_tests/frame_throttling_test.cc
+++ b/third_party/blink/renderer/core/scheduler_integration_tests/frame_throttling_test.cc
@@ -1966,7 +1966,7 @@
   WebPrintParams print_params(gfx::Size(500, 500));
   web_frame->PrintBegin(print_params, blink::WebNode());
   cc::PaintRecorder recorder;
-  web_frame->PrintPage(0, recorder.beginRecording(500, 500));
+  web_frame->PrintPage(0, recorder.beginRecording());
   auto record = recorder.finishRecordingAsPicture();
   String record_string = RecordAsDebugString(*record);
   EXPECT_TRUE(record_string.Contains("drawTextBlob")) << record_string.Utf8();
diff --git a/third_party/blink/renderer/core/style/computed_style.cc b/third_party/blink/renderer/core/style/computed_style.cc
index 7d051dc4..7424063f 100644
--- a/third_party/blink/renderer/core/style/computed_style.cc
+++ b/third_party/blink/renderer/core/style/computed_style.cc
@@ -922,7 +922,7 @@
 void ComputedStyle::AdjustDiffForBackgroundVisuallyEqual(
     const ComputedStyle& other,
     StyleDifference& diff) const {
-  if (BackgroundColorInternal() != other.BackgroundColorInternal()) {
+  if (BackgroundColor() != other.BackgroundColor()) {
     // If the background color change is not due to a composited animation, then
     // paint invalidation is required; but we can defer the decision until we
     // know whether the color change will be rendered by the compositor.
@@ -2039,7 +2039,7 @@
   list->data.shrink_to_fit();
 }
 
-void ComputedStyle::OverrideTextDecorationColors(Color override_color) {
+void ComputedStyle::OverrideTextDecorationColors(blink::Color override_color) {
   scoped_refptr<AppliedTextDecorationList>& list =
       MutableAppliedTextDecorationsInternal();
   DCHECK(list);
@@ -2051,7 +2051,7 @@
 }
 
 void ComputedStyle::ApplyTextDecorations(
-    const Color& parent_text_decoration_color,
+    const blink::Color& parent_text_decoration_color,
     bool override_existing_colors) {
   if (GetTextDecorationLine() == TextDecorationLine::kNone &&
       !HasSimpleUnderlineInternal() && !AppliedTextDecorationsInternal())
@@ -2059,7 +2059,7 @@
 
   // If there are any color changes or decorations set by this element, stop
   // using m_hasSimpleUnderline.
-  Color current_text_decoration_color =
+  blink::Color current_text_decoration_color =
       VisitedDependentColor(GetCSSPropertyTextDecorationColor());
   if (HasSimpleUnderlineInternal() &&
       (GetTextDecorationLine() != TextDecorationLine::kNone ||
@@ -2124,7 +2124,8 @@
     StyleColor text_stroke_style_color =
         visited_link ? InternalVisitedTextStrokeColor() : TextStrokeColor();
     if (!text_stroke_style_color.IsCurrentColor() &&
-        text_stroke_style_color.Resolve(Color(), UsedColorScheme()).Alpha()) {
+        text_stroke_style_color.Resolve(blink::Color(), UsedColorScheme())
+            .Alpha()) {
       return text_stroke_style_color;
     }
   }
@@ -2132,11 +2133,12 @@
   return visited_link ? InternalVisitedTextFillColor() : TextFillColor();
 }
 
-Color ComputedStyle::VisitedDependentColor(const CSSProperty& color_property,
-                                           bool* is_current_color) const {
+blink::Color ComputedStyle::VisitedDependentColor(
+    const CSSProperty& color_property,
+    bool* is_current_color) const {
   DCHECK(!color_property.IsVisited());
 
-  Color unvisited_color =
+  blink::Color unvisited_color =
       To<Longhand>(color_property)
           .ColorIncludingFallback(false, *this, is_current_color);
   if (InsideLink() != EInsideLink::kInsideVisitedLink)
@@ -2152,7 +2154,7 @@
     visited_property = visited;
 
   // Overwrite is_current_color based on the visited color.
-  Color visited_color =
+  blink::Color visited_color =
       To<Longhand>(*visited_property)
           .ColorIncludingFallback(true, *this, is_current_color);
 
@@ -2169,14 +2171,14 @@
   // the flag when the unvisited color is ‘currentColor’ would break tests like
   // css/css-pseudo/selection-link-001 and css/css-pseudo/target-text-008.
   // TODO(dazabani@igalia.com) improve behaviour where unvisited is currentColor
-  return Color(visited_color.Red(), visited_color.Green(), visited_color.Blue(),
-               unvisited_color.Alpha());
+  return blink::Color(visited_color.Red(), visited_color.Green(),
+                      visited_color.Blue(), unvisited_color.Alpha());
 }
 
-Color ComputedStyle::ResolvedColor(const StyleColor& color,
-                                   bool* is_current_color) const {
+blink::Color ComputedStyle::ResolvedColor(const StyleColor& color,
+                                          bool* is_current_color) const {
   bool visited_link = (InsideLink() == EInsideLink::kInsideVisitedLink);
-  Color current_color =
+  blink::Color current_color =
       visited_link ? GetInternalVisitedCurrentColor() : GetCurrentColor();
   return color.Resolve(current_color, UsedColorScheme(), is_current_color);
 }
@@ -2290,37 +2292,37 @@
     SetChildHasExplicitInheritance();
 }
 
-Color ComputedStyle::GetCurrentColor(bool* is_current_color) const {
-  DCHECK(!GetColor().IsCurrentColor());
+blink::Color ComputedStyle::GetCurrentColor(bool* is_current_color) const {
+  DCHECK(!Color().IsCurrentColor());
   if (is_current_color)
     *is_current_color = ColorIsCurrentColor();
-  return GetColor().Resolve(Color(), UsedColorScheme());
+  return Color().Resolve(blink::Color(), UsedColorScheme());
 }
 
-Color ComputedStyle::GetInternalVisitedCurrentColor(
+blink::Color ComputedStyle::GetInternalVisitedCurrentColor(
     bool* is_current_color) const {
   DCHECK(!InternalVisitedColor().IsCurrentColor());
   if (is_current_color)
     *is_current_color = InternalVisitedColorIsCurrentColor();
-  return InternalVisitedColor().Resolve(Color(), UsedColorScheme());
+  return InternalVisitedColor().Resolve(blink::Color(), UsedColorScheme());
 }
 
-Color ComputedStyle::GetInternalForcedCurrentColor(
+blink::Color ComputedStyle::GetInternalForcedCurrentColor(
     bool* is_current_color) const {
   DCHECK(!InternalForcedColor().IsCurrentColor());
-  if (GetColor().IsSystemColorIncludingDeprecated())
+  if (Color().IsSystemColorIncludingDeprecated())
     return GetCurrentColor(is_current_color);
-  return InternalForcedColor().Resolve(Color(), UsedColorScheme(),
+  return InternalForcedColor().Resolve(blink::Color(), UsedColorScheme(),
                                        is_current_color,
                                        /* is_forced_color */ true);
 }
 
-Color ComputedStyle::GetInternalForcedVisitedCurrentColor(
+blink::Color ComputedStyle::GetInternalForcedVisitedCurrentColor(
     bool* is_current_color) const {
   DCHECK(!InternalForcedVisitedColor().IsCurrentColor());
   if (InternalVisitedColor().IsSystemColorIncludingDeprecated())
     return GetInternalVisitedCurrentColor(is_current_color);
-  return InternalForcedVisitedColor().Resolve(Color(), UsedColorScheme(),
+  return InternalForcedVisitedColor().Resolve(blink::Color(), UsedColorScheme(),
                                               is_current_color,
                                               /* is_forced_color */ true);
 }
@@ -2339,7 +2341,7 @@
   return ListStyleType()->GetStringValue();
 }
 
-absl::optional<Color> ComputedStyle::AccentColorResolved() const {
+absl::optional<blink::Color> ComputedStyle::AccentColorResolved() const {
   const StyleAutoColor& auto_color = AccentColor();
   if (auto_color.IsAutoColor())
     return absl::nullopt;
diff --git a/third_party/blink/renderer/core/style/computed_style.h b/third_party/blink/renderer/core/style/computed_style.h
index 0e01ac7..be64f48 100644
--- a/third_party/blink/renderer/core/style/computed_style.h
+++ b/third_party/blink/renderer/core/style/computed_style.h
@@ -109,18 +109,15 @@
 namespace css_longhand {
 
 class AccentColor;
-class Appearance;
 class BackgroundColor;
 class BorderBottomColor;
 class BorderLeftColor;
 class BorderRightColor;
 class BorderTopColor;
 class CaretColor;
-class Clear;
 class Color;
 class ColumnRuleColor;
 class Fill;
-class Float;
 class FloodColor;
 class InternalForcedBackgroundColor;
 class InternalForcedBorderColor;
@@ -144,18 +141,13 @@
 class InternalVisitedTextStrokeColor;
 class LightingColor;
 class OutlineColor;
-class PointerEvents;
-class Position;
-class Resize;
 class StopColor;
 class Stroke;
 class TextDecorationColor;
 class TextEmphasisColor;
-class UserSelect;
 class WebkitTapHighlightColor;
 class WebkitTextFillColor;
 class WebkitTextStrokeColor;
-class WebkitUserModify;
 
 }  // namespace css_longhand
 
@@ -225,7 +217,7 @@
 
   // Accesses GetColor().
   friend class ComputedStyleUtils;
-  // These get visited and unvisited colors separately.
+  // Color properties need access to private color utils.
   friend class css_longhand::AccentColor;
   friend class css_longhand::BackgroundColor;
   friend class css_longhand::BorderBottomColor;
@@ -261,23 +253,18 @@
   friend class css_longhand::InternalVisitedTextStrokeColor;
   friend class css_longhand::LightingColor;
   friend class css_longhand::OutlineColor;
-  friend class css_longhand::PointerEvents;
-  friend class css_longhand::Position;
   friend class css_longhand::Resize;
   friend class css_longhand::StopColor;
   friend class css_longhand::Stroke;
   friend class css_longhand::TextDecorationColor;
   friend class css_longhand::TextEmphasisColor;
-  friend class css_longhand::UserSelect;
   friend class css_longhand::WebkitTapHighlightColor;
   friend class css_longhand::WebkitTextFillColor;
   friend class css_longhand::WebkitTextStrokeColor;
-  friend class css_longhand::WebkitUserModify;
   // Access to private Appearance() and HasAppearance().
   friend class LayoutTheme;
   friend class StyleAdjuster;
   friend class StyleCascade;
-  friend class css_longhand::Appearance;
   // Editing has to only reveal unvisited info.
   friend class EditingStyle;
   // Saves Border/Background information for later comparison.
@@ -294,6 +281,10 @@
   // Access to UserModify().
   friend class MatchedPropertiesCache;
 
+  using ComputedStyleBase::Clear;
+  using ComputedStyleBase::Floating;
+  using ComputedStyleBase::Resize;
+
  protected:
   mutable std::unique_ptr<StyleCachedData> cached_data_;
 
@@ -910,9 +901,8 @@
   bool StrokeDashArrayDataEquivalent(const ComputedStyle&) const;
 
   // accent-color
-  const StyleAutoColor& AccentColor() const { return AccentColorInternal(); }
   // An empty optional means the accent-color is 'auto'
-  absl::optional<Color> AccentColorResolved() const;
+  absl::optional<blink::Color> AccentColorResolved() const;
 
   // Comparison operators
   // FIXME: Replace callers of operator== wth a named method instead, e.g.
@@ -986,7 +976,7 @@
     return !HasAutoColumnCount() || !HasAutoColumnWidth();
   }
   bool ColumnRuleIsTransparent() const {
-    return !ColumnRuleColorInternal()
+    return !ColumnRuleColor()
                 .Resolve(GetCurrentColor(), UsedColorScheme())
                 .Alpha();
   }
@@ -1452,15 +1442,15 @@
   bool CanRenderBorderImage() const;
 
   // Float utility functions.
-  bool IsFloating() const { return FloatingInternal() != EFloat::kNone; }
-  EFloat UnresolvedFloating() const { return FloatingInternal(); }
+  bool IsFloating() const { return Floating() != EFloat::kNone; }
+  EFloat UnresolvedFloating() const { return Floating(); }
 
   EFloat Floating(const ComputedStyle& cb_style) const {
     return Floating(cb_style.Direction());
   }
 
   EFloat Floating(TextDirection cb_direction) const {
-    const EFloat value = FloatingInternal();
+    const EFloat value = Floating();
     switch (value) {
       case EFloat::kInlineStart:
         return IsLtr(cb_direction) ? EFloat::kLeft : EFloat::kRight;
@@ -1527,15 +1517,15 @@
   }
 
   // Clear utility functions.
-  bool HasClear() const { return ClearInternal() != EClear::kNone; }
-  EClear UnresolvedClear() const { return ClearInternal(); }
+  bool HasClear() const { return Clear() != EClear::kNone; }
+  EClear UnresolvedClear() const { return Clear(); }
 
   EClear Clear(const ComputedStyle& cb_style) const {
     return Clear(cb_style.Direction());
   }
 
   EClear Clear(TextDirection cb_direction) const {
-    const EClear value = ClearInternal();
+    const EClear value = Clear();
     switch (value) {
       case EClear::kInlineStart:
         return IsLtr(cb_direction) ? EClear::kLeft : EClear::kRight;
@@ -1718,12 +1708,12 @@
 
   // Resize utility functions.
   bool HasResize() const {
-    return StyleType() == kPseudoIdNone && ResizeInternal() != EResize::kNone;
+    return StyleType() == kPseudoIdNone && Resize() != EResize::kNone;
   }
-  EResize UnresolvedResize() const { return ResizeInternal(); }
+  EResize UnresolvedResize() const { return Resize(); }
 
   EResize Resize(const ComputedStyle& cb_style) const {
-    EResize value = ResizeInternal();
+    EResize value = Resize();
     switch (value) {
       case EResize::kBlock:
         return cb_style.IsHorizontalWritingMode() ? EResize::kVertical
@@ -1740,26 +1730,26 @@
   EPointerEvents UsedPointerEvents() const {
     if (IsInert())
       return EPointerEvents::kNone;
-    return PointerEventsInternal();
+    return PointerEvents();
   }
 
   // User modify utility functions.
   EUserModify UsedUserModify() const {
     if (IsInert())
       return EUserModify::kReadOnly;
-    return UserModifyInternal();
+    return UserModify();
   }
 
   // User select utility functions.
   EUserSelect UsedUserSelect() const {
     if (IsInert())
       return EUserSelect::kNone;
-    return UserSelectInternal();
+    return UserSelect();
   }
 
   bool IsSelectable() const {
-    return !IsInert() && !(UserSelectInternal() == EUserSelect::kNone &&
-                           UserModifyInternal() == EUserModify::kReadOnly);
+    return !IsInert() && !(UserSelect() == EUserSelect::kNone &&
+                           UserModify() == EUserModify::kReadOnly);
   }
 
   bool IsFocusable() const {
@@ -1773,7 +1763,7 @@
 
   // Text decoration utility functions.
   bool TextDecorationVisualOverflowEqual(const ComputedStyle& o) const;
-  void ApplyTextDecorations(const Color& parent_text_decoration_color,
+  void ApplyTextDecorations(const blink::Color& parent_text_decoration_color,
                             bool override_existing_colors);
   void ClearAppliedTextDecorations();
   void RestoreParentTextDecorations(const ComputedStyle& parent_style);
@@ -2223,7 +2213,7 @@
     return ShadowListHasCurrentColor(BoxShadow());
   }
   bool HasBackground() const {
-    Color color = VisitedDependentColor(GetCSSPropertyBackgroundColor());
+    blink::Color color = VisitedDependentColor(GetCSSPropertyBackgroundColor());
     if (color.Alpha())
       return true;
     // When background color animation is running on the compositor thread, we
@@ -2236,9 +2226,9 @@
   }
 
   // Color utility functions.
-  CORE_EXPORT Color
-  VisitedDependentColor(const CSSProperty& color_property,
-                        bool* is_current_color = nullptr) const;
+  CORE_EXPORT blink::Color VisitedDependentColor(
+      const CSSProperty& color_property,
+      bool* is_current_color = nullptr) const;
 
   // -webkit-appearance utility functions.
   bool HasEffectiveAppearance() const {
@@ -2321,13 +2311,6 @@
   bool HasViewportUnits() const { return ViewportUnitFlags(); }
 
  private:
-  EClear Clear() const { return ClearInternal(); }
-  EFloat Floating() const { return FloatingInternal(); }
-  EPointerEvents PointerEvents() const { return PointerEventsInternal(); }
-  EResize Resize() const { return ResizeInternal(); }
-  EUserModify UserModify() const { return UserModifyInternal(); }
-  EUserSelect UserSelect() const { return UserSelectInternal(); }
-
   bool IsInlineSizeContainer() const {
     return ContainerType() & kContainerTypeInlineSize;
   }
@@ -2394,138 +2377,44 @@
            display == EDisplay::kTableCaption;
   }
 
-  // Color accessors are all private to make sure callers use
-  // VisitedDependentColor instead to access them.
-  const StyleColor& BorderLeftColor() const {
-    return BorderLeftColorInternal();
-  }
-  const StyleColor& BorderRightColor() const {
-    return BorderRightColorInternal();
-  }
-  const StyleColor& BorderTopColor() const { return BorderTopColorInternal(); }
-  const StyleColor& BorderBottomColor() const {
-    return BorderBottomColorInternal();
-  }
-
-  const StyleColor& BackgroundColor() const {
-    return BackgroundColorInternal();
-  }
-  const StyleAutoColor& CaretColor() const { return CaretColorInternal(); }
-  const StyleColor& GetColor() const { return ColorInternal(); }
-  bool ColorIsCurrentColor() const { return ColorIsCurrentColorInternal(); }
-  const StyleColor& ColumnRuleColor() const {
-    return ColumnRuleColorInternal();
-  }
-  const StyleColor& OutlineColor() const { return OutlineColorInternal(); }
-  const StyleColor& StopColor() const { return StopColorInternal(); }
-  const StyleColor& TextDecorationColor() const {
-    return TextDecorationColorInternal();
-  }
-  const StyleColor& TextEmphasisColor() const {
-    return TextEmphasisColorInternal();
-  }
-  const StyleColor& TextFillColor() const { return TextFillColorInternal(); }
-  const StyleColor& TextStrokeColor() const {
-    return TextStrokeColorInternal();
-  }
-  const StyleColor& InternalVisitedColor() const {
-    return InternalVisitedColorInternal();
-  }
-  bool InternalVisitedColorIsCurrentColor() const {
-    return InternalVisitedColorIsCurrentColorInternal();
-  }
-  const StyleAutoColor& InternalVisitedCaretColor() const {
-    return InternalVisitedCaretColorInternal();
-  }
-  const StyleColor& InternalVisitedBackgroundColor() const {
-    return InternalVisitedBackgroundColorInternal();
-  }
-  const StyleColor& InternalVisitedBorderLeftColor() const {
-    return InternalVisitedBorderLeftColorInternal();
-  }
   bool InternalVisitedBorderLeftColorHasNotChanged(
       const ComputedStyle& other) const {
     return (InternalVisitedBorderLeftColor() ==
                 other.InternalVisitedBorderLeftColor() ||
             !BorderLeftWidth());
   }
-  const StyleColor& InternalVisitedBorderRightColor() const {
-    return InternalVisitedBorderRightColorInternal();
-  }
   bool InternalVisitedBorderRightColorHasNotChanged(
       const ComputedStyle& other) const {
     return (InternalVisitedBorderRightColor() ==
                 other.InternalVisitedBorderRightColor() ||
             !BorderRightWidth());
   }
-  const StyleColor& InternalVisitedBorderBottomColor() const {
-    return InternalVisitedBorderBottomColorInternal();
-  }
   bool InternalVisitedBorderBottomColorHasNotChanged(
       const ComputedStyle& other) const {
     return (InternalVisitedBorderBottomColor() ==
                 other.InternalVisitedBorderBottomColor() ||
             !BorderBottomWidth());
   }
-  const StyleColor& InternalVisitedBorderTopColor() const {
-    return InternalVisitedBorderTopColorInternal();
-  }
   bool InternalVisitedBorderTopColorHasNotChanged(
       const ComputedStyle& other) const {
     return (InternalVisitedBorderTopColor() ==
                 other.InternalVisitedBorderTopColor() ||
             !BorderTopWidth());
   }
-  const StyleColor& InternalVisitedOutlineColor() const {
-    return InternalVisitedOutlineColorInternal();
-  }
+
   bool InternalVisitedOutlineColorHasNotChanged(
       const ComputedStyle& other) const {
     return (InternalVisitedOutlineColor() ==
                 other.InternalVisitedOutlineColor() ||
             !OutlineWidth());
   }
-  const StyleColor& InternalVisitedColumnRuleColor() const {
-    return InternalVisitedColumnRuleColorInternal();
-  }
-  const StyleColor& InternalVisitedTextDecorationColor() const {
-    return InternalVisitedTextDecorationColorInternal();
-  }
-  const StyleColor& InternalVisitedTextEmphasisColor() const {
-    return InternalVisitedTextEmphasisColorInternal();
-  }
-  const StyleColor& InternalVisitedTextFillColor() const {
-    return InternalVisitedTextFillColorInternal();
-  }
-  const StyleColor& InternalVisitedTextStrokeColor() const {
-    return InternalVisitedTextStrokeColorInternal();
-  }
-
-  const StyleColor& InternalForcedBackgroundColor() const {
-    return InternalForcedBackgroundColorInternal();
-  }
-  const StyleColor& InternalForcedBorderColor() const {
-    return InternalForcedBorderColorInternal();
-  }
-  const StyleColor& InternalForcedColor() const {
-    return InternalForcedColorInternal();
-  }
-  const StyleColor& InternalForcedOutlineColor() const {
-    return InternalForcedOutlineColorInternal();
-  }
-  const StyleColor& InternalForcedVisitedColor() const {
-    return InternalForcedVisitedColorInternal();
-  }
 
   StyleColor DecorationColorIncludingFallback(bool visited_link) const;
 
-  // Appearance accessors are private to make sure callers use
-  // EffectiveAppearance in almost all cases.
-  ControlPart Appearance() const { return AppearanceInternal(); }
   bool HasAppearance() const { return Appearance() != kNoControlPart; }
 
   void AddAppliedTextDecoration(const AppliedTextDecoration&);
-  void OverrideTextDecorationColors(Color propagated_color);
+  void OverrideTextDecorationColors(blink::Color propagated_color);
   void ApplyMotionPathTransform(float origin_x,
                                 float origin_y,
                                 const gfx::RectF& bounding_box,
@@ -2558,10 +2447,12 @@
   CORE_EXPORT bool CustomPropertiesEqual(const Vector<AtomicString>& properties,
                                          const ComputedStyle& other) const;
 
-  Color GetCurrentColor(bool* is_current_color = nullptr) const;
-  Color GetInternalVisitedCurrentColor(bool* is_current_color = nullptr) const;
-  Color GetInternalForcedCurrentColor(bool* is_current_color = nullptr) const;
-  Color GetInternalForcedVisitedCurrentColor(
+  blink::Color GetCurrentColor(bool* is_current_color = nullptr) const;
+  blink::Color GetInternalVisitedCurrentColor(
+      bool* is_current_color = nullptr) const;
+  blink::Color GetInternalForcedCurrentColor(
+      bool* is_current_color = nullptr) const;
+  blink::Color GetInternalForcedVisitedCurrentColor(
       bool* is_current_color = nullptr) const;
 
   // Helper for resolving a StyleColor which may contain currentColor or a
@@ -2569,8 +2460,8 @@
   // consists of a StyleColor plus additional information. For <color>
   // properties, prefer VisitedDependentColor() or
   // Longhand::ColorIncludingFallback() instead.
-  Color ResolvedColor(const StyleColor& color,
-                      bool* is_current_color = nullptr) const;
+  blink::Color ResolvedColor(const StyleColor& color,
+                             bool* is_current_color = nullptr) const;
 
   static bool ShadowListHasCurrentColor(const ShadowList*);
 
@@ -2724,7 +2615,7 @@
   CORE_EXPORT void InheritFrom(
       const ComputedStyle& inherit_parent,
       IsAtShadowBoundary is_at_shadow_boundary = kNotAtShadowBoundary) {
-    EUserModify current_user_modify = UserModifyInternal();
+    EUserModify current_user_modify = UserModify();
     ComputedStyleBuilderBase::InheritFrom(inherit_parent,
                                           is_at_shadow_boundary);
 
@@ -3111,6 +3002,13 @@
   // zoom
   CORE_EXPORT bool SetEffectiveZoom(float);
 
+  // BaseData
+  const ComputedStyle* GetBaseComputedStyle() const {
+    if (auto* base_data = BaseData().get())
+      return base_data->GetBaseComputedStyle();
+    return nullptr;
+  }
+
   /// CallbackSelector
   void AddCallbackSelector(const String& selector) {
     if (!CallbackSelectors().Contains(selector))
diff --git a/third_party/blink/renderer/core/style/computed_style_extra_fields.json5 b/third_party/blink/renderer/core/style/computed_style_extra_fields.json5
index c1007c9..a1b87a66 100644
--- a/third_party/blink/renderer/core/style/computed_style_extra_fields.json5
+++ b/third_party/blink/renderer/core/style/computed_style_extra_fields.json5
@@ -820,7 +820,7 @@
       type_name: "bool",
       default_value: "true",
       inherited: true,
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
     },
     {
       name: "InternalVisitedColorIsCurrentColor",
@@ -829,7 +829,7 @@
       type_name: "bool",
       default_value: "true",
       inherited: true,
-      computed_style_custom_functions: ["getter"],
+      computed_style_protected_functions: ["getter"],
     },
     {
       name: "WillChangeProperties",
diff --git a/third_party/blink/renderer/core/svg/graphics/filters/svg_fe_image.cc b/third_party/blink/renderer/core/svg/graphics/filters/svg_fe_image.cc
index b68aec9..00acc77 100644
--- a/third_party/blink/renderer/core/svg/graphics/filters/svg_fe_image.cc
+++ b/third_party/blink/renderer/core/svg/graphics/filters/svg_fe_image.cc
@@ -180,8 +180,7 @@
   // cull rect for the paint record.
   gfx::RectF crop_rect = IntersectWithFilterRegion(GetFilter(), dst_rect);
   PaintRecorder paint_recorder;
-  cc::PaintCanvas* canvas =
-      paint_recorder.beginRecording(gfx::RectFToSkRect(crop_rect));
+  cc::PaintCanvas* canvas = paint_recorder.beginRecording();
   canvas->concat(AffineTransformToSkMatrix(transform));
   {
     auto* builder = MakeGarbageCollected<PaintRecordBuilder>();
diff --git a/third_party/blink/renderer/core/svg/graphics/svg_image.cc b/third_party/blink/renderer/core/svg/graphics/svg_image.cc
index a225964..ddd18a3 100644
--- a/third_party/blink/renderer/core/svg/graphics/svg_image.cc
+++ b/third_party/blink/renderer/core/svg/graphics/svg_image.cc
@@ -481,8 +481,7 @@
   const gfx::SizeF size =
       gfx::ScaleSize(draw_info.ContainerSize(), draw_info.Zoom());
   const gfx::Rect dest_rect(gfx::ToRoundedSize(size));
-  cc::PaintCanvas* canvas =
-      recorder.beginRecording(gfx::RectToSkRect(dest_rect));
+  cc::PaintCanvas* canvas = recorder.beginRecording();
   DrawForContainer(draw_info, canvas, cc::PaintFlags(), gfx::RectF(dest_rect),
                    gfx::RectF(size));
   builder.set_paint_record(recorder.finishRecordingAsPicture(), dest_rect,
diff --git a/third_party/blink/renderer/modules/csspaint/paint_rendering_context_2d.cc b/third_party/blink/renderer/modules/csspaint/paint_rendering_context_2d.cc
index 1220823..20381b1 100644
--- a/third_party/blink/renderer/modules/csspaint/paint_rendering_context_2d.cc
+++ b/third_party/blink/renderer/modules/csspaint/paint_rendering_context_2d.cc
@@ -32,9 +32,8 @@
 }
 
 void PaintRenderingContext2D::InitializePaintRecorder() {
-  paint_recorder_ = std::make_unique<PaintRecorder>();
-  cc::PaintCanvas* canvas = paint_recorder_->beginRecording(
-      container_size_.width(), container_size_.height());
+  paint_recorder_ = std::make_unique<cc::InspectablePaintRecorder>();
+  cc::PaintCanvas* canvas = paint_recorder_->beginRecording(container_size_);
 
   // Always save an initial frame, to support resetting the top level matrix
   // and clip.
diff --git a/third_party/blink/renderer/modules/csspaint/paint_rendering_context_2d.h b/third_party/blink/renderer/modules/csspaint/paint_rendering_context_2d.h
index 10bf437e..40b7eb4 100644
--- a/third_party/blink/renderer/modules/csspaint/paint_rendering_context_2d.h
+++ b/third_party/blink/renderer/modules/csspaint/paint_rendering_context_2d.h
@@ -115,7 +115,7 @@
  private:
   void InitializePaintRecorder();
 
-  std::unique_ptr<PaintRecorder> paint_recorder_;
+  std::unique_ptr<cc::InspectablePaintRecorder> paint_recorder_;
   sk_sp<PaintRecord> previous_frame_;
   gfx::Size container_size_;
   Member<const PaintRenderingContext2DSettings> context_settings_;
diff --git a/third_party/blink/renderer/modules/geolocation/geo_notifier.cc b/third_party/blink/renderer/modules/geolocation/geo_notifier.cc
index aa95a984..cdfe1657 100644
--- a/third_party/blink/renderer/modules/geolocation/geo_notifier.cc
+++ b/third_party/blink/renderer/modules/geolocation/geo_notifier.cc
@@ -4,7 +4,6 @@
 
 #include "third_party/blink/renderer/modules/geolocation/geo_notifier.h"
 
-#include "base/metrics/histogram_functions.h"
 #include "third_party/blink/public/platform/task_type.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_position_options.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
@@ -28,11 +27,6 @@
       use_cached_position_(false) {
   DCHECK(geolocation_);
   DCHECK(success_callback_);
-
-  base::UmaHistogramCustomTimes("Geolocation.Timeout",
-                                base::Milliseconds(options_->timeout()),
-                                base::Milliseconds(1), base::Minutes(10),
-                                /* buckets = */ 20);
 }
 
 void GeoNotifier::Trace(Visitor* visitor) const {
@@ -135,11 +129,6 @@
                      GeolocationPositionError::kTimeout, "Timeout expired"));
   }
 
-  base::UmaHistogramCustomTimes("Geolocation.TimeoutExpired",
-                                base::Milliseconds(options_->timeout()),
-                                base::Milliseconds(1), base::Minutes(10),
-                                /* buckets = */ 20);
-
   geolocation_->RequestTimedOut(this);
 }
 
diff --git a/third_party/blink/renderer/modules/webcodecs/video_encoder.cc b/third_party/blink/renderer/modules/webcodecs/video_encoder.cc
index 111c3947..15e6a77 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_encoder.cc
+++ b/third_party/blink/renderer/modules/webcodecs/video_encoder.cc
@@ -112,7 +112,8 @@
 bool IsAcceleratedConfigurationSupported(
     media::VideoCodecProfile profile,
     const media::VideoEncoder::Options& options,
-    media::GpuVideoAcceleratorFactories* gpu_factories) {
+    media::GpuVideoAcceleratorFactories* gpu_factories,
+    bool allow_software_codecs) {
   if (!gpu_factories || !gpu_factories->IsGpuVideoEncodeAcceleratorEnabled())
     return false;
 
@@ -125,9 +126,14 @@
     if (supported_profile.profile != profile)
       continue;
 
-    if (supported_profile.min_resolution.width() > options.frame_size.width() ||
-        supported_profile.min_resolution.height() >
-            options.frame_size.height()) {
+    // TODO(crbug.com/1383643): We should retrieve a separate list of supported
+    // profiles tagged with software capabilities instead of just ignoring the
+    // minimum resolution, but the practical outcome ends up the same, so for
+    // now just do the simple thing.
+    if (!allow_software_codecs && (supported_profile.min_resolution.width() >
+                                       options.frame_size.width() ||
+                                   supported_profile.min_resolution.height() >
+                                       options.frame_size.height())) {
       continue;
     }
 
@@ -469,6 +475,33 @@
              IsGpuMemoryBufferReadbackFromTextureEnabled();
 }
 
+bool MayHaveOSSoftwareEncoder(media::VideoCodecProfile profile) {
+  // The AV1 and VPx software encoders are fully featured while OpenH264 only
+  // supports H264PROFILE_BASELINE. Allow OS software encoding when we don't
+  // have an equivalent software encoder.
+  //
+  // Note: Since we don't enumerate OS software encoders this may still fail and
+  // trigger fallback to our bundled software encoder (if any).
+  //
+  // Note 2: It's not ideal to have this logic live here, but to move it to the
+  // accelerator we'd need to always wait for GpuFactories enumeration.
+  //
+  // TODO(crbug.com/1383643): Add IS_ANDROID, IS_WIN here once we can force
+  // selection of a software encoder on those platforms.
+#if BUILDFLAG(IS_MAC)
+  const auto codec = media::VideoCodecProfileToVideoCodec(profile);
+  return codec == media::VideoCodec::kHEVC ||
+#if BUILDFLAG(ENABLE_OPENH264)
+         (codec == media::VideoCodec::kH264 &&
+          profile != media::H264PROFILE_BASELINE);
+#else
+         codec == media::VideoCodec::kH264;
+#endif  // BUILDFLAG(ENABLE_OPENH264)
+#else
+  return false;
+#endif  // BUILDFLAG(IS_MAC)
+}
+
 }  // namespace
 
 // static
@@ -526,14 +559,30 @@
 VideoEncoder::CreateAcceleratedVideoEncoder(
     media::VideoCodecProfile profile,
     const media::VideoEncoder::Options& options,
-    media::GpuVideoAcceleratorFactories* gpu_factories) {
-  if (!IsAcceleratedConfigurationSupported(profile, options, gpu_factories))
+    media::GpuVideoAcceleratorFactories* gpu_factories,
+    HardwarePreference hw_pref) {
+  bool allow_software_codecs = false;
+  auto required_encoder_type =
+      media::VideoEncodeAccelerator::Config::EncoderType::kHardware;
+  if (hw_pref != HardwarePreference::kPreferHardware &&
+      MayHaveOSSoftwareEncoder(profile)) {
+    required_encoder_type =
+        hw_pref == HardwarePreference::kPreferSoftware
+            ? media::VideoEncodeAccelerator::Config::EncoderType::kSoftware
+            : media::VideoEncodeAccelerator::Config::EncoderType::kNoPreference;
+    allow_software_codecs = true;
+  }
+
+  if (!IsAcceleratedConfigurationSupported(profile, options, gpu_factories,
+                                           allow_software_codecs)) {
     return nullptr;
+  }
 
   return std::make_unique<
       media::AsyncDestroyVideoEncoder<media::VideoEncodeAcceleratorAdapter>>(
       std::make_unique<media::VideoEncodeAcceleratorAdapter>(
-          gpu_factories, logger_->log()->Clone(), callback_runner_));
+          gpu_factories, logger_->log()->Clone(), callback_runner_,
+          required_encoder_type));
 }
 
 std::unique_ptr<media::VideoEncoder> CreateAv1VideoEncoder() {
@@ -593,32 +642,27 @@
 std::unique_ptr<media::VideoEncoder> VideoEncoder::CreateMediaVideoEncoder(
     const ParsedConfig& config,
     media::GpuVideoAcceleratorFactories* gpu_factories) {
-  switch (config.hw_pref) {
-    case HardwarePreference::kPreferHardware: {
-      auto result = CreateAcceleratedVideoEncoder(
-          config.profile, config.options, gpu_factories);
-      if (result)
-        OnMediaEncoderCreated("AcceleratedVideoEncoder", true);
-      return result;
-    }
-    case HardwarePreference::kNoPreference:
-      if (auto result = CreateAcceleratedVideoEncoder(
-              config.profile, config.options, gpu_factories)) {
-        OnMediaEncoderCreated("AcceleratedVideoEncoder", true);
-        return std::make_unique<media::VideoEncoderFallback>(
-            std::move(result),
-            ConvertToBaseOnceCallback(CrossThreadBindOnce(
-                &VideoEncoder::CreateSoftwareVideoEncoder,
-                MakeUnwrappingCrossThreadWeakHandle(this), config.codec)));
-      }
-      [[fallthrough]];
-    case HardwarePreference::kPreferSoftware:
-      return CreateSoftwareVideoEncoder(this, config.codec);
+  if (config.hw_pref == HardwarePreference::kPreferHardware ||
+      config.hw_pref == HardwarePreference::kNoPreference ||
+      MayHaveOSSoftwareEncoder(config.profile)) {
+    auto result = CreateAcceleratedVideoEncoder(config.profile, config.options,
+                                                gpu_factories, config.hw_pref);
+    if (result)
+      OnMediaEncoderCreated("AcceleratedVideoEncoder", true);
 
-    default:
-      NOTREACHED();
-      return nullptr;
+    if (config.hw_pref == HardwarePreference::kPreferHardware) {
+      return result;
+    } else if (result) {
+      // 'no-preference' or 'prefer-software' and we have OS software encoders.
+      return std::make_unique<media::VideoEncoderFallback>(
+          std::move(result),
+          ConvertToBaseOnceCallback(CrossThreadBindOnce(
+              &VideoEncoder::CreateSoftwareVideoEncoder,
+              MakeUnwrappingCrossThreadWeakHandle(this), config.codec)));
+    }
   }
+
+  return CreateSoftwareVideoEncoder(this, config.codec);
 }
 
 void VideoEncoder::ContinueConfigureWithGpuFactories(
@@ -917,7 +961,8 @@
 
   blocking_request_in_progress_ = true;
 
-  if (active_config_->hw_pref == HardwarePreference::kPreferSoftware) {
+  if (active_config_->hw_pref == HardwarePreference::kPreferSoftware &&
+      !MayHaveOSSoftwareEncoder(active_config_->profile)) {
     ContinueConfigureWithGpuFactories(request, nullptr);
     return;
   }
@@ -1150,8 +1195,12 @@
     VideoEncoderSupport* support,
     VideoEncoderTraits::ParsedConfig* config,
     media::GpuVideoAcceleratorFactories* gpu_factories) {
+  const bool allow_software_codecs =
+      config->hw_pref != HardwarePreference::kPreferHardware &&
+      MayHaveOSSoftwareEncoder(config->profile);
+
   bool supported = IsAcceleratedConfigurationSupported(
-      config->profile, config->options, gpu_factories);
+      config->profile, config->options, gpu_factories, allow_software_codecs);
   support->setSupported(supported);
   resolver->Resolve(support);
 }
@@ -1209,7 +1258,8 @@
   // put them into |promises|. Simultaneously run both versions of
   // isConfigSupported(), each version fulfills its own promise.
   HeapVector<ScriptPromise> promises;
-  if (parsed_config->hw_pref != HardwarePreference::kPreferSoftware) {
+  if (parsed_config->hw_pref != HardwarePreference::kPreferSoftware ||
+      MayHaveOSSoftwareEncoder(parsed_config->profile)) {
     // Hardware support not denied, detect support by hardware encoders.
     auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
     promises.push_back(resolver->Promise());
diff --git a/third_party/blink/renderer/modules/webcodecs/video_encoder.h b/third_party/blink/renderer/modules/webcodecs/video_encoder.h
index 702da105..7049fbc 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_encoder.h
+++ b/third_party/blink/renderer/modules/webcodecs/video_encoder.h
@@ -128,7 +128,8 @@
   std::unique_ptr<media::VideoEncoder> CreateAcceleratedVideoEncoder(
       media::VideoCodecProfile profile,
       const media::VideoEncoder::Options& options,
-      media::GpuVideoAcceleratorFactories* gpu_factories);
+      media::GpuVideoAcceleratorFactories* gpu_factories,
+      HardwarePreference hw_pref);
   bool CanReconfigure(ParsedConfig& original_config,
                       ParsedConfig& new_config) override;
 
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc b/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc
index 247164f..2f5e53f 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc
@@ -1306,7 +1306,7 @@
     // |recorder_|.
     recorder_ = std::make_unique<MemoryManagedPaintRecorder>(this);
 
-    return recorder_->beginRecording(Size().width(), Size().height());
+    return recorder_->beginRecording(Size());
   }
   return recorder_->getRecordingCanvas();
 }
@@ -1416,8 +1416,7 @@
   sk_sp<cc::PaintRecord> last_recording = recorder_->finishRecordingAsPicture();
   RasterRecord(last_recording, preserve_recording);
   total_pinned_image_bytes_ = 0;
-  cc::PaintCanvas* canvas =
-      recorder_->beginRecording(Size().width(), Size().height());
+  cc::PaintCanvas* canvas = recorder_->beginRecording(Size());
   if (restore_clip_stack_callback_)
     restore_clip_stack_callback_.Run(canvas);
   if (!preserve_recording)
@@ -1624,8 +1623,7 @@
   if (!HasRecordedDrawOps())
     return;
   recorder_->finishRecordingAsPicture();
-  cc::PaintCanvas* canvas =
-      recorder_->beginRecording(Size().width(), Size().height());
+  cc::PaintCanvas* canvas = recorder_->beginRecording(Size());
   total_pinned_image_bytes_ = 0;
   if (restore_clip_stack_callback_)
     restore_clip_stack_callback_.Run(canvas);
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_chunks_to_cc_layer.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_chunks_to_cc_layer.cc
index bc959d27..66579ba 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_chunks_to_cc_layer.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_chunks_to_cc_layer.cc
@@ -801,7 +801,7 @@
   if (under_invalidation_checking_params) {
     auto& params = *under_invalidation_checking_params;
     PaintRecorder recorder;
-    recorder.beginRecording(gfx::RectToSkRect(params.interest_rect));
+    recorder.beginRecording();
     // Create a complete cloned list for under-invalidation checking. We can't
     // use cc_list because it is not finalized yet.
     auto list_clone = base::MakeRefCounted<cc::DisplayItemList>(
diff --git a/third_party/blink/renderer/platform/graphics/deferred_image_decoder_test.cc b/third_party/blink/renderer/platform/graphics/deferred_image_decoder_test.cc
index 985a63c..33d5fff 100644
--- a/third_party/blink/renderer/platform/graphics/deferred_image_decoder_test.cc
+++ b/third_party/blink/renderer/platform/graphics/deferred_image_decoder_test.cc
@@ -165,7 +165,7 @@
   EXPECT_EQ(1, image.height());
 
   PaintRecorder recorder;
-  cc::PaintCanvas* temp_canvas = recorder.beginRecording(100, 100);
+  cc::PaintCanvas* temp_canvas = recorder.beginRecording();
   temp_canvas->drawImage(image, 0, 0);
   sk_sp<PaintRecord> record = recorder.finishRecordingAsPicture();
   EXPECT_EQ(0, decode_request_count_);
@@ -182,7 +182,7 @@
   // Received only half the file.
   lazy_decoder_->SetData(partial_data, false /* all_data_received */);
   PaintRecorder recorder;
-  cc::PaintCanvas* temp_canvas = recorder.beginRecording(100, 100);
+  cc::PaintCanvas* temp_canvas = recorder.beginRecording();
   PaintImage image =
       CreatePaintImage(PaintImage::CompletionState::PARTIALLY_DONE);
   ASSERT_TRUE(image);
@@ -193,7 +193,7 @@
   lazy_decoder_->SetData(data_, true /* all_data_received */);
   image = CreatePaintImage();
   ASSERT_TRUE(image);
-  temp_canvas = recorder.beginRecording(100, 100);
+  temp_canvas = recorder.beginRecording();
   temp_canvas->drawImage(image, 0, 0);
   canvas_->drawPicture(recorder.finishRecordingAsPicture());
   EXPECT_EQ(SkColorSetARGB(255, 255, 255, 255), bitmap_.getColor(0, 0));
@@ -263,7 +263,7 @@
   EXPECT_EQ(1, image.height());
 
   PaintRecorder recorder;
-  cc::PaintCanvas* temp_canvas = recorder.beginRecording(100, 100);
+  cc::PaintCanvas* temp_canvas = recorder.beginRecording();
   temp_canvas->drawImage(image, 0, 0);
   sk_sp<PaintRecord> record = recorder.finishRecordingAsPicture();
   EXPECT_EQ(0, decode_request_count_);
@@ -353,7 +353,7 @@
 
   // The following code should not fail any assert.
   PaintRecorder recorder;
-  cc::PaintCanvas* temp_canvas = recorder.beginRecording(100, 100);
+  cc::PaintCanvas* temp_canvas = recorder.beginRecording();
   temp_canvas->drawImage(image, 0, 0);
   sk_sp<PaintRecord> record = recorder.finishRecordingAsPicture();
   EXPECT_EQ(0, decode_request_count_);
diff --git a/third_party/blink/renderer/platform/graphics/generated_image.cc b/third_party/blink/renderer/platform/graphics/generated_image.cc
index 5708851..d18016a1 100644
--- a/third_party/blink/renderer/platform/graphics/generated_image.cc
+++ b/third_party/blink/renderer/platform/graphics/generated_image.cc
@@ -71,7 +71,7 @@
   auto paint_controller =
       std::make_unique<PaintController>(PaintController::kTransient);
   GraphicsContext context(*paint_controller);
-  context.BeginRecording(tile_rect);
+  context.BeginRecording();
   DrawTile(context, src_rect, draw_options);
   sk_sp<PaintRecord> record = context.EndRecording();
 
diff --git a/third_party/blink/renderer/platform/graphics/gpu/OWNERS b/third_party/blink/renderer/platform/graphics/gpu/OWNERS
index 7f3941f..b27f1b8e 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/OWNERS
+++ b/third_party/blink/renderer/platform/graphics/gpu/OWNERS
@@ -9,7 +9,6 @@
 
 # WebXR
 bajones@chromium.org
-klausw@chromium.org
 per-file xr_*=file://components/webxr/OWNERS
 
 # SharedGpuContext, ImageLayerBridge, etc.
diff --git a/third_party/blink/renderer/platform/graphics/graphics_context.cc b/third_party/blink/renderer/platform/graphics/graphics_context.cc
index 176ff801..f740e22 100644
--- a/third_party/blink/renderer/platform/graphics/graphics_context.cc
+++ b/third_party/blink/renderer/platform/graphics/graphics_context.cc
@@ -307,9 +307,9 @@
 #endif
 }
 
-void GraphicsContext::BeginRecording(const gfx::RectF& bounds) {
+void GraphicsContext::BeginRecording() {
   DCHECK(!canvas_);
-  canvas_ = paint_recorder_.beginRecording(gfx::RectFToSkRect(bounds));
+  canvas_ = paint_recorder_.beginRecording();
   if (printing_metafile_)
     canvas_->SetPrintingMetafile(printing_metafile_);
   if (paint_preview_tracker_)
diff --git a/third_party/blink/renderer/platform/graphics/graphics_context.h b/third_party/blink/renderer/platform/graphics/graphics_context.h
index ff431826..edcd167 100644
--- a/third_party/blink/renderer/platform/graphics/graphics_context.h
+++ b/third_party/blink/renderer/platform/graphics/graphics_context.h
@@ -471,8 +471,8 @@
 
   // Instead of being dispatched to the active canvas, draw commands following
   // beginRecording() are stored in a display list that can be replayed at a
-  // later time. Pass in the bounding rectangle for the content in the list.
-  void BeginRecording(const gfx::RectF&);
+  // later time.
+  void BeginRecording();
 
   // Returns a record with any recorded draw commands since the prerequisite
   // call to beginRecording().  The record is guaranteed to be non-null (but
diff --git a/third_party/blink/renderer/platform/graphics/graphics_context_test.cc b/third_party/blink/renderer/platform/graphics/graphics_context_test.cc
index e4afdc0c..939c698 100644
--- a/third_party/blink/renderer/platform/graphics/graphics_context_test.cc
+++ b/third_party/blink/renderer/platform/graphics/graphics_context_test.cc
@@ -83,15 +83,14 @@
   GraphicsContext context(*paint_controller);
 
   Color opaque = Color::FromRGBA(255, 0, 0, 255);
-  gfx::RectF bounds(0, 0, 100, 100);
 
-  context.BeginRecording(bounds);
+  context.BeginRecording();
   context.FillRect(gfx::RectF(0, 0, 50, 50), opaque, AutoDarkModeDisabled(),
                    SkBlendMode::kSrcOver);
   canvas.drawPicture(context.EndRecording());
   EXPECT_OPAQUE_PIXELS_ONLY_IN_RECT(bitmap, gfx::Rect(0, 0, 50, 50))
 
-  context.BeginRecording(bounds);
+  context.BeginRecording();
   context.FillRect(gfx::RectF(0, 0, 100, 100), opaque, AutoDarkModeDisabled(),
                    SkBlendMode::kSrcOver);
   // Make sure the opaque region was unaffected by the rect drawn during
@@ -110,11 +109,10 @@
 
   Color opaque = Color::FromRGBA(255, 0, 0, 255);
   Color transparent = Color::kTransparent;
-  gfx::RectF bounds(0, 0, 100, 100);
 
   auto paint_controller = std::make_unique<PaintController>();
   GraphicsContext context(*paint_controller);
-  context.BeginRecording(bounds);
+  context.BeginRecording();
 
   context.SetShouldAntialias(false);
   context.SetMiterLimit(1);
@@ -134,7 +132,7 @@
   canvas.drawPicture(context.EndRecording());
   EXPECT_OPAQUE_PIXELS_ONLY_IN_RECT(bitmap, gfx::Rect(10, 10, 40, 40));
 
-  context.BeginRecording(bounds);
+  context.BeginRecording();
   // Clip to the left edge of the opaque area.
   context.Clip(gfx::Rect(10, 10, 10, 40));
 
@@ -166,7 +164,7 @@
     GraphicsContext context(*paint_controller_);
     if (is_dark_mode_on)
       context.UpdateDarkModeSettingsForTest(settings);
-    context.BeginRecording(gfx::RectF(0, 0, 4, 1));
+    context.BeginRecording();
     context.FillRect(gfx::RectF(0, 0, 1, 1), Color::kBlack,
                      AutoDarkMode(DarkModeFilter::ElementRole::kBackground,
                                   is_dark_mode_on));
diff --git a/third_party/blink/renderer/platform/graphics/image.cc b/third_party/blink/renderer/platform/graphics/image.cc
index c64cd5d..5b3974f 100644
--- a/third_party/blink/renderer/platform/graphics/image.cc
+++ b/third_party/blink/renderer/platform/graphics/image.cc
@@ -218,7 +218,7 @@
                      subset_rect.height() + spacing.height());
 
   PaintRecorder recorder;
-  cc::PaintCanvas* canvas = recorder.beginRecording(tile_rect);
+  cc::PaintCanvas* canvas = recorder.beginRecording();
   cc::PaintFlags flags;
   flags.setAntiAlias(should_antialias);
   canvas->drawImageRect(
diff --git a/third_party/blink/renderer/platform/graphics/memory_managed_paint_canvas.cc b/third_party/blink/renderer/platform/graphics/memory_managed_paint_canvas.cc
index 73e2490..855bf14c 100644
--- a/third_party/blink/renderer/platform/graphics/memory_managed_paint_canvas.cc
+++ b/third_party/blink/renderer/platform/graphics/memory_managed_paint_canvas.cc
@@ -7,9 +7,9 @@
 namespace blink {
 
 MemoryManagedPaintCanvas::MemoryManagedPaintCanvas(cc::DisplayItemList* list,
-                                                   const SkRect& bounds,
+                                                   const gfx::Size& size,
                                                    Client* client)
-    : RecordPaintCanvas(list, bounds), client_(client) {
+    : InspectableRecordPaintCanvas(list, size), client_(client) {
   DCHECK(client);
 }
 
@@ -21,7 +21,7 @@
                                          const SkSamplingOptions& sampling,
                                          const cc::PaintFlags* flags) {
   DCHECK(!image.IsPaintWorklet());
-  RecordPaintCanvas::drawImage(image, left, top, sampling, flags);
+  InspectableRecordPaintCanvas::drawImage(image, left, top, sampling, flags);
   UpdateMemoryUsage(image);
 }
 
@@ -32,8 +32,8 @@
     const SkSamplingOptions& sampling,
     const cc::PaintFlags* flags,
     SkCanvas::SrcRectConstraint constraint) {
-  RecordPaintCanvas::drawImageRect(image, src, dst, sampling, flags,
-                                   constraint);
+  InspectableRecordPaintCanvas::drawImageRect(image, src, dst, sampling, flags,
+                                              constraint);
   UpdateMemoryUsage(image);
 }
 
diff --git a/third_party/blink/renderer/platform/graphics/memory_managed_paint_canvas.h b/third_party/blink/renderer/platform/graphics/memory_managed_paint_canvas.h
index 9da3fa2..2143e8ae 100644
--- a/third_party/blink/renderer/platform/graphics/memory_managed_paint_canvas.h
+++ b/third_party/blink/renderer/platform/graphics/memory_managed_paint_canvas.h
@@ -19,7 +19,7 @@
 // too much memory is used.
 
 class PLATFORM_EXPORT MemoryManagedPaintCanvas final
-    : public cc::RecordPaintCanvas {
+    : public cc::InspectableRecordPaintCanvas {
  public:
   // Base class for clients that receive callbacks from
   // MemoryManagedPaintCanvas.
@@ -29,7 +29,7 @@
   };
 
   MemoryManagedPaintCanvas(cc::DisplayItemList* list,
-                           const SkRect& bounds,
+                           const gfx::Size& size,
                            Client* client);
   explicit MemoryManagedPaintCanvas(const cc::RecordPaintCanvas&) = delete;
   ~MemoryManagedPaintCanvas() override;
diff --git a/third_party/blink/renderer/platform/graphics/memory_managed_paint_recorder.cc b/third_party/blink/renderer/platform/graphics/memory_managed_paint_recorder.cc
index 0e8608e..2f96feb1 100644
--- a/third_party/blink/renderer/platform/graphics/memory_managed_paint_recorder.cc
+++ b/third_party/blink/renderer/platform/graphics/memory_managed_paint_recorder.cc
@@ -33,10 +33,19 @@
   DCHECK(client);
 }
 
-std::unique_ptr<cc::RecordPaintCanvas> MemoryManagedPaintRecorder::CreateCanvas(
-    cc::DisplayItemList* list,
-    const SkRect& bounds) {
-  return std::make_unique<MemoryManagedPaintCanvas>(list, bounds, client_);
+cc::PaintCanvas* MemoryManagedPaintRecorder::beginRecording(
+    const gfx::Size& size) {
+  DCHECK(!canvas_);
+  canvas_ = std::make_unique<MemoryManagedPaintCanvas>(display_item_list_.get(),
+                                                       size, client_);
+  cc::PaintRecorderBase::beginRecording();
+  return canvas_.get();
+}
+
+sk_sp<cc::PaintRecord> MemoryManagedPaintRecorder::finishRecordingAsPicture() {
+  auto record = cc::PaintRecorderBase::finishRecordingAsPicture();
+  canvas_.reset();
+  return record;
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/memory_managed_paint_recorder.h b/third_party/blink/renderer/platform/graphics/memory_managed_paint_recorder.h
index ebbd83d82..2911126b 100644
--- a/third_party/blink/renderer/platform/graphics/memory_managed_paint_recorder.h
+++ b/third_party/blink/renderer/platform/graphics/memory_managed_paint_recorder.h
@@ -32,14 +32,13 @@
 
 namespace blink {
 
-class PLATFORM_EXPORT MemoryManagedPaintRecorder : public cc::PaintRecorder {
+class PLATFORM_EXPORT MemoryManagedPaintRecorder
+    : public cc::PaintRecorderBase {
  public:
-  MemoryManagedPaintRecorder(MemoryManagedPaintCanvas::Client* client);
+  explicit MemoryManagedPaintRecorder(MemoryManagedPaintCanvas::Client* client);
 
- protected:
-  std::unique_ptr<cc::RecordPaintCanvas> CreateCanvas(
-      cc::DisplayItemList* list,
-      const SkRect& bounds) override;
+  cc::PaintCanvas* beginRecording(const gfx::Size& size);
+  sk_sp<cc::PaintRecord> finishRecordingAsPicture();
 
  private:
   MemoryManagedPaintCanvas::Client* client_;
diff --git a/third_party/blink/renderer/platform/graphics/paint/drawing_display_item_test.cc b/third_party/blink/renderer/platform/graphics/paint/drawing_display_item_test.cc
index 12e042c..3529b47 100644
--- a/third_party/blink/renderer/platform/graphics/paint/drawing_display_item_test.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/drawing_display_item_test.cc
@@ -33,8 +33,7 @@
 
 static sk_sp<PaintRecord> CreateRectRecord(const gfx::RectF& record_bounds) {
   PaintRecorder recorder;
-  cc::PaintCanvas* canvas =
-      recorder.beginRecording(record_bounds.width(), record_bounds.height());
+  cc::PaintCanvas* canvas = recorder.beginRecording();
   canvas->drawRect(gfx::RectFToSkRect(record_bounds), cc::PaintFlags());
   return recorder.finishRecordingAsPicture();
 }
@@ -44,8 +43,7 @@
     float dx,
     float dy) {
   PaintRecorder recorder;
-  cc::PaintCanvas* canvas =
-      recorder.beginRecording(record_bounds.width(), record_bounds.height());
+  cc::PaintCanvas* canvas = recorder.beginRecording();
   canvas->save();
   canvas->translate(dx, dy);
   canvas->drawRect(gfx::RectFToSkRect(record_bounds), cc::PaintFlags());
@@ -160,8 +158,7 @@
   gfx::RectF record_bounds(5, 6, 10, 10);
 
   PaintRecorder recorder;
-  cc::PaintCanvas* canvas =
-      recorder.beginRecording(record_bounds.width(), record_bounds.height());
+  cc::PaintCanvas* canvas = recorder.beginRecording();
   canvas->drawOval(gfx::RectFToSkRect(record_bounds), cc::PaintFlags());
 
   DrawingDisplayItem item(client_->Id(), DisplayItem::Type::kDocumentBackground,
@@ -200,9 +197,8 @@
   flags.setColor(SK_ColorWHITE);
   for (float r = kRadiusStep; r < kSize / 2; r += kRadiusStep) {
     PaintRecorder recorder;
-    recorder.beginRecording(kSize, kSize)
-        ->drawRRect(SkRRect::MakeRectXY(SkRect::MakeWH(kSize, kSize), r, r),
-                    flags);
+    recorder.beginRecording()->drawRRect(
+        SkRRect::MakeRectXY(SkRect::MakeWH(kSize, kSize), r, r), flags);
     DrawingDisplayItem item(
         client_->Id(), DisplayItem::Type::kDocumentBackground,
         gfx::Rect(0, 0, kSize, kSize), recorder.finishRecordingAsPicture(),
@@ -226,7 +222,7 @@
     SkRRect rrect;
     SkVector radii[4] = {{r, r}, {r, r * 2}, {r * 4, r * 3}, {r, r * 5}};
     rrect.setRectRadii(SkRect::MakeWH(kSize, kSize), radii);
-    recorder.beginRecording(kSize, kSize)->drawRRect(rrect, flags);
+    recorder.beginRecording()->drawRRect(rrect, flags);
     DrawingDisplayItem item(
         client_->Id(), DisplayItem::Type::kDocumentBackground,
         gfx::Rect(0, 0, kSize, kSize), recorder.finishRecordingAsPicture(),
@@ -243,9 +239,9 @@
                    .set_id(1)
                    .TakePaintImage();
   PaintRecorder recorder;
-  recorder.beginRecording(100, 100)->drawImageRect(
-      image, SkRect::MakeEmpty(), SkRect::MakeEmpty(),
-      SkCanvas::kFast_SrcRectConstraint);
+  recorder.beginRecording()->drawImageRect(image, SkRect::MakeEmpty(),
+                                           SkRect::MakeEmpty(),
+                                           SkCanvas::kFast_SrcRectConstraint);
   DrawingDisplayItem item(
       client_->Id(), DisplayItem::kBoxDecorationBackground, gfx::Rect(10, 20),
       recorder.finishRecordingAsPicture(), RasterEffectOutset::kNone);
diff --git a/third_party/blink/renderer/platform/graphics/paint/drawing_recorder.cc b/third_party/blink/renderer/platform/graphics/paint/drawing_recorder.cc
index caff443..f432c57 100644
--- a/third_party/blink/renderer/platform/graphics/paint/drawing_recorder.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/drawing_recorder.cc
@@ -28,7 +28,7 @@
   DCHECK(DisplayItem::IsDrawingType(display_item_type));
 
   context.SetInDrawingRecorder(true);
-  context.BeginRecording(gfx::RectF());
+  context.BeginRecording();
 
   if (context.NeedsDOMNodeId()) {
     DOMNodeId dom_node_id = display_item_client.OwnerNodeId();
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_chunker_test.cc b/third_party/blink/renderer/platform/graphics/paint/paint_chunker_test.cc
index a92a42f..712980a 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_chunker_test.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_chunker_test.cc
@@ -51,7 +51,7 @@
 
 sk_sp<const PaintRecord> OpaquePaintRecord(const gfx::Rect& visual_rect) {
   PaintRecorder recorder;
-  auto* canvas = recorder.beginRecording(gfx::RectToSkRect(visual_rect));
+  auto* canvas = recorder.beginRecording();
   cc::PaintFlags flags;
   flags.setColor(SK_ColorBLACK);
   canvas->drawRect(gfx::RectToSkRect(visual_rect), flags);
diff --git a/third_party/blink/renderer/platform/graphics/paint/raster_invalidation_tracking.cc b/third_party/blink/renderer/platform/graphics/paint/raster_invalidation_tracking.cc
index 58a2b8f..d685d2e4 100644
--- a/third_party/blink/renderer/platform/graphics/paint/raster_invalidation_tracking.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/raster_invalidation_tracking.cc
@@ -241,7 +241,7 @@
     return;
 
   PaintRecorder recorder;
-  recorder.beginRecording(gfx::RectToSkRect(rect));
+  recorder.beginRecording();
   auto* canvas = recorder.getRecordingCanvas();
   if (under_invalidation_record_)
     canvas->drawPicture(std::move(under_invalidation_record_));
diff --git a/third_party/blink/renderer/platform/graphics/paint/scrollbar_display_item.cc b/third_party/blink/renderer/platform/graphics/paint/scrollbar_display_item.cc
index a2a5a2e..6c1946a 100644
--- a/third_party/blink/renderer/platform/graphics/paint/scrollbar_display_item.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/scrollbar_display_item.cc
@@ -50,7 +50,7 @@
 
   PaintRecorder recorder;
   const gfx::Rect& rect = VisualRect();
-  recorder.beginRecording(gfx::RectToSkRect(rect));
+  recorder.beginRecording();
   auto* canvas = recorder.getRecordingCanvas();
   scrollbar->PaintPart(canvas, cc::ScrollbarPart::TRACK_BUTTONS_TICKMARKS,
                        rect);
diff --git a/third_party/blink/renderer/platform/graphics/placeholder_image.cc b/third_party/blink/renderer/platform/graphics/placeholder_image.cc
index 409a1c8..87ac0657 100644
--- a/third_party/blink/renderer/platform/graphics/placeholder_image.cc
+++ b/third_party/blink/renderer/platform/graphics/placeholder_image.cc
@@ -250,9 +250,8 @@
   }
 
   PaintRecorder paint_recorder;
-  Draw(paint_recorder.beginRecording(gfx::RectToSkRect(dest_rect)),
-       cc::PaintFlags(), gfx::RectF(dest_rect), gfx::RectF(dest_rect),
-       ImageDrawOptions());
+  Draw(paint_recorder.beginRecording(), cc::PaintFlags(), gfx::RectF(dest_rect),
+       gfx::RectF(dest_rect), ImageDrawOptions());
 
   paint_record_for_current_frame_ = paint_recorder.finishRecordingAsPicture();
   paint_record_content_id_ = PaintImage::GetNextContentId();
diff --git a/third_party/blink/renderer/platform/testing/test_paint_artifact.cc b/third_party/blink/renderer/platform/testing/test_paint_artifact.cc
index 5c95a1f6..a9bf0f4 100644
--- a/third_party/blink/renderer/platform/testing/test_paint_artifact.cc
+++ b/third_party/blink/renderer/platform/testing/test_paint_artifact.cc
@@ -90,7 +90,7 @@
                                                   const gfx::Rect& bounds,
                                                   Color color) {
   PaintRecorder recorder;
-  cc::PaintCanvas* canvas = recorder.beginRecording(gfx::RectToSkRect(bounds));
+  cc::PaintCanvas* canvas = recorder.beginRecording();
   if (!bounds.IsEmpty()) {
     cc::PaintFlags flags;
     flags.setColor(color.Rgb());
diff --git a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
index 2df7449..51f0ab8 100755
--- a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
+++ b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
@@ -319,6 +319,8 @@
             'base::TickClock',
 
             # cc painting types.
+            'cc::InspectablePaintRecorder',
+            'cc::InspectableRecordPaintCanvas',
             'cc::PaintCanvas',
             'cc::PaintFlags',
             'cc::PaintImage',
diff --git a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
index e02f8c1a..4495916 100644
--- a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
+++ b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
@@ -12,7 +12,6 @@
 
 # ====== New tests from wpt-importer added here ======
 crbug.com/626703 external/wpt/html/user-activation/consumption-crossorigin.sub.tentative.html [ Failure Timeout ]
-crbug.com/626703 external/wpt/web-share/disabled-by-permissions-policy-cross-origin.https.sub.html [ Crash ]
 crbug.com/626703 external/wpt/dom/events/scrolling/scrollend-event-handler-content-attributes.html [ Timeout ]
 crbug.com/626703 external/wpt/screen-orientation/active-lock.html [ Timeout ]
 crbug.com/626703 external/wpt/css/selectors/media/media-loading-state.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 9f545fe..6db07afb6 100644
--- a/third_party/blink/web_tests/FlagExpectations/disable-site-isolation-trials
+++ b/third_party/blink/web_tests/FlagExpectations/disable-site-isolation-trials
@@ -82,7 +82,6 @@
 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/web-share/disabled-by-permissions-policy-cross-origin.https.sub.html [ Crash ]
 crbug.com/626703 external/wpt/dom/events/scrolling/scrollend-event-handler-content-attributes.html [ Timeout ]
 crbug.com/626703 external/wpt/screen-orientation/active-lock.html [ Timeout ]
 crbug.com/626703 external/wpt/css/selectors/media/media-loading-state.html [ Timeout ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 165bc37..f39f2156 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -4606,7 +4606,6 @@
 # Sheriff 2020-05-18
 crbug.com/1084256 [ Linux ] http/tests/misc/insert-iframe-into-xml-document-before-xsl-transform.html [ Failure Pass ]
 crbug.com/1084256 [ Mac ] http/tests/misc/insert-iframe-into-xml-document-before-xsl-transform.html [ Failure Pass ]
-crbug.com/1084276 [ Mac ] http/tests/security/offscreencanvas-placeholder-read-blocked-no-crossorigin.html [ Crash Failure Pass ]
 
 # Assorted virtual/threaded/.../wpt/scroll-animations tests
 crbug.com/1279648 virtual/threaded/external/wpt/scroll-animations/css/view-timeline-inset-animation.html [ Failure Pass Timeout ]
@@ -4631,9 +4630,6 @@
 # Sheriff 2020-06-09
 crbug.com/1093003 [ Mac ] external/wpt/html/rendering/replaced-elements/embedded-content/cross-domain-iframe.sub.html [ Failure Pass ]
 
-# Sheriff 2020-06-11
-crbug.com/1093026 [ Linux ] http/tests/security/offscreencanvas-placeholder-read-blocked-no-crossorigin.html [ Failure Pass ]
-
 # Ecosystem-Infra Rotation 2020-06-15
 crbug.com/924472 external/wpt/css/css-transforms/transform-box/cssbox-content-box.html [ Failure ]
 crbug.com/924472 external/wpt/css/css-transforms/transform-box/cssbox-border-box.html [ Failure ]
@@ -6920,7 +6916,7 @@
 crbug.com/1385413 media/controls/text-track-menu-pointer-selection.html [ Failure Pass Skip Timeout ]
 
 # Sheriff 2022-11-17
-crbug.com/1385642 [ Linux ] external/wpt/css/css-values/calc-in-media-queries-with-mixed-units.html [ Skip Failure Pass Timeout ]
+crbug.com/1385642 [ Linux ] external/wpt/css/css-values/calc-in-media-queries-with-mixed-units.html [ Failure Pass Skip Timeout ]
 crbug.com/1385681 external/wpt/web-share/disabled-by-permissions-policy-cross-origin.https.sub.html [ Failure Pass ]
 crbug.com/1372166 fast/spatial-navigation/snav-div-scrollable-but-without-focusable-content.html [ Failure Pass ]
 crbug.com/1385497 http/tests/inspector-protocol/tracing/page-load-metrics.js [ Failure Pass ]
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index 196c2dc6..9e71efc 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -1266,6 +1266,13 @@
              "--enable-blink-features=DocumentPictureInPictureAPI"]
   },
 
+  {
+    "prefix": "idna-2008",
+    "platforms": ["Linux"],
+    "bases": [ "external/wpt/url", "fast/url"],
+    "args": ["--enable-features=UseIDNA2008NonTransitional"]
+  },
+
   "Tests in the virtual/origin-agent-cluster-default suite. These tests",
   "require isolation behaviour as in the actual browser and are therefore",
   "incompatible with auto-wpt-origin-isolation. Thus they are only run in",
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 4e41510..b9fcccc 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
@@ -257526,16 +257526,6 @@
    }
   },
   "support": {
-   ".cache": {
-    "gitignore2.json": [
-     "48531bf5259e9c30432667063666c4f8751d0452",
-     []
-    ],
-    "mtime.json": [
-     "711bf7895fb57e893c5a72563560d9a102da8e4d",
-     []
-    ]
-   },
    ".gitignore": [
     "d93e645d547894b50149d3726de2654957b6e06f",
     []
@@ -361500,7 +361490,7 @@
      []
     ],
     "getdisplaymedia.https-expected.txt": [
-     "5fc0b6806845f8e6dba22a007c617f3cefbbd958",
+     "651b3774aadaf9c752ae93fd7a2a5eecb1f77391",
      []
     ],
     "getdisplaymedia.https.html.ini": [
@@ -373114,7 +373104,7 @@
      []
     ],
     "video-encoder-utils.js": [
-     "2c97d2346f6c2b9fb4acae8ed4bbac79f233f5c9",
+     "fad6b909505feddbf32d9bb26392de5649e8e9c5",
      []
     ],
     "videoDecoder-codec-specific.https.any.js.ini": [
@@ -429558,7 +429548,7 @@
       ]
      ],
      "dir-pseudo-on-input-element.html": [
-      "3bb49790e997a6a777bd84904e3dfd0633559fa2",
+      "b61a829b30a8cac765953be2ffe190648fd3c802",
       [
        null,
        {}
@@ -509409,7 +509399,7 @@
       ]
      ],
      "delegate-fullscreen-request-subframe-cross-origin.https.sub.tentative.html": [
-      "d61f0dd8c2fd7039ad2259c47668204bce6de596",
+      "517860c8963777b409da7614fe8ab10f54fa8679",
       [
        null,
        {
@@ -570703,7 +570693,7 @@
      ]
     ],
     "getdisplaymedia.https.html": [
-     "989835af9bb81f4f3219435b843134ad6bb6d323",
+     "d2229e119c184009ebcb254665bf5698e5248ca4",
      [
       null,
       {
@@ -571187,7 +571177,7 @@
       ]
      ],
      "animation-timeline-named-scroll-progress-timeline.tentative.html": [
-      "1f891caca3c2be871f16b112563115fbfbc2916e",
+      "6b7bab9b6501af86be088d3a6f4a6e1cd4127dd9",
       [
        null,
        {}
@@ -601083,7 +601073,7 @@
      ]
     ],
     "audio-decoder.crossOriginIsolated.https.any.js": [
-     "a13fb87b928ebadbf8d454197e07dc00a8cffbeb",
+     "17009e0118c1e92401cf6952e35100bc33692df0",
      [
       "webcodecs/audio-decoder.crossOriginIsolated.https.any.html",
       {
@@ -601200,7 +601190,7 @@
      ]
     ],
     "audioDecoder-codec-specific.https.any.js": [
-     "b53965c529d475c6fdc8df8061936f14391013ce",
+     "92513be087011445b306a802f288c7aae8cd7a35",
      [
       "webcodecs/audioDecoder-codec-specific.https.any.html?adts_aac",
       {
@@ -603091,7 +603081,7 @@
      ]
     ],
     "video-decoder.crossOriginIsolated.https.any.js": [
-     "14777c55991a1bca4699f7598966f0f31474bd76",
+     "3232844a31b240eb0f46e1df7a71f3efdd76e1ce",
      [
       "webcodecs/video-decoder.crossOriginIsolated.https.any.html",
       {
@@ -603280,7 +603270,7 @@
      ]
     ],
     "videoDecoder-codec-specific.https.any.js": [
-     "d74fff886a954d1f1e6ec624eca05f8e2e4ab921",
+     "20553017f95f5fc49cff3805eb92f07e1792ff87",
      [
       "webcodecs/videoDecoder-codec-specific.https.any.html?av1",
       {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-color/nested-color-mix-with-currentcolor-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-color/nested-color-mix-with-currentcolor-expected.txt
new file mode 100644
index 0000000..13c385b0
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-color/nested-color-mix-with-currentcolor-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Nested color-mix function with inner currentColor should inherit unresolved assert_equals: expected "lch(0 0 0)" but got "lch(0 0 0 / 0.75)"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-color/nested-color-mix-with-currentcolor.html b/third_party/blink/web_tests/external/wpt/css/css-color/nested-color-mix-with-currentcolor.html
new file mode 100644
index 0000000..a4dd687
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-color/nested-color-mix-with-currentcolor.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<title>CSS Color: Nested color-mix() with currentColor</title>
+<link rel="help" href="https://www.w3.org/TR/css-color-5/#color-mix">
+<link rel="help" href="https://www.w3.org/TR/css-color-5/#resolving-color-values">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  #parent {
+    color: red;
+    background-color: color-mix(in lch, color-mix(in lch, black, currentColor), black);
+  }
+  #child {
+    color: black;
+    background-color: inherit;
+  }
+</style>
+<div id="parent">
+  <div id="child"></div>
+</div>
+<script>
+  test(() => {
+    assert_equals(getComputedStyle(child).backgroundColor, "lch(0 0 0)");
+  }, "Nested color-mix function with inner currentColor should inherit unresolved");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-pseudo/dir-pseudo-on-input-element.html b/third_party/blink/web_tests/external/wpt/css/css-pseudo/dir-pseudo-on-input-element.html
index 3bb49790..b61a829b 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-pseudo/dir-pseudo-on-input-element.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-pseudo/dir-pseudo-on-input-element.html
@@ -60,8 +60,28 @@
     container.setAttribute('dir', 'rtl');
     container.appendChild(input);
 
+    // Insert the element into the document so that we can also check for
+    // 'direction' in computed style.
+    document.body.appendChild(container);
+
     assert_true(input.matches(':dir(ltr)'));
     assert_false(input.matches(':dir(rtl)'));
+    // Per https://html.spec.whatwg.org/multipage/rendering.html#bidi-rendering:
+    assert_equals(getComputedStyle(input).direction, 'ltr');
+
+    // Changing to a different type causes the special type=tel rule to no longer apply.
+    input.type = 'text';
+    assert_false(input.matches(':dir(ltr)'));
+    assert_true(input.matches(':dir(rtl)'));
+    assert_equals(getComputedStyle(input).direction, 'rtl');
+
+    // And restoring type=tel brings back that behavior.
+    input.type = 'tel';
+    assert_true(input.matches(':dir(ltr)'));
+    assert_false(input.matches(':dir(rtl)'));
+    assert_equals(getComputedStyle(input).direction, 'ltr');
+
+    document.body.removeChild(container);
 }, 'input element whose type attribute is in the telephone state in a RTL block');
 
 for (let type of ['text', 'search', 'url', 'email']) {
diff --git a/third_party/blink/web_tests/external/wpt/html/capability-delegation/delegate-fullscreen-request-subframe-cross-origin.https.sub.tentative.html b/third_party/blink/web_tests/external/wpt/html/capability-delegation/delegate-fullscreen-request-subframe-cross-origin.https.sub.tentative.html
index d61f0dd8..517860c 100644
--- a/third_party/blink/web_tests/external/wpt/html/capability-delegation/delegate-fullscreen-request-subframe-cross-origin.https.sub.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/html/capability-delegation/delegate-fullscreen-request-subframe-cross-origin.https.sub.tentative.html
@@ -4,6 +4,7 @@
      https://github.com/WICG/capability-delegation/issues/10
 -->
 <title>Capability Delegation of Fullscreen Requests: Subframe Cross-Origin</title>
+<script src="/common/get-host-info.sub.js"></script>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/testdriver.js"></script>
@@ -20,14 +21,15 @@
   See wpt/html/user-activation/propagation*.html for frame tree user activation visibility tests.
 </div>
 
-<iframe allow="fullscreen" width="300px" height="50px"
-        src="https://{{hosts[alt][www]}}:{{ports[https][0]}}/html/capability-delegation/resources/delegate-fullscreen-request-recipient.html">
+<iframe allow="fullscreen" width="300px" height="50px">
 </iframe>
 
 <script>
+  const origin = get_host_info().HTTPS_REMOTE_ORIGIN;
+  document.querySelector("iframe").src = origin + "/html/capability-delegation/resources/delegate-fullscreen-request-recipient.html";
+
   function testCrossOriginSubframeFullscreenDelegation(capability, activate, expectation) {
       const message = {"type": "make-fullscreen-request"};
-      const origin = "https://{{hosts[alt][www]}}:{{ports[https][0]}}";
       const expectationType = expectation ? "succeeds" : "fails";
       const delegationType = capability ? "with delegation" : "without delegation";
       const activationType = activate ? "with user activation" : "with no user activation";
diff --git a/third_party/blink/web_tests/external/wpt/layout-instability/resources/util.js b/third_party/blink/web_tests/external/wpt/layout-instability/resources/util.js
index d6f54bc..1b3c82d 100644
--- a/third_party/blink/web_tests/external/wpt/layout-instability/resources/util.js
+++ b/third_party/blink/web_tests/external/wpt/layout-instability/resources/util.js
@@ -34,6 +34,9 @@
   return impactFraction * distanceFraction;
 };
 
+// An list to record all the entries with startTime and score.
+let watcher_entry_record = [];
+
 // An object that tracks the document cumulative layout shift score.
 // Usage:
 //
@@ -61,6 +64,7 @@
     list.getEntries().forEach(entry => {
       this.lastEntry = entry;
       this.score += entry.value;
+      watcher_entry_record.push({startTime: entry.startTime, score: entry.value});
       if (!entry.hadRecentInput)
         this.scoreWithInputExclusion += entry.value;
       this.resolve();
@@ -76,6 +80,10 @@
     check_sources(expectation.sources, this.lastEntry.sources);
 };
 
+ScoreWatcher.prototype.get_entry_record = function() {
+  return watcher_entry_record;
+};
+
 check_sources = (expect_sources, actual_sources) => {
   assert_equals(expect_sources.length, actual_sources.length);
   let rect_match = (e, a) =>
diff --git a/third_party/blink/web_tests/external/wpt/layout-instability/simple-block-movement.html b/third_party/blink/web_tests/external/wpt/layout-instability/simple-block-movement.html
index 837b438e..10261f7 100644
--- a/third_party/blink/web_tests/external/wpt/layout-instability/simple-block-movement.html
+++ b/third_party/blink/web_tests/external/wpt/layout-instability/simple-block-movement.html
@@ -11,9 +11,8 @@
 <div id="shifter"></div>
 <script>
 
+const watcher = new ScoreWatcher;
 promise_test(async () => {
-  const watcher = new ScoreWatcher;
-
   // Wait for the initial render to complete.
   await waitForAnimationFrames(2);
 
diff --git a/third_party/blink/web_tests/external/wpt/webcodecs/audio-decoder.crossOriginIsolated.https.any.js b/third_party/blink/web_tests/external/wpt/webcodecs/audio-decoder.crossOriginIsolated.https.any.js
index a13fb87..17009e0 100644
--- a/third_party/blink/web_tests/external/wpt/webcodecs/audio-decoder.crossOriginIsolated.https.any.js
+++ b/third_party/blink/web_tests/external/wpt/webcodecs/audio-decoder.crossOriginIsolated.https.any.js
@@ -20,6 +20,7 @@
   const data = testData;
 
   // Don't run test if the codec is not supported.
+  assert_equals("function", typeof AudioDecoder.isConfigSupported);
   let supported = false;
   return AudioDecoder
       .isConfigSupported({
diff --git a/third_party/blink/web_tests/external/wpt/webcodecs/audioDecoder-codec-specific.https.any.js b/third_party/blink/web_tests/external/wpt/webcodecs/audioDecoder-codec-specific.https.any.js
index b53965c5..92513be 100644
--- a/third_party/blink/web_tests/external/wpt/webcodecs/audioDecoder-codec-specific.https.any.js
+++ b/third_party/blink/web_tests/external/wpt/webcodecs/audioDecoder-codec-specific.https.any.js
@@ -155,6 +155,7 @@
   }[location.search];
 
   // Don't run any tests if the codec is not supported.
+  assert_equals("function", typeof AudioDecoder.isConfigSupported);
   let supported = false;
   try {
     const support = await AudioDecoder.isConfigSupported({
diff --git a/third_party/blink/web_tests/external/wpt/webcodecs/video-decoder.crossOriginIsolated.https.any.js b/third_party/blink/web_tests/external/wpt/webcodecs/video-decoder.crossOriginIsolated.https.any.js
index 14777c55..3232844 100644
--- a/third_party/blink/web_tests/external/wpt/webcodecs/video-decoder.crossOriginIsolated.https.any.js
+++ b/third_party/blink/web_tests/external/wpt/webcodecs/video-decoder.crossOriginIsolated.https.any.js
@@ -22,6 +22,7 @@
   const data = testData;
 
   // Don't run test if the codec is not supported.
+  assert_equals("function", typeof VideoDecoder.isConfigSupported);
   let supported = false;
   return VideoDecoder.isConfigSupported({codec: data.config.codec})
       .catch(_ => {
diff --git a/third_party/blink/web_tests/external/wpt/webcodecs/video-encoder-utils.js b/third_party/blink/web_tests/external/wpt/webcodecs/video-encoder-utils.js
index 2c97d23..fad6b90 100644
--- a/third_party/blink/web_tests/external/wpt/webcodecs/video-encoder-utils.js
+++ b/third_party/blink/web_tests/external/wpt/webcodecs/video-encoder-utils.js
@@ -1,4 +1,5 @@
 async function checkEncoderSupport(test, config) {
+  assert_equals("function", typeof VideoEncoder.isConfigSupported);
   let supported = false;
   try {
     const support = await VideoEncoder.isConfigSupported(config);
diff --git a/third_party/blink/web_tests/external/wpt/webcodecs/videoDecoder-codec-specific.https.any.js b/third_party/blink/web_tests/external/wpt/webcodecs/videoDecoder-codec-specific.https.any.js
index d74fff8..20553017 100644
--- a/third_party/blink/web_tests/external/wpt/webcodecs/videoDecoder-codec-specific.https.any.js
+++ b/third_party/blink/web_tests/external/wpt/webcodecs/videoDecoder-codec-specific.https.any.js
@@ -146,6 +146,7 @@
   }[location.search];
 
   // Don't run any tests if the codec is not supported.
+  assert_equals("function", typeof VideoDecoder.isConfigSupported);
   let supported = false;
   try {
     // TODO(sandersd): To properly support H.264 in AVC format, this should
diff --git a/third_party/blink/web_tests/http/tests/security/offscreencanvas-placeholder-read-blocked-no-crossorigin.html b/third_party/blink/web_tests/http/tests/security/offscreencanvas-placeholder-read-blocked-no-crossorigin.html
index 98e168c..3ce9151 100644
--- a/third_party/blink/web_tests/http/tests/security/offscreencanvas-placeholder-read-blocked-no-crossorigin.html
+++ b/third_party/blink/web_tests/http/tests/security/offscreencanvas-placeholder-read-blocked-no-crossorigin.html
@@ -2,42 +2,53 @@
 <script src="../../../resources/testharness.js"></script>
 <script src="../../../resources/testharnessreport.js"></script>
 <script>
-async_test(t => {
-  var image = new Image();
-  // Notice that we don't set the image.crossOrigin property.
-  image.src = "http://localhost:8000/security/resources/abe-allow-star.php";
-  image.onload = function() {
-    var canvas = document.createElement('canvas');
-    canvas.width = canvas.height = 10;
-    var offscreen = canvas.transferControlToOffscreen();
-    var ctx = offscreen.getContext('2d');
+  const canvasSize = 10;
+
+  assertImageEmpty = async function (dataUrl) {
+    const readbackCanvas = document.createElement("canvas");
+    const readbackCtx = readbackCanvas.getContext('2d');
+    const img = new Image();
+    img.src = dataUrl;
+    await img.decode();
+    readbackCtx.drawImage(img, 0, 0);
+    imageData = readbackCtx.getImageData(0, 0, canvasSize, canvasSize);
+    assert_array_equals(imageData.data,
+      new Array(canvasSize * canvasSize * 4).fill(0),
+      "Expected image to be empty.");
+  };
+
+  promise_test(async t => {
+    const image = new Image();
+    // Notice that we don't set the image.crossOrigin property.
+    image.src = "http://localhost:8000/security/resources/abe-allow-star.php";
+    await image.decode();
+    const canvas = document.createElement('canvas');
+    canvas.width = canvas.height = canvasSize;
+
+    const offscreen = canvas.transferControlToOffscreen();
+    const ctx = offscreen.getContext('2d');
     ctx.drawImage(image, 0, 0);
-    t.step(function() {
-      canvas.toDataURL(); // Succeeds by not throwing
-    });
-    // TODO(junov): Use the Promise returned by commit to schedule after the
-    // commit. (crbug.com/709484)
-    setTimeout(function() {
-      setTimeout(function() {
-        t.step(function() {
-          assert_throws_dom("SecurityError", function() {
-            canvas.toDataURL();
-          }, "Check toDataURL blocked.");
-        });
-        // TODO(junov): Use the Promise returned by commit to schedule after the
-        // commit. (crbug.com/709484)
-        setTimeout(function() {
-          setTimeout(function() {
-            t.step(function() {
-              assert_throws_dom("SecurityError", function() {
-                canvas.toDataURL();
-              });
-            });
-            t.done();
-          }, 0);
-        }, 0);
-      }, 0);
-    }, 0);
-  }
-}, "Verify that the placeholder <canvas> associated with an OffscreenCanvas tainted with cross-origin content cannot be read once commit has propagated.");
+
+    // Tests that the placerholder canvas is empty until the offscreen frame
+    // propagates and unreadable afterward.
+    readbackCanvas = async (reject) => {
+      (async () => {
+        // Should either throw if the frame propagated, or be an empty image.
+        const dataUrl = canvas.toDataURL();
+        await assertImageEmpty(dataUrl);
+      })()
+        // Try again, until the frame propagates and `toDataURL` throws.
+        .then(() => requestAnimationFrame(() => readbackCanvas(reject)))
+        .catch(reject);
+    };
+
+    // Succeeds when `toDataURL` throws. The test will fail with a timeout if it
+    // never happens.
+    await promise_rejects_dom(t, 'SecurityError',
+      // Create a promise whose `reject` handler will be called asynchronously,
+      // now or in any future animation frames.
+      new Promise((_, reject) => readbackCanvas(reject)));
+
+  }, "Verify that the placeholder <canvas> associated with an OffscreenCanvas tainted with cross-origin content cannot be read once commit has propagated.");
+
 </script>
diff --git a/third_party/blink/web_tests/platform/generic/virtual/offsetparent-old-behavior/external/wpt/shadow-dom/offsetParent-across-shadow-boundaries-expected.txt b/third_party/blink/web_tests/platform/generic/virtual/offsetparent-old-behavior/external/wpt/shadow-dom/offsetParent-across-shadow-boundaries-expected.txt
deleted file mode 100644
index 10c7e31..0000000
--- a/third_party/blink/web_tests/platform/generic/virtual/offsetparent-old-behavior/external/wpt/shadow-dom/offsetParent-across-shadow-boundaries-expected.txt
+++ /dev/null
@@ -1,19 +0,0 @@
-This is a testharness.js-based test.
-PASS offsetParent must return the offset parent in the same shadow tree of open mode
-PASS offsetParent must return the offset parent in the same shadow tree of closed mode
-PASS offsetParent must return the offset parent in the same shadow tree of open mode even when nested
-PASS offsetParent must return the offset parent in the same shadow tree of closed mode even when nested
-FAIL offsetParent must skip offset parents of an element when the context object is assigned to a slot in a shadow tree of open mode assert_equals: expected Element node <div id="container" style="position: relative"><div><div ... but got Element node <div style="position: relative; padding-left: 85px; paddi...
-PASS offsetParent must skip offset parents of an element when the context object is assigned to a slot in a shadow tree of closed mode
-FAIL offsetParent must skip multiple offset parents of an element when the context object is assigned to a slot in a shadow tree of open mode assert_equals: expected Element node <div id="container" style="position: relative"><div style... but got Element node <div style="position: absolute; top: 10px; left: 10px;"><...
-FAIL offsetParent must skip multiple offset parents of an element when the context object is assigned to a slot in a shadow tree of closed mode assert_equals: expected 30 but got 0
-FAIL offsetParent must skip offset parents of an element when the context object is assigned to a slot in nested shadow trees of open mode assert_equals: expected Element node <div id="container" style="position: relative"><section><... but got Element node <div style="position: absolute; top: 200px; margin-left: ...
-FAIL offsetParent must skip offset parents of an element when the context object is assigned to a slot in nested shadow trees of closed mode assert_equals: expected 150 but got 0
-FAIL offsetParent must find the first offset parent which is a shadow-including ancestor of the context object even some shadow tree of open mode did not have any offset parent assert_equals: expected Element node <div id="container" style="position: relative"><section><... but got Element node <div style="position: absolute; top: 23px; left: 24px;"><...
-FAIL offsetParent must find the first offset parent which is a shadow-including ancestor of the context object even some shadow tree of closed mode did not have any offset parent assert_equals: expected 24 but got 0
-PASS offsetParent must return null on a child element of a shadow host for the shadow tree in open mode which is not assigned to any slot
-PASS offsetParent must return null on a child element of a shadow host for the shadow tree in closed mode which is not assigned to any slot
-PASS offsetParent must return null on a child element of a shadow host for the shadow tree in open mode which is not in the flat tree
-PASS offsetParent must return null on a child element of a shadow host for the shadow tree in closed mode which is not in the flat tree
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/generic/virtual/offsetparent-old-behavior/external/wpt/shadow-dom/offsetTop-offsetLeft-across-shadow-boundaries-expected.txt b/third_party/blink/web_tests/platform/generic/virtual/offsetparent-old-behavior/external/wpt/shadow-dom/offsetTop-offsetLeft-across-shadow-boundaries-expected.txt
deleted file mode 100644
index caf6648..0000000
--- a/third_party/blink/web_tests/platform/generic/virtual/offsetparent-old-behavior/external/wpt/shadow-dom/offsetTop-offsetLeft-across-shadow-boundaries-expected.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-This is a testharness.js-based test.
-FAIL Verifies that HTMLElement.offsetTop accounts for shadow boundaries. assert_equals: expected 38 but got 20
-FAIL Verifies that HTMLElement.offsetLeft accounts for shadow boundaries. assert_equals: expected 8 but got 0
-FAIL Verifies that HTMLElement.offsetTop accounts for shadow boundaries when nested in multiple shadow roots. assert_equals: expected 88 but got 20
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/virtual/idna-2008/README.md b/third_party/blink/web_tests/virtual/idna-2008/README.md
new file mode 100644
index 0000000..957badf9
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/idna-2008/README.md
@@ -0,0 +1,50 @@
+This suite runs tests with --enable-features=UseIDNA2008NonTransitional.
+
+To check the results against the baseline, run the following commands:
+```
+
+# wpt/url:
+diff third_party/blink/web_tests/platform/linux/external/wpt/url/a-element-expected.txt \
+     third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/a-element-expected.txt
+
+diff third_party/blink/web_tests/platform/linux/external/wpt/url/a-element-origin-expected.txt \
+     third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/a-element-origin-expected.txt
+
+diff third_party/blink/web_tests/platform/linux/external/wpt/url/a-element-origin-xhtml-expected.txt \
+     third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/a-element-origin-xhtml-expected.txt
+
+diff third_party/blink/web_tests/platform/linux/external/wpt/url/a-element-xhtml-expected.txt \
+     third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/a-element-xhtml-expected.txt
+
+diff third_party/blink/web_tests/platform/wpt/url/toascii.window-expected.txt \
+     third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/toascii.window-expected.txt
+
+diff third_party/blink/web_tests/platform/linux/external/wpt/url/url-constructor.any-expected.txt \
+     third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-constructor.any-expected.txt
+
+diff third_party/blink/web_tests/platform/linux/external/wpt/url/url-constructor.any.worker-expected.txt \
+     third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-constructor.any.worker-expected.txt
+
+diff third_party/blink/web_tests/platform/linux/external/wpt/url/url-origin.any-expected.txt \
+     third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-origin.any-expected.txt
+
+diff third_party/blink/web_tests/platform/linux/external/wpt/url/url-origin.any.worker-expected.txt \
+     third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-origin.any.worker-expected.txt
+
+diff third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters-a-area.window-expected.txt \
+     third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-setters-a-area.window-expected.txt
+
+diff third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters.any-expected.txt \
+     third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-setters.any-expected.txt
+
+diff third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters.any.worker-expected.txt \
+     third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-setters.any.worker-expected.txt
+
+
+# fast/url:
+diff third_party/blink/web_tests/fast/url/idna2003-expected.txt \
+     third_party/blink/web_tests/virtual/idna-2008/fast/url/idna2003-expected.txt
+
+diff third_party/blink/web_tests/fast/url/idna2008-expected.txt \
+     third_party/blink/web_tests/virtual/idna-2008/fast/url/idna2008-expected.txt
+```
\ No newline at end of file
diff --git a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/a-element-expected.txt b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/a-element-expected.txt
new file mode 100644
index 0000000..88efd0c
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/a-element-expected.txt
@@ -0,0 +1,752 @@
+This is a testharness.js-based test.
+Found 736 tests; 419 PASS, 317 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS Loading data…
+PASS Parsing: <http://example	.
+org> against <http://example.org/foo/bar>
+PASS Parsing: <http://user:pass@foo:21/bar;par?b#c> against <http://example.org/foo/bar>
+PASS Parsing: <https://test:@test> against <about:blank>
+PASS Parsing: <https://:@test> against <about:blank>
+FAIL Parsing: <non-special://test:@test/x> against <about:blank> assert_equals: href expected "non-special://test@test/x" but got "non-special://test:@test/x"
+FAIL Parsing: <non-special://:@test/x> against <about:blank> assert_equals: href expected "non-special://test/x" but got "non-special://:@test/x"
+PASS Parsing: <http:foo.com> against <http://example.org/foo/bar>
+PASS Parsing: <	   :foo.com   
+> against <http://example.org/foo/bar>
+PASS Parsing: < foo.com  > against <http://example.org/foo/bar>
+PASS Parsing: <a:	 foo.com> against <http://example.org/foo/bar>
+PASS Parsing: <http://f:21/ b ? d # e > against <http://example.org/foo/bar>
+PASS Parsing: <lolscheme:x x#x x> against <about:blank>
+PASS Parsing: <http://f:/c> against <http://example.org/foo/bar>
+PASS Parsing: <http://f:0/c> against <http://example.org/foo/bar>
+PASS Parsing: <http://f:00000000000000/c> against <http://example.org/foo/bar>
+PASS Parsing: <http://f:00000000000000000000080/c> against <http://example.org/foo/bar>
+PASS Parsing: <http://f:b/c> against <http://example.org/foo/bar>
+FAIL Parsing: <http://f: /c> against <http://example.org/foo/bar> assert_equals: failure should set href to input expected "http://f: /c" but got "http://f:%20/c"
+PASS Parsing: <http://f:
+/c> against <http://example.org/foo/bar>
+PASS Parsing: <http://f:fifty-two/c> against <http://example.org/foo/bar>
+PASS Parsing: <http://f:999999/c> against <http://example.org/foo/bar>
+FAIL Parsing: <non-special://f:999999/c> against <http://example.org/foo/bar> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://f: 21 / b ? d # e > against <http://example.org/foo/bar> assert_equals: failure should set href to input expected "http://f: 21 / b ? d # e " but got "http://f:%2021%20/%20b%20?%20d%20#%20e"
+PASS Parsing: <> against <http://example.org/foo/bar>
+PASS Parsing: <  	> against <http://example.org/foo/bar>
+PASS Parsing: <:foo.com/> against <http://example.org/foo/bar>
+PASS Parsing: <:foo.com\> against <http://example.org/foo/bar>
+PASS Parsing: <:> against <http://example.org/foo/bar>
+PASS Parsing: <:a> against <http://example.org/foo/bar>
+PASS Parsing: <:/> against <http://example.org/foo/bar>
+PASS Parsing: <:\> against <http://example.org/foo/bar>
+PASS Parsing: <:#> against <http://example.org/foo/bar>
+PASS Parsing: <#> against <http://example.org/foo/bar>
+PASS Parsing: <#/> against <http://example.org/foo/bar>
+PASS Parsing: <#\> against <http://example.org/foo/bar>
+PASS Parsing: <#;?> against <http://example.org/foo/bar>
+PASS Parsing: <?> against <http://example.org/foo/bar>
+PASS Parsing: </> against <http://example.org/foo/bar>
+PASS Parsing: <:23> against <http://example.org/foo/bar>
+PASS Parsing: </:23> against <http://example.org/foo/bar>
+PASS Parsing: <\x> against <http://example.org/foo/bar>
+PASS Parsing: <\\x\hello> against <http://example.org/foo/bar>
+PASS Parsing: <::> against <http://example.org/foo/bar>
+PASS Parsing: <::23> against <http://example.org/foo/bar>
+FAIL Parsing: <foo://> against <http://example.org/foo/bar> assert_equals: pathname expected "" but got "//"
+PASS Parsing: <http://a:b@c:29/d> against <http://example.org/foo/bar>
+PASS Parsing: <http::@c:29> against <http://example.org/foo/bar>
+PASS Parsing: <http://&a:foo(b]c@d:2/> against <http://example.org/foo/bar>
+PASS Parsing: <http://::@c@d:2> against <http://example.org/foo/bar>
+PASS Parsing: <http://foo.com:b@d/> against <http://example.org/foo/bar>
+PASS Parsing: <http://foo.com/\@> against <http://example.org/foo/bar>
+PASS Parsing: <http:\\foo.com\> against <http://example.org/foo/bar>
+PASS Parsing: <http:\\a\b:c\d@foo.com\> against <http://example.org/foo/bar>
+PASS Parsing: <foo:/> against <http://example.org/foo/bar>
+PASS Parsing: <foo:/bar.com/> against <http://example.org/foo/bar>
+FAIL Parsing: <foo://///////> against <http://example.org/foo/bar> assert_equals: pathname expected "///////" but got "/////////"
+FAIL Parsing: <foo://///////bar.com/> against <http://example.org/foo/bar> assert_equals: pathname expected "///////bar.com/" but got "/////////bar.com/"
+FAIL Parsing: <foo:////://///> against <http://example.org/foo/bar> assert_equals: pathname expected "//://///" but got "////://///"
+PASS Parsing: <c:/foo> against <http://example.org/foo/bar>
+PASS Parsing: <//foo/bar> against <http://example.org/foo/bar>
+PASS Parsing: <http://foo/path;a??e#f#g> against <http://example.org/foo/bar>
+PASS Parsing: <http://foo/abcd?efgh?ijkl> against <http://example.org/foo/bar>
+PASS Parsing: <http://foo/abcd#foo?bar> against <http://example.org/foo/bar>
+PASS Parsing: <[61:24:74]:98> against <http://example.org/foo/bar>
+PASS Parsing: <http:[61:27]/:foo> against <http://example.org/foo/bar>
+FAIL Parsing: <http://[1::2]:3:4> against <http://example.org/foo/bar> assert_equals: failure should set href to input expected "http://[1::2]:3:4" but got "http://[1::2]:3:4/"
+FAIL Parsing: <http://2001::1> against <http://example.org/foo/bar> assert_equals: failure should set href to input expected "http://2001::1" but got "http://2001::1/"
+FAIL Parsing: <http://2001::1]> against <http://example.org/foo/bar> assert_equals: failure should set href to input expected "http://2001::1]" but got "http://2001::1]/"
+FAIL Parsing: <http://2001::1]:80> against <http://example.org/foo/bar> assert_equals: failure should set href to input expected "http://2001::1]:80" but got "http://2001::1]/"
+PASS Parsing: <http://[2001::1]> against <http://example.org/foo/bar>
+PASS Parsing: <http://[::127.0.0.1]> against <http://example.org/foo/bar>
+PASS Parsing: <http://[0:0:0:0:0:0:13.1.68.3]> against <http://example.org/foo/bar>
+PASS Parsing: <http://[2001::1]:80> against <http://example.org/foo/bar>
+PASS Parsing: <http:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <ftp:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <https:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <madeupscheme:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <file:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <file://example:1/> against <about:blank>
+PASS Parsing: <file://example:test/> against <about:blank>
+FAIL Parsing: <file://example%/> against <about:blank> assert_equals: failure should set href to input expected "file://example%/" but got "file://example%25/"
+PASS Parsing: <file://[example]/> against <about:blank>
+PASS Parsing: <ftps:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <gopher:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <ws:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <wss:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <data:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <javascript:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <mailto:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <http:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <ftp:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <https:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <madeupscheme:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <ftps:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <gopher:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <ws:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <wss:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <data:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <javascript:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <mailto:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: </a/b/c> against <http://example.org/foo/bar>
+PASS Parsing: </a/ /c> against <http://example.org/foo/bar>
+PASS Parsing: </a%2fc> against <http://example.org/foo/bar>
+PASS Parsing: </a/%2f/c> against <http://example.org/foo/bar>
+PASS Parsing: <#β> against <http://example.org/foo/bar>
+PASS Parsing: <data:text/html,test#test> against <http://example.org/foo/bar>
+PASS Parsing: <tel:1234567890> against <http://example.org/foo/bar>
+FAIL Parsing: <ssh://example.com/foo/bar.git> against <http://example.org/> assert_equals: host expected "example.com" but got ""
+FAIL Parsing: <file:c:\foo\bar.html> against <file:///tmp/mock/path> assert_equals: href expected "file:///c:/foo/bar.html" but got "file:///tmp/mock/c:/foo/bar.html"
+FAIL Parsing: <  File:c|////foo\bar.html> against <file:///tmp/mock/path> assert_equals: href expected "file:///c:////foo/bar.html" but got "file:///tmp/mock/c%7C////foo/bar.html"
+FAIL Parsing: <C|/foo/bar> against <file:///tmp/mock/path> assert_equals: href expected "file:///C:/foo/bar" but got "file:///tmp/mock/C%7C/foo/bar"
+FAIL Parsing: </C|\foo\bar> against <file:///tmp/mock/path> assert_equals: href expected "file:///C:/foo/bar" but got "file:///C%7C/foo/bar"
+FAIL Parsing: <//C|/foo/bar> against <file:///tmp/mock/path> assert_equals: href expected "file:///C:/foo/bar" but got "file://c%7C/foo/bar"
+PASS Parsing: <//server/file> against <file:///tmp/mock/path>
+PASS Parsing: <\\server\file> against <file:///tmp/mock/path>
+PASS Parsing: </\server/file> against <file:///tmp/mock/path>
+PASS Parsing: <file:///foo/bar.txt> against <file:///tmp/mock/path>
+PASS Parsing: <file:///home/me> against <file:///tmp/mock/path>
+PASS Parsing: <//> against <file:///tmp/mock/path>
+PASS Parsing: <///> against <file:///tmp/mock/path>
+PASS Parsing: <///test> against <file:///tmp/mock/path>
+PASS Parsing: <file://test> against <file:///tmp/mock/path>
+FAIL Parsing: <file://localhost> against <file:///tmp/mock/path> assert_equals: href expected "file:///" but got "file://localhost/"
+FAIL Parsing: <file://localhost/> against <file:///tmp/mock/path> assert_equals: href expected "file:///" but got "file://localhost/"
+FAIL Parsing: <file://localhost/test> against <file:///tmp/mock/path> assert_equals: href expected "file:///test" but got "file://localhost/test"
+PASS Parsing: <test> against <file:///tmp/mock/path>
+PASS Parsing: <file:test> against <file:///tmp/mock/path>
+PASS Parsing: <http://example.com/././foo> against <about:blank>
+PASS Parsing: <http://example.com/./.foo> against <about:blank>
+PASS Parsing: <http://example.com/foo/.> against <about:blank>
+PASS Parsing: <http://example.com/foo/./> against <about:blank>
+PASS Parsing: <http://example.com/foo/bar/..> against <about:blank>
+PASS Parsing: <http://example.com/foo/bar/../> against <about:blank>
+PASS Parsing: <http://example.com/foo/..bar> against <about:blank>
+PASS Parsing: <http://example.com/foo/bar/../ton> against <about:blank>
+PASS Parsing: <http://example.com/foo/bar/../ton/../../a> against <about:blank>
+PASS Parsing: <http://example.com/foo/../../..> against <about:blank>
+PASS Parsing: <http://example.com/foo/../../../ton> against <about:blank>
+PASS Parsing: <http://example.com/foo/%2e> against <about:blank>
+FAIL Parsing: <http://example.com/foo/%2e%2> against <about:blank> assert_equals: href expected "http://example.com/foo/%2e%2" but got "http://example.com/foo/.%2"
+FAIL Parsing: <http://example.com/foo/%2e./%2e%2e/.%2e/%2e.bar> against <about:blank> assert_equals: href expected "http://example.com/%2e.bar" but got "http://example.com/..bar"
+PASS Parsing: <http://example.com////../..> against <about:blank>
+PASS Parsing: <http://example.com/foo/bar//../..> against <about:blank>
+PASS Parsing: <http://example.com/foo/bar//..> against <about:blank>
+PASS Parsing: <http://example.com/foo> against <about:blank>
+PASS Parsing: <http://example.com/%20foo> against <about:blank>
+PASS Parsing: <http://example.com/foo%> against <about:blank>
+PASS Parsing: <http://example.com/foo%2> against <about:blank>
+PASS Parsing: <http://example.com/foo%2zbar> against <about:blank>
+PASS Parsing: <http://example.com/foo%2©zbar> against <about:blank>
+FAIL Parsing: <http://example.com/foo%41%7a> against <about:blank> assert_equals: href expected "http://example.com/foo%41%7a" but got "http://example.com/fooAz"
+PASS Parsing: <http://example.com/foo	‘%91> against <about:blank>
+FAIL Parsing: <http://example.com/foo%00%51> against <about:blank> assert_equals: href expected "http://example.com/foo%00%51" but got "http://example.com/foo%00Q"
+PASS Parsing: <http://example.com/(%28:%3A%29)> against <about:blank>
+PASS Parsing: <http://example.com/%3A%3a%3C%3c> against <about:blank>
+PASS Parsing: <http://example.com/foo	bar> against <about:blank>
+PASS Parsing: <http://example.com\\foo\\bar> against <about:blank>
+PASS Parsing: <http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd> against <about:blank>
+PASS Parsing: <http://example.com/@asdf%40> against <about:blank>
+PASS Parsing: <http://example.com/你好你好> against <about:blank>
+PASS Parsing: <http://example.com/‥/foo> against <about:blank>
+PASS Parsing: <http://example.com//foo> against <about:blank>
+PASS Parsing: <http://example.com/‮/foo/‭/bar> against <about:blank>
+PASS Parsing: <http://www.google.com/foo?bar=baz#> against <about:blank>
+PASS Parsing: <http://www.google.com/foo?bar=baz# »> against <about:blank>
+PASS Parsing: <data:test# »> against <about:blank>
+PASS Parsing: <http://www.google.com> against <about:blank>
+PASS Parsing: <http://192.0x00A80001> against <about:blank>
+FAIL Parsing: <http://www/foo%2Ehtml> against <about:blank> assert_equals: href expected "http://www/foo%2Ehtml" but got "http://www/foo.html"
+PASS Parsing: <http://www/foo/%2E/html> against <about:blank>
+PASS Parsing: <http://user:pass@/> against <about:blank>
+PASS Parsing: <http://%25DOMAIN:foobar@foodomain.com/> against <about:blank>
+PASS Parsing: <http:\\www.google.com\foo> against <about:blank>
+PASS Parsing: <http://foo:80/> against <about:blank>
+PASS Parsing: <http://foo:81/> against <about:blank>
+FAIL Parsing: <httpa://foo:80/> against <about:blank> assert_equals: host expected "foo:80" but got ""
+PASS Parsing: <http://foo:-80/> against <about:blank>
+PASS Parsing: <https://foo:443/> against <about:blank>
+PASS Parsing: <https://foo:80/> against <about:blank>
+PASS Parsing: <ftp://foo:21/> against <about:blank>
+PASS Parsing: <ftp://foo:80/> against <about:blank>
+FAIL Parsing: <gopher://foo:70/> against <about:blank> assert_equals: host expected "foo:70" but got ""
+FAIL Parsing: <gopher://foo:443/> against <about:blank> assert_equals: host expected "foo:443" but got ""
+PASS Parsing: <ws://foo:80/> against <about:blank>
+PASS Parsing: <ws://foo:81/> against <about:blank>
+PASS Parsing: <ws://foo:443/> against <about:blank>
+PASS Parsing: <ws://foo:815/> against <about:blank>
+PASS Parsing: <wss://foo:80/> against <about:blank>
+PASS Parsing: <wss://foo:81/> against <about:blank>
+PASS Parsing: <wss://foo:443/> against <about:blank>
+PASS Parsing: <wss://foo:815/> against <about:blank>
+PASS Parsing: <http:/example.com/> against <about:blank>
+PASS Parsing: <ftp:/example.com/> against <about:blank>
+PASS Parsing: <https:/example.com/> against <about:blank>
+PASS Parsing: <madeupscheme:/example.com/> against <about:blank>
+PASS Parsing: <file:/example.com/> against <about:blank>
+PASS Parsing: <ftps:/example.com/> against <about:blank>
+PASS Parsing: <gopher:/example.com/> against <about:blank>
+PASS Parsing: <ws:/example.com/> against <about:blank>
+PASS Parsing: <wss:/example.com/> against <about:blank>
+PASS Parsing: <data:/example.com/> against <about:blank>
+PASS Parsing: <javascript:/example.com/> against <about:blank>
+PASS Parsing: <mailto:/example.com/> against <about:blank>
+PASS Parsing: <http:example.com/> against <about:blank>
+PASS Parsing: <ftp:example.com/> against <about:blank>
+PASS Parsing: <https:example.com/> against <about:blank>
+PASS Parsing: <madeupscheme:example.com/> against <about:blank>
+PASS Parsing: <ftps:example.com/> against <about:blank>
+PASS Parsing: <gopher:example.com/> against <about:blank>
+PASS Parsing: <ws:example.com/> against <about:blank>
+PASS Parsing: <wss:example.com/> against <about:blank>
+PASS Parsing: <data:example.com/> against <about:blank>
+PASS Parsing: <javascript:example.com/> against <about:blank>
+PASS Parsing: <mailto:example.com/> against <about:blank>
+PASS Parsing: <http:@www.example.com> against <about:blank>
+PASS Parsing: <http:/@www.example.com> against <about:blank>
+PASS Parsing: <http://@www.example.com> against <about:blank>
+PASS Parsing: <http:a:b@www.example.com> against <about:blank>
+PASS Parsing: <http:/a:b@www.example.com> against <about:blank>
+PASS Parsing: <http://a:b@www.example.com> against <about:blank>
+PASS Parsing: <http://@pple.com> against <about:blank>
+PASS Parsing: <http::b@www.example.com> against <about:blank>
+PASS Parsing: <http:/:b@www.example.com> against <about:blank>
+PASS Parsing: <http://:b@www.example.com> against <about:blank>
+FAIL Parsing: <http:/:@/www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http:/:@/www.example.com" but got "http:///www.example.com"
+PASS Parsing: <http://user@/www.example.com> against <about:blank>
+FAIL Parsing: <http:@/www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http:@/www.example.com" but got "http:///www.example.com"
+FAIL Parsing: <http:/@/www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http:/@/www.example.com" but got "http:///www.example.com"
+FAIL Parsing: <http://@/www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http://@/www.example.com" but got "http:///www.example.com"
+FAIL Parsing: <https:@/www.example.com> against <about:blank> assert_equals: failure should set href to input expected "https:@/www.example.com" but got "https:///www.example.com"
+FAIL Parsing: <http:a:b@/www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http:a:b@/www.example.com" but got "http://a:b@/www.example.com"
+FAIL Parsing: <http:/a:b@/www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http:/a:b@/www.example.com" but got "http://a:b@/www.example.com"
+PASS Parsing: <http://a:b@/www.example.com> against <about:blank>
+FAIL Parsing: <http::@/www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http::@/www.example.com" but got "http:///www.example.com"
+PASS Parsing: <http:a:@www.example.com> against <about:blank>
+PASS Parsing: <http:/a:@www.example.com> against <about:blank>
+PASS Parsing: <http://a:@www.example.com> against <about:blank>
+PASS Parsing: <http://www.@pple.com> against <about:blank>
+FAIL Parsing: <http:@:www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http:@:www.example.com" but got "http://:www.example.com/"
+FAIL Parsing: <http:/@:www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http:/@:www.example.com" but got "http://:www.example.com/"
+FAIL Parsing: <http://@:www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http://@:www.example.com" but got "http://:www.example.com/"
+PASS Parsing: <http://:@www.example.com> against <about:blank>
+PASS Parsing: </> against <http://www.example.com/test>
+PASS Parsing: </test.txt> against <http://www.example.com/test>
+PASS Parsing: <.> against <http://www.example.com/test>
+PASS Parsing: <..> against <http://www.example.com/test>
+PASS Parsing: <test.txt> against <http://www.example.com/test>
+PASS Parsing: <./test.txt> against <http://www.example.com/test>
+PASS Parsing: <../test.txt> against <http://www.example.com/test>
+PASS Parsing: <../aaa/test.txt> against <http://www.example.com/test>
+PASS Parsing: <../../test.txt> against <http://www.example.com/test>
+PASS Parsing: <中/test.txt> against <http://www.example.com/test>
+PASS Parsing: <http://www.example2.com> against <http://www.example.com/test>
+PASS Parsing: <//www.example2.com> against <http://www.example.com/test>
+PASS Parsing: <file:...> against <http://www.example.com/test>
+PASS Parsing: <file:..> against <http://www.example.com/test>
+PASS Parsing: <file:a> against <http://www.example.com/test>
+PASS Parsing: <http://ExAmPlE.CoM> against <http://other.com/>
+FAIL Parsing: <http://example example.com> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://Goo%20 goo%7C|.com> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://[]> against <http://other.com/> assert_equals: failure should set href to input expected "http://[]" but got "http://[]/"
+FAIL Parsing: <http://[:]> against <http://other.com/> assert_equals: failure should set href to input expected "http://[:]" but got "http://[:]/"
+FAIL Parsing: <http://GOO  goo.com> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
+PASS Parsing: <http://GOO​⁠goo.com> against <http://other.com/>
+PASS Parsing: <\0 http://example.com/ \r > against <about:blank>
+PASS Parsing: <http://www.foo。bar.com> against <http://other.com/>
+FAIL Parsing: <http://﷐zyx.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://﷐zyx.com" but got "http://%EF%BF%BDzyx.com/"
+FAIL Parsing: <http://%ef%b7%90zyx.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%ef%b7%90zyx.com" but got "http://%EF%BF%BDzyx.com/"
+FAIL Parsing: <https://�> against <about:blank> assert_equals: failure should set href to input expected "https://\ufffd" but got "https://%EF%BF%BD/"
+FAIL Parsing: <https://%EF%BF%BD> against <about:blank> assert_equals: failure should set href to input expected "https://%EF%BF%BD" but got "https://%EF%BF%BD/"
+PASS Parsing: <https://x/�?�#�> against <about:blank>
+FAIL Parsing: <http://a.b.c.xn--pokxncvks> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://10.0.0.xn--pokxncvks> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://a.b.c.XN--pokxncvks> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://a.b.c.Xn--pokxncvks> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://10.0.0.XN--pokxncvks> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://10.0.0.xN--pokxncvks> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+PASS Parsing: <http://Go.com> against <http://other.com/>
+FAIL Parsing: <http://%41.com> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://%ef%bc%85%ef%bc%94%ef%bc%91.com> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://%00.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%00.com" but got "http://%00.com/"
+FAIL Parsing: <http://%ef%bc%85%ef%bc%90%ef%bc%90.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%ef%bc%85%ef%bc%90%ef%bc%90.com" but got "http://%00.com/"
+PASS Parsing: <http://你好你好> against <http://other.com/>
+PASS Parsing: <https://faß.ExAmPlE/> against <about:blank>
+FAIL Parsing: <sc://faß.ExAmPlE/> against <about:blank> assert_equals: host expected "fa%C3%9F.ExAmPlE" but got ""
+FAIL Parsing: <http://%zz%66%a.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%zz%66%a.com" but got "http://%25zzf%25a.com/"
+FAIL Parsing: <http://%25> against <http://other.com/> assert_equals: failure should set href to input expected "http://%25" but got "http://%25/"
+FAIL Parsing: <http://hello%00> against <http://other.com/> assert_equals: failure should set href to input expected "http://hello%00" but got "http://hello%00/"
+PASS Parsing: <http://%30%78%63%30%2e%30%32%35%30.01> against <http://other.com/>
+PASS Parsing: <http://%30%78%63%30%2e%30%32%35%30.01%2e> against <http://other.com/>
+FAIL Parsing: <http://192.168.0.257> against <http://other.com/> assert_equals: failure should set href to input expected "http://192.168.0.257" but got "http://192.168.0.257/"
+FAIL Parsing: <http://%3g%78%63%30%2e%30%32%35%30%2E.01> against <http://other.com/> assert_equals: failure should set href to input expected "http://%3g%78%63%30%2e%30%32%35%30%2E.01" but got "http://%253gxc0.0250..01/"
+FAIL Parsing: <http://192.168.0.1 hello> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <https://x x:12> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+PASS Parsing: <http://0Xc0.0250.01> against <http://other.com/>
+PASS Parsing: <http://./> against <about:blank>
+PASS Parsing: <http://../> against <about:blank>
+PASS Parsing: <http://[www.google.com]/> against <about:blank>
+FAIL Parsing: <http://[google.com]> against <http://other.com/> assert_equals: failure should set href to input expected "http://[google.com]" but got "http://[google.com]/"
+FAIL Parsing: <http://[::1.2.3.4x]> against <http://other.com/> assert_equals: failure should set href to input expected "http://[::1.2.3.4x]" but got "http://[::1.2.3.4x]/"
+FAIL Parsing: <http://[::1.2.3.]> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://[::1.2.]> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://[::1.]> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
+PASS Parsing: <http://foo:💩@example.com/bar> against <http://other.com/>
+PASS Parsing: <#> against <test:test>
+PASS Parsing: <#x> against <mailto:x@x.com>
+FAIL Parsing: <#x> against <data:,> assert_equals: href expected "data:,#x" but got "mailto:x@x.com#x"
+PASS Parsing: <#x> against <about:blank>
+PASS Parsing: <#> against <test:test?test>
+PASS Parsing: <https://@test@test@example:800/> against <http://doesnotmatter/>
+PASS Parsing: <https://@@@example> against <http://doesnotmatter/>
+PASS Parsing: <http://`{}:`{}@h/`{}?`{}> against <http://doesnotmatter/>
+PASS Parsing: <http://host/?'> against <about:blank>
+FAIL Parsing: <notspecial://host/?'> against <about:blank> assert_equals: href expected "notspecial://host/?'" but got "notspecial://host/?%27"
+PASS Parsing: </some/path> against <http://user@example.org/smth>
+PASS Parsing: <> against <http://user:pass@example.org:21/smth>
+PASS Parsing: </some/path> against <http://user:pass@example.org:21/smth>
+FAIL Parsing: <i> against <sc:sd> assert_equals: failure should set href to input expected "i" but got ""
+FAIL Parsing: <i> against <sc:sd/sd> assert_equals: failure should set href to input expected "i" but got ""
+PASS Parsing: <i> against <sc:/pa/pa>
+FAIL Parsing: <i> against <sc://ho/pa> assert_equals: host expected "ho" but got ""
+FAIL Parsing: <i> against <sc:///pa/pa> assert_equals: pathname expected "/pa/i" but got "///pa/i"
+FAIL Parsing: <../i> against <sc:sd> assert_equals: failure should set href to input expected "../i" but got ""
+FAIL Parsing: <../i> against <sc:sd/sd> assert_equals: failure should set href to input expected "../i" but got ""
+PASS Parsing: <../i> against <sc:/pa/pa>
+FAIL Parsing: <../i> against <sc://ho/pa> assert_equals: host expected "ho" but got ""
+FAIL Parsing: <../i> against <sc:///pa/pa> assert_equals: href expected "sc:///i" but got "sc:///pa/i"
+FAIL Parsing: </i> against <sc:sd> assert_equals: failure should set href to input expected "/i" but got ""
+FAIL Parsing: </i> against <sc:sd/sd> assert_equals: failure should set href to input expected "/i" but got ""
+PASS Parsing: </i> against <sc:/pa/pa>
+FAIL Parsing: </i> against <sc://ho/pa> assert_equals: host expected "ho" but got ""
+FAIL Parsing: </i> against <sc:///pa/pa> assert_equals: href expected "sc:///i" but got "sc:///pa/i"
+FAIL Parsing: <?i> against <sc:sd> assert_equals: failure should set href to input expected "?i" but got ""
+FAIL Parsing: <?i> against <sc:sd/sd> assert_equals: failure should set href to input expected "?i" but got ""
+PASS Parsing: <?i> against <sc:/pa/pa>
+FAIL Parsing: <?i> against <sc://ho/pa> assert_equals: host expected "ho" but got ""
+FAIL Parsing: <?i> against <sc:///pa/pa> assert_equals: pathname expected "/pa/pa" but got "///pa/pa"
+PASS Parsing: <#i> against <sc:sd>
+PASS Parsing: <#i> against <sc:sd/sd>
+PASS Parsing: <#i> against <sc:/pa/pa>
+FAIL Parsing: <#i> against <sc://ho/pa> assert_equals: host expected "ho" but got ""
+FAIL Parsing: <#i> against <sc:///pa/pa> assert_equals: pathname expected "/pa/pa" but got "///pa/pa"
+FAIL Parsing: <about:/../> against <about:blank> assert_equals: href expected "about:/" but got "about:/../"
+FAIL Parsing: <data:/../> against <about:blank> assert_equals: href expected "data:/" but got "data:/../"
+FAIL Parsing: <javascript:/../> against <about:blank> assert_equals: href expected "javascript:/" but got "javascript:/../"
+FAIL Parsing: <mailto:/../> against <about:blank> assert_equals: href expected "mailto:/" but got "mailto:/../"
+FAIL Parsing: <sc://ñ.test/> against <about:blank> assert_equals: host expected "%C3%B1.test" but got ""
+FAIL Parsing: <sc://%/> against <about:blank> assert_equals: host expected "%" but got ""
+FAIL Parsing: <sc://@/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <sc://te@s:t@/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <sc://:/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <sc://:12/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <x> against <sc://ñ> assert_equals: href expected "sc://%C3%B1/x" but got "sc://%C3%B1"
+PASS Parsing: <sc:\../> against <about:blank>
+PASS Parsing: <sc::a@example.net> against <about:blank>
+PASS Parsing: <wow:%NBD> against <about:blank>
+PASS Parsing: <wow:%1G> against <about:blank>
+FAIL Parsing: <wow:￿> against <about:blank> assert_equals: href expected "wow:%EF%BF%BF" but got "wow:%EF%BF%BD"
+FAIL Parsing: <http://example.com/U+d800𐟾U+dfff﷐﷏﷯ﷰ￾￿?U+d800𐟾U+dfff﷐﷏﷯ﷰ￾￿> against <about:blank> assert_equals: href expected "http://example.com/%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%B7%90%EF%B7%8F%EF%B7%AF%EF%B7%B0%EF%BF%BE%EF%BF%BF?%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%B7%90%EF%B7%8F%EF%B7%AF%EF%B7%B0%EF%BF%BE%EF%BF%BF" but got "http://example.com/%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%BF%BD%EF%B7%8F%EF%BF%BD%EF%B7%B0%EF%BF%BD%EF%BF%BD?%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%BF%BD%EF%B7%8F%EF%BF%BD%EF%B7%B0%EF%BF%BD%EF%BF%BD"
+FAIL Parsing: <sc://a\0b/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <sc://a b/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <sc://a<b> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <sc://a>b> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <sc://a[b/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <sc://a\b/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <sc://a]b/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <sc://a^b> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <sc://a|b/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <foo://ho	st/> against <about:blank> assert_equals: host expected "host" but got ""
+FAIL Parsing: <foo://ho
+st/> against <about:blank> assert_equals: host expected "host" but got ""
+FAIL Parsing: <foo://ho\rst/> against <about:blank> assert_equals: host expected "host" but got ""
+FAIL Parsing: <http://a\0b/> against <about:blank> assert_equals: failure should set href to input expected "http://a\0b/" but got "http://a%00b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x01b/" but got "http://a%01b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x02b/" but got "http://a%02b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x03b/" but got "http://a%03b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x04b/" but got "http://a%04b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x05b/" but got "http://a%05b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x06b/" but got "http://a%06b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x07b/" but got "http://a%07b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\bb/" but got "http://a%08b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\vb/" but got "http://a%0Bb/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\fb/" but got "http://a%0Cb/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x0eb/" but got "http://a%0Eb/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x0fb/" but got "http://a%0Fb/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x10b/" but got "http://a%10b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x11b/" but got "http://a%11b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x12b/" but got "http://a%12b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x13b/" but got "http://a%13b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x14b/" but got "http://a%14b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x15b/" but got "http://a%15b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x16b/" but got "http://a%16b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x17b/" but got "http://a%17b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x18b/" but got "http://a%18b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x19b/" but got "http://a%19b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x1ab/" but got "http://a%1Ab/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x1bb/" but got "http://a%1Bb/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x1cb/" but got "http://a%1Cb/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x1db/" but got "http://a%1Db/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x1eb/" but got "http://a%1Eb/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x1fb/" but got "http://a%1Fb/"
+FAIL Parsing: <http://a b/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://a%b/> against <about:blank> assert_equals: failure should set href to input expected "http://a%b/" but got "http://a%25b/"
+FAIL Parsing: <http://a<b> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://a>b> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+PASS Parsing: <http://a[b/> against <about:blank>
+PASS Parsing: <http://a]b/> against <about:blank>
+FAIL Parsing: <http://a^b> against <about:blank> assert_equals: failure should set href to input expected "http://a^b" but got "http://a%5Eb/"
+FAIL Parsing: <http://a|b/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://ab/" but got "http://a%7Fb/"
+PASS Parsing: <http://ho	st/> against <about:blank>
+PASS Parsing: <http://ho
+st/> against <about:blank>
+PASS Parsing: <http://ho\rst/> against <about:blank>
+PASS Parsing: <http://ho%00st/> against <about:blank>
+PASS Parsing: <http://ho%01st/> against <about:blank>
+PASS Parsing: <http://ho%02st/> against <about:blank>
+PASS Parsing: <http://ho%03st/> against <about:blank>
+PASS Parsing: <http://ho%04st/> against <about:blank>
+PASS Parsing: <http://ho%05st/> against <about:blank>
+PASS Parsing: <http://ho%06st/> against <about:blank>
+PASS Parsing: <http://ho%07st/> against <about:blank>
+PASS Parsing: <http://ho%08st/> against <about:blank>
+PASS Parsing: <http://ho%09st/> against <about:blank>
+PASS Parsing: <http://ho%0Ast/> against <about:blank>
+PASS Parsing: <http://ho%0Bst/> against <about:blank>
+PASS Parsing: <http://ho%0Cst/> against <about:blank>
+PASS Parsing: <http://ho%0Dst/> against <about:blank>
+PASS Parsing: <http://ho%0Est/> against <about:blank>
+PASS Parsing: <http://ho%0Fst/> against <about:blank>
+PASS Parsing: <http://ho%10st/> against <about:blank>
+PASS Parsing: <http://ho%11st/> against <about:blank>
+PASS Parsing: <http://ho%12st/> against <about:blank>
+PASS Parsing: <http://ho%13st/> against <about:blank>
+PASS Parsing: <http://ho%14st/> against <about:blank>
+PASS Parsing: <http://ho%15st/> against <about:blank>
+PASS Parsing: <http://ho%16st/> against <about:blank>
+PASS Parsing: <http://ho%17st/> against <about:blank>
+PASS Parsing: <http://ho%18st/> against <about:blank>
+PASS Parsing: <http://ho%19st/> against <about:blank>
+PASS Parsing: <http://ho%1Ast/> against <about:blank>
+PASS Parsing: <http://ho%1Bst/> against <about:blank>
+PASS Parsing: <http://ho%1Cst/> against <about:blank>
+PASS Parsing: <http://ho%1Dst/> against <about:blank>
+PASS Parsing: <http://ho%1Est/> against <about:blank>
+PASS Parsing: <http://ho%1Fst/> against <about:blank>
+FAIL Parsing: <http://ho%20st/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://ho%23st/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+PASS Parsing: <http://ho%25st/> against <about:blank>
+PASS Parsing: <http://ho%2Fst/> against <about:blank>
+FAIL Parsing: <http://ho%3Ast/> against <about:blank> assert_equals: failure should set href to input expected "http://ho%3Ast/" but got "http://ho:st/"
+FAIL Parsing: <http://ho%3Cst/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://ho%3Est/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+PASS Parsing: <http://ho%3Fst/> against <about:blank>
+FAIL Parsing: <http://ho%40st/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://ho%5Bst/> against <about:blank> assert_equals: failure should set href to input expected "http://ho%5Bst/" but got "http://ho[st/"
+PASS Parsing: <http://ho%5Cst/> against <about:blank>
+FAIL Parsing: <http://ho%5Dst/> against <about:blank> assert_equals: failure should set href to input expected "http://ho%5Dst/" but got "http://ho]st/"
+FAIL Parsing: <http://ho%7Cst/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+PASS Parsing: <http://ho%7Fst/> against <about:blank>
+FAIL Parsing: <http://!"$&'()*+,-.;=_`{}~/> against <about:blank> assert_equals: href expected "http://!\"$&'()*+,-.;=_`{}~/" but got "http://%21%22%24%26%27%28%29%2A+%2C-.%3B%3D_%60%7B%7D%7E/"
+FAIL Parsing: <sc://!"$%&'()*+,-.;=_`{}~/> against <about:blank> assert_equals: host expected "%01%02%03%04%05%06%07%08%0B%0C%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%7F!\"$%&'()*+,-.;=_`{}~" but got ""
+FAIL Parsing: <ftp://example.com%80/> against <about:blank> assert_equals: failure should set href to input expected "ftp://example.com%80/" but got "ftp://example.com%EF%BF%BD/"
+FAIL Parsing: <ftp://example.com%A0/> against <about:blank> assert_equals: failure should set href to input expected "ftp://example.com%A0/" but got "ftp://example.com%EF%BF%BD/"
+FAIL Parsing: <https://example.com%80/> against <about:blank> assert_equals: failure should set href to input expected "https://example.com%80/" but got "https://example.com%EF%BF%BD/"
+FAIL Parsing: <https://example.com%A0/> against <about:blank> assert_equals: failure should set href to input expected "https://example.com%A0/" but got "https://example.com%EF%BF%BD/"
+PASS Parsing: <ftp://%e2%98%83> against <about:blank>
+PASS Parsing: <https://%e2%98%83> against <about:blank>
+PASS Parsing: <http://127.0.0.1:10100/relative_import.html> against <about:blank>
+PASS Parsing: <http://facebook.com/?foo=%7B%22abc%22> against <about:blank>
+PASS Parsing: <https://localhost:3000/jqueryui@1.2.3> against <about:blank>
+PASS Parsing: <h	t
+t\rp://h	o
+s\rt:9	0
+0\r0/p	a
+t\rh?q	u
+e\rry#f	r
+a\rg> against <about:blank>
+PASS Parsing: <?a=b&c=d> against <http://example.org/foo/bar>
+PASS Parsing: <??a=b&c=d> against <http://example.org/foo/bar>
+PASS Parsing: <http:> against <http://example.org/foo/bar>
+PASS Parsing: <http:> against <https://example.org/foo/bar>
+PASS Parsing: <sc:> against <https://example.org/foo/bar>
+PASS Parsing: <http://foo.bar/baz?qux#foobar> against <about:blank>
+PASS Parsing: <http://foo.bar/baz?qux#foo"bar> against <about:blank>
+PASS Parsing: <http://foo.bar/baz?qux#foo<bar> against <about:blank>
+PASS Parsing: <http://foo.bar/baz?qux#foo>bar> against <about:blank>
+PASS Parsing: <http://foo.bar/baz?qux#foo`bar> against <about:blank>
+PASS Parsing: <http://1.2.3.4/> against <http://other.com/>
+PASS Parsing: <http://1.2.3.4./> against <http://other.com/>
+PASS Parsing: <http://192.168.257> against <http://other.com/>
+PASS Parsing: <http://192.168.257.> against <http://other.com/>
+PASS Parsing: <http://192.168.257.com> against <http://other.com/>
+PASS Parsing: <http://256> against <http://other.com/>
+PASS Parsing: <http://256.com> against <http://other.com/>
+PASS Parsing: <http://999999999> against <http://other.com/>
+PASS Parsing: <http://999999999.> against <http://other.com/>
+PASS Parsing: <http://999999999.com> against <http://other.com/>
+FAIL Parsing: <http://10000000000> against <http://other.com/> assert_equals: failure should set href to input expected "http://10000000000" but got "http://10000000000/"
+PASS Parsing: <http://10000000000.com> against <http://other.com/>
+PASS Parsing: <http://4294967295> against <http://other.com/>
+FAIL Parsing: <http://4294967296> against <http://other.com/> assert_equals: failure should set href to input expected "http://4294967296" but got "http://4294967296/"
+PASS Parsing: <http://0xffffffff> against <http://other.com/>
+FAIL Parsing: <http://0xffffffff1> against <http://other.com/> assert_equals: failure should set href to input expected "http://0xffffffff1" but got "http://0xffffffff1/"
+FAIL Parsing: <http://256.256.256.256> against <http://other.com/> assert_equals: failure should set href to input expected "http://256.256.256.256" but got "http://256.256.256.256/"
+PASS Parsing: <https://0x.0x.0> against <about:blank>
+PASS Parsing: <https://0x100000000/test> against <about:blank>
+PASS Parsing: <https://256.0.0.1/test> against <about:blank>
+PASS Parsing: <file:///C%3A/> against <about:blank>
+PASS Parsing: <file:///C%7C/> against <about:blank>
+FAIL Parsing: <file://%43%3A> against <about:blank> assert_equals: failure should set href to input expected "file://%43%3A" but got "file://c:/"
+FAIL Parsing: <file://%43%7C> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <file://%43|> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <file://C%7C> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <file://%43%7C/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <https://%43%7C/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <asdf://%43|/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <asdf://%43%7C/> against <about:blank> assert_equals: host expected "%43%7C" but got ""
+PASS Parsing: <pix/submit.gif> against <file:///C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/anchor.html>
+FAIL Parsing: <..> against <file:///C:/> assert_equals: href expected "file:///C:/" but got "file:///"
+PASS Parsing: <..> against <file:///>
+FAIL Parsing: </> against <file:///C:/a/b> assert_equals: href expected "file:///C:/" but got "file:///"
+FAIL Parsing: </> against <file://h/C:/a/b> assert_equals: href expected "file://h/C:/" but got "file://h/"
+PASS Parsing: </> against <file://h/a/b>
+FAIL Parsing: <//d:> against <file:///C:/a/b> assert_equals: href expected "file:///d:" but got "file://d:/"
+FAIL Parsing: <//d:/..> against <file:///C:/a/b> assert_equals: href expected "file:///d:/" but got "file://d:/"
+PASS Parsing: <..> against <file:///ab:/>
+PASS Parsing: <..> against <file:///1:/>
+PASS Parsing: <> against <file:///test?test#test>
+PASS Parsing: <file:> against <file:///test?test#test>
+PASS Parsing: <?x> against <file:///test?test#test>
+PASS Parsing: <file:?x> against <file:///test?test#test>
+PASS Parsing: <#x> against <file:///test?test#test>
+PASS Parsing: <file:#x> against <file:///test?test#test>
+FAIL Parsing: <file:\\//> against <about:blank> assert_equals: href expected "file:////" but got "file:///"
+FAIL Parsing: <file:\\\\> against <about:blank> assert_equals: href expected "file:////" but got "file:///"
+FAIL Parsing: <file:\\\\?fox> against <about:blank> assert_equals: href expected "file:////?fox" but got "file:///?fox"
+FAIL Parsing: <file:\\\\#guppy> against <about:blank> assert_equals: href expected "file:////#guppy" but got "file:///#guppy"
+PASS Parsing: <file://spider///> against <about:blank>
+FAIL Parsing: <file:\\localhost//> against <about:blank> assert_equals: href expected "file:////" but got "file://localhost//"
+PASS Parsing: <file:///localhost//cat> against <about:blank>
+FAIL Parsing: <file://\/localhost//cat> against <about:blank> assert_equals: href expected "file:////localhost//cat" but got "file:///localhost//cat"
+FAIL Parsing: <file://localhost//a//../..//> against <about:blank> assert_equals: href expected "file://///" but got "file://localhost///"
+FAIL Parsing: </////mouse> against <file:///elephant> assert_equals: href expected "file://///mouse" but got "file:///mouse"
+PASS Parsing: <\//pig> against <file://lion/>
+FAIL Parsing: <\/localhost//pig> against <file://lion/> assert_equals: href expected "file:////pig" but got "file://localhost//pig"
+FAIL Parsing: <//localhost//pig> against <file://lion/> assert_equals: href expected "file:////pig" but got "file://localhost//pig"
+PASS Parsing: </..//localhost//pig> against <file://lion/>
+PASS Parsing: <file://> against <file://ape/>
+PASS Parsing: </rooibos> against <file://tea/>
+PASS Parsing: </?chai> against <file://tea/>
+FAIL Parsing: <C|> against <file://host/dir/file> assert_equals: href expected "file://host/C:" but got "file://host/dir/C%7C"
+FAIL Parsing: <C|> against <file://host/D:/dir1/dir2/file> assert_equals: href expected "file://host/C:" but got "file://host/D:/dir1/dir2/C%7C"
+FAIL Parsing: <C|#> against <file://host/dir/file> assert_equals: href expected "file://host/C:#" but got "file://host/dir/C%7C#"
+FAIL Parsing: <C|?> against <file://host/dir/file> assert_equals: href expected "file://host/C:?" but got "file://host/dir/C%7C?"
+FAIL Parsing: <C|/> against <file://host/dir/file> assert_equals: href expected "file://host/C:/" but got "file://host/dir/C%7C/"
+FAIL Parsing: <C|
+/> against <file://host/dir/file> assert_equals: href expected "file://host/C:/" but got "file://host/dir/C%7C/"
+FAIL Parsing: <C|\> against <file://host/dir/file> assert_equals: href expected "file://host/C:/" but got "file://host/dir/C%7C/"
+PASS Parsing: <C> against <file://host/dir/file>
+FAIL Parsing: <C|a> against <file://host/dir/file> assert_equals: href expected "file://host/dir/C|a" but got "file://host/dir/C%7Ca"
+PASS Parsing: </c:/foo/bar> against <file:///c:/baz/qux>
+FAIL Parsing: </c|/foo/bar> against <file:///c:/baz/qux> assert_equals: href expected "file:///c:/foo/bar" but got "file:///c%7C/foo/bar"
+PASS Parsing: <file:\c:\foo\bar> against <file:///c:/baz/qux>
+PASS Parsing: </c:/foo/bar> against <file://host/path>
+PASS Parsing: <file://example.net/C:/> against <about:blank>
+PASS Parsing: <file://1.2.3.4/C:/> against <about:blank>
+PASS Parsing: <file://[1::8]/C:/> against <about:blank>
+FAIL Parsing: <C|/> against <file://host/> assert_equals: href expected "file://host/C:/" but got "file://host/C%7C/"
+PASS Parsing: </C:/> against <file://host/>
+PASS Parsing: <file:C:/> against <file://host/>
+PASS Parsing: <file:/C:/> against <file://host/>
+FAIL Parsing: <//C:/> against <file://host/> assert_equals: href expected "file:///C:/" but got "file://c:/"
+FAIL Parsing: <file://C:/> against <file://host/> assert_equals: href expected "file:///C:/" but got "file://c:/"
+PASS Parsing: <///C:/> against <file://host/>
+PASS Parsing: <file:///C:/> against <file://host/>
+FAIL Parsing: <file:/C|/> against <about:blank> assert_equals: href expected "file:///C:/" but got "file:///C%7C/"
+FAIL Parsing: <file://C|/> against <about:blank> assert_equals: href expected "file:///C:/" but got "file://c%7C/"
+PASS Parsing: <file:> against <about:blank>
+PASS Parsing: <file:?q=v> against <about:blank>
+PASS Parsing: <file:#frag> against <about:blank>
+PASS Parsing: <file:///Y:> against <about:blank>
+PASS Parsing: <file:///Y:/> against <about:blank>
+PASS Parsing: <file:///./Y> against <about:blank>
+PASS Parsing: <file:///./Y:> against <about:blank>
+FAIL Parsing: <\\\.\Y:> against <about:blank> assert_equals: failure should set href to input expected "\\\\\\.\\Y:" but got ""
+PASS Parsing: <file:///y:> against <about:blank>
+PASS Parsing: <file:///y:/> against <about:blank>
+PASS Parsing: <file:///./y> against <about:blank>
+PASS Parsing: <file:///./y:> against <about:blank>
+FAIL Parsing: <\\\.\y:> against <about:blank> assert_equals: failure should set href to input expected "\\\\\\.\\y:" but got ""
+FAIL Parsing: <file://localhost//a//../..//foo> against <about:blank> assert_equals: href expected "file://///foo" but got "file://localhost///foo"
+FAIL Parsing: <file://localhost////foo> against <about:blank> assert_equals: href expected "file://////foo" but got "file://localhost////foo"
+FAIL Parsing: <file:////foo> against <about:blank> assert_equals: href expected "file:////foo" but got "file:///foo"
+PASS Parsing: <file:///one/two> against <file:///>
+FAIL Parsing: <file:////one/two> against <file:///> assert_equals: href expected "file:////one/two" but got "file:///one/two"
+PASS Parsing: <//one/two> against <file:///>
+PASS Parsing: <///one/two> against <file:///>
+FAIL Parsing: <////one/two> against <file:///> assert_equals: href expected "file:////one/two" but got "file:///one/two"
+PASS Parsing: <file:///.//> against <file:////>
+PASS Parsing: <file:.//p> against <about:blank>
+PASS Parsing: <file:/.//p> against <about:blank>
+PASS Parsing: <http://[1:0::]> against <http://example.net/>
+FAIL Parsing: <http://[0:1:2:3:4:5:6:7:8]> against <http://example.net/> assert_equals: failure should set href to input expected "http://[0:1:2:3:4:5:6:7:8]" but got "http://[0:1:2:3:4:5:6:7:8]/"
+FAIL Parsing: <https://[0::0::0]> against <about:blank> assert_equals: failure should set href to input expected "https://[0::0::0]" but got "https://[0::0::0]/"
+FAIL Parsing: <https://[0:.0]> against <about:blank> assert_equals: failure should set href to input expected "https://[0:.0]" but got "https://[0:.0]/"
+FAIL Parsing: <https://[0:0:]> against <about:blank> assert_equals: failure should set href to input expected "https://[0:0:]" but got "https://[0:0:]/"
+FAIL Parsing: <https://[0:1:2:3:4:5:6:7.0.0.0.1]> against <about:blank> assert_equals: failure should set href to input expected "https://[0:1:2:3:4:5:6:7.0.0.0.1]" but got "https://[0:1:2:3:4:5:6:7.0.0.0.1]/"
+FAIL Parsing: <https://[0:1.00.0.0.0]> against <about:blank> assert_equals: failure should set href to input expected "https://[0:1.00.0.0.0]" but got "https://[0:1.00.0.0.0]/"
+FAIL Parsing: <https://[0:1.290.0.0.0]> against <about:blank> assert_equals: failure should set href to input expected "https://[0:1.290.0.0.0]" but got "https://[0:1.290.0.0.0]/"
+FAIL Parsing: <https://[0:1.23.23]> against <about:blank> assert_equals: failure should set href to input expected "https://[0:1.23.23]" but got "https://[0:1.23.23]/"
+FAIL Parsing: <http://?> against <about:blank> assert_equals: failure should set href to input expected "http://?" but got "http:/?"
+FAIL Parsing: <http://#> against <about:blank> assert_equals: failure should set href to input expected "http://#" but got "http:/#"
+PASS Parsing: <http://f:4294967377/c> against <http://example.org/>
+PASS Parsing: <http://f:18446744073709551697/c> against <http://example.org/>
+PASS Parsing: <http://f:340282366920938463463374607431768211537/c> against <http://example.org/>
+FAIL Parsing: <sc://ñ> against <about:blank> assert_equals: host expected "%C3%B1" but got ""
+FAIL Parsing: <sc://ñ?x> against <about:blank> assert_equals: host expected "%C3%B1" but got ""
+FAIL Parsing: <sc://ñ#x> against <about:blank> assert_equals: host expected "%C3%B1" but got ""
+FAIL Parsing: <#x> against <sc://ñ> assert_equals: href expected "sc://%C3%B1#x" but got "sc://%C3%B1"
+FAIL Parsing: <?x> against <sc://ñ> assert_equals: href expected "sc://%C3%B1?x" but got "sc://%C3%B1"
+FAIL Parsing: <sc://?> against <about:blank> assert_equals: pathname expected "" but got "//"
+FAIL Parsing: <sc://#> against <about:blank> assert_equals: pathname expected "" but got "//"
+FAIL Parsing: <///> against <sc://x/> assert_equals: href expected "sc:///" but got "sc:"
+FAIL Parsing: <////> against <sc://x/> assert_equals: href expected "sc:////" but got "sc:"
+FAIL Parsing: <////x/> against <sc://x/> assert_equals: href expected "sc:////x/" but got "sc://x/"
+FAIL Parsing: <tftp://foobar.com/someconfig;mode=netascii> against <about:blank> assert_equals: host expected "foobar.com" but got ""
+FAIL Parsing: <telnet://user:pass@foobar.com:23/> against <about:blank> assert_equals: username expected "user" but got ""
+FAIL Parsing: <ut2004://10.10.10.10:7777/Index.ut2> against <about:blank> assert_equals: host expected "10.10.10.10:7777" but got ""
+FAIL Parsing: <redis://foo:bar@somehost:6379/0?baz=bam&qux=baz> against <about:blank> assert_equals: username expected "foo" but got ""
+FAIL Parsing: <rsync://foo@host:911/sup> against <about:blank> assert_equals: username expected "foo" but got ""
+FAIL Parsing: <git://github.com/foo/bar.git> against <about:blank> assert_equals: host expected "github.com" but got ""
+FAIL Parsing: <irc://myserver.com:6999/channel?passwd> against <about:blank> assert_equals: host expected "myserver.com:6999" but got ""
+FAIL Parsing: <dns://fw.example.org:9999/foo.bar.org?type=TXT> against <about:blank> assert_equals: host expected "fw.example.org:9999" but got ""
+FAIL Parsing: <ldap://localhost:389/ou=People,o=JNDITutorial> against <about:blank> assert_equals: host expected "localhost:389" but got ""
+FAIL Parsing: <git+https://github.com/foo/bar> against <about:blank> assert_equals: host expected "github.com" but got ""
+PASS Parsing: <urn:ietf:rfc:2648> against <about:blank>
+PASS Parsing: <tag:joe@example.org,2001:foo/bar> against <about:blank>
+FAIL Parsing: <non-spec:/.//> against <about:blank> assert_equals: pathname expected "//" but got "/.//"
+FAIL Parsing: <non-spec:/..//> against <about:blank> assert_equals: href expected "non-spec:/.//" but got "non-spec:/..//"
+FAIL Parsing: <non-spec:/a/..//> against <about:blank> assert_equals: href expected "non-spec:/.//" but got "non-spec:/a/..//"
+FAIL Parsing: <non-spec:/.//path> against <about:blank> assert_equals: pathname expected "//path" but got "/.//path"
+FAIL Parsing: <non-spec:/..//path> against <about:blank> assert_equals: href expected "non-spec:/.//path" but got "non-spec:/..//path"
+FAIL Parsing: <non-spec:/a/..//path> against <about:blank> assert_equals: href expected "non-spec:/.//path" but got "non-spec:/a/..//path"
+FAIL Parsing: </.//path> against <non-spec:/p> assert_equals: href expected "non-spec:/.//path" but got "non-spec://path"
+FAIL Parsing: </..//path> against <non-spec:/p> assert_equals: href expected "non-spec:/.//path" but got "non-spec://path"
+FAIL Parsing: <..//path> against <non-spec:/p> assert_equals: href expected "non-spec:/.//path" but got "non-spec://path"
+FAIL Parsing: <a/..//path> against <non-spec:/p> assert_equals: href expected "non-spec:/.//path" but got "non-spec://path"
+FAIL Parsing: <> against <non-spec:/..//p> assert_equals: href expected "non-spec:/.//p" but got "non-spec:/..//p"
+FAIL Parsing: <path> against <non-spec:/..//p> assert_equals: href expected "non-spec:/.//path" but got "non-spec:/..//path"
+FAIL Parsing: <../path> against <non-spec:/.//p> assert_equals: href expected "non-spec:/path" but got "non-spec:/./path"
+FAIL Parsing: <non-special://%E2%80%A0/> against <about:blank> assert_equals: host expected "%E2%80%A0" but got ""
+FAIL Parsing: <non-special://H%4fSt/path> against <about:blank> assert_equals: host expected "H%4fSt" but got ""
+FAIL Parsing: <non-special://[1:2:0:0:5:0:0:0]/> against <about:blank> assert_equals: href expected "non-special://[1:2:0:0:5::]/" but got "non-special://[1:2:0:0:5:0:0:0]/"
+FAIL Parsing: <non-special://[1:2:0:0:0:0:0:3]/> against <about:blank> assert_equals: href expected "non-special://[1:2::3]/" but got "non-special://[1:2:0:0:0:0:0:3]/"
+FAIL Parsing: <non-special://[1:2::3]:80/> against <about:blank> assert_equals: host expected "[1:2::3]:80" but got ""
+FAIL Parsing: <non-special://[:80/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+PASS Parsing: <blob:https://example.com:443/> against <about:blank>
+PASS Parsing: <blob:d3958f5c-0777-0845-9dcf-2cb28783acaf> against <about:blank>
+PASS Parsing: <blob:> against <about:blank>
+PASS Parsing: <http://0x7f.0.0.0x7g> against <about:blank>
+PASS Parsing: <http://0X7F.0.0.0X7G> against <about:blank>
+FAIL Parsing: <http://[::127.0.0.0.1]> against <about:blank> assert_equals: failure should set href to input expected "http://[::127.0.0.0.1]" but got "http://[::127.0.0.0.1]/"
+PASS Parsing: <http://[0:1:0:1:0:1:0:1]> against <about:blank>
+PASS Parsing: <http://[1:0:1:0:1:0:1:0]> against <about:blank>
+PASS Parsing: <http://example.org/test?"> against <about:blank>
+PASS Parsing: <http://example.org/test?#> against <about:blank>
+PASS Parsing: <http://example.org/test?<> against <about:blank>
+PASS Parsing: <http://example.org/test?>> against <about:blank>
+PASS Parsing: <http://example.org/test?⌣> against <about:blank>
+PASS Parsing: <http://example.org/test?%23%23> against <about:blank>
+PASS Parsing: <http://example.org/test?%GH> against <about:blank>
+PASS Parsing: <http://example.org/test?a#%EF> against <about:blank>
+PASS Parsing: <http://example.org/test?a#%GH> against <about:blank>
+FAIL Parsing: <a> against <about:blank> assert_equals: failure should set href to input expected "a" but got ""
+FAIL Parsing: <a/> against <about:blank> assert_equals: failure should set href to input expected "a/" but got ""
+FAIL Parsing: <a//> against <about:blank> assert_equals: failure should set href to input expected "a//" but got ""
+FAIL Parsing: <test-a-colon.html> against <a:> assert_equals: failure should set href to input expected "test-a-colon.html" but got ""
+FAIL Parsing: <test-a-colon-b.html> against <a:b> assert_equals: failure should set href to input expected "test-a-colon-b.html" but got ""
+PASS Parsing: <test-a-colon-slash.html> against <a:/>
+FAIL Parsing: <test-a-colon-slash-slash.html> against <a://> assert_equals: href expected "a:///test-a-colon-slash-slash.html" but got ""
+PASS Parsing: <test-a-colon-slash-b.html> against <a:/b>
+FAIL Parsing: <test-a-colon-slash-slash-b.html> against <a://b> assert_equals: href expected "a://b/test-a-colon-slash-slash-b.html" but got "a://b"
+PASS Parsing: <http://example.org/test?a#b\0c> against <about:blank>
+FAIL Parsing: <non-spec://example.org/test?a#b\0c> against <about:blank> assert_equals: host expected "example.org" but got ""
+PASS Parsing: <non-spec:/test?a#b\0c> against <about:blank>
+PASS Parsing: <10.0.0.7:8080/foo.html> against <file:///some/dir/bar.html>
+PASS Parsing: <a!@$*=/foo.html> against <file:///some/dir/bar.html>
+PASS Parsing: <a1234567890-+.:foo/bar> against <http://example.com/dir/file>
+PASS Parsing: <file://a­b/p> against <about:blank>
+PASS Parsing: <file://a%C2%ADb/p> against <about:blank>
+FAIL Parsing: <file://­/p> against <about:blank> assert_equals: failure should set href to input expected "file://­/p" but got "file://%C2%AD/p"
+PASS Parsing: <file://%C2%AD/p> against <about:blank>
+FAIL Parsing: <file://xn--/p> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+PASS Parsing: <#link> against <https://example.org/##link>
+PASS Parsing: <non-special:cannot-be-a-base-url-\0~€> against <about:blank>
+PASS Parsing: <https://www.example.com/path{path.html?query'=query#fragment<fragment> against <about:blank>
+PASS Parsing: <https://user:pass[@foo/bar> against <http://example.org>
+FAIL Parsing: <foo:// !"$%&'()*+,-.;<=>@[\]^_`{|}~@host/> against <about:blank> assert_equals: href expected "foo://%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~@host/" but got "foo:// !\"$%&'()*+,-.;<=>@[\\]^_`{|}~@host/"
+FAIL Parsing: <wss:// !"$%&'()*+,-.;<=>@[]^_`{|}~@host/> against <about:blank> assert_equals: href expected "wss://%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/" but got "wss://%20!%22$%&%27()*+,-.%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/"
+FAIL Parsing: <foo://joe: !"$%&'()*+,-.:;<=>@[\]^_`{|}~@host/> against <about:blank> assert_equals: href expected "foo://joe:%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~@host/" but got "foo://joe: !\"$%&'()*+,-.:;<=>@[\\]^_`{|}~@host/"
+FAIL Parsing: <wss://joe: !"$%&'()*+,-.:;<=>@[]^_`{|}~@host/> against <about:blank> assert_equals: href expected "wss://joe:%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/" but got "wss://joe:%20!%22$%&%27()*+,-.%3A%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/"
+FAIL Parsing: <foo://!"$%&'()*+,-.;=_`{}~/> against <about:blank> assert_equals: host expected "!\"$%&'()*+,-.;=_`{}~" but got ""
+FAIL Parsing: <wss://!"$&'()*+,-.;=_`{}~/> against <about:blank> assert_equals: href expected "wss://!\"$&'()*+,-.;=_`{}~/" but got "wss://%21%22%24%26%27%28%29%2A+%2C-.%3B%3D_%60%7B%7D%7E/"
+FAIL Parsing: <foo://host/ !"$%&'()*+,-./:;<=>@[\]^_`{|}~> against <about:blank> assert_equals: href expected "foo://host/%20!%22$%&'()*+,-./:;%3C=%3E@[\\]^_%60%7B|%7D~" but got "foo://host/ !\"$%&'()*+,-./:;<=>@[\\]^_`{|}~"
+FAIL Parsing: <wss://host/ !"$%&'()*+,-./:;<=>@[\]^_`{|}~> against <about:blank> assert_equals: href expected "wss://host/%20!%22$%&'()*+,-./:;%3C=%3E@[/]^_%60%7B|%7D~" but got "wss://host/%20!%22$%&'()*+,-./:;%3C=%3E@[/]%5E_%60%7B%7C%7D~"
+FAIL Parsing: <foo://host/dir/? !"$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank> assert_equals: href expected "foo://host/dir/?%20!%22$%&'()*+,-./:;%3C=%3E?@[\\]^_`{|}~" but got "foo://host/dir/?%20!%22$%&%27()*+,-./:;%3C=%3E?@[\\]^_`{|}~"
+PASS Parsing: <wss://host/dir/? !"$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
+FAIL Parsing: <foo://host/dir/# !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank> assert_equals: host expected "host" but got ""
+PASS Parsing: <wss://host/dir/# !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
+FAIL Parsing: <abc:rootless> against <abc://host/path> assert_equals: href expected "abc:rootless" but got "abc://host/rootless"
+FAIL Parsing: <abc:rootless> against <abc:/path> assert_equals: href expected "abc:rootless" but got "abc:/rootless"
+PASS Parsing: <abc:rootless> against <abc:path>
+FAIL Parsing: <abc:/rooted> against <abc://host/path> assert_equals: href expected "abc:/rooted" but got "abc://host/rooted"
+FAIL Parsing: <http://1.2.3.4.5> against <http://other.com/> assert_equals: failure should set href to input expected "http://1.2.3.4.5" but got "http://1.2.3.4.5/"
+FAIL Parsing: <http://1.2.3.4.5.> against <http://other.com/> assert_equals: failure should set href to input expected "http://1.2.3.4.5." but got "http://1.2.3.4.5./"
+PASS Parsing: <http://0..0x300/> against <about:blank>
+PASS Parsing: <http://0..0x300./> against <about:blank>
+FAIL Parsing: <http://256.256.256.256.256> against <http://other.com/> assert_equals: failure should set href to input expected "http://256.256.256.256.256" but got "http://256.256.256.256.256/"
+FAIL Parsing: <http://256.256.256.256.256.> against <http://other.com/> assert_equals: failure should set href to input expected "http://256.256.256.256.256." but got "http://256.256.256.256.256./"
+FAIL Parsing: <http://1.2.3.08> against <about:blank> assert_equals: failure should set href to input expected "http://1.2.3.08" but got "http://1.2.3.08/"
+FAIL Parsing: <http://1.2.3.08.> against <about:blank> assert_equals: failure should set href to input expected "http://1.2.3.08." but got "http://1.2.3.08./"
+FAIL Parsing: <http://1.2.3.09> against <about:blank> assert_equals: failure should set href to input expected "http://1.2.3.09" but got "http://1.2.3.09/"
+FAIL Parsing: <http://09.2.3.4> against <about:blank> assert_equals: failure should set href to input expected "http://09.2.3.4" but got "http://09.2.3.4/"
+FAIL Parsing: <http://09.2.3.4.> against <about:blank> assert_equals: failure should set href to input expected "http://09.2.3.4." but got "http://09.2.3.4./"
+FAIL Parsing: <http://01.2.3.4.5> against <about:blank> assert_equals: failure should set href to input expected "http://01.2.3.4.5" but got "http://01.2.3.4.5/"
+FAIL Parsing: <http://01.2.3.4.5.> against <about:blank> assert_equals: failure should set href to input expected "http://01.2.3.4.5." but got "http://01.2.3.4.5./"
+FAIL Parsing: <http://0x100.2.3.4> against <about:blank> assert_equals: failure should set href to input expected "http://0x100.2.3.4" but got "http://0x100.2.3.4/"
+FAIL Parsing: <http://0x100.2.3.4.> against <about:blank> assert_equals: failure should set href to input expected "http://0x100.2.3.4." but got "http://0x100.2.3.4./"
+FAIL Parsing: <http://0x1.2.3.4.5> against <about:blank> assert_equals: failure should set href to input expected "http://0x1.2.3.4.5" but got "http://0x1.2.3.4.5/"
+FAIL Parsing: <http://0x1.2.3.4.5.> against <about:blank> assert_equals: failure should set href to input expected "http://0x1.2.3.4.5." but got "http://0x1.2.3.4.5./"
+FAIL Parsing: <http://foo.1.2.3.4> against <about:blank> assert_equals: failure should set href to input expected "http://foo.1.2.3.4" but got "http://foo.1.2.3.4/"
+FAIL Parsing: <http://foo.1.2.3.4.> against <about:blank> assert_equals: failure should set href to input expected "http://foo.1.2.3.4." but got "http://foo.1.2.3.4./"
+FAIL Parsing: <http://foo.2.3.4> against <about:blank> assert_equals: failure should set href to input expected "http://foo.2.3.4" but got "http://foo.2.3.4/"
+FAIL Parsing: <http://foo.2.3.4.> against <about:blank> assert_equals: failure should set href to input expected "http://foo.2.3.4." but got "http://foo.2.3.4./"
+FAIL Parsing: <http://foo.09> against <about:blank> assert_equals: failure should set href to input expected "http://foo.09" but got "http://foo.09/"
+FAIL Parsing: <http://foo.09.> against <about:blank> assert_equals: failure should set href to input expected "http://foo.09." but got "http://foo.09./"
+FAIL Parsing: <http://foo.0x4> against <about:blank> assert_equals: failure should set href to input expected "http://foo.0x4" but got "http://foo.0x4/"
+FAIL Parsing: <http://foo.0x4.> against <about:blank> assert_equals: failure should set href to input expected "http://foo.0x4." but got "http://foo.0x4./"
+PASS Parsing: <http://foo.09..> against <about:blank>
+PASS Parsing: <http://0999999999999999999/> against <about:blank>
+FAIL Parsing: <http://foo.0x> against <about:blank> assert_equals: failure should set href to input expected "http://foo.0x" but got "http://foo.0x/"
+FAIL Parsing: <http://foo.0XFfFfFfFfFfFfFfFfFfAcE123> against <about:blank> assert_equals: failure should set href to input expected "http://foo.0XFfFfFfFfFfFfFfFfFfAcE123" but got "http://foo.0xfffffffffffffffffface123/"
+FAIL Parsing: <http://💩.123/> against <about:blank> assert_equals: failure should set href to input expected "http://💩.123/" but got "http://xn--ls8h.123/"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/a-element-origin-expected.txt b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/a-element-origin-expected.txt
new file mode 100644
index 0000000..fb60c03b
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/a-element-origin-expected.txt
@@ -0,0 +1,342 @@
+This is a testharness.js-based test.
+Found 329 tests; 325 PASS, 4 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS Loading data…
+PASS Parsing origin: <http://example	.
+org> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://user:pass@foo:21/bar;par?b#c> against <http://example.org/foo/bar>
+PASS Parsing origin: <https://test:@test> against <about:blank>
+PASS Parsing origin: <https://:@test> against <about:blank>
+PASS Parsing origin: <non-special://test:@test/x> against <about:blank>
+PASS Parsing origin: <non-special://:@test/x> against <about:blank>
+PASS Parsing origin: <http:foo.com> against <http://example.org/foo/bar>
+PASS Parsing origin: <	   :foo.com   
+> against <http://example.org/foo/bar>
+PASS Parsing origin: < foo.com  > against <http://example.org/foo/bar>
+PASS Parsing origin: <a:	 foo.com> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://f:21/ b ? d # e > against <http://example.org/foo/bar>
+PASS Parsing origin: <http://f:/c> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://f:0/c> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://f:00000000000000/c> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://f:00000000000000000000080/c> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://f:
+/c> against <http://example.org/foo/bar>
+PASS Parsing origin: <> against <http://example.org/foo/bar>
+PASS Parsing origin: <  	> against <http://example.org/foo/bar>
+PASS Parsing origin: <:foo.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <:foo.com\> against <http://example.org/foo/bar>
+PASS Parsing origin: <:> against <http://example.org/foo/bar>
+PASS Parsing origin: <:a> against <http://example.org/foo/bar>
+PASS Parsing origin: <:/> against <http://example.org/foo/bar>
+PASS Parsing origin: <:\> against <http://example.org/foo/bar>
+PASS Parsing origin: <:#> against <http://example.org/foo/bar>
+PASS Parsing origin: <#> against <http://example.org/foo/bar>
+PASS Parsing origin: <#/> against <http://example.org/foo/bar>
+PASS Parsing origin: <#\> against <http://example.org/foo/bar>
+PASS Parsing origin: <#;?> against <http://example.org/foo/bar>
+PASS Parsing origin: <?> against <http://example.org/foo/bar>
+PASS Parsing origin: </> against <http://example.org/foo/bar>
+PASS Parsing origin: <:23> against <http://example.org/foo/bar>
+PASS Parsing origin: </:23> against <http://example.org/foo/bar>
+PASS Parsing origin: <\x> against <http://example.org/foo/bar>
+PASS Parsing origin: <\\x\hello> against <http://example.org/foo/bar>
+PASS Parsing origin: <::> against <http://example.org/foo/bar>
+PASS Parsing origin: <::23> against <http://example.org/foo/bar>
+PASS Parsing origin: <foo://> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://a:b@c:29/d> against <http://example.org/foo/bar>
+PASS Parsing origin: <http::@c:29> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://&a:foo(b]c@d:2/> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://::@c@d:2> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://foo.com:b@d/> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://foo.com/\@> against <http://example.org/foo/bar>
+PASS Parsing origin: <http:\\foo.com\> against <http://example.org/foo/bar>
+PASS Parsing origin: <http:\\a\b:c\d@foo.com\> against <http://example.org/foo/bar>
+PASS Parsing origin: <foo:/> against <http://example.org/foo/bar>
+PASS Parsing origin: <foo:/bar.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <foo://///////> against <http://example.org/foo/bar>
+PASS Parsing origin: <foo://///////bar.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <foo:////://///> against <http://example.org/foo/bar>
+PASS Parsing origin: <c:/foo> against <http://example.org/foo/bar>
+PASS Parsing origin: <//foo/bar> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://foo/path;a??e#f#g> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://foo/abcd?efgh?ijkl> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://foo/abcd#foo?bar> against <http://example.org/foo/bar>
+PASS Parsing origin: <[61:24:74]:98> against <http://example.org/foo/bar>
+PASS Parsing origin: <http:[61:27]/:foo> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://[2001::1]> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://[::127.0.0.1]> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://[0:0:0:0:0:0:13.1.68.3]> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://[2001::1]:80> against <http://example.org/foo/bar>
+PASS Parsing origin: <http:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <ftp:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <https:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <madeupscheme:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <ftps:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <gopher:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <ws:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <wss:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <data:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <javascript:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <mailto:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <http:example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <ftp:example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <https:example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <madeupscheme:example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <ftps:example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <gopher:example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <ws:example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <wss:example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <data:example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <javascript:example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <mailto:example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: </a/b/c> against <http://example.org/foo/bar>
+PASS Parsing origin: </a/ /c> against <http://example.org/foo/bar>
+PASS Parsing origin: </a%2fc> against <http://example.org/foo/bar>
+PASS Parsing origin: </a/%2f/c> against <http://example.org/foo/bar>
+PASS Parsing origin: <#β> against <http://example.org/foo/bar>
+PASS Parsing origin: <data:text/html,test#test> against <http://example.org/foo/bar>
+PASS Parsing origin: <tel:1234567890> against <http://example.org/foo/bar>
+PASS Parsing origin: <ssh://example.com/foo/bar.git> against <http://example.org/>
+PASS Parsing origin: <http://example.com/././foo> against <about:blank>
+PASS Parsing origin: <http://example.com/./.foo> against <about:blank>
+PASS Parsing origin: <http://example.com/foo/.> against <about:blank>
+PASS Parsing origin: <http://example.com/foo/./> against <about:blank>
+PASS Parsing origin: <http://example.com/foo/bar/..> against <about:blank>
+PASS Parsing origin: <http://example.com/foo/bar/../> against <about:blank>
+PASS Parsing origin: <http://example.com/foo/..bar> against <about:blank>
+PASS Parsing origin: <http://example.com/foo/bar/../ton> against <about:blank>
+PASS Parsing origin: <http://example.com/foo/bar/../ton/../../a> against <about:blank>
+PASS Parsing origin: <http://example.com/foo/../../..> against <about:blank>
+PASS Parsing origin: <http://example.com/foo/../../../ton> against <about:blank>
+PASS Parsing origin: <http://example.com/foo/%2e> against <about:blank>
+PASS Parsing origin: <http://example.com/foo/%2e%2> against <about:blank>
+PASS Parsing origin: <http://example.com/foo/%2e./%2e%2e/.%2e/%2e.bar> against <about:blank>
+PASS Parsing origin: <http://example.com////../..> against <about:blank>
+PASS Parsing origin: <http://example.com/foo/bar//../..> against <about:blank>
+PASS Parsing origin: <http://example.com/foo/bar//..> against <about:blank>
+PASS Parsing origin: <http://example.com/foo> against <about:blank>
+PASS Parsing origin: <http://example.com/%20foo> against <about:blank>
+PASS Parsing origin: <http://example.com/foo%> against <about:blank>
+PASS Parsing origin: <http://example.com/foo%2> against <about:blank>
+PASS Parsing origin: <http://example.com/foo%2zbar> against <about:blank>
+PASS Parsing origin: <http://example.com/foo%2©zbar> against <about:blank>
+PASS Parsing origin: <http://example.com/foo%41%7a> against <about:blank>
+PASS Parsing origin: <http://example.com/foo	‘%91> against <about:blank>
+FAIL Parsing origin: <http://example.com/foo%00%51> against <about:blank> assert_equals: origin expected "http://example.com" but got "null"
+PASS Parsing origin: <http://example.com/(%28:%3A%29)> against <about:blank>
+PASS Parsing origin: <http://example.com/%3A%3a%3C%3c> against <about:blank>
+PASS Parsing origin: <http://example.com/foo	bar> against <about:blank>
+PASS Parsing origin: <http://example.com\\foo\\bar> against <about:blank>
+PASS Parsing origin: <http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd> against <about:blank>
+PASS Parsing origin: <http://example.com/@asdf%40> against <about:blank>
+PASS Parsing origin: <http://example.com/你好你好> against <about:blank>
+PASS Parsing origin: <http://example.com/‥/foo> against <about:blank>
+PASS Parsing origin: <http://example.com//foo> against <about:blank>
+PASS Parsing origin: <http://example.com/‮/foo/‭/bar> against <about:blank>
+PASS Parsing origin: <http://www.google.com/foo?bar=baz#> against <about:blank>
+PASS Parsing origin: <http://www.google.com/foo?bar=baz# »> against <about:blank>
+PASS Parsing origin: <data:test# »> against <about:blank>
+PASS Parsing origin: <http://www.google.com> against <about:blank>
+PASS Parsing origin: <http://192.0x00A80001> against <about:blank>
+PASS Parsing origin: <http://www/foo%2Ehtml> against <about:blank>
+PASS Parsing origin: <http://www/foo/%2E/html> against <about:blank>
+PASS Parsing origin: <http://%25DOMAIN:foobar@foodomain.com/> against <about:blank>
+PASS Parsing origin: <http:\\www.google.com\foo> against <about:blank>
+PASS Parsing origin: <http://foo:80/> against <about:blank>
+PASS Parsing origin: <http://foo:81/> against <about:blank>
+PASS Parsing origin: <httpa://foo:80/> against <about:blank>
+PASS Parsing origin: <https://foo:443/> against <about:blank>
+PASS Parsing origin: <https://foo:80/> against <about:blank>
+PASS Parsing origin: <ftp://foo:21/> against <about:blank>
+PASS Parsing origin: <ftp://foo:80/> against <about:blank>
+PASS Parsing origin: <gopher://foo:70/> against <about:blank>
+PASS Parsing origin: <gopher://foo:443/> against <about:blank>
+PASS Parsing origin: <ws://foo:80/> against <about:blank>
+PASS Parsing origin: <ws://foo:81/> against <about:blank>
+PASS Parsing origin: <ws://foo:443/> against <about:blank>
+PASS Parsing origin: <ws://foo:815/> against <about:blank>
+PASS Parsing origin: <wss://foo:80/> against <about:blank>
+PASS Parsing origin: <wss://foo:81/> against <about:blank>
+PASS Parsing origin: <wss://foo:443/> against <about:blank>
+PASS Parsing origin: <wss://foo:815/> against <about:blank>
+PASS Parsing origin: <http:/example.com/> against <about:blank>
+PASS Parsing origin: <ftp:/example.com/> against <about:blank>
+PASS Parsing origin: <https:/example.com/> against <about:blank>
+PASS Parsing origin: <madeupscheme:/example.com/> against <about:blank>
+PASS Parsing origin: <ftps:/example.com/> against <about:blank>
+PASS Parsing origin: <gopher:/example.com/> against <about:blank>
+PASS Parsing origin: <ws:/example.com/> against <about:blank>
+PASS Parsing origin: <wss:/example.com/> against <about:blank>
+PASS Parsing origin: <data:/example.com/> against <about:blank>
+PASS Parsing origin: <javascript:/example.com/> against <about:blank>
+PASS Parsing origin: <mailto:/example.com/> against <about:blank>
+PASS Parsing origin: <http:example.com/> against <about:blank>
+PASS Parsing origin: <ftp:example.com/> against <about:blank>
+PASS Parsing origin: <https:example.com/> against <about:blank>
+PASS Parsing origin: <madeupscheme:example.com/> against <about:blank>
+PASS Parsing origin: <ftps:example.com/> against <about:blank>
+PASS Parsing origin: <gopher:example.com/> against <about:blank>
+PASS Parsing origin: <ws:example.com/> against <about:blank>
+PASS Parsing origin: <wss:example.com/> against <about:blank>
+PASS Parsing origin: <data:example.com/> against <about:blank>
+PASS Parsing origin: <javascript:example.com/> against <about:blank>
+PASS Parsing origin: <mailto:example.com/> against <about:blank>
+PASS Parsing origin: <http:@www.example.com> against <about:blank>
+PASS Parsing origin: <http:/@www.example.com> against <about:blank>
+PASS Parsing origin: <http://@www.example.com> against <about:blank>
+PASS Parsing origin: <http:a:b@www.example.com> against <about:blank>
+PASS Parsing origin: <http:/a:b@www.example.com> against <about:blank>
+PASS Parsing origin: <http://a:b@www.example.com> against <about:blank>
+PASS Parsing origin: <http://@pple.com> against <about:blank>
+PASS Parsing origin: <http::b@www.example.com> against <about:blank>
+PASS Parsing origin: <http:/:b@www.example.com> against <about:blank>
+PASS Parsing origin: <http://:b@www.example.com> against <about:blank>
+PASS Parsing origin: <http:a:@www.example.com> against <about:blank>
+PASS Parsing origin: <http:/a:@www.example.com> against <about:blank>
+PASS Parsing origin: <http://a:@www.example.com> against <about:blank>
+PASS Parsing origin: <http://www.@pple.com> against <about:blank>
+PASS Parsing origin: <http://:@www.example.com> against <about:blank>
+PASS Parsing origin: </> against <http://www.example.com/test>
+PASS Parsing origin: </test.txt> against <http://www.example.com/test>
+PASS Parsing origin: <.> against <http://www.example.com/test>
+PASS Parsing origin: <..> against <http://www.example.com/test>
+PASS Parsing origin: <test.txt> against <http://www.example.com/test>
+PASS Parsing origin: <./test.txt> against <http://www.example.com/test>
+PASS Parsing origin: <../test.txt> against <http://www.example.com/test>
+PASS Parsing origin: <../aaa/test.txt> against <http://www.example.com/test>
+PASS Parsing origin: <../../test.txt> against <http://www.example.com/test>
+PASS Parsing origin: <中/test.txt> against <http://www.example.com/test>
+PASS Parsing origin: <http://www.example2.com> against <http://www.example.com/test>
+PASS Parsing origin: <//www.example2.com> against <http://www.example.com/test>
+PASS Parsing origin: <http://ExAmPlE.CoM> against <http://other.com/>
+PASS Parsing origin: <http://GOO​⁠goo.com> against <http://other.com/>
+PASS Parsing origin: <\0 http://example.com/ \r > against <about:blank>
+PASS Parsing origin: <http://www.foo。bar.com> against <http://other.com/>
+PASS Parsing origin: <https://x/�?�#�> against <about:blank>
+PASS Parsing origin: <http://Go.com> against <http://other.com/>
+PASS Parsing origin: <http://你好你好> against <http://other.com/>
+PASS Parsing origin: <https://faß.ExAmPlE/> against <about:blank>
+PASS Parsing origin: <sc://faß.ExAmPlE/> against <about:blank>
+PASS Parsing origin: <http://%30%78%63%30%2e%30%32%35%30.01> against <http://other.com/>
+PASS Parsing origin: <http://%30%78%63%30%2e%30%32%35%30.01%2e> against <http://other.com/>
+PASS Parsing origin: <http://0Xc0.0250.01> against <http://other.com/>
+PASS Parsing origin: <http://./> against <about:blank>
+PASS Parsing origin: <http://../> against <about:blank>
+PASS Parsing origin: <http://foo:💩@example.com/bar> against <http://other.com/>
+PASS Parsing origin: <#> against <test:test>
+PASS Parsing origin: <#x> against <mailto:x@x.com>
+PASS Parsing origin: <#x> against <data:,>
+PASS Parsing origin: <#x> against <about:blank>
+PASS Parsing origin: <#> against <test:test?test>
+PASS Parsing origin: <https://@test@test@example:800/> against <http://doesnotmatter/>
+PASS Parsing origin: <https://@@@example> against <http://doesnotmatter/>
+PASS Parsing origin: <http://`{}:`{}@h/`{}?`{}> against <http://doesnotmatter/>
+PASS Parsing origin: <http://host/?'> against <about:blank>
+PASS Parsing origin: <notspecial://host/?'> against <about:blank>
+PASS Parsing origin: </some/path> against <http://user@example.org/smth>
+PASS Parsing origin: <> against <http://user:pass@example.org:21/smth>
+PASS Parsing origin: </some/path> against <http://user:pass@example.org:21/smth>
+PASS Parsing origin: <i> against <sc:/pa/pa>
+PASS Parsing origin: <i> against <sc://ho/pa>
+PASS Parsing origin: <i> against <sc:///pa/pa>
+PASS Parsing origin: <../i> against <sc:/pa/pa>
+PASS Parsing origin: <../i> against <sc://ho/pa>
+PASS Parsing origin: <../i> against <sc:///pa/pa>
+PASS Parsing origin: </i> against <sc:/pa/pa>
+PASS Parsing origin: </i> against <sc://ho/pa>
+PASS Parsing origin: </i> against <sc:///pa/pa>
+PASS Parsing origin: <?i> against <sc:/pa/pa>
+PASS Parsing origin: <?i> against <sc://ho/pa>
+PASS Parsing origin: <?i> against <sc:///pa/pa>
+PASS Parsing origin: <#i> against <sc:sd>
+PASS Parsing origin: <#i> against <sc:sd/sd>
+PASS Parsing origin: <#i> against <sc:/pa/pa>
+PASS Parsing origin: <#i> against <sc://ho/pa>
+PASS Parsing origin: <#i> against <sc:///pa/pa>
+PASS Parsing origin: <about:/../> against <about:blank>
+PASS Parsing origin: <data:/../> against <about:blank>
+PASS Parsing origin: <javascript:/../> against <about:blank>
+PASS Parsing origin: <mailto:/../> against <about:blank>
+PASS Parsing origin: <sc://ñ.test/> against <about:blank>
+PASS Parsing origin: <x> against <sc://ñ>
+PASS Parsing origin: <sc:\../> against <about:blank>
+PASS Parsing origin: <sc::a@example.net> against <about:blank>
+PASS Parsing origin: <wow:%NBD> against <about:blank>
+PASS Parsing origin: <wow:%1G> against <about:blank>
+PASS Parsing origin: <wow:￿> against <about:blank>
+FAIL Parsing origin: <http://example.com/U+d800𐟾U+dfff﷐﷏﷯ﷰ￾￿?U+d800𐟾U+dfff﷐﷏﷯ﷰ￾￿> against <about:blank> assert_equals: origin expected "http://example.com" but got "null"
+FAIL Parsing origin: <http://!"$&'()*+,-.;=_`{}~/> against <about:blank> assert_equals: origin expected "http://!\"$&'()*+,-.;=_`{}~" but got "null"
+PASS Parsing origin: <sc://!"$%&'()*+,-.;=_`{}~/> against <about:blank>
+PASS Parsing origin: <ftp://%e2%98%83> against <about:blank>
+PASS Parsing origin: <https://%e2%98%83> against <about:blank>
+PASS Parsing origin: <http://127.0.0.1:10100/relative_import.html> against <about:blank>
+PASS Parsing origin: <http://facebook.com/?foo=%7B%22abc%22> against <about:blank>
+PASS Parsing origin: <https://localhost:3000/jqueryui@1.2.3> against <about:blank>
+PASS Parsing origin: <h	t
+t\rp://h	o
+s\rt:9	0
+0\r0/p	a
+t\rh?q	u
+e\rry#f	r
+a\rg> against <about:blank>
+PASS Parsing origin: <?a=b&c=d> against <http://example.org/foo/bar>
+PASS Parsing origin: <??a=b&c=d> against <http://example.org/foo/bar>
+PASS Parsing origin: <http:> against <http://example.org/foo/bar>
+PASS Parsing origin: <sc:> against <https://example.org/foo/bar>
+PASS Parsing origin: <http://foo.bar/baz?qux#foobar> against <about:blank>
+PASS Parsing origin: <http://foo.bar/baz?qux#foo"bar> against <about:blank>
+PASS Parsing origin: <http://foo.bar/baz?qux#foo<bar> against <about:blank>
+PASS Parsing origin: <http://foo.bar/baz?qux#foo>bar> against <about:blank>
+PASS Parsing origin: <http://foo.bar/baz?qux#foo`bar> against <about:blank>
+PASS Parsing origin: <http://1.2.3.4/> against <http://other.com/>
+PASS Parsing origin: <http://1.2.3.4./> against <http://other.com/>
+PASS Parsing origin: <http://192.168.257> against <http://other.com/>
+PASS Parsing origin: <http://192.168.257.> against <http://other.com/>
+PASS Parsing origin: <http://192.168.257.com> against <http://other.com/>
+PASS Parsing origin: <http://256> against <http://other.com/>
+PASS Parsing origin: <http://256.com> against <http://other.com/>
+PASS Parsing origin: <http://999999999> against <http://other.com/>
+PASS Parsing origin: <http://999999999.> against <http://other.com/>
+PASS Parsing origin: <http://999999999.com> against <http://other.com/>
+PASS Parsing origin: <http://10000000000.com> against <http://other.com/>
+PASS Parsing origin: <http://4294967295> against <http://other.com/>
+PASS Parsing origin: <http://0xffffffff> against <http://other.com/>
+PASS Parsing origin: <https://0x.0x.0> against <about:blank>
+PASS Parsing origin: <asdf://%43%7C/> against <about:blank>
+PASS Parsing origin: <http://[1:0::]> against <http://example.net/>
+PASS Parsing origin: <sc://ñ> against <about:blank>
+PASS Parsing origin: <sc://ñ?x> against <about:blank>
+PASS Parsing origin: <sc://ñ#x> against <about:blank>
+PASS Parsing origin: <#x> against <sc://ñ>
+PASS Parsing origin: <?x> against <sc://ñ>
+PASS Parsing origin: <tftp://foobar.com/someconfig;mode=netascii> against <about:blank>
+PASS Parsing origin: <telnet://user:pass@foobar.com:23/> against <about:blank>
+PASS Parsing origin: <ut2004://10.10.10.10:7777/Index.ut2> against <about:blank>
+PASS Parsing origin: <redis://foo:bar@somehost:6379/0?baz=bam&qux=baz> against <about:blank>
+PASS Parsing origin: <rsync://foo@host:911/sup> against <about:blank>
+PASS Parsing origin: <git://github.com/foo/bar.git> against <about:blank>
+PASS Parsing origin: <irc://myserver.com:6999/channel?passwd> against <about:blank>
+PASS Parsing origin: <dns://fw.example.org:9999/foo.bar.org?type=TXT> against <about:blank>
+PASS Parsing origin: <ldap://localhost:389/ou=People,o=JNDITutorial> against <about:blank>
+PASS Parsing origin: <git+https://github.com/foo/bar> against <about:blank>
+PASS Parsing origin: <urn:ietf:rfc:2648> against <about:blank>
+PASS Parsing origin: <tag:joe@example.org,2001:foo/bar> against <about:blank>
+PASS Parsing origin: <blob:https://example.com:443/> against <about:blank>
+PASS Parsing origin: <blob:d3958f5c-0777-0845-9dcf-2cb28783acaf> against <about:blank>
+PASS Parsing origin: <blob:> against <about:blank>
+PASS Parsing origin: <non-special:cannot-be-a-base-url-\0~€> against <about:blank>
+PASS Parsing origin: <https://www.example.com/path{path.html?query'=query#fragment<fragment> against <about:blank>
+PASS Parsing origin: <https://user:pass[@foo/bar> against <http://example.org>
+PASS Parsing origin: <foo:// !"$%&'()*+,-.;<=>@[\]^_`{|}~@host/> against <about:blank>
+PASS Parsing origin: <wss:// !"$%&'()*+,-.;<=>@[]^_`{|}~@host/> against <about:blank>
+PASS Parsing origin: <foo://joe: !"$%&'()*+,-.:;<=>@[\]^_`{|}~@host/> against <about:blank>
+PASS Parsing origin: <wss://joe: !"$%&'()*+,-.:;<=>@[]^_`{|}~@host/> against <about:blank>
+PASS Parsing origin: <foo://!"$%&'()*+,-.;=_`{}~/> against <about:blank>
+FAIL Parsing origin: <wss://!"$&'()*+,-.;=_`{}~/> against <about:blank> assert_equals: origin expected "wss://!\"$&'()*+,-.;=_`{}~" but got "null"
+PASS Parsing origin: <foo://host/ !"$%&'()*+,-./:;<=>@[\]^_`{|}~> against <about:blank>
+PASS Parsing origin: <wss://host/ !"$%&'()*+,-./:;<=>@[\]^_`{|}~> against <about:blank>
+PASS Parsing origin: <foo://host/dir/? !"$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
+PASS Parsing origin: <wss://host/dir/? !"$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
+PASS Parsing origin: <foo://host/dir/# !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
+PASS Parsing origin: <wss://host/dir/# !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/a-element-origin-xhtml-expected.txt b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/a-element-origin-xhtml-expected.txt
new file mode 100644
index 0000000..fb60c03b
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/a-element-origin-xhtml-expected.txt
@@ -0,0 +1,342 @@
+This is a testharness.js-based test.
+Found 329 tests; 325 PASS, 4 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS Loading data…
+PASS Parsing origin: <http://example	.
+org> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://user:pass@foo:21/bar;par?b#c> against <http://example.org/foo/bar>
+PASS Parsing origin: <https://test:@test> against <about:blank>
+PASS Parsing origin: <https://:@test> against <about:blank>
+PASS Parsing origin: <non-special://test:@test/x> against <about:blank>
+PASS Parsing origin: <non-special://:@test/x> against <about:blank>
+PASS Parsing origin: <http:foo.com> against <http://example.org/foo/bar>
+PASS Parsing origin: <	   :foo.com   
+> against <http://example.org/foo/bar>
+PASS Parsing origin: < foo.com  > against <http://example.org/foo/bar>
+PASS Parsing origin: <a:	 foo.com> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://f:21/ b ? d # e > against <http://example.org/foo/bar>
+PASS Parsing origin: <http://f:/c> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://f:0/c> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://f:00000000000000/c> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://f:00000000000000000000080/c> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://f:
+/c> against <http://example.org/foo/bar>
+PASS Parsing origin: <> against <http://example.org/foo/bar>
+PASS Parsing origin: <  	> against <http://example.org/foo/bar>
+PASS Parsing origin: <:foo.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <:foo.com\> against <http://example.org/foo/bar>
+PASS Parsing origin: <:> against <http://example.org/foo/bar>
+PASS Parsing origin: <:a> against <http://example.org/foo/bar>
+PASS Parsing origin: <:/> against <http://example.org/foo/bar>
+PASS Parsing origin: <:\> against <http://example.org/foo/bar>
+PASS Parsing origin: <:#> against <http://example.org/foo/bar>
+PASS Parsing origin: <#> against <http://example.org/foo/bar>
+PASS Parsing origin: <#/> against <http://example.org/foo/bar>
+PASS Parsing origin: <#\> against <http://example.org/foo/bar>
+PASS Parsing origin: <#;?> against <http://example.org/foo/bar>
+PASS Parsing origin: <?> against <http://example.org/foo/bar>
+PASS Parsing origin: </> against <http://example.org/foo/bar>
+PASS Parsing origin: <:23> against <http://example.org/foo/bar>
+PASS Parsing origin: </:23> against <http://example.org/foo/bar>
+PASS Parsing origin: <\x> against <http://example.org/foo/bar>
+PASS Parsing origin: <\\x\hello> against <http://example.org/foo/bar>
+PASS Parsing origin: <::> against <http://example.org/foo/bar>
+PASS Parsing origin: <::23> against <http://example.org/foo/bar>
+PASS Parsing origin: <foo://> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://a:b@c:29/d> against <http://example.org/foo/bar>
+PASS Parsing origin: <http::@c:29> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://&a:foo(b]c@d:2/> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://::@c@d:2> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://foo.com:b@d/> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://foo.com/\@> against <http://example.org/foo/bar>
+PASS Parsing origin: <http:\\foo.com\> against <http://example.org/foo/bar>
+PASS Parsing origin: <http:\\a\b:c\d@foo.com\> against <http://example.org/foo/bar>
+PASS Parsing origin: <foo:/> against <http://example.org/foo/bar>
+PASS Parsing origin: <foo:/bar.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <foo://///////> against <http://example.org/foo/bar>
+PASS Parsing origin: <foo://///////bar.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <foo:////://///> against <http://example.org/foo/bar>
+PASS Parsing origin: <c:/foo> against <http://example.org/foo/bar>
+PASS Parsing origin: <//foo/bar> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://foo/path;a??e#f#g> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://foo/abcd?efgh?ijkl> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://foo/abcd#foo?bar> against <http://example.org/foo/bar>
+PASS Parsing origin: <[61:24:74]:98> against <http://example.org/foo/bar>
+PASS Parsing origin: <http:[61:27]/:foo> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://[2001::1]> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://[::127.0.0.1]> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://[0:0:0:0:0:0:13.1.68.3]> against <http://example.org/foo/bar>
+PASS Parsing origin: <http://[2001::1]:80> against <http://example.org/foo/bar>
+PASS Parsing origin: <http:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <ftp:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <https:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <madeupscheme:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <ftps:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <gopher:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <ws:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <wss:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <data:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <javascript:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <mailto:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <http:example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <ftp:example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <https:example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <madeupscheme:example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <ftps:example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <gopher:example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <ws:example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <wss:example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <data:example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <javascript:example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: <mailto:example.com/> against <http://example.org/foo/bar>
+PASS Parsing origin: </a/b/c> against <http://example.org/foo/bar>
+PASS Parsing origin: </a/ /c> against <http://example.org/foo/bar>
+PASS Parsing origin: </a%2fc> against <http://example.org/foo/bar>
+PASS Parsing origin: </a/%2f/c> against <http://example.org/foo/bar>
+PASS Parsing origin: <#β> against <http://example.org/foo/bar>
+PASS Parsing origin: <data:text/html,test#test> against <http://example.org/foo/bar>
+PASS Parsing origin: <tel:1234567890> against <http://example.org/foo/bar>
+PASS Parsing origin: <ssh://example.com/foo/bar.git> against <http://example.org/>
+PASS Parsing origin: <http://example.com/././foo> against <about:blank>
+PASS Parsing origin: <http://example.com/./.foo> against <about:blank>
+PASS Parsing origin: <http://example.com/foo/.> against <about:blank>
+PASS Parsing origin: <http://example.com/foo/./> against <about:blank>
+PASS Parsing origin: <http://example.com/foo/bar/..> against <about:blank>
+PASS Parsing origin: <http://example.com/foo/bar/../> against <about:blank>
+PASS Parsing origin: <http://example.com/foo/..bar> against <about:blank>
+PASS Parsing origin: <http://example.com/foo/bar/../ton> against <about:blank>
+PASS Parsing origin: <http://example.com/foo/bar/../ton/../../a> against <about:blank>
+PASS Parsing origin: <http://example.com/foo/../../..> against <about:blank>
+PASS Parsing origin: <http://example.com/foo/../../../ton> against <about:blank>
+PASS Parsing origin: <http://example.com/foo/%2e> against <about:blank>
+PASS Parsing origin: <http://example.com/foo/%2e%2> against <about:blank>
+PASS Parsing origin: <http://example.com/foo/%2e./%2e%2e/.%2e/%2e.bar> against <about:blank>
+PASS Parsing origin: <http://example.com////../..> against <about:blank>
+PASS Parsing origin: <http://example.com/foo/bar//../..> against <about:blank>
+PASS Parsing origin: <http://example.com/foo/bar//..> against <about:blank>
+PASS Parsing origin: <http://example.com/foo> against <about:blank>
+PASS Parsing origin: <http://example.com/%20foo> against <about:blank>
+PASS Parsing origin: <http://example.com/foo%> against <about:blank>
+PASS Parsing origin: <http://example.com/foo%2> against <about:blank>
+PASS Parsing origin: <http://example.com/foo%2zbar> against <about:blank>
+PASS Parsing origin: <http://example.com/foo%2©zbar> against <about:blank>
+PASS Parsing origin: <http://example.com/foo%41%7a> against <about:blank>
+PASS Parsing origin: <http://example.com/foo	‘%91> against <about:blank>
+FAIL Parsing origin: <http://example.com/foo%00%51> against <about:blank> assert_equals: origin expected "http://example.com" but got "null"
+PASS Parsing origin: <http://example.com/(%28:%3A%29)> against <about:blank>
+PASS Parsing origin: <http://example.com/%3A%3a%3C%3c> against <about:blank>
+PASS Parsing origin: <http://example.com/foo	bar> against <about:blank>
+PASS Parsing origin: <http://example.com\\foo\\bar> against <about:blank>
+PASS Parsing origin: <http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd> against <about:blank>
+PASS Parsing origin: <http://example.com/@asdf%40> against <about:blank>
+PASS Parsing origin: <http://example.com/你好你好> against <about:blank>
+PASS Parsing origin: <http://example.com/‥/foo> against <about:blank>
+PASS Parsing origin: <http://example.com//foo> against <about:blank>
+PASS Parsing origin: <http://example.com/‮/foo/‭/bar> against <about:blank>
+PASS Parsing origin: <http://www.google.com/foo?bar=baz#> against <about:blank>
+PASS Parsing origin: <http://www.google.com/foo?bar=baz# »> against <about:blank>
+PASS Parsing origin: <data:test# »> against <about:blank>
+PASS Parsing origin: <http://www.google.com> against <about:blank>
+PASS Parsing origin: <http://192.0x00A80001> against <about:blank>
+PASS Parsing origin: <http://www/foo%2Ehtml> against <about:blank>
+PASS Parsing origin: <http://www/foo/%2E/html> against <about:blank>
+PASS Parsing origin: <http://%25DOMAIN:foobar@foodomain.com/> against <about:blank>
+PASS Parsing origin: <http:\\www.google.com\foo> against <about:blank>
+PASS Parsing origin: <http://foo:80/> against <about:blank>
+PASS Parsing origin: <http://foo:81/> against <about:blank>
+PASS Parsing origin: <httpa://foo:80/> against <about:blank>
+PASS Parsing origin: <https://foo:443/> against <about:blank>
+PASS Parsing origin: <https://foo:80/> against <about:blank>
+PASS Parsing origin: <ftp://foo:21/> against <about:blank>
+PASS Parsing origin: <ftp://foo:80/> against <about:blank>
+PASS Parsing origin: <gopher://foo:70/> against <about:blank>
+PASS Parsing origin: <gopher://foo:443/> against <about:blank>
+PASS Parsing origin: <ws://foo:80/> against <about:blank>
+PASS Parsing origin: <ws://foo:81/> against <about:blank>
+PASS Parsing origin: <ws://foo:443/> against <about:blank>
+PASS Parsing origin: <ws://foo:815/> against <about:blank>
+PASS Parsing origin: <wss://foo:80/> against <about:blank>
+PASS Parsing origin: <wss://foo:81/> against <about:blank>
+PASS Parsing origin: <wss://foo:443/> against <about:blank>
+PASS Parsing origin: <wss://foo:815/> against <about:blank>
+PASS Parsing origin: <http:/example.com/> against <about:blank>
+PASS Parsing origin: <ftp:/example.com/> against <about:blank>
+PASS Parsing origin: <https:/example.com/> against <about:blank>
+PASS Parsing origin: <madeupscheme:/example.com/> against <about:blank>
+PASS Parsing origin: <ftps:/example.com/> against <about:blank>
+PASS Parsing origin: <gopher:/example.com/> against <about:blank>
+PASS Parsing origin: <ws:/example.com/> against <about:blank>
+PASS Parsing origin: <wss:/example.com/> against <about:blank>
+PASS Parsing origin: <data:/example.com/> against <about:blank>
+PASS Parsing origin: <javascript:/example.com/> against <about:blank>
+PASS Parsing origin: <mailto:/example.com/> against <about:blank>
+PASS Parsing origin: <http:example.com/> against <about:blank>
+PASS Parsing origin: <ftp:example.com/> against <about:blank>
+PASS Parsing origin: <https:example.com/> against <about:blank>
+PASS Parsing origin: <madeupscheme:example.com/> against <about:blank>
+PASS Parsing origin: <ftps:example.com/> against <about:blank>
+PASS Parsing origin: <gopher:example.com/> against <about:blank>
+PASS Parsing origin: <ws:example.com/> against <about:blank>
+PASS Parsing origin: <wss:example.com/> against <about:blank>
+PASS Parsing origin: <data:example.com/> against <about:blank>
+PASS Parsing origin: <javascript:example.com/> against <about:blank>
+PASS Parsing origin: <mailto:example.com/> against <about:blank>
+PASS Parsing origin: <http:@www.example.com> against <about:blank>
+PASS Parsing origin: <http:/@www.example.com> against <about:blank>
+PASS Parsing origin: <http://@www.example.com> against <about:blank>
+PASS Parsing origin: <http:a:b@www.example.com> against <about:blank>
+PASS Parsing origin: <http:/a:b@www.example.com> against <about:blank>
+PASS Parsing origin: <http://a:b@www.example.com> against <about:blank>
+PASS Parsing origin: <http://@pple.com> against <about:blank>
+PASS Parsing origin: <http::b@www.example.com> against <about:blank>
+PASS Parsing origin: <http:/:b@www.example.com> against <about:blank>
+PASS Parsing origin: <http://:b@www.example.com> against <about:blank>
+PASS Parsing origin: <http:a:@www.example.com> against <about:blank>
+PASS Parsing origin: <http:/a:@www.example.com> against <about:blank>
+PASS Parsing origin: <http://a:@www.example.com> against <about:blank>
+PASS Parsing origin: <http://www.@pple.com> against <about:blank>
+PASS Parsing origin: <http://:@www.example.com> against <about:blank>
+PASS Parsing origin: </> against <http://www.example.com/test>
+PASS Parsing origin: </test.txt> against <http://www.example.com/test>
+PASS Parsing origin: <.> against <http://www.example.com/test>
+PASS Parsing origin: <..> against <http://www.example.com/test>
+PASS Parsing origin: <test.txt> against <http://www.example.com/test>
+PASS Parsing origin: <./test.txt> against <http://www.example.com/test>
+PASS Parsing origin: <../test.txt> against <http://www.example.com/test>
+PASS Parsing origin: <../aaa/test.txt> against <http://www.example.com/test>
+PASS Parsing origin: <../../test.txt> against <http://www.example.com/test>
+PASS Parsing origin: <中/test.txt> against <http://www.example.com/test>
+PASS Parsing origin: <http://www.example2.com> against <http://www.example.com/test>
+PASS Parsing origin: <//www.example2.com> against <http://www.example.com/test>
+PASS Parsing origin: <http://ExAmPlE.CoM> against <http://other.com/>
+PASS Parsing origin: <http://GOO​⁠goo.com> against <http://other.com/>
+PASS Parsing origin: <\0 http://example.com/ \r > against <about:blank>
+PASS Parsing origin: <http://www.foo。bar.com> against <http://other.com/>
+PASS Parsing origin: <https://x/�?�#�> against <about:blank>
+PASS Parsing origin: <http://Go.com> against <http://other.com/>
+PASS Parsing origin: <http://你好你好> against <http://other.com/>
+PASS Parsing origin: <https://faß.ExAmPlE/> against <about:blank>
+PASS Parsing origin: <sc://faß.ExAmPlE/> against <about:blank>
+PASS Parsing origin: <http://%30%78%63%30%2e%30%32%35%30.01> against <http://other.com/>
+PASS Parsing origin: <http://%30%78%63%30%2e%30%32%35%30.01%2e> against <http://other.com/>
+PASS Parsing origin: <http://0Xc0.0250.01> against <http://other.com/>
+PASS Parsing origin: <http://./> against <about:blank>
+PASS Parsing origin: <http://../> against <about:blank>
+PASS Parsing origin: <http://foo:💩@example.com/bar> against <http://other.com/>
+PASS Parsing origin: <#> against <test:test>
+PASS Parsing origin: <#x> against <mailto:x@x.com>
+PASS Parsing origin: <#x> against <data:,>
+PASS Parsing origin: <#x> against <about:blank>
+PASS Parsing origin: <#> against <test:test?test>
+PASS Parsing origin: <https://@test@test@example:800/> against <http://doesnotmatter/>
+PASS Parsing origin: <https://@@@example> against <http://doesnotmatter/>
+PASS Parsing origin: <http://`{}:`{}@h/`{}?`{}> against <http://doesnotmatter/>
+PASS Parsing origin: <http://host/?'> against <about:blank>
+PASS Parsing origin: <notspecial://host/?'> against <about:blank>
+PASS Parsing origin: </some/path> against <http://user@example.org/smth>
+PASS Parsing origin: <> against <http://user:pass@example.org:21/smth>
+PASS Parsing origin: </some/path> against <http://user:pass@example.org:21/smth>
+PASS Parsing origin: <i> against <sc:/pa/pa>
+PASS Parsing origin: <i> against <sc://ho/pa>
+PASS Parsing origin: <i> against <sc:///pa/pa>
+PASS Parsing origin: <../i> against <sc:/pa/pa>
+PASS Parsing origin: <../i> against <sc://ho/pa>
+PASS Parsing origin: <../i> against <sc:///pa/pa>
+PASS Parsing origin: </i> against <sc:/pa/pa>
+PASS Parsing origin: </i> against <sc://ho/pa>
+PASS Parsing origin: </i> against <sc:///pa/pa>
+PASS Parsing origin: <?i> against <sc:/pa/pa>
+PASS Parsing origin: <?i> against <sc://ho/pa>
+PASS Parsing origin: <?i> against <sc:///pa/pa>
+PASS Parsing origin: <#i> against <sc:sd>
+PASS Parsing origin: <#i> against <sc:sd/sd>
+PASS Parsing origin: <#i> against <sc:/pa/pa>
+PASS Parsing origin: <#i> against <sc://ho/pa>
+PASS Parsing origin: <#i> against <sc:///pa/pa>
+PASS Parsing origin: <about:/../> against <about:blank>
+PASS Parsing origin: <data:/../> against <about:blank>
+PASS Parsing origin: <javascript:/../> against <about:blank>
+PASS Parsing origin: <mailto:/../> against <about:blank>
+PASS Parsing origin: <sc://ñ.test/> against <about:blank>
+PASS Parsing origin: <x> against <sc://ñ>
+PASS Parsing origin: <sc:\../> against <about:blank>
+PASS Parsing origin: <sc::a@example.net> against <about:blank>
+PASS Parsing origin: <wow:%NBD> against <about:blank>
+PASS Parsing origin: <wow:%1G> against <about:blank>
+PASS Parsing origin: <wow:￿> against <about:blank>
+FAIL Parsing origin: <http://example.com/U+d800𐟾U+dfff﷐﷏﷯ﷰ￾￿?U+d800𐟾U+dfff﷐﷏﷯ﷰ￾￿> against <about:blank> assert_equals: origin expected "http://example.com" but got "null"
+FAIL Parsing origin: <http://!"$&'()*+,-.;=_`{}~/> against <about:blank> assert_equals: origin expected "http://!\"$&'()*+,-.;=_`{}~" but got "null"
+PASS Parsing origin: <sc://!"$%&'()*+,-.;=_`{}~/> against <about:blank>
+PASS Parsing origin: <ftp://%e2%98%83> against <about:blank>
+PASS Parsing origin: <https://%e2%98%83> against <about:blank>
+PASS Parsing origin: <http://127.0.0.1:10100/relative_import.html> against <about:blank>
+PASS Parsing origin: <http://facebook.com/?foo=%7B%22abc%22> against <about:blank>
+PASS Parsing origin: <https://localhost:3000/jqueryui@1.2.3> against <about:blank>
+PASS Parsing origin: <h	t
+t\rp://h	o
+s\rt:9	0
+0\r0/p	a
+t\rh?q	u
+e\rry#f	r
+a\rg> against <about:blank>
+PASS Parsing origin: <?a=b&c=d> against <http://example.org/foo/bar>
+PASS Parsing origin: <??a=b&c=d> against <http://example.org/foo/bar>
+PASS Parsing origin: <http:> against <http://example.org/foo/bar>
+PASS Parsing origin: <sc:> against <https://example.org/foo/bar>
+PASS Parsing origin: <http://foo.bar/baz?qux#foobar> against <about:blank>
+PASS Parsing origin: <http://foo.bar/baz?qux#foo"bar> against <about:blank>
+PASS Parsing origin: <http://foo.bar/baz?qux#foo<bar> against <about:blank>
+PASS Parsing origin: <http://foo.bar/baz?qux#foo>bar> against <about:blank>
+PASS Parsing origin: <http://foo.bar/baz?qux#foo`bar> against <about:blank>
+PASS Parsing origin: <http://1.2.3.4/> against <http://other.com/>
+PASS Parsing origin: <http://1.2.3.4./> against <http://other.com/>
+PASS Parsing origin: <http://192.168.257> against <http://other.com/>
+PASS Parsing origin: <http://192.168.257.> against <http://other.com/>
+PASS Parsing origin: <http://192.168.257.com> against <http://other.com/>
+PASS Parsing origin: <http://256> against <http://other.com/>
+PASS Parsing origin: <http://256.com> against <http://other.com/>
+PASS Parsing origin: <http://999999999> against <http://other.com/>
+PASS Parsing origin: <http://999999999.> against <http://other.com/>
+PASS Parsing origin: <http://999999999.com> against <http://other.com/>
+PASS Parsing origin: <http://10000000000.com> against <http://other.com/>
+PASS Parsing origin: <http://4294967295> against <http://other.com/>
+PASS Parsing origin: <http://0xffffffff> against <http://other.com/>
+PASS Parsing origin: <https://0x.0x.0> against <about:blank>
+PASS Parsing origin: <asdf://%43%7C/> against <about:blank>
+PASS Parsing origin: <http://[1:0::]> against <http://example.net/>
+PASS Parsing origin: <sc://ñ> against <about:blank>
+PASS Parsing origin: <sc://ñ?x> against <about:blank>
+PASS Parsing origin: <sc://ñ#x> against <about:blank>
+PASS Parsing origin: <#x> against <sc://ñ>
+PASS Parsing origin: <?x> against <sc://ñ>
+PASS Parsing origin: <tftp://foobar.com/someconfig;mode=netascii> against <about:blank>
+PASS Parsing origin: <telnet://user:pass@foobar.com:23/> against <about:blank>
+PASS Parsing origin: <ut2004://10.10.10.10:7777/Index.ut2> against <about:blank>
+PASS Parsing origin: <redis://foo:bar@somehost:6379/0?baz=bam&qux=baz> against <about:blank>
+PASS Parsing origin: <rsync://foo@host:911/sup> against <about:blank>
+PASS Parsing origin: <git://github.com/foo/bar.git> against <about:blank>
+PASS Parsing origin: <irc://myserver.com:6999/channel?passwd> against <about:blank>
+PASS Parsing origin: <dns://fw.example.org:9999/foo.bar.org?type=TXT> against <about:blank>
+PASS Parsing origin: <ldap://localhost:389/ou=People,o=JNDITutorial> against <about:blank>
+PASS Parsing origin: <git+https://github.com/foo/bar> against <about:blank>
+PASS Parsing origin: <urn:ietf:rfc:2648> against <about:blank>
+PASS Parsing origin: <tag:joe@example.org,2001:foo/bar> against <about:blank>
+PASS Parsing origin: <blob:https://example.com:443/> against <about:blank>
+PASS Parsing origin: <blob:d3958f5c-0777-0845-9dcf-2cb28783acaf> against <about:blank>
+PASS Parsing origin: <blob:> against <about:blank>
+PASS Parsing origin: <non-special:cannot-be-a-base-url-\0~€> against <about:blank>
+PASS Parsing origin: <https://www.example.com/path{path.html?query'=query#fragment<fragment> against <about:blank>
+PASS Parsing origin: <https://user:pass[@foo/bar> against <http://example.org>
+PASS Parsing origin: <foo:// !"$%&'()*+,-.;<=>@[\]^_`{|}~@host/> against <about:blank>
+PASS Parsing origin: <wss:// !"$%&'()*+,-.;<=>@[]^_`{|}~@host/> against <about:blank>
+PASS Parsing origin: <foo://joe: !"$%&'()*+,-.:;<=>@[\]^_`{|}~@host/> against <about:blank>
+PASS Parsing origin: <wss://joe: !"$%&'()*+,-.:;<=>@[]^_`{|}~@host/> against <about:blank>
+PASS Parsing origin: <foo://!"$%&'()*+,-.;=_`{}~/> against <about:blank>
+FAIL Parsing origin: <wss://!"$&'()*+,-.;=_`{}~/> against <about:blank> assert_equals: origin expected "wss://!\"$&'()*+,-.;=_`{}~" but got "null"
+PASS Parsing origin: <foo://host/ !"$%&'()*+,-./:;<=>@[\]^_`{|}~> against <about:blank>
+PASS Parsing origin: <wss://host/ !"$%&'()*+,-./:;<=>@[\]^_`{|}~> against <about:blank>
+PASS Parsing origin: <foo://host/dir/? !"$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
+PASS Parsing origin: <wss://host/dir/? !"$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
+PASS Parsing origin: <foo://host/dir/# !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
+PASS Parsing origin: <wss://host/dir/# !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/a-element-xhtml-expected.txt b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/a-element-xhtml-expected.txt
new file mode 100644
index 0000000..88efd0c
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/a-element-xhtml-expected.txt
@@ -0,0 +1,752 @@
+This is a testharness.js-based test.
+Found 736 tests; 419 PASS, 317 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS Loading data…
+PASS Parsing: <http://example	.
+org> against <http://example.org/foo/bar>
+PASS Parsing: <http://user:pass@foo:21/bar;par?b#c> against <http://example.org/foo/bar>
+PASS Parsing: <https://test:@test> against <about:blank>
+PASS Parsing: <https://:@test> against <about:blank>
+FAIL Parsing: <non-special://test:@test/x> against <about:blank> assert_equals: href expected "non-special://test@test/x" but got "non-special://test:@test/x"
+FAIL Parsing: <non-special://:@test/x> against <about:blank> assert_equals: href expected "non-special://test/x" but got "non-special://:@test/x"
+PASS Parsing: <http:foo.com> against <http://example.org/foo/bar>
+PASS Parsing: <	   :foo.com   
+> against <http://example.org/foo/bar>
+PASS Parsing: < foo.com  > against <http://example.org/foo/bar>
+PASS Parsing: <a:	 foo.com> against <http://example.org/foo/bar>
+PASS Parsing: <http://f:21/ b ? d # e > against <http://example.org/foo/bar>
+PASS Parsing: <lolscheme:x x#x x> against <about:blank>
+PASS Parsing: <http://f:/c> against <http://example.org/foo/bar>
+PASS Parsing: <http://f:0/c> against <http://example.org/foo/bar>
+PASS Parsing: <http://f:00000000000000/c> against <http://example.org/foo/bar>
+PASS Parsing: <http://f:00000000000000000000080/c> against <http://example.org/foo/bar>
+PASS Parsing: <http://f:b/c> against <http://example.org/foo/bar>
+FAIL Parsing: <http://f: /c> against <http://example.org/foo/bar> assert_equals: failure should set href to input expected "http://f: /c" but got "http://f:%20/c"
+PASS Parsing: <http://f:
+/c> against <http://example.org/foo/bar>
+PASS Parsing: <http://f:fifty-two/c> against <http://example.org/foo/bar>
+PASS Parsing: <http://f:999999/c> against <http://example.org/foo/bar>
+FAIL Parsing: <non-special://f:999999/c> against <http://example.org/foo/bar> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://f: 21 / b ? d # e > against <http://example.org/foo/bar> assert_equals: failure should set href to input expected "http://f: 21 / b ? d # e " but got "http://f:%2021%20/%20b%20?%20d%20#%20e"
+PASS Parsing: <> against <http://example.org/foo/bar>
+PASS Parsing: <  	> against <http://example.org/foo/bar>
+PASS Parsing: <:foo.com/> against <http://example.org/foo/bar>
+PASS Parsing: <:foo.com\> against <http://example.org/foo/bar>
+PASS Parsing: <:> against <http://example.org/foo/bar>
+PASS Parsing: <:a> against <http://example.org/foo/bar>
+PASS Parsing: <:/> against <http://example.org/foo/bar>
+PASS Parsing: <:\> against <http://example.org/foo/bar>
+PASS Parsing: <:#> against <http://example.org/foo/bar>
+PASS Parsing: <#> against <http://example.org/foo/bar>
+PASS Parsing: <#/> against <http://example.org/foo/bar>
+PASS Parsing: <#\> against <http://example.org/foo/bar>
+PASS Parsing: <#;?> against <http://example.org/foo/bar>
+PASS Parsing: <?> against <http://example.org/foo/bar>
+PASS Parsing: </> against <http://example.org/foo/bar>
+PASS Parsing: <:23> against <http://example.org/foo/bar>
+PASS Parsing: </:23> against <http://example.org/foo/bar>
+PASS Parsing: <\x> against <http://example.org/foo/bar>
+PASS Parsing: <\\x\hello> against <http://example.org/foo/bar>
+PASS Parsing: <::> against <http://example.org/foo/bar>
+PASS Parsing: <::23> against <http://example.org/foo/bar>
+FAIL Parsing: <foo://> against <http://example.org/foo/bar> assert_equals: pathname expected "" but got "//"
+PASS Parsing: <http://a:b@c:29/d> against <http://example.org/foo/bar>
+PASS Parsing: <http::@c:29> against <http://example.org/foo/bar>
+PASS Parsing: <http://&a:foo(b]c@d:2/> against <http://example.org/foo/bar>
+PASS Parsing: <http://::@c@d:2> against <http://example.org/foo/bar>
+PASS Parsing: <http://foo.com:b@d/> against <http://example.org/foo/bar>
+PASS Parsing: <http://foo.com/\@> against <http://example.org/foo/bar>
+PASS Parsing: <http:\\foo.com\> against <http://example.org/foo/bar>
+PASS Parsing: <http:\\a\b:c\d@foo.com\> against <http://example.org/foo/bar>
+PASS Parsing: <foo:/> against <http://example.org/foo/bar>
+PASS Parsing: <foo:/bar.com/> against <http://example.org/foo/bar>
+FAIL Parsing: <foo://///////> against <http://example.org/foo/bar> assert_equals: pathname expected "///////" but got "/////////"
+FAIL Parsing: <foo://///////bar.com/> against <http://example.org/foo/bar> assert_equals: pathname expected "///////bar.com/" but got "/////////bar.com/"
+FAIL Parsing: <foo:////://///> against <http://example.org/foo/bar> assert_equals: pathname expected "//://///" but got "////://///"
+PASS Parsing: <c:/foo> against <http://example.org/foo/bar>
+PASS Parsing: <//foo/bar> against <http://example.org/foo/bar>
+PASS Parsing: <http://foo/path;a??e#f#g> against <http://example.org/foo/bar>
+PASS Parsing: <http://foo/abcd?efgh?ijkl> against <http://example.org/foo/bar>
+PASS Parsing: <http://foo/abcd#foo?bar> against <http://example.org/foo/bar>
+PASS Parsing: <[61:24:74]:98> against <http://example.org/foo/bar>
+PASS Parsing: <http:[61:27]/:foo> against <http://example.org/foo/bar>
+FAIL Parsing: <http://[1::2]:3:4> against <http://example.org/foo/bar> assert_equals: failure should set href to input expected "http://[1::2]:3:4" but got "http://[1::2]:3:4/"
+FAIL Parsing: <http://2001::1> against <http://example.org/foo/bar> assert_equals: failure should set href to input expected "http://2001::1" but got "http://2001::1/"
+FAIL Parsing: <http://2001::1]> against <http://example.org/foo/bar> assert_equals: failure should set href to input expected "http://2001::1]" but got "http://2001::1]/"
+FAIL Parsing: <http://2001::1]:80> against <http://example.org/foo/bar> assert_equals: failure should set href to input expected "http://2001::1]:80" but got "http://2001::1]/"
+PASS Parsing: <http://[2001::1]> against <http://example.org/foo/bar>
+PASS Parsing: <http://[::127.0.0.1]> against <http://example.org/foo/bar>
+PASS Parsing: <http://[0:0:0:0:0:0:13.1.68.3]> against <http://example.org/foo/bar>
+PASS Parsing: <http://[2001::1]:80> against <http://example.org/foo/bar>
+PASS Parsing: <http:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <ftp:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <https:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <madeupscheme:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <file:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <file://example:1/> against <about:blank>
+PASS Parsing: <file://example:test/> against <about:blank>
+FAIL Parsing: <file://example%/> against <about:blank> assert_equals: failure should set href to input expected "file://example%/" but got "file://example%25/"
+PASS Parsing: <file://[example]/> against <about:blank>
+PASS Parsing: <ftps:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <gopher:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <ws:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <wss:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <data:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <javascript:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <mailto:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <http:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <ftp:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <https:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <madeupscheme:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <ftps:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <gopher:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <ws:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <wss:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <data:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <javascript:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <mailto:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: </a/b/c> against <http://example.org/foo/bar>
+PASS Parsing: </a/ /c> against <http://example.org/foo/bar>
+PASS Parsing: </a%2fc> against <http://example.org/foo/bar>
+PASS Parsing: </a/%2f/c> against <http://example.org/foo/bar>
+PASS Parsing: <#β> against <http://example.org/foo/bar>
+PASS Parsing: <data:text/html,test#test> against <http://example.org/foo/bar>
+PASS Parsing: <tel:1234567890> against <http://example.org/foo/bar>
+FAIL Parsing: <ssh://example.com/foo/bar.git> against <http://example.org/> assert_equals: host expected "example.com" but got ""
+FAIL Parsing: <file:c:\foo\bar.html> against <file:///tmp/mock/path> assert_equals: href expected "file:///c:/foo/bar.html" but got "file:///tmp/mock/c:/foo/bar.html"
+FAIL Parsing: <  File:c|////foo\bar.html> against <file:///tmp/mock/path> assert_equals: href expected "file:///c:////foo/bar.html" but got "file:///tmp/mock/c%7C////foo/bar.html"
+FAIL Parsing: <C|/foo/bar> against <file:///tmp/mock/path> assert_equals: href expected "file:///C:/foo/bar" but got "file:///tmp/mock/C%7C/foo/bar"
+FAIL Parsing: </C|\foo\bar> against <file:///tmp/mock/path> assert_equals: href expected "file:///C:/foo/bar" but got "file:///C%7C/foo/bar"
+FAIL Parsing: <//C|/foo/bar> against <file:///tmp/mock/path> assert_equals: href expected "file:///C:/foo/bar" but got "file://c%7C/foo/bar"
+PASS Parsing: <//server/file> against <file:///tmp/mock/path>
+PASS Parsing: <\\server\file> against <file:///tmp/mock/path>
+PASS Parsing: </\server/file> against <file:///tmp/mock/path>
+PASS Parsing: <file:///foo/bar.txt> against <file:///tmp/mock/path>
+PASS Parsing: <file:///home/me> against <file:///tmp/mock/path>
+PASS Parsing: <//> against <file:///tmp/mock/path>
+PASS Parsing: <///> against <file:///tmp/mock/path>
+PASS Parsing: <///test> against <file:///tmp/mock/path>
+PASS Parsing: <file://test> against <file:///tmp/mock/path>
+FAIL Parsing: <file://localhost> against <file:///tmp/mock/path> assert_equals: href expected "file:///" but got "file://localhost/"
+FAIL Parsing: <file://localhost/> against <file:///tmp/mock/path> assert_equals: href expected "file:///" but got "file://localhost/"
+FAIL Parsing: <file://localhost/test> against <file:///tmp/mock/path> assert_equals: href expected "file:///test" but got "file://localhost/test"
+PASS Parsing: <test> against <file:///tmp/mock/path>
+PASS Parsing: <file:test> against <file:///tmp/mock/path>
+PASS Parsing: <http://example.com/././foo> against <about:blank>
+PASS Parsing: <http://example.com/./.foo> against <about:blank>
+PASS Parsing: <http://example.com/foo/.> against <about:blank>
+PASS Parsing: <http://example.com/foo/./> against <about:blank>
+PASS Parsing: <http://example.com/foo/bar/..> against <about:blank>
+PASS Parsing: <http://example.com/foo/bar/../> against <about:blank>
+PASS Parsing: <http://example.com/foo/..bar> against <about:blank>
+PASS Parsing: <http://example.com/foo/bar/../ton> against <about:blank>
+PASS Parsing: <http://example.com/foo/bar/../ton/../../a> against <about:blank>
+PASS Parsing: <http://example.com/foo/../../..> against <about:blank>
+PASS Parsing: <http://example.com/foo/../../../ton> against <about:blank>
+PASS Parsing: <http://example.com/foo/%2e> against <about:blank>
+FAIL Parsing: <http://example.com/foo/%2e%2> against <about:blank> assert_equals: href expected "http://example.com/foo/%2e%2" but got "http://example.com/foo/.%2"
+FAIL Parsing: <http://example.com/foo/%2e./%2e%2e/.%2e/%2e.bar> against <about:blank> assert_equals: href expected "http://example.com/%2e.bar" but got "http://example.com/..bar"
+PASS Parsing: <http://example.com////../..> against <about:blank>
+PASS Parsing: <http://example.com/foo/bar//../..> against <about:blank>
+PASS Parsing: <http://example.com/foo/bar//..> against <about:blank>
+PASS Parsing: <http://example.com/foo> against <about:blank>
+PASS Parsing: <http://example.com/%20foo> against <about:blank>
+PASS Parsing: <http://example.com/foo%> against <about:blank>
+PASS Parsing: <http://example.com/foo%2> against <about:blank>
+PASS Parsing: <http://example.com/foo%2zbar> against <about:blank>
+PASS Parsing: <http://example.com/foo%2©zbar> against <about:blank>
+FAIL Parsing: <http://example.com/foo%41%7a> against <about:blank> assert_equals: href expected "http://example.com/foo%41%7a" but got "http://example.com/fooAz"
+PASS Parsing: <http://example.com/foo	‘%91> against <about:blank>
+FAIL Parsing: <http://example.com/foo%00%51> against <about:blank> assert_equals: href expected "http://example.com/foo%00%51" but got "http://example.com/foo%00Q"
+PASS Parsing: <http://example.com/(%28:%3A%29)> against <about:blank>
+PASS Parsing: <http://example.com/%3A%3a%3C%3c> against <about:blank>
+PASS Parsing: <http://example.com/foo	bar> against <about:blank>
+PASS Parsing: <http://example.com\\foo\\bar> against <about:blank>
+PASS Parsing: <http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd> against <about:blank>
+PASS Parsing: <http://example.com/@asdf%40> against <about:blank>
+PASS Parsing: <http://example.com/你好你好> against <about:blank>
+PASS Parsing: <http://example.com/‥/foo> against <about:blank>
+PASS Parsing: <http://example.com//foo> against <about:blank>
+PASS Parsing: <http://example.com/‮/foo/‭/bar> against <about:blank>
+PASS Parsing: <http://www.google.com/foo?bar=baz#> against <about:blank>
+PASS Parsing: <http://www.google.com/foo?bar=baz# »> against <about:blank>
+PASS Parsing: <data:test# »> against <about:blank>
+PASS Parsing: <http://www.google.com> against <about:blank>
+PASS Parsing: <http://192.0x00A80001> against <about:blank>
+FAIL Parsing: <http://www/foo%2Ehtml> against <about:blank> assert_equals: href expected "http://www/foo%2Ehtml" but got "http://www/foo.html"
+PASS Parsing: <http://www/foo/%2E/html> against <about:blank>
+PASS Parsing: <http://user:pass@/> against <about:blank>
+PASS Parsing: <http://%25DOMAIN:foobar@foodomain.com/> against <about:blank>
+PASS Parsing: <http:\\www.google.com\foo> against <about:blank>
+PASS Parsing: <http://foo:80/> against <about:blank>
+PASS Parsing: <http://foo:81/> against <about:blank>
+FAIL Parsing: <httpa://foo:80/> against <about:blank> assert_equals: host expected "foo:80" but got ""
+PASS Parsing: <http://foo:-80/> against <about:blank>
+PASS Parsing: <https://foo:443/> against <about:blank>
+PASS Parsing: <https://foo:80/> against <about:blank>
+PASS Parsing: <ftp://foo:21/> against <about:blank>
+PASS Parsing: <ftp://foo:80/> against <about:blank>
+FAIL Parsing: <gopher://foo:70/> against <about:blank> assert_equals: host expected "foo:70" but got ""
+FAIL Parsing: <gopher://foo:443/> against <about:blank> assert_equals: host expected "foo:443" but got ""
+PASS Parsing: <ws://foo:80/> against <about:blank>
+PASS Parsing: <ws://foo:81/> against <about:blank>
+PASS Parsing: <ws://foo:443/> against <about:blank>
+PASS Parsing: <ws://foo:815/> against <about:blank>
+PASS Parsing: <wss://foo:80/> against <about:blank>
+PASS Parsing: <wss://foo:81/> against <about:blank>
+PASS Parsing: <wss://foo:443/> against <about:blank>
+PASS Parsing: <wss://foo:815/> against <about:blank>
+PASS Parsing: <http:/example.com/> against <about:blank>
+PASS Parsing: <ftp:/example.com/> against <about:blank>
+PASS Parsing: <https:/example.com/> against <about:blank>
+PASS Parsing: <madeupscheme:/example.com/> against <about:blank>
+PASS Parsing: <file:/example.com/> against <about:blank>
+PASS Parsing: <ftps:/example.com/> against <about:blank>
+PASS Parsing: <gopher:/example.com/> against <about:blank>
+PASS Parsing: <ws:/example.com/> against <about:blank>
+PASS Parsing: <wss:/example.com/> against <about:blank>
+PASS Parsing: <data:/example.com/> against <about:blank>
+PASS Parsing: <javascript:/example.com/> against <about:blank>
+PASS Parsing: <mailto:/example.com/> against <about:blank>
+PASS Parsing: <http:example.com/> against <about:blank>
+PASS Parsing: <ftp:example.com/> against <about:blank>
+PASS Parsing: <https:example.com/> against <about:blank>
+PASS Parsing: <madeupscheme:example.com/> against <about:blank>
+PASS Parsing: <ftps:example.com/> against <about:blank>
+PASS Parsing: <gopher:example.com/> against <about:blank>
+PASS Parsing: <ws:example.com/> against <about:blank>
+PASS Parsing: <wss:example.com/> against <about:blank>
+PASS Parsing: <data:example.com/> against <about:blank>
+PASS Parsing: <javascript:example.com/> against <about:blank>
+PASS Parsing: <mailto:example.com/> against <about:blank>
+PASS Parsing: <http:@www.example.com> against <about:blank>
+PASS Parsing: <http:/@www.example.com> against <about:blank>
+PASS Parsing: <http://@www.example.com> against <about:blank>
+PASS Parsing: <http:a:b@www.example.com> against <about:blank>
+PASS Parsing: <http:/a:b@www.example.com> against <about:blank>
+PASS Parsing: <http://a:b@www.example.com> against <about:blank>
+PASS Parsing: <http://@pple.com> against <about:blank>
+PASS Parsing: <http::b@www.example.com> against <about:blank>
+PASS Parsing: <http:/:b@www.example.com> against <about:blank>
+PASS Parsing: <http://:b@www.example.com> against <about:blank>
+FAIL Parsing: <http:/:@/www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http:/:@/www.example.com" but got "http:///www.example.com"
+PASS Parsing: <http://user@/www.example.com> against <about:blank>
+FAIL Parsing: <http:@/www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http:@/www.example.com" but got "http:///www.example.com"
+FAIL Parsing: <http:/@/www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http:/@/www.example.com" but got "http:///www.example.com"
+FAIL Parsing: <http://@/www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http://@/www.example.com" but got "http:///www.example.com"
+FAIL Parsing: <https:@/www.example.com> against <about:blank> assert_equals: failure should set href to input expected "https:@/www.example.com" but got "https:///www.example.com"
+FAIL Parsing: <http:a:b@/www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http:a:b@/www.example.com" but got "http://a:b@/www.example.com"
+FAIL Parsing: <http:/a:b@/www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http:/a:b@/www.example.com" but got "http://a:b@/www.example.com"
+PASS Parsing: <http://a:b@/www.example.com> against <about:blank>
+FAIL Parsing: <http::@/www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http::@/www.example.com" but got "http:///www.example.com"
+PASS Parsing: <http:a:@www.example.com> against <about:blank>
+PASS Parsing: <http:/a:@www.example.com> against <about:blank>
+PASS Parsing: <http://a:@www.example.com> against <about:blank>
+PASS Parsing: <http://www.@pple.com> against <about:blank>
+FAIL Parsing: <http:@:www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http:@:www.example.com" but got "http://:www.example.com/"
+FAIL Parsing: <http:/@:www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http:/@:www.example.com" but got "http://:www.example.com/"
+FAIL Parsing: <http://@:www.example.com> against <about:blank> assert_equals: failure should set href to input expected "http://@:www.example.com" but got "http://:www.example.com/"
+PASS Parsing: <http://:@www.example.com> against <about:blank>
+PASS Parsing: </> against <http://www.example.com/test>
+PASS Parsing: </test.txt> against <http://www.example.com/test>
+PASS Parsing: <.> against <http://www.example.com/test>
+PASS Parsing: <..> against <http://www.example.com/test>
+PASS Parsing: <test.txt> against <http://www.example.com/test>
+PASS Parsing: <./test.txt> against <http://www.example.com/test>
+PASS Parsing: <../test.txt> against <http://www.example.com/test>
+PASS Parsing: <../aaa/test.txt> against <http://www.example.com/test>
+PASS Parsing: <../../test.txt> against <http://www.example.com/test>
+PASS Parsing: <中/test.txt> against <http://www.example.com/test>
+PASS Parsing: <http://www.example2.com> against <http://www.example.com/test>
+PASS Parsing: <//www.example2.com> against <http://www.example.com/test>
+PASS Parsing: <file:...> against <http://www.example.com/test>
+PASS Parsing: <file:..> against <http://www.example.com/test>
+PASS Parsing: <file:a> against <http://www.example.com/test>
+PASS Parsing: <http://ExAmPlE.CoM> against <http://other.com/>
+FAIL Parsing: <http://example example.com> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://Goo%20 goo%7C|.com> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://[]> against <http://other.com/> assert_equals: failure should set href to input expected "http://[]" but got "http://[]/"
+FAIL Parsing: <http://[:]> against <http://other.com/> assert_equals: failure should set href to input expected "http://[:]" but got "http://[:]/"
+FAIL Parsing: <http://GOO  goo.com> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
+PASS Parsing: <http://GOO​⁠goo.com> against <http://other.com/>
+PASS Parsing: <\0 http://example.com/ \r > against <about:blank>
+PASS Parsing: <http://www.foo。bar.com> against <http://other.com/>
+FAIL Parsing: <http://﷐zyx.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://﷐zyx.com" but got "http://%EF%BF%BDzyx.com/"
+FAIL Parsing: <http://%ef%b7%90zyx.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%ef%b7%90zyx.com" but got "http://%EF%BF%BDzyx.com/"
+FAIL Parsing: <https://�> against <about:blank> assert_equals: failure should set href to input expected "https://\ufffd" but got "https://%EF%BF%BD/"
+FAIL Parsing: <https://%EF%BF%BD> against <about:blank> assert_equals: failure should set href to input expected "https://%EF%BF%BD" but got "https://%EF%BF%BD/"
+PASS Parsing: <https://x/�?�#�> against <about:blank>
+FAIL Parsing: <http://a.b.c.xn--pokxncvks> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://10.0.0.xn--pokxncvks> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://a.b.c.XN--pokxncvks> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://a.b.c.Xn--pokxncvks> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://10.0.0.XN--pokxncvks> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://10.0.0.xN--pokxncvks> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+PASS Parsing: <http://Go.com> against <http://other.com/>
+FAIL Parsing: <http://%41.com> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://%ef%bc%85%ef%bc%94%ef%bc%91.com> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://%00.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%00.com" but got "http://%00.com/"
+FAIL Parsing: <http://%ef%bc%85%ef%bc%90%ef%bc%90.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%ef%bc%85%ef%bc%90%ef%bc%90.com" but got "http://%00.com/"
+PASS Parsing: <http://你好你好> against <http://other.com/>
+PASS Parsing: <https://faß.ExAmPlE/> against <about:blank>
+FAIL Parsing: <sc://faß.ExAmPlE/> against <about:blank> assert_equals: host expected "fa%C3%9F.ExAmPlE" but got ""
+FAIL Parsing: <http://%zz%66%a.com> against <http://other.com/> assert_equals: failure should set href to input expected "http://%zz%66%a.com" but got "http://%25zzf%25a.com/"
+FAIL Parsing: <http://%25> against <http://other.com/> assert_equals: failure should set href to input expected "http://%25" but got "http://%25/"
+FAIL Parsing: <http://hello%00> against <http://other.com/> assert_equals: failure should set href to input expected "http://hello%00" but got "http://hello%00/"
+PASS Parsing: <http://%30%78%63%30%2e%30%32%35%30.01> against <http://other.com/>
+PASS Parsing: <http://%30%78%63%30%2e%30%32%35%30.01%2e> against <http://other.com/>
+FAIL Parsing: <http://192.168.0.257> against <http://other.com/> assert_equals: failure should set href to input expected "http://192.168.0.257" but got "http://192.168.0.257/"
+FAIL Parsing: <http://%3g%78%63%30%2e%30%32%35%30%2E.01> against <http://other.com/> assert_equals: failure should set href to input expected "http://%3g%78%63%30%2e%30%32%35%30%2E.01" but got "http://%253gxc0.0250..01/"
+FAIL Parsing: <http://192.168.0.1 hello> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <https://x x:12> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+PASS Parsing: <http://0Xc0.0250.01> against <http://other.com/>
+PASS Parsing: <http://./> against <about:blank>
+PASS Parsing: <http://../> against <about:blank>
+PASS Parsing: <http://[www.google.com]/> against <about:blank>
+FAIL Parsing: <http://[google.com]> against <http://other.com/> assert_equals: failure should set href to input expected "http://[google.com]" but got "http://[google.com]/"
+FAIL Parsing: <http://[::1.2.3.4x]> against <http://other.com/> assert_equals: failure should set href to input expected "http://[::1.2.3.4x]" but got "http://[::1.2.3.4x]/"
+FAIL Parsing: <http://[::1.2.3.]> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://[::1.2.]> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://[::1.]> against <http://other.com/> assert_unreached: Expected URL to fail parsing Reached unreachable code
+PASS Parsing: <http://foo:💩@example.com/bar> against <http://other.com/>
+PASS Parsing: <#> against <test:test>
+PASS Parsing: <#x> against <mailto:x@x.com>
+FAIL Parsing: <#x> against <data:,> assert_equals: href expected "data:,#x" but got "mailto:x@x.com#x"
+PASS Parsing: <#x> against <about:blank>
+PASS Parsing: <#> against <test:test?test>
+PASS Parsing: <https://@test@test@example:800/> against <http://doesnotmatter/>
+PASS Parsing: <https://@@@example> against <http://doesnotmatter/>
+PASS Parsing: <http://`{}:`{}@h/`{}?`{}> against <http://doesnotmatter/>
+PASS Parsing: <http://host/?'> against <about:blank>
+FAIL Parsing: <notspecial://host/?'> against <about:blank> assert_equals: href expected "notspecial://host/?'" but got "notspecial://host/?%27"
+PASS Parsing: </some/path> against <http://user@example.org/smth>
+PASS Parsing: <> against <http://user:pass@example.org:21/smth>
+PASS Parsing: </some/path> against <http://user:pass@example.org:21/smth>
+FAIL Parsing: <i> against <sc:sd> assert_equals: failure should set href to input expected "i" but got ""
+FAIL Parsing: <i> against <sc:sd/sd> assert_equals: failure should set href to input expected "i" but got ""
+PASS Parsing: <i> against <sc:/pa/pa>
+FAIL Parsing: <i> against <sc://ho/pa> assert_equals: host expected "ho" but got ""
+FAIL Parsing: <i> against <sc:///pa/pa> assert_equals: pathname expected "/pa/i" but got "///pa/i"
+FAIL Parsing: <../i> against <sc:sd> assert_equals: failure should set href to input expected "../i" but got ""
+FAIL Parsing: <../i> against <sc:sd/sd> assert_equals: failure should set href to input expected "../i" but got ""
+PASS Parsing: <../i> against <sc:/pa/pa>
+FAIL Parsing: <../i> against <sc://ho/pa> assert_equals: host expected "ho" but got ""
+FAIL Parsing: <../i> against <sc:///pa/pa> assert_equals: href expected "sc:///i" but got "sc:///pa/i"
+FAIL Parsing: </i> against <sc:sd> assert_equals: failure should set href to input expected "/i" but got ""
+FAIL Parsing: </i> against <sc:sd/sd> assert_equals: failure should set href to input expected "/i" but got ""
+PASS Parsing: </i> against <sc:/pa/pa>
+FAIL Parsing: </i> against <sc://ho/pa> assert_equals: host expected "ho" but got ""
+FAIL Parsing: </i> against <sc:///pa/pa> assert_equals: href expected "sc:///i" but got "sc:///pa/i"
+FAIL Parsing: <?i> against <sc:sd> assert_equals: failure should set href to input expected "?i" but got ""
+FAIL Parsing: <?i> against <sc:sd/sd> assert_equals: failure should set href to input expected "?i" but got ""
+PASS Parsing: <?i> against <sc:/pa/pa>
+FAIL Parsing: <?i> against <sc://ho/pa> assert_equals: host expected "ho" but got ""
+FAIL Parsing: <?i> against <sc:///pa/pa> assert_equals: pathname expected "/pa/pa" but got "///pa/pa"
+PASS Parsing: <#i> against <sc:sd>
+PASS Parsing: <#i> against <sc:sd/sd>
+PASS Parsing: <#i> against <sc:/pa/pa>
+FAIL Parsing: <#i> against <sc://ho/pa> assert_equals: host expected "ho" but got ""
+FAIL Parsing: <#i> against <sc:///pa/pa> assert_equals: pathname expected "/pa/pa" but got "///pa/pa"
+FAIL Parsing: <about:/../> against <about:blank> assert_equals: href expected "about:/" but got "about:/../"
+FAIL Parsing: <data:/../> against <about:blank> assert_equals: href expected "data:/" but got "data:/../"
+FAIL Parsing: <javascript:/../> against <about:blank> assert_equals: href expected "javascript:/" but got "javascript:/../"
+FAIL Parsing: <mailto:/../> against <about:blank> assert_equals: href expected "mailto:/" but got "mailto:/../"
+FAIL Parsing: <sc://ñ.test/> against <about:blank> assert_equals: host expected "%C3%B1.test" but got ""
+FAIL Parsing: <sc://%/> against <about:blank> assert_equals: host expected "%" but got ""
+FAIL Parsing: <sc://@/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <sc://te@s:t@/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <sc://:/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <sc://:12/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <x> against <sc://ñ> assert_equals: href expected "sc://%C3%B1/x" but got "sc://%C3%B1"
+PASS Parsing: <sc:\../> against <about:blank>
+PASS Parsing: <sc::a@example.net> against <about:blank>
+PASS Parsing: <wow:%NBD> against <about:blank>
+PASS Parsing: <wow:%1G> against <about:blank>
+FAIL Parsing: <wow:￿> against <about:blank> assert_equals: href expected "wow:%EF%BF%BF" but got "wow:%EF%BF%BD"
+FAIL Parsing: <http://example.com/U+d800𐟾U+dfff﷐﷏﷯ﷰ￾￿?U+d800𐟾U+dfff﷐﷏﷯ﷰ￾￿> against <about:blank> assert_equals: href expected "http://example.com/%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%B7%90%EF%B7%8F%EF%B7%AF%EF%B7%B0%EF%BF%BE%EF%BF%BF?%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%B7%90%EF%B7%8F%EF%B7%AF%EF%B7%B0%EF%BF%BE%EF%BF%BF" but got "http://example.com/%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%BF%BD%EF%B7%8F%EF%BF%BD%EF%B7%B0%EF%BF%BD%EF%BF%BD?%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%BF%BD%EF%B7%8F%EF%BF%BD%EF%B7%B0%EF%BF%BD%EF%BF%BD"
+FAIL Parsing: <sc://a\0b/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <sc://a b/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <sc://a<b> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <sc://a>b> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <sc://a[b/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <sc://a\b/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <sc://a]b/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <sc://a^b> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <sc://a|b/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <foo://ho	st/> against <about:blank> assert_equals: host expected "host" but got ""
+FAIL Parsing: <foo://ho
+st/> against <about:blank> assert_equals: host expected "host" but got ""
+FAIL Parsing: <foo://ho\rst/> against <about:blank> assert_equals: host expected "host" but got ""
+FAIL Parsing: <http://a\0b/> against <about:blank> assert_equals: failure should set href to input expected "http://a\0b/" but got "http://a%00b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x01b/" but got "http://a%01b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x02b/" but got "http://a%02b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x03b/" but got "http://a%03b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x04b/" but got "http://a%04b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x05b/" but got "http://a%05b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x06b/" but got "http://a%06b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x07b/" but got "http://a%07b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\bb/" but got "http://a%08b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\vb/" but got "http://a%0Bb/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\fb/" but got "http://a%0Cb/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x0eb/" but got "http://a%0Eb/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x0fb/" but got "http://a%0Fb/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x10b/" but got "http://a%10b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x11b/" but got "http://a%11b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x12b/" but got "http://a%12b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x13b/" but got "http://a%13b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x14b/" but got "http://a%14b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x15b/" but got "http://a%15b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x16b/" but got "http://a%16b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x17b/" but got "http://a%17b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x18b/" but got "http://a%18b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x19b/" but got "http://a%19b/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x1ab/" but got "http://a%1Ab/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x1bb/" but got "http://a%1Bb/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x1cb/" but got "http://a%1Cb/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x1db/" but got "http://a%1Db/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x1eb/" but got "http://a%1Eb/"
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://a\x1fb/" but got "http://a%1Fb/"
+FAIL Parsing: <http://a b/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://a%b/> against <about:blank> assert_equals: failure should set href to input expected "http://a%b/" but got "http://a%25b/"
+FAIL Parsing: <http://a<b> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://a>b> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+PASS Parsing: <http://a[b/> against <about:blank>
+PASS Parsing: <http://a]b/> against <about:blank>
+FAIL Parsing: <http://a^b> against <about:blank> assert_equals: failure should set href to input expected "http://a^b" but got "http://a%5Eb/"
+FAIL Parsing: <http://a|b/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://ab/> against <about:blank> assert_equals: failure should set href to input expected "http://ab/" but got "http://a%7Fb/"
+PASS Parsing: <http://ho	st/> against <about:blank>
+PASS Parsing: <http://ho
+st/> against <about:blank>
+PASS Parsing: <http://ho\rst/> against <about:blank>
+PASS Parsing: <http://ho%00st/> against <about:blank>
+PASS Parsing: <http://ho%01st/> against <about:blank>
+PASS Parsing: <http://ho%02st/> against <about:blank>
+PASS Parsing: <http://ho%03st/> against <about:blank>
+PASS Parsing: <http://ho%04st/> against <about:blank>
+PASS Parsing: <http://ho%05st/> against <about:blank>
+PASS Parsing: <http://ho%06st/> against <about:blank>
+PASS Parsing: <http://ho%07st/> against <about:blank>
+PASS Parsing: <http://ho%08st/> against <about:blank>
+PASS Parsing: <http://ho%09st/> against <about:blank>
+PASS Parsing: <http://ho%0Ast/> against <about:blank>
+PASS Parsing: <http://ho%0Bst/> against <about:blank>
+PASS Parsing: <http://ho%0Cst/> against <about:blank>
+PASS Parsing: <http://ho%0Dst/> against <about:blank>
+PASS Parsing: <http://ho%0Est/> against <about:blank>
+PASS Parsing: <http://ho%0Fst/> against <about:blank>
+PASS Parsing: <http://ho%10st/> against <about:blank>
+PASS Parsing: <http://ho%11st/> against <about:blank>
+PASS Parsing: <http://ho%12st/> against <about:blank>
+PASS Parsing: <http://ho%13st/> against <about:blank>
+PASS Parsing: <http://ho%14st/> against <about:blank>
+PASS Parsing: <http://ho%15st/> against <about:blank>
+PASS Parsing: <http://ho%16st/> against <about:blank>
+PASS Parsing: <http://ho%17st/> against <about:blank>
+PASS Parsing: <http://ho%18st/> against <about:blank>
+PASS Parsing: <http://ho%19st/> against <about:blank>
+PASS Parsing: <http://ho%1Ast/> against <about:blank>
+PASS Parsing: <http://ho%1Bst/> against <about:blank>
+PASS Parsing: <http://ho%1Cst/> against <about:blank>
+PASS Parsing: <http://ho%1Dst/> against <about:blank>
+PASS Parsing: <http://ho%1Est/> against <about:blank>
+PASS Parsing: <http://ho%1Fst/> against <about:blank>
+FAIL Parsing: <http://ho%20st/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://ho%23st/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+PASS Parsing: <http://ho%25st/> against <about:blank>
+PASS Parsing: <http://ho%2Fst/> against <about:blank>
+FAIL Parsing: <http://ho%3Ast/> against <about:blank> assert_equals: failure should set href to input expected "http://ho%3Ast/" but got "http://ho:st/"
+FAIL Parsing: <http://ho%3Cst/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://ho%3Est/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+PASS Parsing: <http://ho%3Fst/> against <about:blank>
+FAIL Parsing: <http://ho%40st/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <http://ho%5Bst/> against <about:blank> assert_equals: failure should set href to input expected "http://ho%5Bst/" but got "http://ho[st/"
+PASS Parsing: <http://ho%5Cst/> against <about:blank>
+FAIL Parsing: <http://ho%5Dst/> against <about:blank> assert_equals: failure should set href to input expected "http://ho%5Dst/" but got "http://ho]st/"
+FAIL Parsing: <http://ho%7Cst/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+PASS Parsing: <http://ho%7Fst/> against <about:blank>
+FAIL Parsing: <http://!"$&'()*+,-.;=_`{}~/> against <about:blank> assert_equals: href expected "http://!\"$&'()*+,-.;=_`{}~/" but got "http://%21%22%24%26%27%28%29%2A+%2C-.%3B%3D_%60%7B%7D%7E/"
+FAIL Parsing: <sc://!"$%&'()*+,-.;=_`{}~/> against <about:blank> assert_equals: host expected "%01%02%03%04%05%06%07%08%0B%0C%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%7F!\"$%&'()*+,-.;=_`{}~" but got ""
+FAIL Parsing: <ftp://example.com%80/> against <about:blank> assert_equals: failure should set href to input expected "ftp://example.com%80/" but got "ftp://example.com%EF%BF%BD/"
+FAIL Parsing: <ftp://example.com%A0/> against <about:blank> assert_equals: failure should set href to input expected "ftp://example.com%A0/" but got "ftp://example.com%EF%BF%BD/"
+FAIL Parsing: <https://example.com%80/> against <about:blank> assert_equals: failure should set href to input expected "https://example.com%80/" but got "https://example.com%EF%BF%BD/"
+FAIL Parsing: <https://example.com%A0/> against <about:blank> assert_equals: failure should set href to input expected "https://example.com%A0/" but got "https://example.com%EF%BF%BD/"
+PASS Parsing: <ftp://%e2%98%83> against <about:blank>
+PASS Parsing: <https://%e2%98%83> against <about:blank>
+PASS Parsing: <http://127.0.0.1:10100/relative_import.html> against <about:blank>
+PASS Parsing: <http://facebook.com/?foo=%7B%22abc%22> against <about:blank>
+PASS Parsing: <https://localhost:3000/jqueryui@1.2.3> against <about:blank>
+PASS Parsing: <h	t
+t\rp://h	o
+s\rt:9	0
+0\r0/p	a
+t\rh?q	u
+e\rry#f	r
+a\rg> against <about:blank>
+PASS Parsing: <?a=b&c=d> against <http://example.org/foo/bar>
+PASS Parsing: <??a=b&c=d> against <http://example.org/foo/bar>
+PASS Parsing: <http:> against <http://example.org/foo/bar>
+PASS Parsing: <http:> against <https://example.org/foo/bar>
+PASS Parsing: <sc:> against <https://example.org/foo/bar>
+PASS Parsing: <http://foo.bar/baz?qux#foobar> against <about:blank>
+PASS Parsing: <http://foo.bar/baz?qux#foo"bar> against <about:blank>
+PASS Parsing: <http://foo.bar/baz?qux#foo<bar> against <about:blank>
+PASS Parsing: <http://foo.bar/baz?qux#foo>bar> against <about:blank>
+PASS Parsing: <http://foo.bar/baz?qux#foo`bar> against <about:blank>
+PASS Parsing: <http://1.2.3.4/> against <http://other.com/>
+PASS Parsing: <http://1.2.3.4./> against <http://other.com/>
+PASS Parsing: <http://192.168.257> against <http://other.com/>
+PASS Parsing: <http://192.168.257.> against <http://other.com/>
+PASS Parsing: <http://192.168.257.com> against <http://other.com/>
+PASS Parsing: <http://256> against <http://other.com/>
+PASS Parsing: <http://256.com> against <http://other.com/>
+PASS Parsing: <http://999999999> against <http://other.com/>
+PASS Parsing: <http://999999999.> against <http://other.com/>
+PASS Parsing: <http://999999999.com> against <http://other.com/>
+FAIL Parsing: <http://10000000000> against <http://other.com/> assert_equals: failure should set href to input expected "http://10000000000" but got "http://10000000000/"
+PASS Parsing: <http://10000000000.com> against <http://other.com/>
+PASS Parsing: <http://4294967295> against <http://other.com/>
+FAIL Parsing: <http://4294967296> against <http://other.com/> assert_equals: failure should set href to input expected "http://4294967296" but got "http://4294967296/"
+PASS Parsing: <http://0xffffffff> against <http://other.com/>
+FAIL Parsing: <http://0xffffffff1> against <http://other.com/> assert_equals: failure should set href to input expected "http://0xffffffff1" but got "http://0xffffffff1/"
+FAIL Parsing: <http://256.256.256.256> against <http://other.com/> assert_equals: failure should set href to input expected "http://256.256.256.256" but got "http://256.256.256.256/"
+PASS Parsing: <https://0x.0x.0> against <about:blank>
+PASS Parsing: <https://0x100000000/test> against <about:blank>
+PASS Parsing: <https://256.0.0.1/test> against <about:blank>
+PASS Parsing: <file:///C%3A/> against <about:blank>
+PASS Parsing: <file:///C%7C/> against <about:blank>
+FAIL Parsing: <file://%43%3A> against <about:blank> assert_equals: failure should set href to input expected "file://%43%3A" but got "file://c:/"
+FAIL Parsing: <file://%43%7C> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <file://%43|> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <file://C%7C> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <file://%43%7C/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <https://%43%7C/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <asdf://%43|/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+FAIL Parsing: <asdf://%43%7C/> against <about:blank> assert_equals: host expected "%43%7C" but got ""
+PASS Parsing: <pix/submit.gif> against <file:///C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/anchor.html>
+FAIL Parsing: <..> against <file:///C:/> assert_equals: href expected "file:///C:/" but got "file:///"
+PASS Parsing: <..> against <file:///>
+FAIL Parsing: </> against <file:///C:/a/b> assert_equals: href expected "file:///C:/" but got "file:///"
+FAIL Parsing: </> against <file://h/C:/a/b> assert_equals: href expected "file://h/C:/" but got "file://h/"
+PASS Parsing: </> against <file://h/a/b>
+FAIL Parsing: <//d:> against <file:///C:/a/b> assert_equals: href expected "file:///d:" but got "file://d:/"
+FAIL Parsing: <//d:/..> against <file:///C:/a/b> assert_equals: href expected "file:///d:/" but got "file://d:/"
+PASS Parsing: <..> against <file:///ab:/>
+PASS Parsing: <..> against <file:///1:/>
+PASS Parsing: <> against <file:///test?test#test>
+PASS Parsing: <file:> against <file:///test?test#test>
+PASS Parsing: <?x> against <file:///test?test#test>
+PASS Parsing: <file:?x> against <file:///test?test#test>
+PASS Parsing: <#x> against <file:///test?test#test>
+PASS Parsing: <file:#x> against <file:///test?test#test>
+FAIL Parsing: <file:\\//> against <about:blank> assert_equals: href expected "file:////" but got "file:///"
+FAIL Parsing: <file:\\\\> against <about:blank> assert_equals: href expected "file:////" but got "file:///"
+FAIL Parsing: <file:\\\\?fox> against <about:blank> assert_equals: href expected "file:////?fox" but got "file:///?fox"
+FAIL Parsing: <file:\\\\#guppy> against <about:blank> assert_equals: href expected "file:////#guppy" but got "file:///#guppy"
+PASS Parsing: <file://spider///> against <about:blank>
+FAIL Parsing: <file:\\localhost//> against <about:blank> assert_equals: href expected "file:////" but got "file://localhost//"
+PASS Parsing: <file:///localhost//cat> against <about:blank>
+FAIL Parsing: <file://\/localhost//cat> against <about:blank> assert_equals: href expected "file:////localhost//cat" but got "file:///localhost//cat"
+FAIL Parsing: <file://localhost//a//../..//> against <about:blank> assert_equals: href expected "file://///" but got "file://localhost///"
+FAIL Parsing: </////mouse> against <file:///elephant> assert_equals: href expected "file://///mouse" but got "file:///mouse"
+PASS Parsing: <\//pig> against <file://lion/>
+FAIL Parsing: <\/localhost//pig> against <file://lion/> assert_equals: href expected "file:////pig" but got "file://localhost//pig"
+FAIL Parsing: <//localhost//pig> against <file://lion/> assert_equals: href expected "file:////pig" but got "file://localhost//pig"
+PASS Parsing: </..//localhost//pig> against <file://lion/>
+PASS Parsing: <file://> against <file://ape/>
+PASS Parsing: </rooibos> against <file://tea/>
+PASS Parsing: </?chai> against <file://tea/>
+FAIL Parsing: <C|> against <file://host/dir/file> assert_equals: href expected "file://host/C:" but got "file://host/dir/C%7C"
+FAIL Parsing: <C|> against <file://host/D:/dir1/dir2/file> assert_equals: href expected "file://host/C:" but got "file://host/D:/dir1/dir2/C%7C"
+FAIL Parsing: <C|#> against <file://host/dir/file> assert_equals: href expected "file://host/C:#" but got "file://host/dir/C%7C#"
+FAIL Parsing: <C|?> against <file://host/dir/file> assert_equals: href expected "file://host/C:?" but got "file://host/dir/C%7C?"
+FAIL Parsing: <C|/> against <file://host/dir/file> assert_equals: href expected "file://host/C:/" but got "file://host/dir/C%7C/"
+FAIL Parsing: <C|
+/> against <file://host/dir/file> assert_equals: href expected "file://host/C:/" but got "file://host/dir/C%7C/"
+FAIL Parsing: <C|\> against <file://host/dir/file> assert_equals: href expected "file://host/C:/" but got "file://host/dir/C%7C/"
+PASS Parsing: <C> against <file://host/dir/file>
+FAIL Parsing: <C|a> against <file://host/dir/file> assert_equals: href expected "file://host/dir/C|a" but got "file://host/dir/C%7Ca"
+PASS Parsing: </c:/foo/bar> against <file:///c:/baz/qux>
+FAIL Parsing: </c|/foo/bar> against <file:///c:/baz/qux> assert_equals: href expected "file:///c:/foo/bar" but got "file:///c%7C/foo/bar"
+PASS Parsing: <file:\c:\foo\bar> against <file:///c:/baz/qux>
+PASS Parsing: </c:/foo/bar> against <file://host/path>
+PASS Parsing: <file://example.net/C:/> against <about:blank>
+PASS Parsing: <file://1.2.3.4/C:/> against <about:blank>
+PASS Parsing: <file://[1::8]/C:/> against <about:blank>
+FAIL Parsing: <C|/> against <file://host/> assert_equals: href expected "file://host/C:/" but got "file://host/C%7C/"
+PASS Parsing: </C:/> against <file://host/>
+PASS Parsing: <file:C:/> against <file://host/>
+PASS Parsing: <file:/C:/> against <file://host/>
+FAIL Parsing: <//C:/> against <file://host/> assert_equals: href expected "file:///C:/" but got "file://c:/"
+FAIL Parsing: <file://C:/> against <file://host/> assert_equals: href expected "file:///C:/" but got "file://c:/"
+PASS Parsing: <///C:/> against <file://host/>
+PASS Parsing: <file:///C:/> against <file://host/>
+FAIL Parsing: <file:/C|/> against <about:blank> assert_equals: href expected "file:///C:/" but got "file:///C%7C/"
+FAIL Parsing: <file://C|/> against <about:blank> assert_equals: href expected "file:///C:/" but got "file://c%7C/"
+PASS Parsing: <file:> against <about:blank>
+PASS Parsing: <file:?q=v> against <about:blank>
+PASS Parsing: <file:#frag> against <about:blank>
+PASS Parsing: <file:///Y:> against <about:blank>
+PASS Parsing: <file:///Y:/> against <about:blank>
+PASS Parsing: <file:///./Y> against <about:blank>
+PASS Parsing: <file:///./Y:> against <about:blank>
+FAIL Parsing: <\\\.\Y:> against <about:blank> assert_equals: failure should set href to input expected "\\\\\\.\\Y:" but got ""
+PASS Parsing: <file:///y:> against <about:blank>
+PASS Parsing: <file:///y:/> against <about:blank>
+PASS Parsing: <file:///./y> against <about:blank>
+PASS Parsing: <file:///./y:> against <about:blank>
+FAIL Parsing: <\\\.\y:> against <about:blank> assert_equals: failure should set href to input expected "\\\\\\.\\y:" but got ""
+FAIL Parsing: <file://localhost//a//../..//foo> against <about:blank> assert_equals: href expected "file://///foo" but got "file://localhost///foo"
+FAIL Parsing: <file://localhost////foo> against <about:blank> assert_equals: href expected "file://////foo" but got "file://localhost////foo"
+FAIL Parsing: <file:////foo> against <about:blank> assert_equals: href expected "file:////foo" but got "file:///foo"
+PASS Parsing: <file:///one/two> against <file:///>
+FAIL Parsing: <file:////one/two> against <file:///> assert_equals: href expected "file:////one/two" but got "file:///one/two"
+PASS Parsing: <//one/two> against <file:///>
+PASS Parsing: <///one/two> against <file:///>
+FAIL Parsing: <////one/two> against <file:///> assert_equals: href expected "file:////one/two" but got "file:///one/two"
+PASS Parsing: <file:///.//> against <file:////>
+PASS Parsing: <file:.//p> against <about:blank>
+PASS Parsing: <file:/.//p> against <about:blank>
+PASS Parsing: <http://[1:0::]> against <http://example.net/>
+FAIL Parsing: <http://[0:1:2:3:4:5:6:7:8]> against <http://example.net/> assert_equals: failure should set href to input expected "http://[0:1:2:3:4:5:6:7:8]" but got "http://[0:1:2:3:4:5:6:7:8]/"
+FAIL Parsing: <https://[0::0::0]> against <about:blank> assert_equals: failure should set href to input expected "https://[0::0::0]" but got "https://[0::0::0]/"
+FAIL Parsing: <https://[0:.0]> against <about:blank> assert_equals: failure should set href to input expected "https://[0:.0]" but got "https://[0:.0]/"
+FAIL Parsing: <https://[0:0:]> against <about:blank> assert_equals: failure should set href to input expected "https://[0:0:]" but got "https://[0:0:]/"
+FAIL Parsing: <https://[0:1:2:3:4:5:6:7.0.0.0.1]> against <about:blank> assert_equals: failure should set href to input expected "https://[0:1:2:3:4:5:6:7.0.0.0.1]" but got "https://[0:1:2:3:4:5:6:7.0.0.0.1]/"
+FAIL Parsing: <https://[0:1.00.0.0.0]> against <about:blank> assert_equals: failure should set href to input expected "https://[0:1.00.0.0.0]" but got "https://[0:1.00.0.0.0]/"
+FAIL Parsing: <https://[0:1.290.0.0.0]> against <about:blank> assert_equals: failure should set href to input expected "https://[0:1.290.0.0.0]" but got "https://[0:1.290.0.0.0]/"
+FAIL Parsing: <https://[0:1.23.23]> against <about:blank> assert_equals: failure should set href to input expected "https://[0:1.23.23]" but got "https://[0:1.23.23]/"
+FAIL Parsing: <http://?> against <about:blank> assert_equals: failure should set href to input expected "http://?" but got "http:/?"
+FAIL Parsing: <http://#> against <about:blank> assert_equals: failure should set href to input expected "http://#" but got "http:/#"
+PASS Parsing: <http://f:4294967377/c> against <http://example.org/>
+PASS Parsing: <http://f:18446744073709551697/c> against <http://example.org/>
+PASS Parsing: <http://f:340282366920938463463374607431768211537/c> against <http://example.org/>
+FAIL Parsing: <sc://ñ> against <about:blank> assert_equals: host expected "%C3%B1" but got ""
+FAIL Parsing: <sc://ñ?x> against <about:blank> assert_equals: host expected "%C3%B1" but got ""
+FAIL Parsing: <sc://ñ#x> against <about:blank> assert_equals: host expected "%C3%B1" but got ""
+FAIL Parsing: <#x> against <sc://ñ> assert_equals: href expected "sc://%C3%B1#x" but got "sc://%C3%B1"
+FAIL Parsing: <?x> against <sc://ñ> assert_equals: href expected "sc://%C3%B1?x" but got "sc://%C3%B1"
+FAIL Parsing: <sc://?> against <about:blank> assert_equals: pathname expected "" but got "//"
+FAIL Parsing: <sc://#> against <about:blank> assert_equals: pathname expected "" but got "//"
+FAIL Parsing: <///> against <sc://x/> assert_equals: href expected "sc:///" but got "sc:"
+FAIL Parsing: <////> against <sc://x/> assert_equals: href expected "sc:////" but got "sc:"
+FAIL Parsing: <////x/> against <sc://x/> assert_equals: href expected "sc:////x/" but got "sc://x/"
+FAIL Parsing: <tftp://foobar.com/someconfig;mode=netascii> against <about:blank> assert_equals: host expected "foobar.com" but got ""
+FAIL Parsing: <telnet://user:pass@foobar.com:23/> against <about:blank> assert_equals: username expected "user" but got ""
+FAIL Parsing: <ut2004://10.10.10.10:7777/Index.ut2> against <about:blank> assert_equals: host expected "10.10.10.10:7777" but got ""
+FAIL Parsing: <redis://foo:bar@somehost:6379/0?baz=bam&qux=baz> against <about:blank> assert_equals: username expected "foo" but got ""
+FAIL Parsing: <rsync://foo@host:911/sup> against <about:blank> assert_equals: username expected "foo" but got ""
+FAIL Parsing: <git://github.com/foo/bar.git> against <about:blank> assert_equals: host expected "github.com" but got ""
+FAIL Parsing: <irc://myserver.com:6999/channel?passwd> against <about:blank> assert_equals: host expected "myserver.com:6999" but got ""
+FAIL Parsing: <dns://fw.example.org:9999/foo.bar.org?type=TXT> against <about:blank> assert_equals: host expected "fw.example.org:9999" but got ""
+FAIL Parsing: <ldap://localhost:389/ou=People,o=JNDITutorial> against <about:blank> assert_equals: host expected "localhost:389" but got ""
+FAIL Parsing: <git+https://github.com/foo/bar> against <about:blank> assert_equals: host expected "github.com" but got ""
+PASS Parsing: <urn:ietf:rfc:2648> against <about:blank>
+PASS Parsing: <tag:joe@example.org,2001:foo/bar> against <about:blank>
+FAIL Parsing: <non-spec:/.//> against <about:blank> assert_equals: pathname expected "//" but got "/.//"
+FAIL Parsing: <non-spec:/..//> against <about:blank> assert_equals: href expected "non-spec:/.//" but got "non-spec:/..//"
+FAIL Parsing: <non-spec:/a/..//> against <about:blank> assert_equals: href expected "non-spec:/.//" but got "non-spec:/a/..//"
+FAIL Parsing: <non-spec:/.//path> against <about:blank> assert_equals: pathname expected "//path" but got "/.//path"
+FAIL Parsing: <non-spec:/..//path> against <about:blank> assert_equals: href expected "non-spec:/.//path" but got "non-spec:/..//path"
+FAIL Parsing: <non-spec:/a/..//path> against <about:blank> assert_equals: href expected "non-spec:/.//path" but got "non-spec:/a/..//path"
+FAIL Parsing: </.//path> against <non-spec:/p> assert_equals: href expected "non-spec:/.//path" but got "non-spec://path"
+FAIL Parsing: </..//path> against <non-spec:/p> assert_equals: href expected "non-spec:/.//path" but got "non-spec://path"
+FAIL Parsing: <..//path> against <non-spec:/p> assert_equals: href expected "non-spec:/.//path" but got "non-spec://path"
+FAIL Parsing: <a/..//path> against <non-spec:/p> assert_equals: href expected "non-spec:/.//path" but got "non-spec://path"
+FAIL Parsing: <> against <non-spec:/..//p> assert_equals: href expected "non-spec:/.//p" but got "non-spec:/..//p"
+FAIL Parsing: <path> against <non-spec:/..//p> assert_equals: href expected "non-spec:/.//path" but got "non-spec:/..//path"
+FAIL Parsing: <../path> against <non-spec:/.//p> assert_equals: href expected "non-spec:/path" but got "non-spec:/./path"
+FAIL Parsing: <non-special://%E2%80%A0/> against <about:blank> assert_equals: host expected "%E2%80%A0" but got ""
+FAIL Parsing: <non-special://H%4fSt/path> against <about:blank> assert_equals: host expected "H%4fSt" but got ""
+FAIL Parsing: <non-special://[1:2:0:0:5:0:0:0]/> against <about:blank> assert_equals: href expected "non-special://[1:2:0:0:5::]/" but got "non-special://[1:2:0:0:5:0:0:0]/"
+FAIL Parsing: <non-special://[1:2:0:0:0:0:0:3]/> against <about:blank> assert_equals: href expected "non-special://[1:2::3]/" but got "non-special://[1:2:0:0:0:0:0:3]/"
+FAIL Parsing: <non-special://[1:2::3]:80/> against <about:blank> assert_equals: host expected "[1:2::3]:80" but got ""
+FAIL Parsing: <non-special://[:80/> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+PASS Parsing: <blob:https://example.com:443/> against <about:blank>
+PASS Parsing: <blob:d3958f5c-0777-0845-9dcf-2cb28783acaf> against <about:blank>
+PASS Parsing: <blob:> against <about:blank>
+PASS Parsing: <http://0x7f.0.0.0x7g> against <about:blank>
+PASS Parsing: <http://0X7F.0.0.0X7G> against <about:blank>
+FAIL Parsing: <http://[::127.0.0.0.1]> against <about:blank> assert_equals: failure should set href to input expected "http://[::127.0.0.0.1]" but got "http://[::127.0.0.0.1]/"
+PASS Parsing: <http://[0:1:0:1:0:1:0:1]> against <about:blank>
+PASS Parsing: <http://[1:0:1:0:1:0:1:0]> against <about:blank>
+PASS Parsing: <http://example.org/test?"> against <about:blank>
+PASS Parsing: <http://example.org/test?#> against <about:blank>
+PASS Parsing: <http://example.org/test?<> against <about:blank>
+PASS Parsing: <http://example.org/test?>> against <about:blank>
+PASS Parsing: <http://example.org/test?⌣> against <about:blank>
+PASS Parsing: <http://example.org/test?%23%23> against <about:blank>
+PASS Parsing: <http://example.org/test?%GH> against <about:blank>
+PASS Parsing: <http://example.org/test?a#%EF> against <about:blank>
+PASS Parsing: <http://example.org/test?a#%GH> against <about:blank>
+FAIL Parsing: <a> against <about:blank> assert_equals: failure should set href to input expected "a" but got ""
+FAIL Parsing: <a/> against <about:blank> assert_equals: failure should set href to input expected "a/" but got ""
+FAIL Parsing: <a//> against <about:blank> assert_equals: failure should set href to input expected "a//" but got ""
+FAIL Parsing: <test-a-colon.html> against <a:> assert_equals: failure should set href to input expected "test-a-colon.html" but got ""
+FAIL Parsing: <test-a-colon-b.html> against <a:b> assert_equals: failure should set href to input expected "test-a-colon-b.html" but got ""
+PASS Parsing: <test-a-colon-slash.html> against <a:/>
+FAIL Parsing: <test-a-colon-slash-slash.html> against <a://> assert_equals: href expected "a:///test-a-colon-slash-slash.html" but got ""
+PASS Parsing: <test-a-colon-slash-b.html> against <a:/b>
+FAIL Parsing: <test-a-colon-slash-slash-b.html> against <a://b> assert_equals: href expected "a://b/test-a-colon-slash-slash-b.html" but got "a://b"
+PASS Parsing: <http://example.org/test?a#b\0c> against <about:blank>
+FAIL Parsing: <non-spec://example.org/test?a#b\0c> against <about:blank> assert_equals: host expected "example.org" but got ""
+PASS Parsing: <non-spec:/test?a#b\0c> against <about:blank>
+PASS Parsing: <10.0.0.7:8080/foo.html> against <file:///some/dir/bar.html>
+PASS Parsing: <a!@$*=/foo.html> against <file:///some/dir/bar.html>
+PASS Parsing: <a1234567890-+.:foo/bar> against <http://example.com/dir/file>
+PASS Parsing: <file://a­b/p> against <about:blank>
+PASS Parsing: <file://a%C2%ADb/p> against <about:blank>
+FAIL Parsing: <file://­/p> against <about:blank> assert_equals: failure should set href to input expected "file://­/p" but got "file://%C2%AD/p"
+PASS Parsing: <file://%C2%AD/p> against <about:blank>
+FAIL Parsing: <file://xn--/p> against <about:blank> assert_unreached: Expected URL to fail parsing Reached unreachable code
+PASS Parsing: <#link> against <https://example.org/##link>
+PASS Parsing: <non-special:cannot-be-a-base-url-\0~€> against <about:blank>
+PASS Parsing: <https://www.example.com/path{path.html?query'=query#fragment<fragment> against <about:blank>
+PASS Parsing: <https://user:pass[@foo/bar> against <http://example.org>
+FAIL Parsing: <foo:// !"$%&'()*+,-.;<=>@[\]^_`{|}~@host/> against <about:blank> assert_equals: href expected "foo://%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~@host/" but got "foo:// !\"$%&'()*+,-.;<=>@[\\]^_`{|}~@host/"
+FAIL Parsing: <wss:// !"$%&'()*+,-.;<=>@[]^_`{|}~@host/> against <about:blank> assert_equals: href expected "wss://%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/" but got "wss://%20!%22$%&%27()*+,-.%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/"
+FAIL Parsing: <foo://joe: !"$%&'()*+,-.:;<=>@[\]^_`{|}~@host/> against <about:blank> assert_equals: href expected "foo://joe:%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~@host/" but got "foo://joe: !\"$%&'()*+,-.:;<=>@[\\]^_`{|}~@host/"
+FAIL Parsing: <wss://joe: !"$%&'()*+,-.:;<=>@[]^_`{|}~@host/> against <about:blank> assert_equals: href expected "wss://joe:%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/" but got "wss://joe:%20!%22$%&%27()*+,-.%3A%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/"
+FAIL Parsing: <foo://!"$%&'()*+,-.;=_`{}~/> against <about:blank> assert_equals: host expected "!\"$%&'()*+,-.;=_`{}~" but got ""
+FAIL Parsing: <wss://!"$&'()*+,-.;=_`{}~/> against <about:blank> assert_equals: href expected "wss://!\"$&'()*+,-.;=_`{}~/" but got "wss://%21%22%24%26%27%28%29%2A+%2C-.%3B%3D_%60%7B%7D%7E/"
+FAIL Parsing: <foo://host/ !"$%&'()*+,-./:;<=>@[\]^_`{|}~> against <about:blank> assert_equals: href expected "foo://host/%20!%22$%&'()*+,-./:;%3C=%3E@[\\]^_%60%7B|%7D~" but got "foo://host/ !\"$%&'()*+,-./:;<=>@[\\]^_`{|}~"
+FAIL Parsing: <wss://host/ !"$%&'()*+,-./:;<=>@[\]^_`{|}~> against <about:blank> assert_equals: href expected "wss://host/%20!%22$%&'()*+,-./:;%3C=%3E@[/]^_%60%7B|%7D~" but got "wss://host/%20!%22$%&'()*+,-./:;%3C=%3E@[/]%5E_%60%7B%7C%7D~"
+FAIL Parsing: <foo://host/dir/? !"$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank> assert_equals: href expected "foo://host/dir/?%20!%22$%&'()*+,-./:;%3C=%3E?@[\\]^_`{|}~" but got "foo://host/dir/?%20!%22$%&%27()*+,-./:;%3C=%3E?@[\\]^_`{|}~"
+PASS Parsing: <wss://host/dir/? !"$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
+FAIL Parsing: <foo://host/dir/# !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank> assert_equals: host expected "host" but got ""
+PASS Parsing: <wss://host/dir/# !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
+FAIL Parsing: <abc:rootless> against <abc://host/path> assert_equals: href expected "abc:rootless" but got "abc://host/rootless"
+FAIL Parsing: <abc:rootless> against <abc:/path> assert_equals: href expected "abc:rootless" but got "abc:/rootless"
+PASS Parsing: <abc:rootless> against <abc:path>
+FAIL Parsing: <abc:/rooted> against <abc://host/path> assert_equals: href expected "abc:/rooted" but got "abc://host/rooted"
+FAIL Parsing: <http://1.2.3.4.5> against <http://other.com/> assert_equals: failure should set href to input expected "http://1.2.3.4.5" but got "http://1.2.3.4.5/"
+FAIL Parsing: <http://1.2.3.4.5.> against <http://other.com/> assert_equals: failure should set href to input expected "http://1.2.3.4.5." but got "http://1.2.3.4.5./"
+PASS Parsing: <http://0..0x300/> against <about:blank>
+PASS Parsing: <http://0..0x300./> against <about:blank>
+FAIL Parsing: <http://256.256.256.256.256> against <http://other.com/> assert_equals: failure should set href to input expected "http://256.256.256.256.256" but got "http://256.256.256.256.256/"
+FAIL Parsing: <http://256.256.256.256.256.> against <http://other.com/> assert_equals: failure should set href to input expected "http://256.256.256.256.256." but got "http://256.256.256.256.256./"
+FAIL Parsing: <http://1.2.3.08> against <about:blank> assert_equals: failure should set href to input expected "http://1.2.3.08" but got "http://1.2.3.08/"
+FAIL Parsing: <http://1.2.3.08.> against <about:blank> assert_equals: failure should set href to input expected "http://1.2.3.08." but got "http://1.2.3.08./"
+FAIL Parsing: <http://1.2.3.09> against <about:blank> assert_equals: failure should set href to input expected "http://1.2.3.09" but got "http://1.2.3.09/"
+FAIL Parsing: <http://09.2.3.4> against <about:blank> assert_equals: failure should set href to input expected "http://09.2.3.4" but got "http://09.2.3.4/"
+FAIL Parsing: <http://09.2.3.4.> against <about:blank> assert_equals: failure should set href to input expected "http://09.2.3.4." but got "http://09.2.3.4./"
+FAIL Parsing: <http://01.2.3.4.5> against <about:blank> assert_equals: failure should set href to input expected "http://01.2.3.4.5" but got "http://01.2.3.4.5/"
+FAIL Parsing: <http://01.2.3.4.5.> against <about:blank> assert_equals: failure should set href to input expected "http://01.2.3.4.5." but got "http://01.2.3.4.5./"
+FAIL Parsing: <http://0x100.2.3.4> against <about:blank> assert_equals: failure should set href to input expected "http://0x100.2.3.4" but got "http://0x100.2.3.4/"
+FAIL Parsing: <http://0x100.2.3.4.> against <about:blank> assert_equals: failure should set href to input expected "http://0x100.2.3.4." but got "http://0x100.2.3.4./"
+FAIL Parsing: <http://0x1.2.3.4.5> against <about:blank> assert_equals: failure should set href to input expected "http://0x1.2.3.4.5" but got "http://0x1.2.3.4.5/"
+FAIL Parsing: <http://0x1.2.3.4.5.> against <about:blank> assert_equals: failure should set href to input expected "http://0x1.2.3.4.5." but got "http://0x1.2.3.4.5./"
+FAIL Parsing: <http://foo.1.2.3.4> against <about:blank> assert_equals: failure should set href to input expected "http://foo.1.2.3.4" but got "http://foo.1.2.3.4/"
+FAIL Parsing: <http://foo.1.2.3.4.> against <about:blank> assert_equals: failure should set href to input expected "http://foo.1.2.3.4." but got "http://foo.1.2.3.4./"
+FAIL Parsing: <http://foo.2.3.4> against <about:blank> assert_equals: failure should set href to input expected "http://foo.2.3.4" but got "http://foo.2.3.4/"
+FAIL Parsing: <http://foo.2.3.4.> against <about:blank> assert_equals: failure should set href to input expected "http://foo.2.3.4." but got "http://foo.2.3.4./"
+FAIL Parsing: <http://foo.09> against <about:blank> assert_equals: failure should set href to input expected "http://foo.09" but got "http://foo.09/"
+FAIL Parsing: <http://foo.09.> against <about:blank> assert_equals: failure should set href to input expected "http://foo.09." but got "http://foo.09./"
+FAIL Parsing: <http://foo.0x4> against <about:blank> assert_equals: failure should set href to input expected "http://foo.0x4" but got "http://foo.0x4/"
+FAIL Parsing: <http://foo.0x4.> against <about:blank> assert_equals: failure should set href to input expected "http://foo.0x4." but got "http://foo.0x4./"
+PASS Parsing: <http://foo.09..> against <about:blank>
+PASS Parsing: <http://0999999999999999999/> against <about:blank>
+FAIL Parsing: <http://foo.0x> against <about:blank> assert_equals: failure should set href to input expected "http://foo.0x" but got "http://foo.0x/"
+FAIL Parsing: <http://foo.0XFfFfFfFfFfFfFfFfFfAcE123> against <about:blank> assert_equals: failure should set href to input expected "http://foo.0XFfFfFfFfFfFfFfFfFfAcE123" but got "http://foo.0xfffffffffffffffffface123/"
+FAIL Parsing: <http://💩.123/> against <about:blank> assert_equals: failure should set href to input expected "http://💩.123/" but got "http://xn--ls8h.123/"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/toascii.window-expected.txt b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/toascii.window-expected.txt
new file mode 100644
index 0000000..2ca7f866
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/toascii.window-expected.txt
@@ -0,0 +1,356 @@
+This is a testharness.js-based test.
+Found 352 tests; 279 PASS, 73 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS Loading data…
+PASS aa-- (using URL)
+PASS aa-- (using URL.host)
+PASS aa-- (using URL.hostname)
+PASS aa-- (using <a>)
+PASS aa-- (using <a>.host)
+PASS aa-- (using <a>.hostname)
+PASS aa-- (using <area>)
+PASS aa-- (using <area>.host)
+PASS aa-- (using <area>.hostname)
+PASS a†-- (using URL)
+PASS a†-- (using URL.host)
+PASS a†-- (using URL.hostname)
+PASS a†-- (using <a>)
+PASS a†-- (using <a>.host)
+PASS a†-- (using <a>.hostname)
+PASS a†-- (using <area>)
+PASS a†-- (using <area>.host)
+PASS a†-- (using <area>.hostname)
+PASS ab--c (using URL)
+PASS ab--c (using URL.host)
+PASS ab--c (using URL.hostname)
+PASS ab--c (using <a>)
+PASS ab--c (using <a>.host)
+PASS ab--c (using <a>.hostname)
+PASS ab--c (using <area>)
+PASS ab--c (using <area>.host)
+PASS ab--c (using <area>.hostname)
+PASS -x (using URL)
+PASS -x (using URL.host)
+PASS -x (using URL.hostname)
+PASS -x (using <a>)
+PASS -x (using <a>.host)
+PASS -x (using <a>.hostname)
+PASS -x (using <area>)
+PASS -x (using <area>.host)
+PASS -x (using <area>.hostname)
+PASS -† (using URL)
+PASS -† (using URL.host)
+PASS -† (using URL.hostname)
+PASS -† (using <a>)
+PASS -† (using <a>.host)
+PASS -† (using <a>.hostname)
+PASS -† (using <area>)
+PASS -† (using <area>.host)
+PASS -† (using <area>.hostname)
+PASS -x.xn--zca (using URL)
+PASS -x.xn--zca (using URL.host)
+PASS -x.xn--zca (using URL.hostname)
+PASS -x.xn--zca (using <a>)
+PASS -x.xn--zca (using <a>.host)
+PASS -x.xn--zca (using <a>.hostname)
+PASS -x.xn--zca (using <area>)
+PASS -x.xn--zca (using <area>.host)
+PASS -x.xn--zca (using <area>.hostname)
+PASS -x.ß (using URL)
+PASS -x.ß (using URL.host)
+PASS -x.ß (using URL.hostname)
+PASS -x.ß (using <a>)
+PASS -x.ß (using <a>.host)
+PASS -x.ß (using <a>.hostname)
+PASS -x.ß (using <area>)
+PASS -x.ß (using <area>.host)
+PASS -x.ß (using <area>.hostname)
+PASS x-.xn--zca (using URL)
+PASS x-.xn--zca (using URL.host)
+PASS x-.xn--zca (using URL.hostname)
+PASS x-.xn--zca (using <a>)
+PASS x-.xn--zca (using <a>.host)
+PASS x-.xn--zca (using <a>.hostname)
+PASS x-.xn--zca (using <area>)
+PASS x-.xn--zca (using <area>.host)
+PASS x-.xn--zca (using <area>.hostname)
+PASS x-.ß (using URL)
+PASS x-.ß (using URL.host)
+PASS x-.ß (using URL.hostname)
+PASS x-.ß (using <a>)
+PASS x-.ß (using <a>.host)
+PASS x-.ß (using <a>.hostname)
+PASS x-.ß (using <area>)
+PASS x-.ß (using <area>.host)
+PASS x-.ß (using <area>.hostname)
+PASS x..xn--zca (using URL)
+PASS x..xn--zca (using URL.host)
+PASS x..xn--zca (using URL.hostname)
+PASS x..xn--zca (using <a>)
+PASS x..xn--zca (using <a>.host)
+PASS x..xn--zca (using <a>.hostname)
+PASS x..xn--zca (using <area>)
+PASS x..xn--zca (using <area>.host)
+PASS x..xn--zca (using <area>.hostname)
+PASS x..ß (using URL)
+PASS x..ß (using URL.host)
+PASS x..ß (using URL.hostname)
+PASS x..ß (using <a>)
+PASS x..ß (using <a>.host)
+PASS x..ß (using <a>.hostname)
+PASS x..ß (using <area>)
+PASS x..ß (using <area>.host)
+PASS x..ß (using <area>.hostname)
+FAIL xn--a (using URL) assert_throws_js: function "() => makeURL("url", hostTest.input)" did not throw
+FAIL xn--a (using URL.host) assert_equals: expected "x" but got "xn--a"
+FAIL xn--a (using URL.hostname) assert_equals: expected "x" but got "xn--a"
+FAIL xn--a (using <a>) assert_equals: expected "" but got "xn--a"
+FAIL xn--a (using <a>.host) assert_equals: expected "x" but got "xn--a"
+FAIL xn--a (using <a>.hostname) assert_equals: expected "x" but got "xn--a"
+FAIL xn--a (using <area>) assert_equals: expected "" but got "xn--a"
+FAIL xn--a (using <area>.host) assert_equals: expected "x" but got "xn--a"
+FAIL xn--a (using <area>.hostname) assert_equals: expected "x" but got "xn--a"
+FAIL xn--a.xn--zca (using URL) assert_throws_js: function "() => makeURL("url", hostTest.input)" did not throw
+FAIL xn--a.xn--zca (using URL.host) assert_equals: expected "x" but got "xn--a.xn--zca"
+FAIL xn--a.xn--zca (using URL.hostname) assert_equals: expected "x" but got "xn--a.xn--zca"
+FAIL xn--a.xn--zca (using <a>) assert_equals: expected "" but got "xn--a.xn--zca"
+FAIL xn--a.xn--zca (using <a>.host) assert_equals: expected "x" but got "xn--a.xn--zca"
+FAIL xn--a.xn--zca (using <a>.hostname) assert_equals: expected "x" but got "xn--a.xn--zca"
+FAIL xn--a.xn--zca (using <area>) assert_equals: expected "" but got "xn--a.xn--zca"
+FAIL xn--a.xn--zca (using <area>.host) assert_equals: expected "x" but got "xn--a.xn--zca"
+FAIL xn--a.xn--zca (using <area>.hostname) assert_equals: expected "x" but got "xn--a.xn--zca"
+PASS xn--a.ß (using URL)
+PASS xn--a.ß (using URL.host)
+PASS xn--a.ß (using URL.hostname)
+FAIL xn--a.ß (using <a>) assert_equals: expected "https://xn--a.ß/x" but got "https://xn--a.%C3%9F/x"
+PASS xn--a.ß (using <a>.host)
+PASS xn--a.ß (using <a>.hostname)
+FAIL xn--a.ß (using <area>) assert_equals: expected "https://xn--a.ß/x" but got "https://xn--a.%C3%9F/x"
+PASS xn--a.ß (using <area>.host)
+PASS xn--a.ß (using <area>.hostname)
+PASS xn--tešla (using URL)
+PASS xn--tešla (using URL.host)
+PASS xn--tešla (using URL.hostname)
+FAIL xn--tešla (using <a>) assert_equals: expected "https://xn--tešla/x" but got "https://xn--te%C5%A1la/x"
+PASS xn--tešla (using <a>.host)
+PASS xn--tešla (using <a>.hostname)
+FAIL xn--tešla (using <area>) assert_equals: expected "https://xn--tešla/x" but got "https://xn--te%C5%A1la/x"
+PASS xn--tešla (using <area>.host)
+PASS xn--tešla (using <area>.hostname)
+PASS xn--zca.xn--zca (using URL)
+PASS xn--zca.xn--zca (using URL.host)
+PASS xn--zca.xn--zca (using URL.hostname)
+PASS xn--zca.xn--zca (using <a>)
+PASS xn--zca.xn--zca (using <a>.host)
+PASS xn--zca.xn--zca (using <a>.hostname)
+PASS xn--zca.xn--zca (using <area>)
+PASS xn--zca.xn--zca (using <area>.host)
+PASS xn--zca.xn--zca (using <area>.hostname)
+PASS xn--zca.ß (using URL)
+PASS xn--zca.ß (using URL.host)
+PASS xn--zca.ß (using URL.hostname)
+PASS xn--zca.ß (using <a>)
+PASS xn--zca.ß (using <a>.host)
+PASS xn--zca.ß (using <a>.hostname)
+PASS xn--zca.ß (using <area>)
+PASS xn--zca.ß (using <area>.host)
+PASS xn--zca.ß (using <area>.hostname)
+PASS ab--c.xn--zca (using URL)
+PASS ab--c.xn--zca (using URL.host)
+PASS ab--c.xn--zca (using URL.hostname)
+PASS ab--c.xn--zca (using <a>)
+PASS ab--c.xn--zca (using <a>.host)
+PASS ab--c.xn--zca (using <a>.hostname)
+PASS ab--c.xn--zca (using <area>)
+PASS ab--c.xn--zca (using <area>.host)
+PASS ab--c.xn--zca (using <area>.hostname)
+PASS ab--c.ß (using URL)
+PASS ab--c.ß (using URL.host)
+PASS ab--c.ß (using URL.hostname)
+PASS ab--c.ß (using <a>)
+PASS ab--c.ß (using <a>.host)
+PASS ab--c.ß (using <a>.hostname)
+PASS ab--c.ß (using <area>)
+PASS ab--c.ß (using <area>.host)
+PASS ab--c.ß (using <area>.hostname)
+FAIL ‍.example (using URL) assert_throws_js: function "() => makeURL("url", hostTest.input)" did not throw
+FAIL ‍.example (using URL.host) assert_equals: expected "x" but got "xn--1ug.example"
+FAIL ‍.example (using URL.hostname) assert_equals: expected "x" but got "xn--1ug.example"
+FAIL ‍.example (using <a>) assert_equals: expected "" but got "xn--1ug.example"
+FAIL ‍.example (using <a>.host) assert_equals: expected "x" but got "xn--1ug.example"
+FAIL ‍.example (using <a>.hostname) assert_equals: expected "x" but got "xn--1ug.example"
+FAIL ‍.example (using <area>) assert_equals: expected "" but got "xn--1ug.example"
+FAIL ‍.example (using <area>.host) assert_equals: expected "x" but got "xn--1ug.example"
+FAIL ‍.example (using <area>.hostname) assert_equals: expected "x" but got "xn--1ug.example"
+FAIL xn--1ug.example (using URL) assert_throws_js: function "() => makeURL("url", hostTest.input)" did not throw
+FAIL xn--1ug.example (using URL.host) assert_equals: expected "x" but got "xn--1ug.example"
+FAIL xn--1ug.example (using URL.hostname) assert_equals: expected "x" but got "xn--1ug.example"
+FAIL xn--1ug.example (using <a>) assert_equals: expected "" but got "xn--1ug.example"
+FAIL xn--1ug.example (using <a>.host) assert_equals: expected "x" but got "xn--1ug.example"
+FAIL xn--1ug.example (using <a>.hostname) assert_equals: expected "x" but got "xn--1ug.example"
+FAIL xn--1ug.example (using <area>) assert_equals: expected "" but got "xn--1ug.example"
+FAIL xn--1ug.example (using <area>.host) assert_equals: expected "x" but got "xn--1ug.example"
+FAIL xn--1ug.example (using <area>.hostname) assert_equals: expected "x" but got "xn--1ug.example"
+PASS يa (using URL)
+PASS يa (using URL.host)
+PASS يa (using URL.hostname)
+FAIL يa (using <a>) assert_equals: expected "https://يa/x" but got "https://%D9%8Aa/x"
+PASS يa (using <a>.host)
+PASS يa (using <a>.hostname)
+FAIL يa (using <area>) assert_equals: expected "https://يa/x" but got "https://%D9%8Aa/x"
+PASS يa (using <area>.host)
+PASS يa (using <area>.hostname)
+FAIL xn--a-yoc (using URL) assert_throws_js: function "() => makeURL("url", hostTest.input)" did not throw
+FAIL xn--a-yoc (using URL.host) assert_equals: expected "x" but got "xn--a-yoc"
+FAIL xn--a-yoc (using URL.hostname) assert_equals: expected "x" but got "xn--a-yoc"
+FAIL xn--a-yoc (using <a>) assert_equals: expected "" but got "xn--a-yoc"
+FAIL xn--a-yoc (using <a>.host) assert_equals: expected "x" but got "xn--a-yoc"
+FAIL xn--a-yoc (using <a>.hostname) assert_equals: expected "x" but got "xn--a-yoc"
+FAIL xn--a-yoc (using <area>) assert_equals: expected "" but got "xn--a-yoc"
+FAIL xn--a-yoc (using <area>.host) assert_equals: expected "x" but got "xn--a-yoc"
+FAIL xn--a-yoc (using <area>.hostname) assert_equals: expected "x" but got "xn--a-yoc"
+PASS ශ්‍රී (using URL)
+PASS ශ්‍රී (using URL.host)
+PASS ශ්‍රී (using URL.hostname)
+PASS ශ්‍රී (using <a>)
+PASS ශ්‍රී (using <a>.host)
+PASS ශ්‍රී (using <a>.hostname)
+PASS ශ්‍රී (using <area>)
+PASS ශ්‍රී (using <area>.host)
+PASS ශ්‍රී (using <area>.hostname)
+PASS نامه‌ای (using URL)
+PASS نامه‌ای (using URL.host)
+PASS نامه‌ای (using URL.hostname)
+PASS نامه‌ای (using <a>)
+PASS نامه‌ای (using <a>.host)
+PASS نامه‌ای (using <a>.hostname)
+PASS نامه‌ای (using <area>)
+PASS نامه‌ای (using <area>.host)
+PASS نامه‌ای (using <area>.hostname)
+PASS �.com (using URL)
+PASS �.com (using URL.host)
+PASS �.com (using URL.hostname)
+FAIL �.com (using <a>) assert_equals: expected "https://\ufffd.com/x" but got "https://%EF%BF%BD.com/x"
+PASS �.com (using <a>.host)
+PASS �.com (using <a>.hostname)
+FAIL �.com (using <area>) assert_equals: expected "https://\ufffd.com/x" but got "https://%EF%BF%BD.com/x"
+PASS �.com (using <area>.host)
+PASS �.com (using <area>.hostname)
+FAIL xn--zn7c.com (using URL) assert_throws_js: function "() => makeURL("url", hostTest.input)" did not throw
+FAIL xn--zn7c.com (using URL.host) assert_equals: expected "x" but got "xn--zn7c.com"
+FAIL xn--zn7c.com (using URL.hostname) assert_equals: expected "x" but got "xn--zn7c.com"
+FAIL xn--zn7c.com (using <a>) assert_equals: expected "" but got "xn--zn7c.com"
+FAIL xn--zn7c.com (using <a>.host) assert_equals: expected "x" but got "xn--zn7c.com"
+FAIL xn--zn7c.com (using <a>.hostname) assert_equals: expected "x" but got "xn--zn7c.com"
+FAIL xn--zn7c.com (using <area>) assert_equals: expected "" but got "xn--zn7c.com"
+FAIL xn--zn7c.com (using <area>.host) assert_equals: expected "x" but got "xn--zn7c.com"
+FAIL xn--zn7c.com (using <area>.hostname) assert_equals: expected "x" but got "xn--zn7c.com"
+PASS x01234567890123456789012345678901234567890123456789012345678901x (using URL)
+PASS x01234567890123456789012345678901234567890123456789012345678901x (using URL.host)
+PASS x01234567890123456789012345678901234567890123456789012345678901x (using URL.hostname)
+PASS x01234567890123456789012345678901234567890123456789012345678901x (using <a>)
+PASS x01234567890123456789012345678901234567890123456789012345678901x (using <a>.host)
+PASS x01234567890123456789012345678901234567890123456789012345678901x (using <a>.hostname)
+PASS x01234567890123456789012345678901234567890123456789012345678901x (using <area>)
+PASS x01234567890123456789012345678901234567890123456789012345678901x (using <area>.host)
+PASS x01234567890123456789012345678901234567890123456789012345678901x (using <area>.hostname)
+PASS x01234567890123456789012345678901234567890123456789012345678901† (using URL)
+PASS x01234567890123456789012345678901234567890123456789012345678901† (using URL.host)
+PASS x01234567890123456789012345678901234567890123456789012345678901† (using URL.hostname)
+PASS x01234567890123456789012345678901234567890123456789012345678901† (using <a>)
+PASS x01234567890123456789012345678901234567890123456789012345678901† (using <a>.host)
+PASS x01234567890123456789012345678901234567890123456789012345678901† (using <a>.hostname)
+PASS x01234567890123456789012345678901234567890123456789012345678901† (using <area>)
+PASS x01234567890123456789012345678901234567890123456789012345678901† (using <area>.host)
+PASS x01234567890123456789012345678901234567890123456789012345678901† (using <area>.hostname)
+PASS x01234567890123456789012345678901234567890123456789012345678901x.xn--zca (using URL)
+PASS x01234567890123456789012345678901234567890123456789012345678901x.xn--zca (using URL.host)
+PASS x01234567890123456789012345678901234567890123456789012345678901x.xn--zca (using URL.hostname)
+PASS x01234567890123456789012345678901234567890123456789012345678901x.xn--zca (using <a>)
+PASS x01234567890123456789012345678901234567890123456789012345678901x.xn--zca (using <a>.host)
+PASS x01234567890123456789012345678901234567890123456789012345678901x.xn--zca (using <a>.hostname)
+PASS x01234567890123456789012345678901234567890123456789012345678901x.xn--zca (using <area>)
+PASS x01234567890123456789012345678901234567890123456789012345678901x.xn--zca (using <area>.host)
+PASS x01234567890123456789012345678901234567890123456789012345678901x.xn--zca (using <area>.hostname)
+PASS x01234567890123456789012345678901234567890123456789012345678901x.ß (using URL)
+PASS x01234567890123456789012345678901234567890123456789012345678901x.ß (using URL.host)
+PASS x01234567890123456789012345678901234567890123456789012345678901x.ß (using URL.hostname)
+PASS x01234567890123456789012345678901234567890123456789012345678901x.ß (using <a>)
+PASS x01234567890123456789012345678901234567890123456789012345678901x.ß (using <a>.host)
+PASS x01234567890123456789012345678901234567890123456789012345678901x.ß (using <a>.hostname)
+PASS x01234567890123456789012345678901234567890123456789012345678901x.ß (using <area>)
+PASS x01234567890123456789012345678901234567890123456789012345678901x.ß (using <area>.host)
+PASS x01234567890123456789012345678901234567890123456789012345678901x.ß (using <area>.hostname)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.x (using URL)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.x (using URL.host)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.x (using URL.hostname)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.x (using <a>)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.x (using <a>.host)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.x (using <a>.hostname)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.x (using <area>)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.x (using <area>.host)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.x (using <area>.hostname)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca (using URL)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca (using URL.host)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca (using URL.hostname)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca (using <a>)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca (using <a>.host)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca (using <a>.hostname)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca (using <area>)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca (using <area>.host)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca (using <area>.hostname)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using URL)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using URL.host)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using URL.hostname)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <a>)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <a>.host)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <a>.hostname)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <area>)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <area>.host)
+PASS 01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß (using <area>.hostname)
+PASS a­b (using URL)
+PASS a­b (using URL.host)
+PASS a­b (using URL.hostname)
+PASS a­b (using <a>)
+PASS a­b (using <a>.host)
+PASS a­b (using <a>.hostname)
+PASS a­b (using <area>)
+PASS a­b (using <area>.host)
+PASS a­b (using <area>.hostname)
+PASS a%C2%ADb (using URL)
+PASS a%C2%ADb (using URL.host)
+PASS a%C2%ADb (using URL.hostname)
+PASS a%C2%ADb (using <a>)
+PASS a%C2%ADb (using <a>.host)
+PASS a%C2%ADb (using <a>.hostname)
+PASS a%C2%ADb (using <area>)
+PASS a%C2%ADb (using <area>.host)
+PASS a%C2%ADb (using <area>.hostname)
+PASS ­ (using URL)
+PASS ­ (using URL.host)
+PASS ­ (using URL.hostname)
+FAIL ­ (using <a>) assert_equals: expected "https://­/x" but got "https://%C2%AD/x"
+PASS ­ (using <a>.host)
+PASS ­ (using <a>.hostname)
+FAIL ­ (using <area>) assert_equals: expected "https://­/x" but got "https://%C2%AD/x"
+PASS ­ (using <area>.host)
+PASS ­ (using <area>.hostname)
+PASS %C2%AD (using URL)
+PASS %C2%AD (using URL.host)
+PASS %C2%AD (using URL.hostname)
+PASS %C2%AD (using <a>)
+PASS %C2%AD (using <a>.host)
+PASS %C2%AD (using <a>.hostname)
+PASS %C2%AD (using <area>)
+PASS %C2%AD (using <area>.host)
+PASS %C2%AD (using <area>.hostname)
+FAIL xn-- (using URL) assert_throws_js: function "() => makeURL("url", hostTest.input)" did not throw
+FAIL xn-- (using URL.host) assert_equals: expected "x" but got "xn--"
+FAIL xn-- (using URL.hostname) assert_equals: expected "x" but got "xn--"
+FAIL xn-- (using <a>) assert_equals: expected "" but got "xn--"
+FAIL xn-- (using <a>.host) assert_equals: expected "x" but got "xn--"
+FAIL xn-- (using <a>.hostname) assert_equals: expected "x" but got "xn--"
+FAIL xn-- (using <area>) assert_equals: expected "" but got "xn--"
+FAIL xn-- (using <area>.host) assert_equals: expected "x" but got "xn--"
+FAIL xn-- (using <area>.hostname) assert_equals: expected "x" but got "xn--"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-constructor.any-expected.txt b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-constructor.any-expected.txt
new file mode 100644
index 0000000..88e3db6b
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-constructor.any-expected.txt
@@ -0,0 +1,850 @@
+This is a testharness.js-based test.
+Found 738 tests; 552 PASS, 186 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS Loading data…
+PASS Parsing: <http://example	.
+org> against <http://example.org/foo/bar>
+PASS Parsing: <http://user:pass@foo:21/bar;par?b#c> against <http://example.org/foo/bar>
+PASS Parsing: <https://test:@test> against <about:blank>
+PASS Parsing: <https://:@test> against <about:blank>
+FAIL Parsing: <non-special://test:@test/x> against <about:blank> assert_equals: href expected "non-special://test@test/x" but got "non-special://test:@test/x"
+FAIL Parsing: <non-special://:@test/x> against <about:blank> assert_equals: href expected "non-special://test/x" but got "non-special://:@test/x"
+PASS Parsing: <http:foo.com> against <http://example.org/foo/bar>
+PASS Parsing: <	   :foo.com   
+> against <http://example.org/foo/bar>
+PASS Parsing: < foo.com  > against <http://example.org/foo/bar>
+PASS Parsing: <a:	 foo.com> against <http://example.org/foo/bar>
+PASS Parsing: <http://f:21/ b ? d # e > against <http://example.org/foo/bar>
+PASS Parsing: <lolscheme:x x#x x> against <about:blank>
+PASS Parsing: <http://f:/c> against <http://example.org/foo/bar>
+PASS Parsing: <http://f:0/c> against <http://example.org/foo/bar>
+PASS Parsing: <http://f:00000000000000/c> against <http://example.org/foo/bar>
+PASS Parsing: <http://f:00000000000000000000080/c> against <http://example.org/foo/bar>
+PASS Parsing: <http://f:b/c> against <http://example.org/foo/bar>
+PASS Parsing: <http://f: /c> against <http://example.org/foo/bar>
+PASS Parsing: <http://f:
+/c> against <http://example.org/foo/bar>
+PASS Parsing: <http://f:fifty-two/c> against <http://example.org/foo/bar>
+PASS Parsing: <http://f:999999/c> against <http://example.org/foo/bar>
+FAIL Parsing: <non-special://f:999999/c> against <http://example.org/foo/bar> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+PASS Parsing: <http://f: 21 / b ? d # e > against <http://example.org/foo/bar>
+PASS Parsing: <> against <http://example.org/foo/bar>
+PASS Parsing: <  	> against <http://example.org/foo/bar>
+PASS Parsing: <:foo.com/> against <http://example.org/foo/bar>
+PASS Parsing: <:foo.com\> against <http://example.org/foo/bar>
+PASS Parsing: <:> against <http://example.org/foo/bar>
+PASS Parsing: <:a> against <http://example.org/foo/bar>
+PASS Parsing: <:/> against <http://example.org/foo/bar>
+PASS Parsing: <:\> against <http://example.org/foo/bar>
+PASS Parsing: <:#> against <http://example.org/foo/bar>
+PASS Parsing: <#> against <http://example.org/foo/bar>
+PASS Parsing: <#/> against <http://example.org/foo/bar>
+PASS Parsing: <#\> against <http://example.org/foo/bar>
+PASS Parsing: <#;?> against <http://example.org/foo/bar>
+PASS Parsing: <?> against <http://example.org/foo/bar>
+PASS Parsing: </> against <http://example.org/foo/bar>
+PASS Parsing: <:23> against <http://example.org/foo/bar>
+PASS Parsing: </:23> against <http://example.org/foo/bar>
+PASS Parsing: <\x> against <http://example.org/foo/bar>
+PASS Parsing: <\\x\hello> against <http://example.org/foo/bar>
+PASS Parsing: <::> against <http://example.org/foo/bar>
+PASS Parsing: <::23> against <http://example.org/foo/bar>
+FAIL Parsing: <foo://> against <http://example.org/foo/bar> assert_equals: pathname expected "" but got "//"
+PASS Parsing: <http://a:b@c:29/d> against <http://example.org/foo/bar>
+PASS Parsing: <http::@c:29> against <http://example.org/foo/bar>
+PASS Parsing: <http://&a:foo(b]c@d:2/> against <http://example.org/foo/bar>
+PASS Parsing: <http://::@c@d:2> against <http://example.org/foo/bar>
+PASS Parsing: <http://foo.com:b@d/> against <http://example.org/foo/bar>
+PASS Parsing: <http://foo.com/\@> against <http://example.org/foo/bar>
+PASS Parsing: <http:\\foo.com\> against <http://example.org/foo/bar>
+PASS Parsing: <http:\\a\b:c\d@foo.com\> against <http://example.org/foo/bar>
+PASS Parsing: <foo:/> against <http://example.org/foo/bar>
+PASS Parsing: <foo:/bar.com/> against <http://example.org/foo/bar>
+FAIL Parsing: <foo://///////> against <http://example.org/foo/bar> assert_equals: pathname expected "///////" but got "/////////"
+FAIL Parsing: <foo://///////bar.com/> against <http://example.org/foo/bar> assert_equals: pathname expected "///////bar.com/" but got "/////////bar.com/"
+FAIL Parsing: <foo:////://///> against <http://example.org/foo/bar> assert_equals: pathname expected "//://///" but got "////://///"
+PASS Parsing: <c:/foo> against <http://example.org/foo/bar>
+PASS Parsing: <//foo/bar> against <http://example.org/foo/bar>
+PASS Parsing: <http://foo/path;a??e#f#g> against <http://example.org/foo/bar>
+PASS Parsing: <http://foo/abcd?efgh?ijkl> against <http://example.org/foo/bar>
+PASS Parsing: <http://foo/abcd#foo?bar> against <http://example.org/foo/bar>
+PASS Parsing: <[61:24:74]:98> against <http://example.org/foo/bar>
+PASS Parsing: <http:[61:27]/:foo> against <http://example.org/foo/bar>
+PASS Parsing: <http://[1::2]:3:4> against <http://example.org/foo/bar>
+PASS Parsing: <http://2001::1> against <http://example.org/foo/bar>
+PASS Parsing: <http://2001::1]> against <http://example.org/foo/bar>
+PASS Parsing: <http://2001::1]:80> against <http://example.org/foo/bar>
+PASS Parsing: <http://[2001::1]> against <http://example.org/foo/bar>
+PASS Parsing: <http://[::127.0.0.1]> against <http://example.org/foo/bar>
+PASS Parsing: <http://[0:0:0:0:0:0:13.1.68.3]> against <http://example.org/foo/bar>
+PASS Parsing: <http://[2001::1]:80> against <http://example.org/foo/bar>
+PASS Parsing: <http:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <ftp:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <https:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <madeupscheme:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <file:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <file://example:1/> against <about:blank>
+PASS Parsing: <file://example:test/> against <about:blank>
+PASS Parsing: <file://example%/> against <about:blank>
+PASS Parsing: <file://[example]/> against <about:blank>
+PASS Parsing: <ftps:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <gopher:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <ws:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <wss:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <data:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <javascript:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <mailto:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <http:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <ftp:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <https:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <madeupscheme:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <ftps:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <gopher:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <ws:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <wss:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <data:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <javascript:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <mailto:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: </a/b/c> against <http://example.org/foo/bar>
+PASS Parsing: </a/ /c> against <http://example.org/foo/bar>
+PASS Parsing: </a%2fc> against <http://example.org/foo/bar>
+PASS Parsing: </a/%2f/c> against <http://example.org/foo/bar>
+PASS Parsing: <#β> against <http://example.org/foo/bar>
+PASS Parsing: <data:text/html,test#test> against <http://example.org/foo/bar>
+PASS Parsing: <tel:1234567890> against <http://example.org/foo/bar>
+FAIL Parsing: <ssh://example.com/foo/bar.git> against <http://example.org/> assert_equals: host expected "example.com" but got ""
+FAIL Parsing: <file:c:\foo\bar.html> against <file:///tmp/mock/path> assert_equals: href expected "file:///c:/foo/bar.html" but got "file:///tmp/mock/c:/foo/bar.html"
+FAIL Parsing: <  File:c|////foo\bar.html> against <file:///tmp/mock/path> assert_equals: href expected "file:///c:////foo/bar.html" but got "file:///tmp/mock/c%7C////foo/bar.html"
+FAIL Parsing: <C|/foo/bar> against <file:///tmp/mock/path> assert_equals: href expected "file:///C:/foo/bar" but got "file:///tmp/mock/C%7C/foo/bar"
+FAIL Parsing: </C|\foo\bar> against <file:///tmp/mock/path> assert_equals: href expected "file:///C:/foo/bar" but got "file:///C%7C/foo/bar"
+FAIL Parsing: <//C|/foo/bar> against <file:///tmp/mock/path> assert_equals: href expected "file:///C:/foo/bar" but got "file://c%7C/foo/bar"
+PASS Parsing: <//server/file> against <file:///tmp/mock/path>
+PASS Parsing: <\\server\file> against <file:///tmp/mock/path>
+PASS Parsing: </\server/file> against <file:///tmp/mock/path>
+PASS Parsing: <file:///foo/bar.txt> against <file:///tmp/mock/path>
+PASS Parsing: <file:///home/me> against <file:///tmp/mock/path>
+PASS Parsing: <//> against <file:///tmp/mock/path>
+PASS Parsing: <///> against <file:///tmp/mock/path>
+PASS Parsing: <///test> against <file:///tmp/mock/path>
+PASS Parsing: <file://test> against <file:///tmp/mock/path>
+FAIL Parsing: <file://localhost> against <file:///tmp/mock/path> assert_equals: href expected "file:///" but got "file://localhost/"
+FAIL Parsing: <file://localhost/> against <file:///tmp/mock/path> assert_equals: href expected "file:///" but got "file://localhost/"
+FAIL Parsing: <file://localhost/test> against <file:///tmp/mock/path> assert_equals: href expected "file:///test" but got "file://localhost/test"
+PASS Parsing: <test> against <file:///tmp/mock/path>
+PASS Parsing: <file:test> against <file:///tmp/mock/path>
+PASS Parsing: <http://example.com/././foo> against <about:blank>
+PASS Parsing: <http://example.com/./.foo> against <about:blank>
+PASS Parsing: <http://example.com/foo/.> against <about:blank>
+PASS Parsing: <http://example.com/foo/./> against <about:blank>
+PASS Parsing: <http://example.com/foo/bar/..> against <about:blank>
+PASS Parsing: <http://example.com/foo/bar/../> against <about:blank>
+PASS Parsing: <http://example.com/foo/..bar> against <about:blank>
+PASS Parsing: <http://example.com/foo/bar/../ton> against <about:blank>
+PASS Parsing: <http://example.com/foo/bar/../ton/../../a> against <about:blank>
+PASS Parsing: <http://example.com/foo/../../..> against <about:blank>
+PASS Parsing: <http://example.com/foo/../../../ton> against <about:blank>
+PASS Parsing: <http://example.com/foo/%2e> against <about:blank>
+FAIL Parsing: <http://example.com/foo/%2e%2> against <about:blank> assert_equals: href expected "http://example.com/foo/%2e%2" but got "http://example.com/foo/.%2"
+FAIL Parsing: <http://example.com/foo/%2e./%2e%2e/.%2e/%2e.bar> against <about:blank> assert_equals: href expected "http://example.com/%2e.bar" but got "http://example.com/..bar"
+PASS Parsing: <http://example.com////../..> against <about:blank>
+PASS Parsing: <http://example.com/foo/bar//../..> against <about:blank>
+PASS Parsing: <http://example.com/foo/bar//..> against <about:blank>
+PASS Parsing: <http://example.com/foo> against <about:blank>
+PASS Parsing: <http://example.com/%20foo> against <about:blank>
+PASS Parsing: <http://example.com/foo%> against <about:blank>
+PASS Parsing: <http://example.com/foo%2> against <about:blank>
+PASS Parsing: <http://example.com/foo%2zbar> against <about:blank>
+PASS Parsing: <http://example.com/foo%2©zbar> against <about:blank>
+FAIL Parsing: <http://example.com/foo%41%7a> against <about:blank> assert_equals: href expected "http://example.com/foo%41%7a" but got "http://example.com/fooAz"
+PASS Parsing: <http://example.com/foo	‘%91> against <about:blank>
+FAIL Parsing: <http://example.com/foo%00%51> against <about:blank> Failed to construct 'URL': Invalid URL
+PASS Parsing: <http://example.com/(%28:%3A%29)> against <about:blank>
+PASS Parsing: <http://example.com/%3A%3a%3C%3c> against <about:blank>
+PASS Parsing: <http://example.com/foo	bar> against <about:blank>
+PASS Parsing: <http://example.com\\foo\\bar> against <about:blank>
+PASS Parsing: <http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd> against <about:blank>
+PASS Parsing: <http://example.com/@asdf%40> against <about:blank>
+PASS Parsing: <http://example.com/你好你好> against <about:blank>
+PASS Parsing: <http://example.com/‥/foo> against <about:blank>
+PASS Parsing: <http://example.com//foo> against <about:blank>
+PASS Parsing: <http://example.com/‮/foo/‭/bar> against <about:blank>
+PASS Parsing: <http://www.google.com/foo?bar=baz#> against <about:blank>
+PASS Parsing: <http://www.google.com/foo?bar=baz# »> against <about:blank>
+PASS Parsing: <data:test# »> against <about:blank>
+PASS Parsing: <http://www.google.com> against <about:blank>
+PASS Parsing: <http://192.0x00A80001> against <about:blank>
+FAIL Parsing: <http://www/foo%2Ehtml> against <about:blank> assert_equals: href expected "http://www/foo%2Ehtml" but got "http://www/foo.html"
+PASS Parsing: <http://www/foo/%2E/html> against <about:blank>
+PASS Parsing: <http://user:pass@/> against <about:blank>
+PASS Parsing: <http://%25DOMAIN:foobar@foodomain.com/> against <about:blank>
+PASS Parsing: <http:\\www.google.com\foo> against <about:blank>
+PASS Parsing: <http://foo:80/> against <about:blank>
+PASS Parsing: <http://foo:81/> against <about:blank>
+FAIL Parsing: <httpa://foo:80/> against <about:blank> assert_equals: host expected "foo:80" but got ""
+PASS Parsing: <http://foo:-80/> against <about:blank>
+PASS Parsing: <https://foo:443/> against <about:blank>
+PASS Parsing: <https://foo:80/> against <about:blank>
+PASS Parsing: <ftp://foo:21/> against <about:blank>
+PASS Parsing: <ftp://foo:80/> against <about:blank>
+FAIL Parsing: <gopher://foo:70/> against <about:blank> assert_equals: host expected "foo:70" but got ""
+FAIL Parsing: <gopher://foo:443/> against <about:blank> assert_equals: host expected "foo:443" but got ""
+PASS Parsing: <ws://foo:80/> against <about:blank>
+PASS Parsing: <ws://foo:81/> against <about:blank>
+PASS Parsing: <ws://foo:443/> against <about:blank>
+PASS Parsing: <ws://foo:815/> against <about:blank>
+PASS Parsing: <wss://foo:80/> against <about:blank>
+PASS Parsing: <wss://foo:81/> against <about:blank>
+PASS Parsing: <wss://foo:443/> against <about:blank>
+PASS Parsing: <wss://foo:815/> against <about:blank>
+PASS Parsing: <http:/example.com/> against <about:blank>
+PASS Parsing: <ftp:/example.com/> against <about:blank>
+PASS Parsing: <https:/example.com/> against <about:blank>
+PASS Parsing: <madeupscheme:/example.com/> against <about:blank>
+PASS Parsing: <file:/example.com/> against <about:blank>
+PASS Parsing: <ftps:/example.com/> against <about:blank>
+PASS Parsing: <gopher:/example.com/> against <about:blank>
+PASS Parsing: <ws:/example.com/> against <about:blank>
+PASS Parsing: <wss:/example.com/> against <about:blank>
+PASS Parsing: <data:/example.com/> against <about:blank>
+PASS Parsing: <javascript:/example.com/> against <about:blank>
+PASS Parsing: <mailto:/example.com/> against <about:blank>
+PASS Parsing: <http:example.com/> against <about:blank>
+PASS Parsing: <ftp:example.com/> against <about:blank>
+PASS Parsing: <https:example.com/> against <about:blank>
+PASS Parsing: <madeupscheme:example.com/> against <about:blank>
+PASS Parsing: <ftps:example.com/> against <about:blank>
+PASS Parsing: <gopher:example.com/> against <about:blank>
+PASS Parsing: <ws:example.com/> against <about:blank>
+PASS Parsing: <wss:example.com/> against <about:blank>
+PASS Parsing: <data:example.com/> against <about:blank>
+PASS Parsing: <javascript:example.com/> against <about:blank>
+PASS Parsing: <mailto:example.com/> against <about:blank>
+PASS Parsing: <http:@www.example.com> against <about:blank>
+PASS Parsing: <http:/@www.example.com> against <about:blank>
+PASS Parsing: <http://@www.example.com> against <about:blank>
+PASS Parsing: <http:a:b@www.example.com> against <about:blank>
+PASS Parsing: <http:/a:b@www.example.com> against <about:blank>
+PASS Parsing: <http://a:b@www.example.com> against <about:blank>
+PASS Parsing: <http://@pple.com> against <about:blank>
+PASS Parsing: <http::b@www.example.com> against <about:blank>
+PASS Parsing: <http:/:b@www.example.com> against <about:blank>
+PASS Parsing: <http://:b@www.example.com> against <about:blank>
+PASS Parsing: <http:/:@/www.example.com> against <about:blank>
+PASS Parsing: <http://user@/www.example.com> against <about:blank>
+PASS Parsing: <http:@/www.example.com> against <about:blank>
+PASS Parsing: <http:/@/www.example.com> against <about:blank>
+PASS Parsing: <http://@/www.example.com> against <about:blank>
+PASS Parsing: <https:@/www.example.com> against <about:blank>
+PASS Parsing: <http:a:b@/www.example.com> against <about:blank>
+PASS Parsing: <http:/a:b@/www.example.com> against <about:blank>
+PASS Parsing: <http://a:b@/www.example.com> against <about:blank>
+PASS Parsing: <http::@/www.example.com> against <about:blank>
+PASS Parsing: <http:a:@www.example.com> against <about:blank>
+PASS Parsing: <http:/a:@www.example.com> against <about:blank>
+PASS Parsing: <http://a:@www.example.com> against <about:blank>
+PASS Parsing: <http://www.@pple.com> against <about:blank>
+PASS Parsing: <http:@:www.example.com> against <about:blank>
+PASS Parsing: <http:/@:www.example.com> against <about:blank>
+PASS Parsing: <http://@:www.example.com> against <about:blank>
+PASS Parsing: <http://:@www.example.com> against <about:blank>
+PASS Parsing: </> against <http://www.example.com/test>
+PASS Parsing: </test.txt> against <http://www.example.com/test>
+PASS Parsing: <.> against <http://www.example.com/test>
+PASS Parsing: <..> against <http://www.example.com/test>
+PASS Parsing: <test.txt> against <http://www.example.com/test>
+PASS Parsing: <./test.txt> against <http://www.example.com/test>
+PASS Parsing: <../test.txt> against <http://www.example.com/test>
+PASS Parsing: <../aaa/test.txt> against <http://www.example.com/test>
+PASS Parsing: <../../test.txt> against <http://www.example.com/test>
+PASS Parsing: <中/test.txt> against <http://www.example.com/test>
+PASS Parsing: <http://www.example2.com> against <http://www.example.com/test>
+PASS Parsing: <//www.example2.com> against <http://www.example.com/test>
+PASS Parsing: <file:...> against <http://www.example.com/test>
+PASS Parsing: <file:..> against <http://www.example.com/test>
+PASS Parsing: <file:a> against <http://www.example.com/test>
+PASS Parsing: <http://ExAmPlE.CoM> against <http://other.com/>
+FAIL Parsing: <http://example example.com> against <http://other.com/> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <http://Goo%20 goo%7C|.com> against <http://other.com/> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+PASS Parsing: <http://[]> against <http://other.com/>
+PASS Parsing: <http://[:]> against <http://other.com/>
+FAIL Parsing: <http://GOO  goo.com> against <http://other.com/> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+PASS Parsing: <http://GOO​⁠goo.com> against <http://other.com/>
+PASS Parsing: <\0 http://example.com/ \r > against <about:blank>
+PASS Parsing: <http://www.foo。bar.com> against <http://other.com/>
+PASS Parsing: <http://﷐zyx.com> against <http://other.com/>
+PASS Parsing: <http://%ef%b7%90zyx.com> against <http://other.com/>
+PASS Parsing: <https://�> against <about:blank>
+PASS Parsing: <https://%EF%BF%BD> against <about:blank>
+PASS Parsing: <https://x/�?�#�> against <about:blank>
+FAIL Parsing: <http://a.b.c.xn--pokxncvks> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <http://10.0.0.xn--pokxncvks> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <http://a.b.c.XN--pokxncvks> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <http://a.b.c.Xn--pokxncvks> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <http://10.0.0.XN--pokxncvks> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <http://10.0.0.xN--pokxncvks> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+PASS Parsing: <http://Go.com> against <http://other.com/>
+FAIL Parsing: <http://%41.com> against <http://other.com/> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <http://%ef%bc%85%ef%bc%94%ef%bc%91.com> against <http://other.com/> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+PASS Parsing: <http://%00.com> against <http://other.com/>
+PASS Parsing: <http://%ef%bc%85%ef%bc%90%ef%bc%90.com> against <http://other.com/>
+PASS Parsing: <http://你好你好> against <http://other.com/>
+PASS Parsing: <https://faß.ExAmPlE/> against <about:blank>
+FAIL Parsing: <sc://faß.ExAmPlE/> against <about:blank> assert_equals: host expected "fa%C3%9F.ExAmPlE" but got ""
+PASS Parsing: <http://%zz%66%a.com> against <http://other.com/>
+PASS Parsing: <http://%25> against <http://other.com/>
+PASS Parsing: <http://hello%00> against <http://other.com/>
+PASS Parsing: <http://%30%78%63%30%2e%30%32%35%30.01> against <http://other.com/>
+PASS Parsing: <http://%30%78%63%30%2e%30%32%35%30.01%2e> against <http://other.com/>
+PASS Parsing: <http://192.168.0.257> against <http://other.com/>
+PASS Parsing: <http://%3g%78%63%30%2e%30%32%35%30%2E.01> against <http://other.com/>
+FAIL Parsing: <http://192.168.0.1 hello> against <http://other.com/> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <https://x x:12> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+PASS Parsing: <http://0Xc0.0250.01> against <http://other.com/>
+PASS Parsing: <http://./> against <about:blank>
+PASS Parsing: <http://../> against <about:blank>
+PASS Parsing: <http://[www.google.com]/> against <about:blank>
+PASS Parsing: <http://[google.com]> against <http://other.com/>
+PASS Parsing: <http://[::1.2.3.4x]> against <http://other.com/>
+FAIL Parsing: <http://[::1.2.3.]> against <http://other.com/> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <http://[::1.2.]> against <http://other.com/> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <http://[::1.]> against <http://other.com/> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+PASS Parsing: <http://foo:💩@example.com/bar> against <http://other.com/>
+PASS Parsing: <#> against <test:test>
+PASS Parsing: <#x> against <mailto:x@x.com>
+PASS Parsing: <#x> against <data:,>
+PASS Parsing: <#x> against <about:blank>
+PASS Parsing: <#> against <test:test?test>
+PASS Parsing: <https://@test@test@example:800/> against <http://doesnotmatter/>
+PASS Parsing: <https://@@@example> against <http://doesnotmatter/>
+PASS Parsing: <http://`{}:`{}@h/`{}?`{}> against <http://doesnotmatter/>
+PASS Parsing: <http://host/?'> against <about:blank>
+FAIL Parsing: <notspecial://host/?'> against <about:blank> assert_equals: href expected "notspecial://host/?'" but got "notspecial://host/?%27"
+PASS Parsing: </some/path> against <http://user@example.org/smth>
+PASS Parsing: <> against <http://user:pass@example.org:21/smth>
+PASS Parsing: </some/path> against <http://user:pass@example.org:21/smth>
+PASS Parsing: <i> against <sc:sd>
+PASS Parsing: <i> against <sc:sd/sd>
+PASS Parsing: <i> against <sc:/pa/pa>
+FAIL Parsing: <i> against <sc://ho/pa> assert_equals: host expected "ho" but got ""
+FAIL Parsing: <i> against <sc:///pa/pa> assert_equals: pathname expected "/pa/i" but got "///pa/i"
+PASS Parsing: <../i> against <sc:sd>
+PASS Parsing: <../i> against <sc:sd/sd>
+PASS Parsing: <../i> against <sc:/pa/pa>
+FAIL Parsing: <../i> against <sc://ho/pa> assert_equals: host expected "ho" but got ""
+FAIL Parsing: <../i> against <sc:///pa/pa> assert_equals: href expected "sc:///i" but got "sc:///pa/i"
+PASS Parsing: </i> against <sc:sd>
+PASS Parsing: </i> against <sc:sd/sd>
+PASS Parsing: </i> against <sc:/pa/pa>
+FAIL Parsing: </i> against <sc://ho/pa> assert_equals: host expected "ho" but got ""
+FAIL Parsing: </i> against <sc:///pa/pa> assert_equals: href expected "sc:///i" but got "sc:///pa/i"
+PASS Parsing: <?i> against <sc:sd>
+PASS Parsing: <?i> against <sc:sd/sd>
+PASS Parsing: <?i> against <sc:/pa/pa>
+FAIL Parsing: <?i> against <sc://ho/pa> assert_equals: host expected "ho" but got ""
+FAIL Parsing: <?i> against <sc:///pa/pa> assert_equals: pathname expected "/pa/pa" but got "///pa/pa"
+PASS Parsing: <#i> against <sc:sd>
+PASS Parsing: <#i> against <sc:sd/sd>
+PASS Parsing: <#i> against <sc:/pa/pa>
+FAIL Parsing: <#i> against <sc://ho/pa> assert_equals: host expected "ho" but got ""
+FAIL Parsing: <#i> against <sc:///pa/pa> assert_equals: pathname expected "/pa/pa" but got "///pa/pa"
+FAIL Parsing: <about:/../> against <about:blank> assert_equals: href expected "about:/" but got "about:/../"
+FAIL Parsing: <data:/../> against <about:blank> assert_equals: href expected "data:/" but got "data:/../"
+FAIL Parsing: <javascript:/../> against <about:blank> assert_equals: href expected "javascript:/" but got "javascript:/../"
+FAIL Parsing: <mailto:/../> against <about:blank> assert_equals: href expected "mailto:/" but got "mailto:/../"
+FAIL Parsing: <sc://ñ.test/> against <about:blank> assert_equals: host expected "%C3%B1.test" but got ""
+FAIL Parsing: <sc://%/> against <about:blank> assert_equals: host expected "%" but got ""
+FAIL Parsing: <sc://@/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <sc://te@s:t@/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <sc://:/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <sc://:12/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <x> against <sc://ñ> Failed to construct 'URL': Invalid URL
+PASS Parsing: <sc:\../> against <about:blank>
+PASS Parsing: <sc::a@example.net> against <about:blank>
+PASS Parsing: <wow:%NBD> against <about:blank>
+PASS Parsing: <wow:%1G> against <about:blank>
+FAIL Parsing: <wow:￿> against <about:blank> assert_equals: href expected "wow:%EF%BF%BF" but got "wow:%EF%BF%BD"
+FAIL Parsing: <http://example.com/U+d800𐟾U+dfff﷐﷏﷯ﷰ￾￿?U+d800𐟾U+dfff﷐﷏﷯ﷰ￾￿> against <about:blank> Failed to construct 'URL': Invalid URL
+FAIL Parsing: <sc://a\0b/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <sc://a b/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <sc://a<b> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <sc://a>b> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <sc://a[b/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <sc://a\b/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <sc://a]b/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <sc://a^b> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <sc://a|b/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <foo://ho	st/> against <about:blank> assert_equals: host expected "host" but got ""
+FAIL Parsing: <foo://ho
+st/> against <about:blank> assert_equals: host expected "host" but got ""
+FAIL Parsing: <foo://ho\rst/> against <about:blank> assert_equals: host expected "host" but got ""
+PASS Parsing: <http://a\0b/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+FAIL Parsing: <http://a b/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+PASS Parsing: <http://a%b/> against <about:blank>
+FAIL Parsing: <http://a<b> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <http://a>b> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+PASS Parsing: <http://a[b/> against <about:blank>
+PASS Parsing: <http://a]b/> against <about:blank>
+PASS Parsing: <http://a^b> against <about:blank>
+FAIL Parsing: <http://a|b/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ho	st/> against <about:blank>
+PASS Parsing: <http://ho
+st/> against <about:blank>
+PASS Parsing: <http://ho\rst/> against <about:blank>
+PASS Parsing: <http://ho%00st/> against <about:blank>
+PASS Parsing: <http://ho%01st/> against <about:blank>
+PASS Parsing: <http://ho%02st/> against <about:blank>
+PASS Parsing: <http://ho%03st/> against <about:blank>
+PASS Parsing: <http://ho%04st/> against <about:blank>
+PASS Parsing: <http://ho%05st/> against <about:blank>
+PASS Parsing: <http://ho%06st/> against <about:blank>
+PASS Parsing: <http://ho%07st/> against <about:blank>
+PASS Parsing: <http://ho%08st/> against <about:blank>
+PASS Parsing: <http://ho%09st/> against <about:blank>
+PASS Parsing: <http://ho%0Ast/> against <about:blank>
+PASS Parsing: <http://ho%0Bst/> against <about:blank>
+PASS Parsing: <http://ho%0Cst/> against <about:blank>
+PASS Parsing: <http://ho%0Dst/> against <about:blank>
+PASS Parsing: <http://ho%0Est/> against <about:blank>
+PASS Parsing: <http://ho%0Fst/> against <about:blank>
+PASS Parsing: <http://ho%10st/> against <about:blank>
+PASS Parsing: <http://ho%11st/> against <about:blank>
+PASS Parsing: <http://ho%12st/> against <about:blank>
+PASS Parsing: <http://ho%13st/> against <about:blank>
+PASS Parsing: <http://ho%14st/> against <about:blank>
+PASS Parsing: <http://ho%15st/> against <about:blank>
+PASS Parsing: <http://ho%16st/> against <about:blank>
+PASS Parsing: <http://ho%17st/> against <about:blank>
+PASS Parsing: <http://ho%18st/> against <about:blank>
+PASS Parsing: <http://ho%19st/> against <about:blank>
+PASS Parsing: <http://ho%1Ast/> against <about:blank>
+PASS Parsing: <http://ho%1Bst/> against <about:blank>
+PASS Parsing: <http://ho%1Cst/> against <about:blank>
+PASS Parsing: <http://ho%1Dst/> against <about:blank>
+PASS Parsing: <http://ho%1Est/> against <about:blank>
+PASS Parsing: <http://ho%1Fst/> against <about:blank>
+FAIL Parsing: <http://ho%20st/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <http://ho%23st/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+PASS Parsing: <http://ho%25st/> against <about:blank>
+PASS Parsing: <http://ho%2Fst/> against <about:blank>
+PASS Parsing: <http://ho%3Ast/> against <about:blank>
+FAIL Parsing: <http://ho%3Cst/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <http://ho%3Est/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+PASS Parsing: <http://ho%3Fst/> against <about:blank>
+FAIL Parsing: <http://ho%40st/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+PASS Parsing: <http://ho%5Bst/> against <about:blank>
+PASS Parsing: <http://ho%5Cst/> against <about:blank>
+PASS Parsing: <http://ho%5Dst/> against <about:blank>
+FAIL Parsing: <http://ho%7Cst/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+PASS Parsing: <http://ho%7Fst/> against <about:blank>
+FAIL Parsing: <http://!"$&'()*+,-.;=_`{}~/> against <about:blank> Failed to construct 'URL': Invalid URL
+FAIL Parsing: <sc://!"$%&'()*+,-.;=_`{}~/> against <about:blank> assert_equals: host expected "%01%02%03%04%05%06%07%08%0B%0C%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%7F!\"$%&'()*+,-.;=_`{}~" but got ""
+PASS Parsing: <ftp://example.com%80/> against <about:blank>
+PASS Parsing: <ftp://example.com%A0/> against <about:blank>
+PASS Parsing: <https://example.com%80/> against <about:blank>
+PASS Parsing: <https://example.com%A0/> against <about:blank>
+PASS Parsing: <ftp://%e2%98%83> against <about:blank>
+PASS Parsing: <https://%e2%98%83> against <about:blank>
+PASS Parsing: <http://127.0.0.1:10100/relative_import.html> against <about:blank>
+PASS Parsing: <http://facebook.com/?foo=%7B%22abc%22> against <about:blank>
+PASS Parsing: <https://localhost:3000/jqueryui@1.2.3> against <about:blank>
+PASS Parsing: <h	t
+t\rp://h	o
+s\rt:9	0
+0\r0/p	a
+t\rh?q	u
+e\rry#f	r
+a\rg> against <about:blank>
+PASS Parsing: <?a=b&c=d> against <http://example.org/foo/bar>
+PASS Parsing: <??a=b&c=d> against <http://example.org/foo/bar>
+PASS Parsing: <http:> against <http://example.org/foo/bar>
+PASS Parsing: <http:> against <https://example.org/foo/bar>
+PASS Parsing: <sc:> against <https://example.org/foo/bar>
+PASS Parsing: <http://foo.bar/baz?qux#foobar> against <about:blank>
+PASS Parsing: <http://foo.bar/baz?qux#foo"bar> against <about:blank>
+PASS Parsing: <http://foo.bar/baz?qux#foo<bar> against <about:blank>
+PASS Parsing: <http://foo.bar/baz?qux#foo>bar> against <about:blank>
+PASS Parsing: <http://foo.bar/baz?qux#foo`bar> against <about:blank>
+PASS Parsing: <http://1.2.3.4/> against <http://other.com/>
+PASS Parsing: <http://1.2.3.4./> against <http://other.com/>
+PASS Parsing: <http://192.168.257> against <http://other.com/>
+PASS Parsing: <http://192.168.257.> against <http://other.com/>
+PASS Parsing: <http://192.168.257.com> against <http://other.com/>
+PASS Parsing: <http://256> against <http://other.com/>
+PASS Parsing: <http://256.com> against <http://other.com/>
+PASS Parsing: <http://999999999> against <http://other.com/>
+PASS Parsing: <http://999999999.> against <http://other.com/>
+PASS Parsing: <http://999999999.com> against <http://other.com/>
+PASS Parsing: <http://10000000000> against <http://other.com/>
+PASS Parsing: <http://10000000000.com> against <http://other.com/>
+PASS Parsing: <http://4294967295> against <http://other.com/>
+PASS Parsing: <http://4294967296> against <http://other.com/>
+PASS Parsing: <http://0xffffffff> against <http://other.com/>
+PASS Parsing: <http://0xffffffff1> against <http://other.com/>
+PASS Parsing: <http://256.256.256.256> against <http://other.com/>
+PASS Parsing: <https://0x.0x.0> against <about:blank>
+PASS Parsing: <https://0x100000000/test> against <about:blank>
+PASS Parsing: <https://256.0.0.1/test> against <about:blank>
+PASS Parsing: <file:///C%3A/> against <about:blank>
+PASS Parsing: <file:///C%7C/> against <about:blank>
+PASS Parsing: <file://%43%3A> against <about:blank>
+FAIL Parsing: <file://%43%7C> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <file://%43|> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <file://C%7C> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <file://%43%7C/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <https://%43%7C/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <asdf://%43|/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <asdf://%43%7C/> against <about:blank> assert_equals: host expected "%43%7C" but got ""
+PASS Parsing: <pix/submit.gif> against <file:///C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/anchor.html>
+FAIL Parsing: <..> against <file:///C:/> assert_equals: href expected "file:///C:/" but got "file:///"
+PASS Parsing: <..> against <file:///>
+FAIL Parsing: </> against <file:///C:/a/b> assert_equals: href expected "file:///C:/" but got "file:///"
+FAIL Parsing: </> against <file://h/C:/a/b> assert_equals: href expected "file://h/C:/" but got "file://h/"
+PASS Parsing: </> against <file://h/a/b>
+FAIL Parsing: <//d:> against <file:///C:/a/b> Failed to construct 'URL': Invalid URL
+FAIL Parsing: <//d:/..> against <file:///C:/a/b> Failed to construct 'URL': Invalid URL
+PASS Parsing: <..> against <file:///ab:/>
+PASS Parsing: <..> against <file:///1:/>
+PASS Parsing: <> against <file:///test?test#test>
+PASS Parsing: <file:> against <file:///test?test#test>
+PASS Parsing: <?x> against <file:///test?test#test>
+PASS Parsing: <file:?x> against <file:///test?test#test>
+PASS Parsing: <#x> against <file:///test?test#test>
+PASS Parsing: <file:#x> against <file:///test?test#test>
+FAIL Parsing: <file:\\//> against <about:blank> assert_equals: href expected "file:////" but got "file:///"
+FAIL Parsing: <file:\\\\> against <about:blank> assert_equals: href expected "file:////" but got "file:///"
+FAIL Parsing: <file:\\\\?fox> against <about:blank> assert_equals: href expected "file:////?fox" but got "file:///?fox"
+FAIL Parsing: <file:\\\\#guppy> against <about:blank> assert_equals: href expected "file:////#guppy" but got "file:///#guppy"
+PASS Parsing: <file://spider///> against <about:blank>
+FAIL Parsing: <file:\\localhost//> against <about:blank> assert_equals: href expected "file:////" but got "file://localhost//"
+PASS Parsing: <file:///localhost//cat> against <about:blank>
+FAIL Parsing: <file://\/localhost//cat> against <about:blank> assert_equals: href expected "file:////localhost//cat" but got "file:///localhost//cat"
+FAIL Parsing: <file://localhost//a//../..//> against <about:blank> assert_equals: href expected "file://///" but got "file://localhost///"
+FAIL Parsing: </////mouse> against <file:///elephant> assert_equals: href expected "file://///mouse" but got "file:///mouse"
+PASS Parsing: <\//pig> against <file://lion/>
+FAIL Parsing: <\/localhost//pig> against <file://lion/> assert_equals: href expected "file:////pig" but got "file://localhost//pig"
+FAIL Parsing: <//localhost//pig> against <file://lion/> assert_equals: href expected "file:////pig" but got "file://localhost//pig"
+PASS Parsing: </..//localhost//pig> against <file://lion/>
+PASS Parsing: <file://> against <file://ape/>
+PASS Parsing: </rooibos> against <file://tea/>
+PASS Parsing: </?chai> against <file://tea/>
+FAIL Parsing: <C|> against <file://host/dir/file> assert_equals: href expected "file://host/C:" but got "file://host/dir/C%7C"
+FAIL Parsing: <C|> against <file://host/D:/dir1/dir2/file> assert_equals: href expected "file://host/C:" but got "file://host/D:/dir1/dir2/C%7C"
+FAIL Parsing: <C|#> against <file://host/dir/file> assert_equals: href expected "file://host/C:#" but got "file://host/dir/C%7C#"
+FAIL Parsing: <C|?> against <file://host/dir/file> assert_equals: href expected "file://host/C:?" but got "file://host/dir/C%7C?"
+FAIL Parsing: <C|/> against <file://host/dir/file> assert_equals: href expected "file://host/C:/" but got "file://host/dir/C%7C/"
+FAIL Parsing: <C|
+/> against <file://host/dir/file> assert_equals: href expected "file://host/C:/" but got "file://host/dir/C%7C/"
+FAIL Parsing: <C|\> against <file://host/dir/file> assert_equals: href expected "file://host/C:/" but got "file://host/dir/C%7C/"
+PASS Parsing: <C> against <file://host/dir/file>
+FAIL Parsing: <C|a> against <file://host/dir/file> assert_equals: href expected "file://host/dir/C|a" but got "file://host/dir/C%7Ca"
+PASS Parsing: </c:/foo/bar> against <file:///c:/baz/qux>
+FAIL Parsing: </c|/foo/bar> against <file:///c:/baz/qux> assert_equals: href expected "file:///c:/foo/bar" but got "file:///c%7C/foo/bar"
+PASS Parsing: <file:\c:\foo\bar> against <file:///c:/baz/qux>
+PASS Parsing: </c:/foo/bar> against <file://host/path>
+PASS Parsing: <file://example.net/C:/> against <about:blank>
+PASS Parsing: <file://1.2.3.4/C:/> against <about:blank>
+PASS Parsing: <file://[1::8]/C:/> against <about:blank>
+FAIL Parsing: <C|/> against <file://host/> assert_equals: href expected "file://host/C:/" but got "file://host/C%7C/"
+PASS Parsing: </C:/> against <file://host/>
+PASS Parsing: <file:C:/> against <file://host/>
+PASS Parsing: <file:/C:/> against <file://host/>
+FAIL Parsing: <//C:/> against <file://host/> Failed to construct 'URL': Invalid URL
+FAIL Parsing: <file://C:/> against <file://host/> Failed to construct 'URL': Invalid URL
+PASS Parsing: <///C:/> against <file://host/>
+PASS Parsing: <file:///C:/> against <file://host/>
+FAIL Parsing: <file:/C|/> against <about:blank> assert_equals: href expected "file:///C:/" but got "file:///C%7C/"
+FAIL Parsing: <file://C|/> against <about:blank> assert_equals: href expected "file:///C:/" but got "file://c%7C/"
+PASS Parsing: <file:> against <about:blank>
+PASS Parsing: <file:?q=v> against <about:blank>
+PASS Parsing: <file:#frag> against <about:blank>
+PASS Parsing: <file:///Y:> against <about:blank>
+PASS Parsing: <file:///Y:/> against <about:blank>
+PASS Parsing: <file:///./Y> against <about:blank>
+PASS Parsing: <file:///./Y:> against <about:blank>
+PASS Parsing: <\\\.\Y:> against <about:blank>
+PASS Parsing: <file:///y:> against <about:blank>
+PASS Parsing: <file:///y:/> against <about:blank>
+PASS Parsing: <file:///./y> against <about:blank>
+PASS Parsing: <file:///./y:> against <about:blank>
+PASS Parsing: <\\\.\y:> against <about:blank>
+FAIL Parsing: <file://localhost//a//../..//foo> against <about:blank> assert_equals: href expected "file://///foo" but got "file://localhost///foo"
+FAIL Parsing: <file://localhost////foo> against <about:blank> assert_equals: href expected "file://////foo" but got "file://localhost////foo"
+FAIL Parsing: <file:////foo> against <about:blank> assert_equals: href expected "file:////foo" but got "file:///foo"
+PASS Parsing: <file:///one/two> against <file:///>
+FAIL Parsing: <file:////one/two> against <file:///> assert_equals: href expected "file:////one/two" but got "file:///one/two"
+PASS Parsing: <//one/two> against <file:///>
+PASS Parsing: <///one/two> against <file:///>
+FAIL Parsing: <////one/two> against <file:///> assert_equals: href expected "file:////one/two" but got "file:///one/two"
+PASS Parsing: <file:///.//> against <file:////>
+PASS Parsing: <file:.//p> against <about:blank>
+PASS Parsing: <file:/.//p> against <about:blank>
+PASS Parsing: <http://[1:0::]> against <http://example.net/>
+PASS Parsing: <http://[0:1:2:3:4:5:6:7:8]> against <http://example.net/>
+PASS Parsing: <https://[0::0::0]> against <about:blank>
+PASS Parsing: <https://[0:.0]> against <about:blank>
+PASS Parsing: <https://[0:0:]> against <about:blank>
+PASS Parsing: <https://[0:1:2:3:4:5:6:7.0.0.0.1]> against <about:blank>
+PASS Parsing: <https://[0:1.00.0.0.0]> against <about:blank>
+PASS Parsing: <https://[0:1.290.0.0.0]> against <about:blank>
+PASS Parsing: <https://[0:1.23.23]> against <about:blank>
+PASS Parsing: <http://?> against <about:blank>
+PASS Parsing: <http://#> against <about:blank>
+PASS Parsing: <http://f:4294967377/c> against <http://example.org/>
+PASS Parsing: <http://f:18446744073709551697/c> against <http://example.org/>
+PASS Parsing: <http://f:340282366920938463463374607431768211537/c> against <http://example.org/>
+FAIL Parsing: <sc://ñ> against <about:blank> assert_equals: host expected "%C3%B1" but got ""
+FAIL Parsing: <sc://ñ?x> against <about:blank> assert_equals: host expected "%C3%B1" but got ""
+FAIL Parsing: <sc://ñ#x> against <about:blank> assert_equals: host expected "%C3%B1" but got ""
+FAIL Parsing: <#x> against <sc://ñ> Failed to construct 'URL': Invalid URL
+FAIL Parsing: <?x> against <sc://ñ> Failed to construct 'URL': Invalid URL
+FAIL Parsing: <sc://?> against <about:blank> assert_equals: pathname expected "" but got "//"
+FAIL Parsing: <sc://#> against <about:blank> assert_equals: pathname expected "" but got "//"
+FAIL Parsing: <///> against <sc://x/> Failed to construct 'URL': Invalid URL
+FAIL Parsing: <////> against <sc://x/> Failed to construct 'URL': Invalid URL
+FAIL Parsing: <////x/> against <sc://x/> assert_equals: href expected "sc:////x/" but got "sc://x/"
+FAIL Parsing: <tftp://foobar.com/someconfig;mode=netascii> against <about:blank> assert_equals: host expected "foobar.com" but got ""
+FAIL Parsing: <telnet://user:pass@foobar.com:23/> against <about:blank> assert_equals: username expected "user" but got ""
+FAIL Parsing: <ut2004://10.10.10.10:7777/Index.ut2> against <about:blank> assert_equals: host expected "10.10.10.10:7777" but got ""
+FAIL Parsing: <redis://foo:bar@somehost:6379/0?baz=bam&qux=baz> against <about:blank> assert_equals: username expected "foo" but got ""
+FAIL Parsing: <rsync://foo@host:911/sup> against <about:blank> assert_equals: username expected "foo" but got ""
+FAIL Parsing: <git://github.com/foo/bar.git> against <about:blank> assert_equals: host expected "github.com" but got ""
+FAIL Parsing: <irc://myserver.com:6999/channel?passwd> against <about:blank> assert_equals: host expected "myserver.com:6999" but got ""
+FAIL Parsing: <dns://fw.example.org:9999/foo.bar.org?type=TXT> against <about:blank> assert_equals: host expected "fw.example.org:9999" but got ""
+FAIL Parsing: <ldap://localhost:389/ou=People,o=JNDITutorial> against <about:blank> assert_equals: host expected "localhost:389" but got ""
+FAIL Parsing: <git+https://github.com/foo/bar> against <about:blank> assert_equals: host expected "github.com" but got ""
+PASS Parsing: <urn:ietf:rfc:2648> against <about:blank>
+PASS Parsing: <tag:joe@example.org,2001:foo/bar> against <about:blank>
+FAIL Parsing: <non-spec:/.//> against <about:blank> assert_equals: pathname expected "//" but got "/.//"
+FAIL Parsing: <non-spec:/..//> against <about:blank> assert_equals: href expected "non-spec:/.//" but got "non-spec:/..//"
+FAIL Parsing: <non-spec:/a/..//> against <about:blank> assert_equals: href expected "non-spec:/.//" but got "non-spec:/a/..//"
+FAIL Parsing: <non-spec:/.//path> against <about:blank> assert_equals: pathname expected "//path" but got "/.//path"
+FAIL Parsing: <non-spec:/..//path> against <about:blank> assert_equals: href expected "non-spec:/.//path" but got "non-spec:/..//path"
+FAIL Parsing: <non-spec:/a/..//path> against <about:blank> assert_equals: href expected "non-spec:/.//path" but got "non-spec:/a/..//path"
+FAIL Parsing: </.//path> against <non-spec:/p> assert_equals: href expected "non-spec:/.//path" but got "non-spec://path"
+FAIL Parsing: </..//path> against <non-spec:/p> assert_equals: href expected "non-spec:/.//path" but got "non-spec://path"
+FAIL Parsing: <..//path> against <non-spec:/p> assert_equals: href expected "non-spec:/.//path" but got "non-spec://path"
+FAIL Parsing: <a/..//path> against <non-spec:/p> assert_equals: href expected "non-spec:/.//path" but got "non-spec://path"
+FAIL Parsing: <> against <non-spec:/..//p> assert_equals: href expected "non-spec:/.//p" but got "non-spec:/..//p"
+FAIL Parsing: <path> against <non-spec:/..//p> assert_equals: href expected "non-spec:/.//path" but got "non-spec:/..//path"
+FAIL Parsing: <../path> against <non-spec:/.//p> assert_equals: href expected "non-spec:/path" but got "non-spec:/./path"
+FAIL Parsing: <non-special://%E2%80%A0/> against <about:blank> assert_equals: host expected "%E2%80%A0" but got ""
+FAIL Parsing: <non-special://H%4fSt/path> against <about:blank> assert_equals: host expected "H%4fSt" but got ""
+FAIL Parsing: <non-special://[1:2:0:0:5:0:0:0]/> against <about:blank> assert_equals: href expected "non-special://[1:2:0:0:5::]/" but got "non-special://[1:2:0:0:5:0:0:0]/"
+FAIL Parsing: <non-special://[1:2:0:0:0:0:0:3]/> against <about:blank> assert_equals: href expected "non-special://[1:2::3]/" but got "non-special://[1:2:0:0:0:0:0:3]/"
+FAIL Parsing: <non-special://[1:2::3]:80/> against <about:blank> assert_equals: host expected "[1:2::3]:80" but got ""
+FAIL Parsing: <non-special://[:80/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+PASS Parsing: <blob:https://example.com:443/> against <about:blank>
+PASS Parsing: <blob:d3958f5c-0777-0845-9dcf-2cb28783acaf> against <about:blank>
+PASS Parsing: <blob:> against <about:blank>
+PASS Parsing: <http://0x7f.0.0.0x7g> against <about:blank>
+PASS Parsing: <http://0X7F.0.0.0X7G> against <about:blank>
+PASS Parsing: <http://[::127.0.0.0.1]> against <about:blank>
+PASS Parsing: <http://[0:1:0:1:0:1:0:1]> against <about:blank>
+PASS Parsing: <http://[1:0:1:0:1:0:1:0]> against <about:blank>
+PASS Parsing: <http://example.org/test?"> against <about:blank>
+PASS Parsing: <http://example.org/test?#> against <about:blank>
+PASS Parsing: <http://example.org/test?<> against <about:blank>
+PASS Parsing: <http://example.org/test?>> against <about:blank>
+PASS Parsing: <http://example.org/test?⌣> against <about:blank>
+PASS Parsing: <http://example.org/test?%23%23> against <about:blank>
+PASS Parsing: <http://example.org/test?%GH> against <about:blank>
+PASS Parsing: <http://example.org/test?a#%EF> against <about:blank>
+PASS Parsing: <http://example.org/test?a#%GH> against <about:blank>
+PASS Parsing: <a> against <about:blank>
+PASS Parsing: <a/> against <about:blank>
+PASS Parsing: <a//> against <about:blank>
+PASS Parsing: <test-a-colon.html> against <a:>
+PASS Parsing: <test-a-colon-b.html> against <a:b>
+PASS Parsing: <test-a-colon-slash.html> against <a:/>
+FAIL Parsing: <test-a-colon-slash-slash.html> against <a://> Failed to construct 'URL': Invalid URL
+PASS Parsing: <test-a-colon-slash-b.html> against <a:/b>
+FAIL Parsing: <test-a-colon-slash-slash-b.html> against <a://b> Failed to construct 'URL': Invalid URL
+PASS Parsing: <http://example.org/test?a#b\0c> against <about:blank>
+FAIL Parsing: <non-spec://example.org/test?a#b\0c> against <about:blank> assert_equals: host expected "example.org" but got ""
+PASS Parsing: <non-spec:/test?a#b\0c> against <about:blank>
+PASS Parsing: <10.0.0.7:8080/foo.html> against <file:///some/dir/bar.html>
+PASS Parsing: <a!@$*=/foo.html> against <file:///some/dir/bar.html>
+PASS Parsing: <a1234567890-+.:foo/bar> against <http://example.com/dir/file>
+PASS Parsing: <file://a­b/p> against <about:blank>
+PASS Parsing: <file://a%C2%ADb/p> against <about:blank>
+PASS Parsing: <file://­/p> against <about:blank>
+PASS Parsing: <file://%C2%AD/p> against <about:blank>
+FAIL Parsing: <file://xn--/p> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+PASS Parsing: <#link> against <https://example.org/##link>
+PASS Parsing: <non-special:cannot-be-a-base-url-\0~€> against <about:blank>
+PASS Parsing: <https://www.example.com/path{path.html?query'=query#fragment<fragment> against <about:blank>
+PASS Parsing: <https://user:pass[@foo/bar> against <http://example.org>
+FAIL Parsing: <foo:// !"$%&'()*+,-.;<=>@[\]^_`{|}~@host/> against <about:blank> assert_equals: href expected "foo://%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~@host/" but got "foo:// !\"$%&'()*+,-.;<=>@[\\]^_`{|}~@host/"
+FAIL Parsing: <wss:// !"$%&'()*+,-.;<=>@[]^_`{|}~@host/> against <about:blank> assert_equals: href expected "wss://%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/" but got "wss://%20!%22$%&%27()*+,-.%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/"
+FAIL Parsing: <foo://joe: !"$%&'()*+,-.:;<=>@[\]^_`{|}~@host/> against <about:blank> assert_equals: href expected "foo://joe:%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~@host/" but got "foo://joe: !\"$%&'()*+,-.:;<=>@[\\]^_`{|}~@host/"
+FAIL Parsing: <wss://joe: !"$%&'()*+,-.:;<=>@[]^_`{|}~@host/> against <about:blank> assert_equals: href expected "wss://joe:%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/" but got "wss://joe:%20!%22$%&%27()*+,-.%3A%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/"
+FAIL Parsing: <foo://!"$%&'()*+,-.;=_`{}~/> against <about:blank> assert_equals: host expected "!\"$%&'()*+,-.;=_`{}~" but got ""
+FAIL Parsing: <wss://!"$&'()*+,-.;=_`{}~/> against <about:blank> Failed to construct 'URL': Invalid URL
+FAIL Parsing: <foo://host/ !"$%&'()*+,-./:;<=>@[\]^_`{|}~> against <about:blank> assert_equals: href expected "foo://host/%20!%22$%&'()*+,-./:;%3C=%3E@[\\]^_%60%7B|%7D~" but got "foo://host/ !\"$%&'()*+,-./:;<=>@[\\]^_`{|}~"
+FAIL Parsing: <wss://host/ !"$%&'()*+,-./:;<=>@[\]^_`{|}~> against <about:blank> assert_equals: href expected "wss://host/%20!%22$%&'()*+,-./:;%3C=%3E@[/]^_%60%7B|%7D~" but got "wss://host/%20!%22$%&'()*+,-./:;%3C=%3E@[/]%5E_%60%7B%7C%7D~"
+FAIL Parsing: <foo://host/dir/? !"$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank> assert_equals: href expected "foo://host/dir/?%20!%22$%&'()*+,-./:;%3C=%3E?@[\\]^_`{|}~" but got "foo://host/dir/?%20!%22$%&%27()*+,-./:;%3C=%3E?@[\\]^_`{|}~"
+PASS Parsing: <wss://host/dir/? !"$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
+FAIL Parsing: <foo://host/dir/# !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank> assert_equals: host expected "host" but got ""
+PASS Parsing: <wss://host/dir/# !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
+FAIL Parsing: <abc:rootless> against <abc://host/path> assert_equals: href expected "abc:rootless" but got "abc://host/rootless"
+FAIL Parsing: <abc:rootless> against <abc:/path> assert_equals: href expected "abc:rootless" but got "abc:/rootless"
+PASS Parsing: <abc:rootless> against <abc:path>
+FAIL Parsing: <abc:/rooted> against <abc://host/path> assert_equals: href expected "abc:/rooted" but got "abc://host/rooted"
+PASS Parsing: <#> against <null>
+PASS Parsing: <?> against <null>
+PASS Parsing: <http://1.2.3.4.5> against <http://other.com/>
+PASS Parsing: <http://1.2.3.4.5.> against <http://other.com/>
+PASS Parsing: <http://0..0x300/> against <about:blank>
+PASS Parsing: <http://0..0x300./> against <about:blank>
+PASS Parsing: <http://256.256.256.256.256> against <http://other.com/>
+PASS Parsing: <http://256.256.256.256.256.> against <http://other.com/>
+PASS Parsing: <http://1.2.3.08> against <about:blank>
+PASS Parsing: <http://1.2.3.08.> against <about:blank>
+PASS Parsing: <http://1.2.3.09> against <about:blank>
+PASS Parsing: <http://09.2.3.4> against <about:blank>
+PASS Parsing: <http://09.2.3.4.> against <about:blank>
+PASS Parsing: <http://01.2.3.4.5> against <about:blank>
+PASS Parsing: <http://01.2.3.4.5.> against <about:blank>
+PASS Parsing: <http://0x100.2.3.4> against <about:blank>
+PASS Parsing: <http://0x100.2.3.4.> against <about:blank>
+PASS Parsing: <http://0x1.2.3.4.5> against <about:blank>
+PASS Parsing: <http://0x1.2.3.4.5.> against <about:blank>
+PASS Parsing: <http://foo.1.2.3.4> against <about:blank>
+PASS Parsing: <http://foo.1.2.3.4.> against <about:blank>
+PASS Parsing: <http://foo.2.3.4> against <about:blank>
+PASS Parsing: <http://foo.2.3.4.> against <about:blank>
+PASS Parsing: <http://foo.09> against <about:blank>
+PASS Parsing: <http://foo.09.> against <about:blank>
+PASS Parsing: <http://foo.0x4> against <about:blank>
+PASS Parsing: <http://foo.0x4.> against <about:blank>
+PASS Parsing: <http://foo.09..> against <about:blank>
+PASS Parsing: <http://0999999999999999999/> against <about:blank>
+PASS Parsing: <http://foo.0x> against <about:blank>
+PASS Parsing: <http://foo.0XFfFfFfFfFfFfFfFfFfAcE123> against <about:blank>
+PASS Parsing: <http://💩.123/> against <about:blank>
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-constructor.any.worker-expected.txt b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-constructor.any.worker-expected.txt
new file mode 100644
index 0000000..88e3db6b
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-constructor.any.worker-expected.txt
@@ -0,0 +1,850 @@
+This is a testharness.js-based test.
+Found 738 tests; 552 PASS, 186 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS Loading data…
+PASS Parsing: <http://example	.
+org> against <http://example.org/foo/bar>
+PASS Parsing: <http://user:pass@foo:21/bar;par?b#c> against <http://example.org/foo/bar>
+PASS Parsing: <https://test:@test> against <about:blank>
+PASS Parsing: <https://:@test> against <about:blank>
+FAIL Parsing: <non-special://test:@test/x> against <about:blank> assert_equals: href expected "non-special://test@test/x" but got "non-special://test:@test/x"
+FAIL Parsing: <non-special://:@test/x> against <about:blank> assert_equals: href expected "non-special://test/x" but got "non-special://:@test/x"
+PASS Parsing: <http:foo.com> against <http://example.org/foo/bar>
+PASS Parsing: <	   :foo.com   
+> against <http://example.org/foo/bar>
+PASS Parsing: < foo.com  > against <http://example.org/foo/bar>
+PASS Parsing: <a:	 foo.com> against <http://example.org/foo/bar>
+PASS Parsing: <http://f:21/ b ? d # e > against <http://example.org/foo/bar>
+PASS Parsing: <lolscheme:x x#x x> against <about:blank>
+PASS Parsing: <http://f:/c> against <http://example.org/foo/bar>
+PASS Parsing: <http://f:0/c> against <http://example.org/foo/bar>
+PASS Parsing: <http://f:00000000000000/c> against <http://example.org/foo/bar>
+PASS Parsing: <http://f:00000000000000000000080/c> against <http://example.org/foo/bar>
+PASS Parsing: <http://f:b/c> against <http://example.org/foo/bar>
+PASS Parsing: <http://f: /c> against <http://example.org/foo/bar>
+PASS Parsing: <http://f:
+/c> against <http://example.org/foo/bar>
+PASS Parsing: <http://f:fifty-two/c> against <http://example.org/foo/bar>
+PASS Parsing: <http://f:999999/c> against <http://example.org/foo/bar>
+FAIL Parsing: <non-special://f:999999/c> against <http://example.org/foo/bar> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+PASS Parsing: <http://f: 21 / b ? d # e > against <http://example.org/foo/bar>
+PASS Parsing: <> against <http://example.org/foo/bar>
+PASS Parsing: <  	> against <http://example.org/foo/bar>
+PASS Parsing: <:foo.com/> against <http://example.org/foo/bar>
+PASS Parsing: <:foo.com\> against <http://example.org/foo/bar>
+PASS Parsing: <:> against <http://example.org/foo/bar>
+PASS Parsing: <:a> against <http://example.org/foo/bar>
+PASS Parsing: <:/> against <http://example.org/foo/bar>
+PASS Parsing: <:\> against <http://example.org/foo/bar>
+PASS Parsing: <:#> against <http://example.org/foo/bar>
+PASS Parsing: <#> against <http://example.org/foo/bar>
+PASS Parsing: <#/> against <http://example.org/foo/bar>
+PASS Parsing: <#\> against <http://example.org/foo/bar>
+PASS Parsing: <#;?> against <http://example.org/foo/bar>
+PASS Parsing: <?> against <http://example.org/foo/bar>
+PASS Parsing: </> against <http://example.org/foo/bar>
+PASS Parsing: <:23> against <http://example.org/foo/bar>
+PASS Parsing: </:23> against <http://example.org/foo/bar>
+PASS Parsing: <\x> against <http://example.org/foo/bar>
+PASS Parsing: <\\x\hello> against <http://example.org/foo/bar>
+PASS Parsing: <::> against <http://example.org/foo/bar>
+PASS Parsing: <::23> against <http://example.org/foo/bar>
+FAIL Parsing: <foo://> against <http://example.org/foo/bar> assert_equals: pathname expected "" but got "//"
+PASS Parsing: <http://a:b@c:29/d> against <http://example.org/foo/bar>
+PASS Parsing: <http::@c:29> against <http://example.org/foo/bar>
+PASS Parsing: <http://&a:foo(b]c@d:2/> against <http://example.org/foo/bar>
+PASS Parsing: <http://::@c@d:2> against <http://example.org/foo/bar>
+PASS Parsing: <http://foo.com:b@d/> against <http://example.org/foo/bar>
+PASS Parsing: <http://foo.com/\@> against <http://example.org/foo/bar>
+PASS Parsing: <http:\\foo.com\> against <http://example.org/foo/bar>
+PASS Parsing: <http:\\a\b:c\d@foo.com\> against <http://example.org/foo/bar>
+PASS Parsing: <foo:/> against <http://example.org/foo/bar>
+PASS Parsing: <foo:/bar.com/> against <http://example.org/foo/bar>
+FAIL Parsing: <foo://///////> against <http://example.org/foo/bar> assert_equals: pathname expected "///////" but got "/////////"
+FAIL Parsing: <foo://///////bar.com/> against <http://example.org/foo/bar> assert_equals: pathname expected "///////bar.com/" but got "/////////bar.com/"
+FAIL Parsing: <foo:////://///> against <http://example.org/foo/bar> assert_equals: pathname expected "//://///" but got "////://///"
+PASS Parsing: <c:/foo> against <http://example.org/foo/bar>
+PASS Parsing: <//foo/bar> against <http://example.org/foo/bar>
+PASS Parsing: <http://foo/path;a??e#f#g> against <http://example.org/foo/bar>
+PASS Parsing: <http://foo/abcd?efgh?ijkl> against <http://example.org/foo/bar>
+PASS Parsing: <http://foo/abcd#foo?bar> against <http://example.org/foo/bar>
+PASS Parsing: <[61:24:74]:98> against <http://example.org/foo/bar>
+PASS Parsing: <http:[61:27]/:foo> against <http://example.org/foo/bar>
+PASS Parsing: <http://[1::2]:3:4> against <http://example.org/foo/bar>
+PASS Parsing: <http://2001::1> against <http://example.org/foo/bar>
+PASS Parsing: <http://2001::1]> against <http://example.org/foo/bar>
+PASS Parsing: <http://2001::1]:80> against <http://example.org/foo/bar>
+PASS Parsing: <http://[2001::1]> against <http://example.org/foo/bar>
+PASS Parsing: <http://[::127.0.0.1]> against <http://example.org/foo/bar>
+PASS Parsing: <http://[0:0:0:0:0:0:13.1.68.3]> against <http://example.org/foo/bar>
+PASS Parsing: <http://[2001::1]:80> against <http://example.org/foo/bar>
+PASS Parsing: <http:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <ftp:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <https:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <madeupscheme:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <file:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <file://example:1/> against <about:blank>
+PASS Parsing: <file://example:test/> against <about:blank>
+PASS Parsing: <file://example%/> against <about:blank>
+PASS Parsing: <file://[example]/> against <about:blank>
+PASS Parsing: <ftps:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <gopher:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <ws:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <wss:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <data:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <javascript:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <mailto:/example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <http:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <ftp:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <https:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <madeupscheme:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <ftps:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <gopher:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <ws:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <wss:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <data:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <javascript:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: <mailto:example.com/> against <http://example.org/foo/bar>
+PASS Parsing: </a/b/c> against <http://example.org/foo/bar>
+PASS Parsing: </a/ /c> against <http://example.org/foo/bar>
+PASS Parsing: </a%2fc> against <http://example.org/foo/bar>
+PASS Parsing: </a/%2f/c> against <http://example.org/foo/bar>
+PASS Parsing: <#β> against <http://example.org/foo/bar>
+PASS Parsing: <data:text/html,test#test> against <http://example.org/foo/bar>
+PASS Parsing: <tel:1234567890> against <http://example.org/foo/bar>
+FAIL Parsing: <ssh://example.com/foo/bar.git> against <http://example.org/> assert_equals: host expected "example.com" but got ""
+FAIL Parsing: <file:c:\foo\bar.html> against <file:///tmp/mock/path> assert_equals: href expected "file:///c:/foo/bar.html" but got "file:///tmp/mock/c:/foo/bar.html"
+FAIL Parsing: <  File:c|////foo\bar.html> against <file:///tmp/mock/path> assert_equals: href expected "file:///c:////foo/bar.html" but got "file:///tmp/mock/c%7C////foo/bar.html"
+FAIL Parsing: <C|/foo/bar> against <file:///tmp/mock/path> assert_equals: href expected "file:///C:/foo/bar" but got "file:///tmp/mock/C%7C/foo/bar"
+FAIL Parsing: </C|\foo\bar> against <file:///tmp/mock/path> assert_equals: href expected "file:///C:/foo/bar" but got "file:///C%7C/foo/bar"
+FAIL Parsing: <//C|/foo/bar> against <file:///tmp/mock/path> assert_equals: href expected "file:///C:/foo/bar" but got "file://c%7C/foo/bar"
+PASS Parsing: <//server/file> against <file:///tmp/mock/path>
+PASS Parsing: <\\server\file> against <file:///tmp/mock/path>
+PASS Parsing: </\server/file> against <file:///tmp/mock/path>
+PASS Parsing: <file:///foo/bar.txt> against <file:///tmp/mock/path>
+PASS Parsing: <file:///home/me> against <file:///tmp/mock/path>
+PASS Parsing: <//> against <file:///tmp/mock/path>
+PASS Parsing: <///> against <file:///tmp/mock/path>
+PASS Parsing: <///test> against <file:///tmp/mock/path>
+PASS Parsing: <file://test> against <file:///tmp/mock/path>
+FAIL Parsing: <file://localhost> against <file:///tmp/mock/path> assert_equals: href expected "file:///" but got "file://localhost/"
+FAIL Parsing: <file://localhost/> against <file:///tmp/mock/path> assert_equals: href expected "file:///" but got "file://localhost/"
+FAIL Parsing: <file://localhost/test> against <file:///tmp/mock/path> assert_equals: href expected "file:///test" but got "file://localhost/test"
+PASS Parsing: <test> against <file:///tmp/mock/path>
+PASS Parsing: <file:test> against <file:///tmp/mock/path>
+PASS Parsing: <http://example.com/././foo> against <about:blank>
+PASS Parsing: <http://example.com/./.foo> against <about:blank>
+PASS Parsing: <http://example.com/foo/.> against <about:blank>
+PASS Parsing: <http://example.com/foo/./> against <about:blank>
+PASS Parsing: <http://example.com/foo/bar/..> against <about:blank>
+PASS Parsing: <http://example.com/foo/bar/../> against <about:blank>
+PASS Parsing: <http://example.com/foo/..bar> against <about:blank>
+PASS Parsing: <http://example.com/foo/bar/../ton> against <about:blank>
+PASS Parsing: <http://example.com/foo/bar/../ton/../../a> against <about:blank>
+PASS Parsing: <http://example.com/foo/../../..> against <about:blank>
+PASS Parsing: <http://example.com/foo/../../../ton> against <about:blank>
+PASS Parsing: <http://example.com/foo/%2e> against <about:blank>
+FAIL Parsing: <http://example.com/foo/%2e%2> against <about:blank> assert_equals: href expected "http://example.com/foo/%2e%2" but got "http://example.com/foo/.%2"
+FAIL Parsing: <http://example.com/foo/%2e./%2e%2e/.%2e/%2e.bar> against <about:blank> assert_equals: href expected "http://example.com/%2e.bar" but got "http://example.com/..bar"
+PASS Parsing: <http://example.com////../..> against <about:blank>
+PASS Parsing: <http://example.com/foo/bar//../..> against <about:blank>
+PASS Parsing: <http://example.com/foo/bar//..> against <about:blank>
+PASS Parsing: <http://example.com/foo> against <about:blank>
+PASS Parsing: <http://example.com/%20foo> against <about:blank>
+PASS Parsing: <http://example.com/foo%> against <about:blank>
+PASS Parsing: <http://example.com/foo%2> against <about:blank>
+PASS Parsing: <http://example.com/foo%2zbar> against <about:blank>
+PASS Parsing: <http://example.com/foo%2©zbar> against <about:blank>
+FAIL Parsing: <http://example.com/foo%41%7a> against <about:blank> assert_equals: href expected "http://example.com/foo%41%7a" but got "http://example.com/fooAz"
+PASS Parsing: <http://example.com/foo	‘%91> against <about:blank>
+FAIL Parsing: <http://example.com/foo%00%51> against <about:blank> Failed to construct 'URL': Invalid URL
+PASS Parsing: <http://example.com/(%28:%3A%29)> against <about:blank>
+PASS Parsing: <http://example.com/%3A%3a%3C%3c> against <about:blank>
+PASS Parsing: <http://example.com/foo	bar> against <about:blank>
+PASS Parsing: <http://example.com\\foo\\bar> against <about:blank>
+PASS Parsing: <http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd> against <about:blank>
+PASS Parsing: <http://example.com/@asdf%40> against <about:blank>
+PASS Parsing: <http://example.com/你好你好> against <about:blank>
+PASS Parsing: <http://example.com/‥/foo> against <about:blank>
+PASS Parsing: <http://example.com//foo> against <about:blank>
+PASS Parsing: <http://example.com/‮/foo/‭/bar> against <about:blank>
+PASS Parsing: <http://www.google.com/foo?bar=baz#> against <about:blank>
+PASS Parsing: <http://www.google.com/foo?bar=baz# »> against <about:blank>
+PASS Parsing: <data:test# »> against <about:blank>
+PASS Parsing: <http://www.google.com> against <about:blank>
+PASS Parsing: <http://192.0x00A80001> against <about:blank>
+FAIL Parsing: <http://www/foo%2Ehtml> against <about:blank> assert_equals: href expected "http://www/foo%2Ehtml" but got "http://www/foo.html"
+PASS Parsing: <http://www/foo/%2E/html> against <about:blank>
+PASS Parsing: <http://user:pass@/> against <about:blank>
+PASS Parsing: <http://%25DOMAIN:foobar@foodomain.com/> against <about:blank>
+PASS Parsing: <http:\\www.google.com\foo> against <about:blank>
+PASS Parsing: <http://foo:80/> against <about:blank>
+PASS Parsing: <http://foo:81/> against <about:blank>
+FAIL Parsing: <httpa://foo:80/> against <about:blank> assert_equals: host expected "foo:80" but got ""
+PASS Parsing: <http://foo:-80/> against <about:blank>
+PASS Parsing: <https://foo:443/> against <about:blank>
+PASS Parsing: <https://foo:80/> against <about:blank>
+PASS Parsing: <ftp://foo:21/> against <about:blank>
+PASS Parsing: <ftp://foo:80/> against <about:blank>
+FAIL Parsing: <gopher://foo:70/> against <about:blank> assert_equals: host expected "foo:70" but got ""
+FAIL Parsing: <gopher://foo:443/> against <about:blank> assert_equals: host expected "foo:443" but got ""
+PASS Parsing: <ws://foo:80/> against <about:blank>
+PASS Parsing: <ws://foo:81/> against <about:blank>
+PASS Parsing: <ws://foo:443/> against <about:blank>
+PASS Parsing: <ws://foo:815/> against <about:blank>
+PASS Parsing: <wss://foo:80/> against <about:blank>
+PASS Parsing: <wss://foo:81/> against <about:blank>
+PASS Parsing: <wss://foo:443/> against <about:blank>
+PASS Parsing: <wss://foo:815/> against <about:blank>
+PASS Parsing: <http:/example.com/> against <about:blank>
+PASS Parsing: <ftp:/example.com/> against <about:blank>
+PASS Parsing: <https:/example.com/> against <about:blank>
+PASS Parsing: <madeupscheme:/example.com/> against <about:blank>
+PASS Parsing: <file:/example.com/> against <about:blank>
+PASS Parsing: <ftps:/example.com/> against <about:blank>
+PASS Parsing: <gopher:/example.com/> against <about:blank>
+PASS Parsing: <ws:/example.com/> against <about:blank>
+PASS Parsing: <wss:/example.com/> against <about:blank>
+PASS Parsing: <data:/example.com/> against <about:blank>
+PASS Parsing: <javascript:/example.com/> against <about:blank>
+PASS Parsing: <mailto:/example.com/> against <about:blank>
+PASS Parsing: <http:example.com/> against <about:blank>
+PASS Parsing: <ftp:example.com/> against <about:blank>
+PASS Parsing: <https:example.com/> against <about:blank>
+PASS Parsing: <madeupscheme:example.com/> against <about:blank>
+PASS Parsing: <ftps:example.com/> against <about:blank>
+PASS Parsing: <gopher:example.com/> against <about:blank>
+PASS Parsing: <ws:example.com/> against <about:blank>
+PASS Parsing: <wss:example.com/> against <about:blank>
+PASS Parsing: <data:example.com/> against <about:blank>
+PASS Parsing: <javascript:example.com/> against <about:blank>
+PASS Parsing: <mailto:example.com/> against <about:blank>
+PASS Parsing: <http:@www.example.com> against <about:blank>
+PASS Parsing: <http:/@www.example.com> against <about:blank>
+PASS Parsing: <http://@www.example.com> against <about:blank>
+PASS Parsing: <http:a:b@www.example.com> against <about:blank>
+PASS Parsing: <http:/a:b@www.example.com> against <about:blank>
+PASS Parsing: <http://a:b@www.example.com> against <about:blank>
+PASS Parsing: <http://@pple.com> against <about:blank>
+PASS Parsing: <http::b@www.example.com> against <about:blank>
+PASS Parsing: <http:/:b@www.example.com> against <about:blank>
+PASS Parsing: <http://:b@www.example.com> against <about:blank>
+PASS Parsing: <http:/:@/www.example.com> against <about:blank>
+PASS Parsing: <http://user@/www.example.com> against <about:blank>
+PASS Parsing: <http:@/www.example.com> against <about:blank>
+PASS Parsing: <http:/@/www.example.com> against <about:blank>
+PASS Parsing: <http://@/www.example.com> against <about:blank>
+PASS Parsing: <https:@/www.example.com> against <about:blank>
+PASS Parsing: <http:a:b@/www.example.com> against <about:blank>
+PASS Parsing: <http:/a:b@/www.example.com> against <about:blank>
+PASS Parsing: <http://a:b@/www.example.com> against <about:blank>
+PASS Parsing: <http::@/www.example.com> against <about:blank>
+PASS Parsing: <http:a:@www.example.com> against <about:blank>
+PASS Parsing: <http:/a:@www.example.com> against <about:blank>
+PASS Parsing: <http://a:@www.example.com> against <about:blank>
+PASS Parsing: <http://www.@pple.com> against <about:blank>
+PASS Parsing: <http:@:www.example.com> against <about:blank>
+PASS Parsing: <http:/@:www.example.com> against <about:blank>
+PASS Parsing: <http://@:www.example.com> against <about:blank>
+PASS Parsing: <http://:@www.example.com> against <about:blank>
+PASS Parsing: </> against <http://www.example.com/test>
+PASS Parsing: </test.txt> against <http://www.example.com/test>
+PASS Parsing: <.> against <http://www.example.com/test>
+PASS Parsing: <..> against <http://www.example.com/test>
+PASS Parsing: <test.txt> against <http://www.example.com/test>
+PASS Parsing: <./test.txt> against <http://www.example.com/test>
+PASS Parsing: <../test.txt> against <http://www.example.com/test>
+PASS Parsing: <../aaa/test.txt> against <http://www.example.com/test>
+PASS Parsing: <../../test.txt> against <http://www.example.com/test>
+PASS Parsing: <中/test.txt> against <http://www.example.com/test>
+PASS Parsing: <http://www.example2.com> against <http://www.example.com/test>
+PASS Parsing: <//www.example2.com> against <http://www.example.com/test>
+PASS Parsing: <file:...> against <http://www.example.com/test>
+PASS Parsing: <file:..> against <http://www.example.com/test>
+PASS Parsing: <file:a> against <http://www.example.com/test>
+PASS Parsing: <http://ExAmPlE.CoM> against <http://other.com/>
+FAIL Parsing: <http://example example.com> against <http://other.com/> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <http://Goo%20 goo%7C|.com> against <http://other.com/> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+PASS Parsing: <http://[]> against <http://other.com/>
+PASS Parsing: <http://[:]> against <http://other.com/>
+FAIL Parsing: <http://GOO  goo.com> against <http://other.com/> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+PASS Parsing: <http://GOO​⁠goo.com> against <http://other.com/>
+PASS Parsing: <\0 http://example.com/ \r > against <about:blank>
+PASS Parsing: <http://www.foo。bar.com> against <http://other.com/>
+PASS Parsing: <http://﷐zyx.com> against <http://other.com/>
+PASS Parsing: <http://%ef%b7%90zyx.com> against <http://other.com/>
+PASS Parsing: <https://�> against <about:blank>
+PASS Parsing: <https://%EF%BF%BD> against <about:blank>
+PASS Parsing: <https://x/�?�#�> against <about:blank>
+FAIL Parsing: <http://a.b.c.xn--pokxncvks> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <http://10.0.0.xn--pokxncvks> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <http://a.b.c.XN--pokxncvks> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <http://a.b.c.Xn--pokxncvks> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <http://10.0.0.XN--pokxncvks> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <http://10.0.0.xN--pokxncvks> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+PASS Parsing: <http://Go.com> against <http://other.com/>
+FAIL Parsing: <http://%41.com> against <http://other.com/> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <http://%ef%bc%85%ef%bc%94%ef%bc%91.com> against <http://other.com/> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+PASS Parsing: <http://%00.com> against <http://other.com/>
+PASS Parsing: <http://%ef%bc%85%ef%bc%90%ef%bc%90.com> against <http://other.com/>
+PASS Parsing: <http://你好你好> against <http://other.com/>
+PASS Parsing: <https://faß.ExAmPlE/> against <about:blank>
+FAIL Parsing: <sc://faß.ExAmPlE/> against <about:blank> assert_equals: host expected "fa%C3%9F.ExAmPlE" but got ""
+PASS Parsing: <http://%zz%66%a.com> against <http://other.com/>
+PASS Parsing: <http://%25> against <http://other.com/>
+PASS Parsing: <http://hello%00> against <http://other.com/>
+PASS Parsing: <http://%30%78%63%30%2e%30%32%35%30.01> against <http://other.com/>
+PASS Parsing: <http://%30%78%63%30%2e%30%32%35%30.01%2e> against <http://other.com/>
+PASS Parsing: <http://192.168.0.257> against <http://other.com/>
+PASS Parsing: <http://%3g%78%63%30%2e%30%32%35%30%2E.01> against <http://other.com/>
+FAIL Parsing: <http://192.168.0.1 hello> against <http://other.com/> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <https://x x:12> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+PASS Parsing: <http://0Xc0.0250.01> against <http://other.com/>
+PASS Parsing: <http://./> against <about:blank>
+PASS Parsing: <http://../> against <about:blank>
+PASS Parsing: <http://[www.google.com]/> against <about:blank>
+PASS Parsing: <http://[google.com]> against <http://other.com/>
+PASS Parsing: <http://[::1.2.3.4x]> against <http://other.com/>
+FAIL Parsing: <http://[::1.2.3.]> against <http://other.com/> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <http://[::1.2.]> against <http://other.com/> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <http://[::1.]> against <http://other.com/> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+PASS Parsing: <http://foo:💩@example.com/bar> against <http://other.com/>
+PASS Parsing: <#> against <test:test>
+PASS Parsing: <#x> against <mailto:x@x.com>
+PASS Parsing: <#x> against <data:,>
+PASS Parsing: <#x> against <about:blank>
+PASS Parsing: <#> against <test:test?test>
+PASS Parsing: <https://@test@test@example:800/> against <http://doesnotmatter/>
+PASS Parsing: <https://@@@example> against <http://doesnotmatter/>
+PASS Parsing: <http://`{}:`{}@h/`{}?`{}> against <http://doesnotmatter/>
+PASS Parsing: <http://host/?'> against <about:blank>
+FAIL Parsing: <notspecial://host/?'> against <about:blank> assert_equals: href expected "notspecial://host/?'" but got "notspecial://host/?%27"
+PASS Parsing: </some/path> against <http://user@example.org/smth>
+PASS Parsing: <> against <http://user:pass@example.org:21/smth>
+PASS Parsing: </some/path> against <http://user:pass@example.org:21/smth>
+PASS Parsing: <i> against <sc:sd>
+PASS Parsing: <i> against <sc:sd/sd>
+PASS Parsing: <i> against <sc:/pa/pa>
+FAIL Parsing: <i> against <sc://ho/pa> assert_equals: host expected "ho" but got ""
+FAIL Parsing: <i> against <sc:///pa/pa> assert_equals: pathname expected "/pa/i" but got "///pa/i"
+PASS Parsing: <../i> against <sc:sd>
+PASS Parsing: <../i> against <sc:sd/sd>
+PASS Parsing: <../i> against <sc:/pa/pa>
+FAIL Parsing: <../i> against <sc://ho/pa> assert_equals: host expected "ho" but got ""
+FAIL Parsing: <../i> against <sc:///pa/pa> assert_equals: href expected "sc:///i" but got "sc:///pa/i"
+PASS Parsing: </i> against <sc:sd>
+PASS Parsing: </i> against <sc:sd/sd>
+PASS Parsing: </i> against <sc:/pa/pa>
+FAIL Parsing: </i> against <sc://ho/pa> assert_equals: host expected "ho" but got ""
+FAIL Parsing: </i> against <sc:///pa/pa> assert_equals: href expected "sc:///i" but got "sc:///pa/i"
+PASS Parsing: <?i> against <sc:sd>
+PASS Parsing: <?i> against <sc:sd/sd>
+PASS Parsing: <?i> against <sc:/pa/pa>
+FAIL Parsing: <?i> against <sc://ho/pa> assert_equals: host expected "ho" but got ""
+FAIL Parsing: <?i> against <sc:///pa/pa> assert_equals: pathname expected "/pa/pa" but got "///pa/pa"
+PASS Parsing: <#i> against <sc:sd>
+PASS Parsing: <#i> against <sc:sd/sd>
+PASS Parsing: <#i> against <sc:/pa/pa>
+FAIL Parsing: <#i> against <sc://ho/pa> assert_equals: host expected "ho" but got ""
+FAIL Parsing: <#i> against <sc:///pa/pa> assert_equals: pathname expected "/pa/pa" but got "///pa/pa"
+FAIL Parsing: <about:/../> against <about:blank> assert_equals: href expected "about:/" but got "about:/../"
+FAIL Parsing: <data:/../> against <about:blank> assert_equals: href expected "data:/" but got "data:/../"
+FAIL Parsing: <javascript:/../> against <about:blank> assert_equals: href expected "javascript:/" but got "javascript:/../"
+FAIL Parsing: <mailto:/../> against <about:blank> assert_equals: href expected "mailto:/" but got "mailto:/../"
+FAIL Parsing: <sc://ñ.test/> against <about:blank> assert_equals: host expected "%C3%B1.test" but got ""
+FAIL Parsing: <sc://%/> against <about:blank> assert_equals: host expected "%" but got ""
+FAIL Parsing: <sc://@/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <sc://te@s:t@/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <sc://:/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <sc://:12/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <x> against <sc://ñ> Failed to construct 'URL': Invalid URL
+PASS Parsing: <sc:\../> against <about:blank>
+PASS Parsing: <sc::a@example.net> against <about:blank>
+PASS Parsing: <wow:%NBD> against <about:blank>
+PASS Parsing: <wow:%1G> against <about:blank>
+FAIL Parsing: <wow:￿> against <about:blank> assert_equals: href expected "wow:%EF%BF%BF" but got "wow:%EF%BF%BD"
+FAIL Parsing: <http://example.com/U+d800𐟾U+dfff﷐﷏﷯ﷰ￾￿?U+d800𐟾U+dfff﷐﷏﷯ﷰ￾￿> against <about:blank> Failed to construct 'URL': Invalid URL
+FAIL Parsing: <sc://a\0b/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <sc://a b/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <sc://a<b> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <sc://a>b> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <sc://a[b/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <sc://a\b/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <sc://a]b/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <sc://a^b> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <sc://a|b/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <foo://ho	st/> against <about:blank> assert_equals: host expected "host" but got ""
+FAIL Parsing: <foo://ho
+st/> against <about:blank> assert_equals: host expected "host" but got ""
+FAIL Parsing: <foo://ho\rst/> against <about:blank> assert_equals: host expected "host" but got ""
+PASS Parsing: <http://a\0b/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ab/> against <about:blank>
+FAIL Parsing: <http://a b/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+PASS Parsing: <http://a%b/> against <about:blank>
+FAIL Parsing: <http://a<b> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <http://a>b> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+PASS Parsing: <http://a[b/> against <about:blank>
+PASS Parsing: <http://a]b/> against <about:blank>
+PASS Parsing: <http://a^b> against <about:blank>
+FAIL Parsing: <http://a|b/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+PASS Parsing: <http://ab/> against <about:blank>
+PASS Parsing: <http://ho	st/> against <about:blank>
+PASS Parsing: <http://ho
+st/> against <about:blank>
+PASS Parsing: <http://ho\rst/> against <about:blank>
+PASS Parsing: <http://ho%00st/> against <about:blank>
+PASS Parsing: <http://ho%01st/> against <about:blank>
+PASS Parsing: <http://ho%02st/> against <about:blank>
+PASS Parsing: <http://ho%03st/> against <about:blank>
+PASS Parsing: <http://ho%04st/> against <about:blank>
+PASS Parsing: <http://ho%05st/> against <about:blank>
+PASS Parsing: <http://ho%06st/> against <about:blank>
+PASS Parsing: <http://ho%07st/> against <about:blank>
+PASS Parsing: <http://ho%08st/> against <about:blank>
+PASS Parsing: <http://ho%09st/> against <about:blank>
+PASS Parsing: <http://ho%0Ast/> against <about:blank>
+PASS Parsing: <http://ho%0Bst/> against <about:blank>
+PASS Parsing: <http://ho%0Cst/> against <about:blank>
+PASS Parsing: <http://ho%0Dst/> against <about:blank>
+PASS Parsing: <http://ho%0Est/> against <about:blank>
+PASS Parsing: <http://ho%0Fst/> against <about:blank>
+PASS Parsing: <http://ho%10st/> against <about:blank>
+PASS Parsing: <http://ho%11st/> against <about:blank>
+PASS Parsing: <http://ho%12st/> against <about:blank>
+PASS Parsing: <http://ho%13st/> against <about:blank>
+PASS Parsing: <http://ho%14st/> against <about:blank>
+PASS Parsing: <http://ho%15st/> against <about:blank>
+PASS Parsing: <http://ho%16st/> against <about:blank>
+PASS Parsing: <http://ho%17st/> against <about:blank>
+PASS Parsing: <http://ho%18st/> against <about:blank>
+PASS Parsing: <http://ho%19st/> against <about:blank>
+PASS Parsing: <http://ho%1Ast/> against <about:blank>
+PASS Parsing: <http://ho%1Bst/> against <about:blank>
+PASS Parsing: <http://ho%1Cst/> against <about:blank>
+PASS Parsing: <http://ho%1Dst/> against <about:blank>
+PASS Parsing: <http://ho%1Est/> against <about:blank>
+PASS Parsing: <http://ho%1Fst/> against <about:blank>
+FAIL Parsing: <http://ho%20st/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <http://ho%23st/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+PASS Parsing: <http://ho%25st/> against <about:blank>
+PASS Parsing: <http://ho%2Fst/> against <about:blank>
+PASS Parsing: <http://ho%3Ast/> against <about:blank>
+FAIL Parsing: <http://ho%3Cst/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <http://ho%3Est/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+PASS Parsing: <http://ho%3Fst/> against <about:blank>
+FAIL Parsing: <http://ho%40st/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+PASS Parsing: <http://ho%5Bst/> against <about:blank>
+PASS Parsing: <http://ho%5Cst/> against <about:blank>
+PASS Parsing: <http://ho%5Dst/> against <about:blank>
+FAIL Parsing: <http://ho%7Cst/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+PASS Parsing: <http://ho%7Fst/> against <about:blank>
+FAIL Parsing: <http://!"$&'()*+,-.;=_`{}~/> against <about:blank> Failed to construct 'URL': Invalid URL
+FAIL Parsing: <sc://!"$%&'()*+,-.;=_`{}~/> against <about:blank> assert_equals: host expected "%01%02%03%04%05%06%07%08%0B%0C%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%7F!\"$%&'()*+,-.;=_`{}~" but got ""
+PASS Parsing: <ftp://example.com%80/> against <about:blank>
+PASS Parsing: <ftp://example.com%A0/> against <about:blank>
+PASS Parsing: <https://example.com%80/> against <about:blank>
+PASS Parsing: <https://example.com%A0/> against <about:blank>
+PASS Parsing: <ftp://%e2%98%83> against <about:blank>
+PASS Parsing: <https://%e2%98%83> against <about:blank>
+PASS Parsing: <http://127.0.0.1:10100/relative_import.html> against <about:blank>
+PASS Parsing: <http://facebook.com/?foo=%7B%22abc%22> against <about:blank>
+PASS Parsing: <https://localhost:3000/jqueryui@1.2.3> against <about:blank>
+PASS Parsing: <h	t
+t\rp://h	o
+s\rt:9	0
+0\r0/p	a
+t\rh?q	u
+e\rry#f	r
+a\rg> against <about:blank>
+PASS Parsing: <?a=b&c=d> against <http://example.org/foo/bar>
+PASS Parsing: <??a=b&c=d> against <http://example.org/foo/bar>
+PASS Parsing: <http:> against <http://example.org/foo/bar>
+PASS Parsing: <http:> against <https://example.org/foo/bar>
+PASS Parsing: <sc:> against <https://example.org/foo/bar>
+PASS Parsing: <http://foo.bar/baz?qux#foobar> against <about:blank>
+PASS Parsing: <http://foo.bar/baz?qux#foo"bar> against <about:blank>
+PASS Parsing: <http://foo.bar/baz?qux#foo<bar> against <about:blank>
+PASS Parsing: <http://foo.bar/baz?qux#foo>bar> against <about:blank>
+PASS Parsing: <http://foo.bar/baz?qux#foo`bar> against <about:blank>
+PASS Parsing: <http://1.2.3.4/> against <http://other.com/>
+PASS Parsing: <http://1.2.3.4./> against <http://other.com/>
+PASS Parsing: <http://192.168.257> against <http://other.com/>
+PASS Parsing: <http://192.168.257.> against <http://other.com/>
+PASS Parsing: <http://192.168.257.com> against <http://other.com/>
+PASS Parsing: <http://256> against <http://other.com/>
+PASS Parsing: <http://256.com> against <http://other.com/>
+PASS Parsing: <http://999999999> against <http://other.com/>
+PASS Parsing: <http://999999999.> against <http://other.com/>
+PASS Parsing: <http://999999999.com> against <http://other.com/>
+PASS Parsing: <http://10000000000> against <http://other.com/>
+PASS Parsing: <http://10000000000.com> against <http://other.com/>
+PASS Parsing: <http://4294967295> against <http://other.com/>
+PASS Parsing: <http://4294967296> against <http://other.com/>
+PASS Parsing: <http://0xffffffff> against <http://other.com/>
+PASS Parsing: <http://0xffffffff1> against <http://other.com/>
+PASS Parsing: <http://256.256.256.256> against <http://other.com/>
+PASS Parsing: <https://0x.0x.0> against <about:blank>
+PASS Parsing: <https://0x100000000/test> against <about:blank>
+PASS Parsing: <https://256.0.0.1/test> against <about:blank>
+PASS Parsing: <file:///C%3A/> against <about:blank>
+PASS Parsing: <file:///C%7C/> against <about:blank>
+PASS Parsing: <file://%43%3A> against <about:blank>
+FAIL Parsing: <file://%43%7C> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <file://%43|> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <file://C%7C> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <file://%43%7C/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <https://%43%7C/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <asdf://%43|/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+FAIL Parsing: <asdf://%43%7C/> against <about:blank> assert_equals: host expected "%43%7C" but got ""
+PASS Parsing: <pix/submit.gif> against <file:///C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/anchor.html>
+FAIL Parsing: <..> against <file:///C:/> assert_equals: href expected "file:///C:/" but got "file:///"
+PASS Parsing: <..> against <file:///>
+FAIL Parsing: </> against <file:///C:/a/b> assert_equals: href expected "file:///C:/" but got "file:///"
+FAIL Parsing: </> against <file://h/C:/a/b> assert_equals: href expected "file://h/C:/" but got "file://h/"
+PASS Parsing: </> against <file://h/a/b>
+FAIL Parsing: <//d:> against <file:///C:/a/b> Failed to construct 'URL': Invalid URL
+FAIL Parsing: <//d:/..> against <file:///C:/a/b> Failed to construct 'URL': Invalid URL
+PASS Parsing: <..> against <file:///ab:/>
+PASS Parsing: <..> against <file:///1:/>
+PASS Parsing: <> against <file:///test?test#test>
+PASS Parsing: <file:> against <file:///test?test#test>
+PASS Parsing: <?x> against <file:///test?test#test>
+PASS Parsing: <file:?x> against <file:///test?test#test>
+PASS Parsing: <#x> against <file:///test?test#test>
+PASS Parsing: <file:#x> against <file:///test?test#test>
+FAIL Parsing: <file:\\//> against <about:blank> assert_equals: href expected "file:////" but got "file:///"
+FAIL Parsing: <file:\\\\> against <about:blank> assert_equals: href expected "file:////" but got "file:///"
+FAIL Parsing: <file:\\\\?fox> against <about:blank> assert_equals: href expected "file:////?fox" but got "file:///?fox"
+FAIL Parsing: <file:\\\\#guppy> against <about:blank> assert_equals: href expected "file:////#guppy" but got "file:///#guppy"
+PASS Parsing: <file://spider///> against <about:blank>
+FAIL Parsing: <file:\\localhost//> against <about:blank> assert_equals: href expected "file:////" but got "file://localhost//"
+PASS Parsing: <file:///localhost//cat> against <about:blank>
+FAIL Parsing: <file://\/localhost//cat> against <about:blank> assert_equals: href expected "file:////localhost//cat" but got "file:///localhost//cat"
+FAIL Parsing: <file://localhost//a//../..//> against <about:blank> assert_equals: href expected "file://///" but got "file://localhost///"
+FAIL Parsing: </////mouse> against <file:///elephant> assert_equals: href expected "file://///mouse" but got "file:///mouse"
+PASS Parsing: <\//pig> against <file://lion/>
+FAIL Parsing: <\/localhost//pig> against <file://lion/> assert_equals: href expected "file:////pig" but got "file://localhost//pig"
+FAIL Parsing: <//localhost//pig> against <file://lion/> assert_equals: href expected "file:////pig" but got "file://localhost//pig"
+PASS Parsing: </..//localhost//pig> against <file://lion/>
+PASS Parsing: <file://> against <file://ape/>
+PASS Parsing: </rooibos> against <file://tea/>
+PASS Parsing: </?chai> against <file://tea/>
+FAIL Parsing: <C|> against <file://host/dir/file> assert_equals: href expected "file://host/C:" but got "file://host/dir/C%7C"
+FAIL Parsing: <C|> against <file://host/D:/dir1/dir2/file> assert_equals: href expected "file://host/C:" but got "file://host/D:/dir1/dir2/C%7C"
+FAIL Parsing: <C|#> against <file://host/dir/file> assert_equals: href expected "file://host/C:#" but got "file://host/dir/C%7C#"
+FAIL Parsing: <C|?> against <file://host/dir/file> assert_equals: href expected "file://host/C:?" but got "file://host/dir/C%7C?"
+FAIL Parsing: <C|/> against <file://host/dir/file> assert_equals: href expected "file://host/C:/" but got "file://host/dir/C%7C/"
+FAIL Parsing: <C|
+/> against <file://host/dir/file> assert_equals: href expected "file://host/C:/" but got "file://host/dir/C%7C/"
+FAIL Parsing: <C|\> against <file://host/dir/file> assert_equals: href expected "file://host/C:/" but got "file://host/dir/C%7C/"
+PASS Parsing: <C> against <file://host/dir/file>
+FAIL Parsing: <C|a> against <file://host/dir/file> assert_equals: href expected "file://host/dir/C|a" but got "file://host/dir/C%7Ca"
+PASS Parsing: </c:/foo/bar> against <file:///c:/baz/qux>
+FAIL Parsing: </c|/foo/bar> against <file:///c:/baz/qux> assert_equals: href expected "file:///c:/foo/bar" but got "file:///c%7C/foo/bar"
+PASS Parsing: <file:\c:\foo\bar> against <file:///c:/baz/qux>
+PASS Parsing: </c:/foo/bar> against <file://host/path>
+PASS Parsing: <file://example.net/C:/> against <about:blank>
+PASS Parsing: <file://1.2.3.4/C:/> against <about:blank>
+PASS Parsing: <file://[1::8]/C:/> against <about:blank>
+FAIL Parsing: <C|/> against <file://host/> assert_equals: href expected "file://host/C:/" but got "file://host/C%7C/"
+PASS Parsing: </C:/> against <file://host/>
+PASS Parsing: <file:C:/> against <file://host/>
+PASS Parsing: <file:/C:/> against <file://host/>
+FAIL Parsing: <//C:/> against <file://host/> Failed to construct 'URL': Invalid URL
+FAIL Parsing: <file://C:/> against <file://host/> Failed to construct 'URL': Invalid URL
+PASS Parsing: <///C:/> against <file://host/>
+PASS Parsing: <file:///C:/> against <file://host/>
+FAIL Parsing: <file:/C|/> against <about:blank> assert_equals: href expected "file:///C:/" but got "file:///C%7C/"
+FAIL Parsing: <file://C|/> against <about:blank> assert_equals: href expected "file:///C:/" but got "file://c%7C/"
+PASS Parsing: <file:> against <about:blank>
+PASS Parsing: <file:?q=v> against <about:blank>
+PASS Parsing: <file:#frag> against <about:blank>
+PASS Parsing: <file:///Y:> against <about:blank>
+PASS Parsing: <file:///Y:/> against <about:blank>
+PASS Parsing: <file:///./Y> against <about:blank>
+PASS Parsing: <file:///./Y:> against <about:blank>
+PASS Parsing: <\\\.\Y:> against <about:blank>
+PASS Parsing: <file:///y:> against <about:blank>
+PASS Parsing: <file:///y:/> against <about:blank>
+PASS Parsing: <file:///./y> against <about:blank>
+PASS Parsing: <file:///./y:> against <about:blank>
+PASS Parsing: <\\\.\y:> against <about:blank>
+FAIL Parsing: <file://localhost//a//../..//foo> against <about:blank> assert_equals: href expected "file://///foo" but got "file://localhost///foo"
+FAIL Parsing: <file://localhost////foo> against <about:blank> assert_equals: href expected "file://////foo" but got "file://localhost////foo"
+FAIL Parsing: <file:////foo> against <about:blank> assert_equals: href expected "file:////foo" but got "file:///foo"
+PASS Parsing: <file:///one/two> against <file:///>
+FAIL Parsing: <file:////one/two> against <file:///> assert_equals: href expected "file:////one/two" but got "file:///one/two"
+PASS Parsing: <//one/two> against <file:///>
+PASS Parsing: <///one/two> against <file:///>
+FAIL Parsing: <////one/two> against <file:///> assert_equals: href expected "file:////one/two" but got "file:///one/two"
+PASS Parsing: <file:///.//> against <file:////>
+PASS Parsing: <file:.//p> against <about:blank>
+PASS Parsing: <file:/.//p> against <about:blank>
+PASS Parsing: <http://[1:0::]> against <http://example.net/>
+PASS Parsing: <http://[0:1:2:3:4:5:6:7:8]> against <http://example.net/>
+PASS Parsing: <https://[0::0::0]> against <about:blank>
+PASS Parsing: <https://[0:.0]> against <about:blank>
+PASS Parsing: <https://[0:0:]> against <about:blank>
+PASS Parsing: <https://[0:1:2:3:4:5:6:7.0.0.0.1]> against <about:blank>
+PASS Parsing: <https://[0:1.00.0.0.0]> against <about:blank>
+PASS Parsing: <https://[0:1.290.0.0.0]> against <about:blank>
+PASS Parsing: <https://[0:1.23.23]> against <about:blank>
+PASS Parsing: <http://?> against <about:blank>
+PASS Parsing: <http://#> against <about:blank>
+PASS Parsing: <http://f:4294967377/c> against <http://example.org/>
+PASS Parsing: <http://f:18446744073709551697/c> against <http://example.org/>
+PASS Parsing: <http://f:340282366920938463463374607431768211537/c> against <http://example.org/>
+FAIL Parsing: <sc://ñ> against <about:blank> assert_equals: host expected "%C3%B1" but got ""
+FAIL Parsing: <sc://ñ?x> against <about:blank> assert_equals: host expected "%C3%B1" but got ""
+FAIL Parsing: <sc://ñ#x> against <about:blank> assert_equals: host expected "%C3%B1" but got ""
+FAIL Parsing: <#x> against <sc://ñ> Failed to construct 'URL': Invalid URL
+FAIL Parsing: <?x> against <sc://ñ> Failed to construct 'URL': Invalid URL
+FAIL Parsing: <sc://?> against <about:blank> assert_equals: pathname expected "" but got "//"
+FAIL Parsing: <sc://#> against <about:blank> assert_equals: pathname expected "" but got "//"
+FAIL Parsing: <///> against <sc://x/> Failed to construct 'URL': Invalid URL
+FAIL Parsing: <////> against <sc://x/> Failed to construct 'URL': Invalid URL
+FAIL Parsing: <////x/> against <sc://x/> assert_equals: href expected "sc:////x/" but got "sc://x/"
+FAIL Parsing: <tftp://foobar.com/someconfig;mode=netascii> against <about:blank> assert_equals: host expected "foobar.com" but got ""
+FAIL Parsing: <telnet://user:pass@foobar.com:23/> against <about:blank> assert_equals: username expected "user" but got ""
+FAIL Parsing: <ut2004://10.10.10.10:7777/Index.ut2> against <about:blank> assert_equals: host expected "10.10.10.10:7777" but got ""
+FAIL Parsing: <redis://foo:bar@somehost:6379/0?baz=bam&qux=baz> against <about:blank> assert_equals: username expected "foo" but got ""
+FAIL Parsing: <rsync://foo@host:911/sup> against <about:blank> assert_equals: username expected "foo" but got ""
+FAIL Parsing: <git://github.com/foo/bar.git> against <about:blank> assert_equals: host expected "github.com" but got ""
+FAIL Parsing: <irc://myserver.com:6999/channel?passwd> against <about:blank> assert_equals: host expected "myserver.com:6999" but got ""
+FAIL Parsing: <dns://fw.example.org:9999/foo.bar.org?type=TXT> against <about:blank> assert_equals: host expected "fw.example.org:9999" but got ""
+FAIL Parsing: <ldap://localhost:389/ou=People,o=JNDITutorial> against <about:blank> assert_equals: host expected "localhost:389" but got ""
+FAIL Parsing: <git+https://github.com/foo/bar> against <about:blank> assert_equals: host expected "github.com" but got ""
+PASS Parsing: <urn:ietf:rfc:2648> against <about:blank>
+PASS Parsing: <tag:joe@example.org,2001:foo/bar> against <about:blank>
+FAIL Parsing: <non-spec:/.//> against <about:blank> assert_equals: pathname expected "//" but got "/.//"
+FAIL Parsing: <non-spec:/..//> against <about:blank> assert_equals: href expected "non-spec:/.//" but got "non-spec:/..//"
+FAIL Parsing: <non-spec:/a/..//> against <about:blank> assert_equals: href expected "non-spec:/.//" but got "non-spec:/a/..//"
+FAIL Parsing: <non-spec:/.//path> against <about:blank> assert_equals: pathname expected "//path" but got "/.//path"
+FAIL Parsing: <non-spec:/..//path> against <about:blank> assert_equals: href expected "non-spec:/.//path" but got "non-spec:/..//path"
+FAIL Parsing: <non-spec:/a/..//path> against <about:blank> assert_equals: href expected "non-spec:/.//path" but got "non-spec:/a/..//path"
+FAIL Parsing: </.//path> against <non-spec:/p> assert_equals: href expected "non-spec:/.//path" but got "non-spec://path"
+FAIL Parsing: </..//path> against <non-spec:/p> assert_equals: href expected "non-spec:/.//path" but got "non-spec://path"
+FAIL Parsing: <..//path> against <non-spec:/p> assert_equals: href expected "non-spec:/.//path" but got "non-spec://path"
+FAIL Parsing: <a/..//path> against <non-spec:/p> assert_equals: href expected "non-spec:/.//path" but got "non-spec://path"
+FAIL Parsing: <> against <non-spec:/..//p> assert_equals: href expected "non-spec:/.//p" but got "non-spec:/..//p"
+FAIL Parsing: <path> against <non-spec:/..//p> assert_equals: href expected "non-spec:/.//path" but got "non-spec:/..//path"
+FAIL Parsing: <../path> against <non-spec:/.//p> assert_equals: href expected "non-spec:/path" but got "non-spec:/./path"
+FAIL Parsing: <non-special://%E2%80%A0/> against <about:blank> assert_equals: host expected "%E2%80%A0" but got ""
+FAIL Parsing: <non-special://H%4fSt/path> against <about:blank> assert_equals: host expected "H%4fSt" but got ""
+FAIL Parsing: <non-special://[1:2:0:0:5:0:0:0]/> against <about:blank> assert_equals: href expected "non-special://[1:2:0:0:5::]/" but got "non-special://[1:2:0:0:5:0:0:0]/"
+FAIL Parsing: <non-special://[1:2:0:0:0:0:0:3]/> against <about:blank> assert_equals: href expected "non-special://[1:2::3]/" but got "non-special://[1:2:0:0:0:0:0:3]/"
+FAIL Parsing: <non-special://[1:2::3]:80/> against <about:blank> assert_equals: host expected "[1:2::3]:80" but got ""
+FAIL Parsing: <non-special://[:80/> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+PASS Parsing: <blob:https://example.com:443/> against <about:blank>
+PASS Parsing: <blob:d3958f5c-0777-0845-9dcf-2cb28783acaf> against <about:blank>
+PASS Parsing: <blob:> against <about:blank>
+PASS Parsing: <http://0x7f.0.0.0x7g> against <about:blank>
+PASS Parsing: <http://0X7F.0.0.0X7G> against <about:blank>
+PASS Parsing: <http://[::127.0.0.0.1]> against <about:blank>
+PASS Parsing: <http://[0:1:0:1:0:1:0:1]> against <about:blank>
+PASS Parsing: <http://[1:0:1:0:1:0:1:0]> against <about:blank>
+PASS Parsing: <http://example.org/test?"> against <about:blank>
+PASS Parsing: <http://example.org/test?#> against <about:blank>
+PASS Parsing: <http://example.org/test?<> against <about:blank>
+PASS Parsing: <http://example.org/test?>> against <about:blank>
+PASS Parsing: <http://example.org/test?⌣> against <about:blank>
+PASS Parsing: <http://example.org/test?%23%23> against <about:blank>
+PASS Parsing: <http://example.org/test?%GH> against <about:blank>
+PASS Parsing: <http://example.org/test?a#%EF> against <about:blank>
+PASS Parsing: <http://example.org/test?a#%GH> against <about:blank>
+PASS Parsing: <a> against <about:blank>
+PASS Parsing: <a/> against <about:blank>
+PASS Parsing: <a//> against <about:blank>
+PASS Parsing: <test-a-colon.html> against <a:>
+PASS Parsing: <test-a-colon-b.html> against <a:b>
+PASS Parsing: <test-a-colon-slash.html> against <a:/>
+FAIL Parsing: <test-a-colon-slash-slash.html> against <a://> Failed to construct 'URL': Invalid URL
+PASS Parsing: <test-a-colon-slash-b.html> against <a:/b>
+FAIL Parsing: <test-a-colon-slash-slash-b.html> against <a://b> Failed to construct 'URL': Invalid URL
+PASS Parsing: <http://example.org/test?a#b\0c> against <about:blank>
+FAIL Parsing: <non-spec://example.org/test?a#b\0c> against <about:blank> assert_equals: host expected "example.org" but got ""
+PASS Parsing: <non-spec:/test?a#b\0c> against <about:blank>
+PASS Parsing: <10.0.0.7:8080/foo.html> against <file:///some/dir/bar.html>
+PASS Parsing: <a!@$*=/foo.html> against <file:///some/dir/bar.html>
+PASS Parsing: <a1234567890-+.:foo/bar> against <http://example.com/dir/file>
+PASS Parsing: <file://a­b/p> against <about:blank>
+PASS Parsing: <file://a%C2%ADb/p> against <about:blank>
+PASS Parsing: <file://­/p> against <about:blank>
+PASS Parsing: <file://%C2%AD/p> against <about:blank>
+FAIL Parsing: <file://xn--/p> against <about:blank> assert_throws_js: function "function() {
+          bURL(expected.input, expected.base)
+        }" did not throw
+PASS Parsing: <#link> against <https://example.org/##link>
+PASS Parsing: <non-special:cannot-be-a-base-url-\0~€> against <about:blank>
+PASS Parsing: <https://www.example.com/path{path.html?query'=query#fragment<fragment> against <about:blank>
+PASS Parsing: <https://user:pass[@foo/bar> against <http://example.org>
+FAIL Parsing: <foo:// !"$%&'()*+,-.;<=>@[\]^_`{|}~@host/> against <about:blank> assert_equals: href expected "foo://%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~@host/" but got "foo:// !\"$%&'()*+,-.;<=>@[\\]^_`{|}~@host/"
+FAIL Parsing: <wss:// !"$%&'()*+,-.;<=>@[]^_`{|}~@host/> against <about:blank> assert_equals: href expected "wss://%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/" but got "wss://%20!%22$%&%27()*+,-.%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/"
+FAIL Parsing: <foo://joe: !"$%&'()*+,-.:;<=>@[\]^_`{|}~@host/> against <about:blank> assert_equals: href expected "foo://joe:%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~@host/" but got "foo://joe: !\"$%&'()*+,-.:;<=>@[\\]^_`{|}~@host/"
+FAIL Parsing: <wss://joe: !"$%&'()*+,-.:;<=>@[]^_`{|}~@host/> against <about:blank> assert_equals: href expected "wss://joe:%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/" but got "wss://joe:%20!%22$%&%27()*+,-.%3A%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/"
+FAIL Parsing: <foo://!"$%&'()*+,-.;=_`{}~/> against <about:blank> assert_equals: host expected "!\"$%&'()*+,-.;=_`{}~" but got ""
+FAIL Parsing: <wss://!"$&'()*+,-.;=_`{}~/> against <about:blank> Failed to construct 'URL': Invalid URL
+FAIL Parsing: <foo://host/ !"$%&'()*+,-./:;<=>@[\]^_`{|}~> against <about:blank> assert_equals: href expected "foo://host/%20!%22$%&'()*+,-./:;%3C=%3E@[\\]^_%60%7B|%7D~" but got "foo://host/ !\"$%&'()*+,-./:;<=>@[\\]^_`{|}~"
+FAIL Parsing: <wss://host/ !"$%&'()*+,-./:;<=>@[\]^_`{|}~> against <about:blank> assert_equals: href expected "wss://host/%20!%22$%&'()*+,-./:;%3C=%3E@[/]^_%60%7B|%7D~" but got "wss://host/%20!%22$%&'()*+,-./:;%3C=%3E@[/]%5E_%60%7B%7C%7D~"
+FAIL Parsing: <foo://host/dir/? !"$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank> assert_equals: href expected "foo://host/dir/?%20!%22$%&'()*+,-./:;%3C=%3E?@[\\]^_`{|}~" but got "foo://host/dir/?%20!%22$%&%27()*+,-./:;%3C=%3E?@[\\]^_`{|}~"
+PASS Parsing: <wss://host/dir/? !"$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
+FAIL Parsing: <foo://host/dir/# !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank> assert_equals: host expected "host" but got ""
+PASS Parsing: <wss://host/dir/# !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
+FAIL Parsing: <abc:rootless> against <abc://host/path> assert_equals: href expected "abc:rootless" but got "abc://host/rootless"
+FAIL Parsing: <abc:rootless> against <abc:/path> assert_equals: href expected "abc:rootless" but got "abc:/rootless"
+PASS Parsing: <abc:rootless> against <abc:path>
+FAIL Parsing: <abc:/rooted> against <abc://host/path> assert_equals: href expected "abc:/rooted" but got "abc://host/rooted"
+PASS Parsing: <#> against <null>
+PASS Parsing: <?> against <null>
+PASS Parsing: <http://1.2.3.4.5> against <http://other.com/>
+PASS Parsing: <http://1.2.3.4.5.> against <http://other.com/>
+PASS Parsing: <http://0..0x300/> against <about:blank>
+PASS Parsing: <http://0..0x300./> against <about:blank>
+PASS Parsing: <http://256.256.256.256.256> against <http://other.com/>
+PASS Parsing: <http://256.256.256.256.256.> against <http://other.com/>
+PASS Parsing: <http://1.2.3.08> against <about:blank>
+PASS Parsing: <http://1.2.3.08.> against <about:blank>
+PASS Parsing: <http://1.2.3.09> against <about:blank>
+PASS Parsing: <http://09.2.3.4> against <about:blank>
+PASS Parsing: <http://09.2.3.4.> against <about:blank>
+PASS Parsing: <http://01.2.3.4.5> against <about:blank>
+PASS Parsing: <http://01.2.3.4.5.> against <about:blank>
+PASS Parsing: <http://0x100.2.3.4> against <about:blank>
+PASS Parsing: <http://0x100.2.3.4.> against <about:blank>
+PASS Parsing: <http://0x1.2.3.4.5> against <about:blank>
+PASS Parsing: <http://0x1.2.3.4.5.> against <about:blank>
+PASS Parsing: <http://foo.1.2.3.4> against <about:blank>
+PASS Parsing: <http://foo.1.2.3.4.> against <about:blank>
+PASS Parsing: <http://foo.2.3.4> against <about:blank>
+PASS Parsing: <http://foo.2.3.4.> against <about:blank>
+PASS Parsing: <http://foo.09> against <about:blank>
+PASS Parsing: <http://foo.09.> against <about:blank>
+PASS Parsing: <http://foo.0x4> against <about:blank>
+PASS Parsing: <http://foo.0x4.> against <about:blank>
+PASS Parsing: <http://foo.09..> against <about:blank>
+PASS Parsing: <http://0999999999999999999/> against <about:blank>
+PASS Parsing: <http://foo.0x> against <about:blank>
+PASS Parsing: <http://foo.0XFfFfFfFfFfFfFfFfFfAcE123> against <about:blank>
+PASS Parsing: <http://💩.123/> against <about:blank>
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-origin.any-expected.txt b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-origin.any-expected.txt
new file mode 100644
index 0000000..c8edf0f
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-origin.any-expected.txt
@@ -0,0 +1,342 @@
+This is a testharness.js-based test.
+Found 329 tests; 322 PASS, 7 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS Loading data…
+PASS Origin parsing: <http://example	.
+org> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://user:pass@foo:21/bar;par?b#c> against <http://example.org/foo/bar>
+PASS Origin parsing: <https://test:@test> against <about:blank>
+PASS Origin parsing: <https://:@test> against <about:blank>
+PASS Origin parsing: <non-special://test:@test/x> against <about:blank>
+PASS Origin parsing: <non-special://:@test/x> against <about:blank>
+PASS Origin parsing: <http:foo.com> against <http://example.org/foo/bar>
+PASS Origin parsing: <	   :foo.com   
+> against <http://example.org/foo/bar>
+PASS Origin parsing: < foo.com  > against <http://example.org/foo/bar>
+PASS Origin parsing: <a:	 foo.com> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://f:21/ b ? d # e > against <http://example.org/foo/bar>
+PASS Origin parsing: <http://f:/c> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://f:0/c> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://f:00000000000000/c> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://f:00000000000000000000080/c> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://f:
+/c> against <http://example.org/foo/bar>
+PASS Origin parsing: <> against <http://example.org/foo/bar>
+PASS Origin parsing: <  	> against <http://example.org/foo/bar>
+PASS Origin parsing: <:foo.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <:foo.com\> against <http://example.org/foo/bar>
+PASS Origin parsing: <:> against <http://example.org/foo/bar>
+PASS Origin parsing: <:a> against <http://example.org/foo/bar>
+PASS Origin parsing: <:/> against <http://example.org/foo/bar>
+PASS Origin parsing: <:\> against <http://example.org/foo/bar>
+PASS Origin parsing: <:#> against <http://example.org/foo/bar>
+PASS Origin parsing: <#> against <http://example.org/foo/bar>
+PASS Origin parsing: <#/> against <http://example.org/foo/bar>
+PASS Origin parsing: <#\> against <http://example.org/foo/bar>
+PASS Origin parsing: <#;?> against <http://example.org/foo/bar>
+PASS Origin parsing: <?> against <http://example.org/foo/bar>
+PASS Origin parsing: </> against <http://example.org/foo/bar>
+PASS Origin parsing: <:23> against <http://example.org/foo/bar>
+PASS Origin parsing: </:23> against <http://example.org/foo/bar>
+PASS Origin parsing: <\x> against <http://example.org/foo/bar>
+PASS Origin parsing: <\\x\hello> against <http://example.org/foo/bar>
+PASS Origin parsing: <::> against <http://example.org/foo/bar>
+PASS Origin parsing: <::23> against <http://example.org/foo/bar>
+PASS Origin parsing: <foo://> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://a:b@c:29/d> against <http://example.org/foo/bar>
+PASS Origin parsing: <http::@c:29> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://&a:foo(b]c@d:2/> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://::@c@d:2> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://foo.com:b@d/> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://foo.com/\@> against <http://example.org/foo/bar>
+PASS Origin parsing: <http:\\foo.com\> against <http://example.org/foo/bar>
+PASS Origin parsing: <http:\\a\b:c\d@foo.com\> against <http://example.org/foo/bar>
+PASS Origin parsing: <foo:/> against <http://example.org/foo/bar>
+PASS Origin parsing: <foo:/bar.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <foo://///////> against <http://example.org/foo/bar>
+PASS Origin parsing: <foo://///////bar.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <foo:////://///> against <http://example.org/foo/bar>
+PASS Origin parsing: <c:/foo> against <http://example.org/foo/bar>
+PASS Origin parsing: <//foo/bar> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://foo/path;a??e#f#g> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://foo/abcd?efgh?ijkl> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://foo/abcd#foo?bar> against <http://example.org/foo/bar>
+PASS Origin parsing: <[61:24:74]:98> against <http://example.org/foo/bar>
+PASS Origin parsing: <http:[61:27]/:foo> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://[2001::1]> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://[::127.0.0.1]> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://[0:0:0:0:0:0:13.1.68.3]> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://[2001::1]:80> against <http://example.org/foo/bar>
+PASS Origin parsing: <http:/example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <ftp:/example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <https:/example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <madeupscheme:/example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <ftps:/example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <gopher:/example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <ws:/example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <wss:/example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <data:/example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <javascript:/example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <mailto:/example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <http:example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <ftp:example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <https:example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <madeupscheme:example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <ftps:example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <gopher:example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <ws:example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <wss:example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <data:example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <javascript:example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <mailto:example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: </a/b/c> against <http://example.org/foo/bar>
+PASS Origin parsing: </a/ /c> against <http://example.org/foo/bar>
+PASS Origin parsing: </a%2fc> against <http://example.org/foo/bar>
+PASS Origin parsing: </a/%2f/c> against <http://example.org/foo/bar>
+PASS Origin parsing: <#β> against <http://example.org/foo/bar>
+PASS Origin parsing: <data:text/html,test#test> against <http://example.org/foo/bar>
+PASS Origin parsing: <tel:1234567890> against <http://example.org/foo/bar>
+PASS Origin parsing: <ssh://example.com/foo/bar.git> against <http://example.org/>
+PASS Origin parsing: <http://example.com/././foo> against <about:blank>
+PASS Origin parsing: <http://example.com/./.foo> against <about:blank>
+PASS Origin parsing: <http://example.com/foo/.> against <about:blank>
+PASS Origin parsing: <http://example.com/foo/./> against <about:blank>
+PASS Origin parsing: <http://example.com/foo/bar/..> against <about:blank>
+PASS Origin parsing: <http://example.com/foo/bar/../> against <about:blank>
+PASS Origin parsing: <http://example.com/foo/..bar> against <about:blank>
+PASS Origin parsing: <http://example.com/foo/bar/../ton> against <about:blank>
+PASS Origin parsing: <http://example.com/foo/bar/../ton/../../a> against <about:blank>
+PASS Origin parsing: <http://example.com/foo/../../..> against <about:blank>
+PASS Origin parsing: <http://example.com/foo/../../../ton> against <about:blank>
+PASS Origin parsing: <http://example.com/foo/%2e> against <about:blank>
+PASS Origin parsing: <http://example.com/foo/%2e%2> against <about:blank>
+PASS Origin parsing: <http://example.com/foo/%2e./%2e%2e/.%2e/%2e.bar> against <about:blank>
+PASS Origin parsing: <http://example.com////../..> against <about:blank>
+PASS Origin parsing: <http://example.com/foo/bar//../..> against <about:blank>
+PASS Origin parsing: <http://example.com/foo/bar//..> against <about:blank>
+PASS Origin parsing: <http://example.com/foo> against <about:blank>
+PASS Origin parsing: <http://example.com/%20foo> against <about:blank>
+PASS Origin parsing: <http://example.com/foo%> against <about:blank>
+PASS Origin parsing: <http://example.com/foo%2> against <about:blank>
+PASS Origin parsing: <http://example.com/foo%2zbar> against <about:blank>
+PASS Origin parsing: <http://example.com/foo%2©zbar> against <about:blank>
+PASS Origin parsing: <http://example.com/foo%41%7a> against <about:blank>
+PASS Origin parsing: <http://example.com/foo	‘%91> against <about:blank>
+FAIL Origin parsing: <http://example.com/foo%00%51> against <about:blank> Failed to construct 'URL': Invalid URL
+PASS Origin parsing: <http://example.com/(%28:%3A%29)> against <about:blank>
+PASS Origin parsing: <http://example.com/%3A%3a%3C%3c> against <about:blank>
+PASS Origin parsing: <http://example.com/foo	bar> against <about:blank>
+PASS Origin parsing: <http://example.com\\foo\\bar> against <about:blank>
+PASS Origin parsing: <http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd> against <about:blank>
+PASS Origin parsing: <http://example.com/@asdf%40> against <about:blank>
+PASS Origin parsing: <http://example.com/你好你好> against <about:blank>
+PASS Origin parsing: <http://example.com/‥/foo> against <about:blank>
+PASS Origin parsing: <http://example.com//foo> against <about:blank>
+PASS Origin parsing: <http://example.com/‮/foo/‭/bar> against <about:blank>
+PASS Origin parsing: <http://www.google.com/foo?bar=baz#> against <about:blank>
+PASS Origin parsing: <http://www.google.com/foo?bar=baz# »> against <about:blank>
+PASS Origin parsing: <data:test# »> against <about:blank>
+PASS Origin parsing: <http://www.google.com> against <about:blank>
+PASS Origin parsing: <http://192.0x00A80001> against <about:blank>
+PASS Origin parsing: <http://www/foo%2Ehtml> against <about:blank>
+PASS Origin parsing: <http://www/foo/%2E/html> against <about:blank>
+PASS Origin parsing: <http://%25DOMAIN:foobar@foodomain.com/> against <about:blank>
+PASS Origin parsing: <http:\\www.google.com\foo> against <about:blank>
+PASS Origin parsing: <http://foo:80/> against <about:blank>
+PASS Origin parsing: <http://foo:81/> against <about:blank>
+PASS Origin parsing: <httpa://foo:80/> against <about:blank>
+PASS Origin parsing: <https://foo:443/> against <about:blank>
+PASS Origin parsing: <https://foo:80/> against <about:blank>
+PASS Origin parsing: <ftp://foo:21/> against <about:blank>
+PASS Origin parsing: <ftp://foo:80/> against <about:blank>
+PASS Origin parsing: <gopher://foo:70/> against <about:blank>
+PASS Origin parsing: <gopher://foo:443/> against <about:blank>
+PASS Origin parsing: <ws://foo:80/> against <about:blank>
+PASS Origin parsing: <ws://foo:81/> against <about:blank>
+PASS Origin parsing: <ws://foo:443/> against <about:blank>
+PASS Origin parsing: <ws://foo:815/> against <about:blank>
+PASS Origin parsing: <wss://foo:80/> against <about:blank>
+PASS Origin parsing: <wss://foo:81/> against <about:blank>
+PASS Origin parsing: <wss://foo:443/> against <about:blank>
+PASS Origin parsing: <wss://foo:815/> against <about:blank>
+PASS Origin parsing: <http:/example.com/> against <about:blank>
+PASS Origin parsing: <ftp:/example.com/> against <about:blank>
+PASS Origin parsing: <https:/example.com/> against <about:blank>
+PASS Origin parsing: <madeupscheme:/example.com/> against <about:blank>
+PASS Origin parsing: <ftps:/example.com/> against <about:blank>
+PASS Origin parsing: <gopher:/example.com/> against <about:blank>
+PASS Origin parsing: <ws:/example.com/> against <about:blank>
+PASS Origin parsing: <wss:/example.com/> against <about:blank>
+PASS Origin parsing: <data:/example.com/> against <about:blank>
+PASS Origin parsing: <javascript:/example.com/> against <about:blank>
+PASS Origin parsing: <mailto:/example.com/> against <about:blank>
+PASS Origin parsing: <http:example.com/> against <about:blank>
+PASS Origin parsing: <ftp:example.com/> against <about:blank>
+PASS Origin parsing: <https:example.com/> against <about:blank>
+PASS Origin parsing: <madeupscheme:example.com/> against <about:blank>
+PASS Origin parsing: <ftps:example.com/> against <about:blank>
+PASS Origin parsing: <gopher:example.com/> against <about:blank>
+PASS Origin parsing: <ws:example.com/> against <about:blank>
+PASS Origin parsing: <wss:example.com/> against <about:blank>
+PASS Origin parsing: <data:example.com/> against <about:blank>
+PASS Origin parsing: <javascript:example.com/> against <about:blank>
+PASS Origin parsing: <mailto:example.com/> against <about:blank>
+PASS Origin parsing: <http:@www.example.com> against <about:blank>
+PASS Origin parsing: <http:/@www.example.com> against <about:blank>
+PASS Origin parsing: <http://@www.example.com> against <about:blank>
+PASS Origin parsing: <http:a:b@www.example.com> against <about:blank>
+PASS Origin parsing: <http:/a:b@www.example.com> against <about:blank>
+PASS Origin parsing: <http://a:b@www.example.com> against <about:blank>
+PASS Origin parsing: <http://@pple.com> against <about:blank>
+PASS Origin parsing: <http::b@www.example.com> against <about:blank>
+PASS Origin parsing: <http:/:b@www.example.com> against <about:blank>
+PASS Origin parsing: <http://:b@www.example.com> against <about:blank>
+PASS Origin parsing: <http:a:@www.example.com> against <about:blank>
+PASS Origin parsing: <http:/a:@www.example.com> against <about:blank>
+PASS Origin parsing: <http://a:@www.example.com> against <about:blank>
+PASS Origin parsing: <http://www.@pple.com> against <about:blank>
+PASS Origin parsing: <http://:@www.example.com> against <about:blank>
+PASS Origin parsing: </> against <http://www.example.com/test>
+PASS Origin parsing: </test.txt> against <http://www.example.com/test>
+PASS Origin parsing: <.> against <http://www.example.com/test>
+PASS Origin parsing: <..> against <http://www.example.com/test>
+PASS Origin parsing: <test.txt> against <http://www.example.com/test>
+PASS Origin parsing: <./test.txt> against <http://www.example.com/test>
+PASS Origin parsing: <../test.txt> against <http://www.example.com/test>
+PASS Origin parsing: <../aaa/test.txt> against <http://www.example.com/test>
+PASS Origin parsing: <../../test.txt> against <http://www.example.com/test>
+PASS Origin parsing: <中/test.txt> against <http://www.example.com/test>
+PASS Origin parsing: <http://www.example2.com> against <http://www.example.com/test>
+PASS Origin parsing: <//www.example2.com> against <http://www.example.com/test>
+PASS Origin parsing: <http://ExAmPlE.CoM> against <http://other.com/>
+PASS Origin parsing: <http://GOO​⁠goo.com> against <http://other.com/>
+PASS Origin parsing: <\0 http://example.com/ \r > against <about:blank>
+PASS Origin parsing: <http://www.foo。bar.com> against <http://other.com/>
+PASS Origin parsing: <https://x/�?�#�> against <about:blank>
+PASS Origin parsing: <http://Go.com> against <http://other.com/>
+PASS Origin parsing: <http://你好你好> against <http://other.com/>
+PASS Origin parsing: <https://faß.ExAmPlE/> against <about:blank>
+PASS Origin parsing: <sc://faß.ExAmPlE/> against <about:blank>
+PASS Origin parsing: <http://%30%78%63%30%2e%30%32%35%30.01> against <http://other.com/>
+PASS Origin parsing: <http://%30%78%63%30%2e%30%32%35%30.01%2e> against <http://other.com/>
+PASS Origin parsing: <http://0Xc0.0250.01> against <http://other.com/>
+PASS Origin parsing: <http://./> against <about:blank>
+PASS Origin parsing: <http://../> against <about:blank>
+PASS Origin parsing: <http://foo:💩@example.com/bar> against <http://other.com/>
+PASS Origin parsing: <#> against <test:test>
+PASS Origin parsing: <#x> against <mailto:x@x.com>
+PASS Origin parsing: <#x> against <data:,>
+PASS Origin parsing: <#x> against <about:blank>
+PASS Origin parsing: <#> against <test:test?test>
+PASS Origin parsing: <https://@test@test@example:800/> against <http://doesnotmatter/>
+PASS Origin parsing: <https://@@@example> against <http://doesnotmatter/>
+PASS Origin parsing: <http://`{}:`{}@h/`{}?`{}> against <http://doesnotmatter/>
+PASS Origin parsing: <http://host/?'> against <about:blank>
+PASS Origin parsing: <notspecial://host/?'> against <about:blank>
+PASS Origin parsing: </some/path> against <http://user@example.org/smth>
+PASS Origin parsing: <> against <http://user:pass@example.org:21/smth>
+PASS Origin parsing: </some/path> against <http://user:pass@example.org:21/smth>
+PASS Origin parsing: <i> against <sc:/pa/pa>
+PASS Origin parsing: <i> against <sc://ho/pa>
+PASS Origin parsing: <i> against <sc:///pa/pa>
+PASS Origin parsing: <../i> against <sc:/pa/pa>
+PASS Origin parsing: <../i> against <sc://ho/pa>
+PASS Origin parsing: <../i> against <sc:///pa/pa>
+PASS Origin parsing: </i> against <sc:/pa/pa>
+PASS Origin parsing: </i> against <sc://ho/pa>
+PASS Origin parsing: </i> against <sc:///pa/pa>
+PASS Origin parsing: <?i> against <sc:/pa/pa>
+PASS Origin parsing: <?i> against <sc://ho/pa>
+PASS Origin parsing: <?i> against <sc:///pa/pa>
+PASS Origin parsing: <#i> against <sc:sd>
+PASS Origin parsing: <#i> against <sc:sd/sd>
+PASS Origin parsing: <#i> against <sc:/pa/pa>
+PASS Origin parsing: <#i> against <sc://ho/pa>
+PASS Origin parsing: <#i> against <sc:///pa/pa>
+PASS Origin parsing: <about:/../> against <about:blank>
+PASS Origin parsing: <data:/../> against <about:blank>
+PASS Origin parsing: <javascript:/../> against <about:blank>
+PASS Origin parsing: <mailto:/../> against <about:blank>
+PASS Origin parsing: <sc://ñ.test/> against <about:blank>
+FAIL Origin parsing: <x> against <sc://ñ> Failed to construct 'URL': Invalid URL
+PASS Origin parsing: <sc:\../> against <about:blank>
+PASS Origin parsing: <sc::a@example.net> against <about:blank>
+PASS Origin parsing: <wow:%NBD> against <about:blank>
+PASS Origin parsing: <wow:%1G> against <about:blank>
+PASS Origin parsing: <wow:￿> against <about:blank>
+FAIL Origin parsing: <http://example.com/U+d800𐟾U+dfff﷐﷏﷯ﷰ￾￿?U+d800𐟾U+dfff﷐﷏﷯ﷰ￾￿> against <about:blank> Failed to construct 'URL': Invalid URL
+FAIL Origin parsing: <http://!"$&'()*+,-.;=_`{}~/> against <about:blank> Failed to construct 'URL': Invalid URL
+PASS Origin parsing: <sc://!"$%&'()*+,-.;=_`{}~/> against <about:blank>
+PASS Origin parsing: <ftp://%e2%98%83> against <about:blank>
+PASS Origin parsing: <https://%e2%98%83> against <about:blank>
+PASS Origin parsing: <http://127.0.0.1:10100/relative_import.html> against <about:blank>
+PASS Origin parsing: <http://facebook.com/?foo=%7B%22abc%22> against <about:blank>
+PASS Origin parsing: <https://localhost:3000/jqueryui@1.2.3> against <about:blank>
+PASS Origin parsing: <h	t
+t\rp://h	o
+s\rt:9	0
+0\r0/p	a
+t\rh?q	u
+e\rry#f	r
+a\rg> against <about:blank>
+PASS Origin parsing: <?a=b&c=d> against <http://example.org/foo/bar>
+PASS Origin parsing: <??a=b&c=d> against <http://example.org/foo/bar>
+PASS Origin parsing: <http:> against <http://example.org/foo/bar>
+PASS Origin parsing: <sc:> against <https://example.org/foo/bar>
+PASS Origin parsing: <http://foo.bar/baz?qux#foobar> against <about:blank>
+PASS Origin parsing: <http://foo.bar/baz?qux#foo"bar> against <about:blank>
+PASS Origin parsing: <http://foo.bar/baz?qux#foo<bar> against <about:blank>
+PASS Origin parsing: <http://foo.bar/baz?qux#foo>bar> against <about:blank>
+PASS Origin parsing: <http://foo.bar/baz?qux#foo`bar> against <about:blank>
+PASS Origin parsing: <http://1.2.3.4/> against <http://other.com/>
+PASS Origin parsing: <http://1.2.3.4./> against <http://other.com/>
+PASS Origin parsing: <http://192.168.257> against <http://other.com/>
+PASS Origin parsing: <http://192.168.257.> against <http://other.com/>
+PASS Origin parsing: <http://192.168.257.com> against <http://other.com/>
+PASS Origin parsing: <http://256> against <http://other.com/>
+PASS Origin parsing: <http://256.com> against <http://other.com/>
+PASS Origin parsing: <http://999999999> against <http://other.com/>
+PASS Origin parsing: <http://999999999.> against <http://other.com/>
+PASS Origin parsing: <http://999999999.com> against <http://other.com/>
+PASS Origin parsing: <http://10000000000.com> against <http://other.com/>
+PASS Origin parsing: <http://4294967295> against <http://other.com/>
+PASS Origin parsing: <http://0xffffffff> against <http://other.com/>
+PASS Origin parsing: <https://0x.0x.0> against <about:blank>
+PASS Origin parsing: <asdf://%43%7C/> against <about:blank>
+PASS Origin parsing: <http://[1:0::]> against <http://example.net/>
+PASS Origin parsing: <sc://ñ> against <about:blank>
+PASS Origin parsing: <sc://ñ?x> against <about:blank>
+PASS Origin parsing: <sc://ñ#x> against <about:blank>
+FAIL Origin parsing: <#x> against <sc://ñ> Failed to construct 'URL': Invalid URL
+FAIL Origin parsing: <?x> against <sc://ñ> Failed to construct 'URL': Invalid URL
+PASS Origin parsing: <tftp://foobar.com/someconfig;mode=netascii> against <about:blank>
+PASS Origin parsing: <telnet://user:pass@foobar.com:23/> against <about:blank>
+PASS Origin parsing: <ut2004://10.10.10.10:7777/Index.ut2> against <about:blank>
+PASS Origin parsing: <redis://foo:bar@somehost:6379/0?baz=bam&qux=baz> against <about:blank>
+PASS Origin parsing: <rsync://foo@host:911/sup> against <about:blank>
+PASS Origin parsing: <git://github.com/foo/bar.git> against <about:blank>
+PASS Origin parsing: <irc://myserver.com:6999/channel?passwd> against <about:blank>
+PASS Origin parsing: <dns://fw.example.org:9999/foo.bar.org?type=TXT> against <about:blank>
+PASS Origin parsing: <ldap://localhost:389/ou=People,o=JNDITutorial> against <about:blank>
+PASS Origin parsing: <git+https://github.com/foo/bar> against <about:blank>
+PASS Origin parsing: <urn:ietf:rfc:2648> against <about:blank>
+PASS Origin parsing: <tag:joe@example.org,2001:foo/bar> against <about:blank>
+PASS Origin parsing: <blob:https://example.com:443/> against <about:blank>
+PASS Origin parsing: <blob:d3958f5c-0777-0845-9dcf-2cb28783acaf> against <about:blank>
+PASS Origin parsing: <blob:> against <about:blank>
+PASS Origin parsing: <non-special:cannot-be-a-base-url-\0~€> against <about:blank>
+PASS Origin parsing: <https://www.example.com/path{path.html?query'=query#fragment<fragment> against <about:blank>
+PASS Origin parsing: <https://user:pass[@foo/bar> against <http://example.org>
+PASS Origin parsing: <foo:// !"$%&'()*+,-.;<=>@[\]^_`{|}~@host/> against <about:blank>
+PASS Origin parsing: <wss:// !"$%&'()*+,-.;<=>@[]^_`{|}~@host/> against <about:blank>
+PASS Origin parsing: <foo://joe: !"$%&'()*+,-.:;<=>@[\]^_`{|}~@host/> against <about:blank>
+PASS Origin parsing: <wss://joe: !"$%&'()*+,-.:;<=>@[]^_`{|}~@host/> against <about:blank>
+PASS Origin parsing: <foo://!"$%&'()*+,-.;=_`{}~/> against <about:blank>
+FAIL Origin parsing: <wss://!"$&'()*+,-.;=_`{}~/> against <about:blank> Failed to construct 'URL': Invalid URL
+PASS Origin parsing: <foo://host/ !"$%&'()*+,-./:;<=>@[\]^_`{|}~> against <about:blank>
+PASS Origin parsing: <wss://host/ !"$%&'()*+,-./:;<=>@[\]^_`{|}~> against <about:blank>
+PASS Origin parsing: <foo://host/dir/? !"$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
+PASS Origin parsing: <wss://host/dir/? !"$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
+PASS Origin parsing: <foo://host/dir/# !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
+PASS Origin parsing: <wss://host/dir/# !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-origin.any.worker-expected.txt b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-origin.any.worker-expected.txt
new file mode 100644
index 0000000..c8edf0f
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-origin.any.worker-expected.txt
@@ -0,0 +1,342 @@
+This is a testharness.js-based test.
+Found 329 tests; 322 PASS, 7 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS Loading data…
+PASS Origin parsing: <http://example	.
+org> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://user:pass@foo:21/bar;par?b#c> against <http://example.org/foo/bar>
+PASS Origin parsing: <https://test:@test> against <about:blank>
+PASS Origin parsing: <https://:@test> against <about:blank>
+PASS Origin parsing: <non-special://test:@test/x> against <about:blank>
+PASS Origin parsing: <non-special://:@test/x> against <about:blank>
+PASS Origin parsing: <http:foo.com> against <http://example.org/foo/bar>
+PASS Origin parsing: <	   :foo.com   
+> against <http://example.org/foo/bar>
+PASS Origin parsing: < foo.com  > against <http://example.org/foo/bar>
+PASS Origin parsing: <a:	 foo.com> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://f:21/ b ? d # e > against <http://example.org/foo/bar>
+PASS Origin parsing: <http://f:/c> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://f:0/c> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://f:00000000000000/c> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://f:00000000000000000000080/c> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://f:
+/c> against <http://example.org/foo/bar>
+PASS Origin parsing: <> against <http://example.org/foo/bar>
+PASS Origin parsing: <  	> against <http://example.org/foo/bar>
+PASS Origin parsing: <:foo.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <:foo.com\> against <http://example.org/foo/bar>
+PASS Origin parsing: <:> against <http://example.org/foo/bar>
+PASS Origin parsing: <:a> against <http://example.org/foo/bar>
+PASS Origin parsing: <:/> against <http://example.org/foo/bar>
+PASS Origin parsing: <:\> against <http://example.org/foo/bar>
+PASS Origin parsing: <:#> against <http://example.org/foo/bar>
+PASS Origin parsing: <#> against <http://example.org/foo/bar>
+PASS Origin parsing: <#/> against <http://example.org/foo/bar>
+PASS Origin parsing: <#\> against <http://example.org/foo/bar>
+PASS Origin parsing: <#;?> against <http://example.org/foo/bar>
+PASS Origin parsing: <?> against <http://example.org/foo/bar>
+PASS Origin parsing: </> against <http://example.org/foo/bar>
+PASS Origin parsing: <:23> against <http://example.org/foo/bar>
+PASS Origin parsing: </:23> against <http://example.org/foo/bar>
+PASS Origin parsing: <\x> against <http://example.org/foo/bar>
+PASS Origin parsing: <\\x\hello> against <http://example.org/foo/bar>
+PASS Origin parsing: <::> against <http://example.org/foo/bar>
+PASS Origin parsing: <::23> against <http://example.org/foo/bar>
+PASS Origin parsing: <foo://> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://a:b@c:29/d> against <http://example.org/foo/bar>
+PASS Origin parsing: <http::@c:29> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://&a:foo(b]c@d:2/> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://::@c@d:2> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://foo.com:b@d/> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://foo.com/\@> against <http://example.org/foo/bar>
+PASS Origin parsing: <http:\\foo.com\> against <http://example.org/foo/bar>
+PASS Origin parsing: <http:\\a\b:c\d@foo.com\> against <http://example.org/foo/bar>
+PASS Origin parsing: <foo:/> against <http://example.org/foo/bar>
+PASS Origin parsing: <foo:/bar.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <foo://///////> against <http://example.org/foo/bar>
+PASS Origin parsing: <foo://///////bar.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <foo:////://///> against <http://example.org/foo/bar>
+PASS Origin parsing: <c:/foo> against <http://example.org/foo/bar>
+PASS Origin parsing: <//foo/bar> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://foo/path;a??e#f#g> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://foo/abcd?efgh?ijkl> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://foo/abcd#foo?bar> against <http://example.org/foo/bar>
+PASS Origin parsing: <[61:24:74]:98> against <http://example.org/foo/bar>
+PASS Origin parsing: <http:[61:27]/:foo> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://[2001::1]> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://[::127.0.0.1]> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://[0:0:0:0:0:0:13.1.68.3]> against <http://example.org/foo/bar>
+PASS Origin parsing: <http://[2001::1]:80> against <http://example.org/foo/bar>
+PASS Origin parsing: <http:/example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <ftp:/example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <https:/example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <madeupscheme:/example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <ftps:/example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <gopher:/example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <ws:/example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <wss:/example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <data:/example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <javascript:/example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <mailto:/example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <http:example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <ftp:example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <https:example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <madeupscheme:example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <ftps:example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <gopher:example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <ws:example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <wss:example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <data:example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <javascript:example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: <mailto:example.com/> against <http://example.org/foo/bar>
+PASS Origin parsing: </a/b/c> against <http://example.org/foo/bar>
+PASS Origin parsing: </a/ /c> against <http://example.org/foo/bar>
+PASS Origin parsing: </a%2fc> against <http://example.org/foo/bar>
+PASS Origin parsing: </a/%2f/c> against <http://example.org/foo/bar>
+PASS Origin parsing: <#β> against <http://example.org/foo/bar>
+PASS Origin parsing: <data:text/html,test#test> against <http://example.org/foo/bar>
+PASS Origin parsing: <tel:1234567890> against <http://example.org/foo/bar>
+PASS Origin parsing: <ssh://example.com/foo/bar.git> against <http://example.org/>
+PASS Origin parsing: <http://example.com/././foo> against <about:blank>
+PASS Origin parsing: <http://example.com/./.foo> against <about:blank>
+PASS Origin parsing: <http://example.com/foo/.> against <about:blank>
+PASS Origin parsing: <http://example.com/foo/./> against <about:blank>
+PASS Origin parsing: <http://example.com/foo/bar/..> against <about:blank>
+PASS Origin parsing: <http://example.com/foo/bar/../> against <about:blank>
+PASS Origin parsing: <http://example.com/foo/..bar> against <about:blank>
+PASS Origin parsing: <http://example.com/foo/bar/../ton> against <about:blank>
+PASS Origin parsing: <http://example.com/foo/bar/../ton/../../a> against <about:blank>
+PASS Origin parsing: <http://example.com/foo/../../..> against <about:blank>
+PASS Origin parsing: <http://example.com/foo/../../../ton> against <about:blank>
+PASS Origin parsing: <http://example.com/foo/%2e> against <about:blank>
+PASS Origin parsing: <http://example.com/foo/%2e%2> against <about:blank>
+PASS Origin parsing: <http://example.com/foo/%2e./%2e%2e/.%2e/%2e.bar> against <about:blank>
+PASS Origin parsing: <http://example.com////../..> against <about:blank>
+PASS Origin parsing: <http://example.com/foo/bar//../..> against <about:blank>
+PASS Origin parsing: <http://example.com/foo/bar//..> against <about:blank>
+PASS Origin parsing: <http://example.com/foo> against <about:blank>
+PASS Origin parsing: <http://example.com/%20foo> against <about:blank>
+PASS Origin parsing: <http://example.com/foo%> against <about:blank>
+PASS Origin parsing: <http://example.com/foo%2> against <about:blank>
+PASS Origin parsing: <http://example.com/foo%2zbar> against <about:blank>
+PASS Origin parsing: <http://example.com/foo%2©zbar> against <about:blank>
+PASS Origin parsing: <http://example.com/foo%41%7a> against <about:blank>
+PASS Origin parsing: <http://example.com/foo	‘%91> against <about:blank>
+FAIL Origin parsing: <http://example.com/foo%00%51> against <about:blank> Failed to construct 'URL': Invalid URL
+PASS Origin parsing: <http://example.com/(%28:%3A%29)> against <about:blank>
+PASS Origin parsing: <http://example.com/%3A%3a%3C%3c> against <about:blank>
+PASS Origin parsing: <http://example.com/foo	bar> against <about:blank>
+PASS Origin parsing: <http://example.com\\foo\\bar> against <about:blank>
+PASS Origin parsing: <http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd> against <about:blank>
+PASS Origin parsing: <http://example.com/@asdf%40> against <about:blank>
+PASS Origin parsing: <http://example.com/你好你好> against <about:blank>
+PASS Origin parsing: <http://example.com/‥/foo> against <about:blank>
+PASS Origin parsing: <http://example.com//foo> against <about:blank>
+PASS Origin parsing: <http://example.com/‮/foo/‭/bar> against <about:blank>
+PASS Origin parsing: <http://www.google.com/foo?bar=baz#> against <about:blank>
+PASS Origin parsing: <http://www.google.com/foo?bar=baz# »> against <about:blank>
+PASS Origin parsing: <data:test# »> against <about:blank>
+PASS Origin parsing: <http://www.google.com> against <about:blank>
+PASS Origin parsing: <http://192.0x00A80001> against <about:blank>
+PASS Origin parsing: <http://www/foo%2Ehtml> against <about:blank>
+PASS Origin parsing: <http://www/foo/%2E/html> against <about:blank>
+PASS Origin parsing: <http://%25DOMAIN:foobar@foodomain.com/> against <about:blank>
+PASS Origin parsing: <http:\\www.google.com\foo> against <about:blank>
+PASS Origin parsing: <http://foo:80/> against <about:blank>
+PASS Origin parsing: <http://foo:81/> against <about:blank>
+PASS Origin parsing: <httpa://foo:80/> against <about:blank>
+PASS Origin parsing: <https://foo:443/> against <about:blank>
+PASS Origin parsing: <https://foo:80/> against <about:blank>
+PASS Origin parsing: <ftp://foo:21/> against <about:blank>
+PASS Origin parsing: <ftp://foo:80/> against <about:blank>
+PASS Origin parsing: <gopher://foo:70/> against <about:blank>
+PASS Origin parsing: <gopher://foo:443/> against <about:blank>
+PASS Origin parsing: <ws://foo:80/> against <about:blank>
+PASS Origin parsing: <ws://foo:81/> against <about:blank>
+PASS Origin parsing: <ws://foo:443/> against <about:blank>
+PASS Origin parsing: <ws://foo:815/> against <about:blank>
+PASS Origin parsing: <wss://foo:80/> against <about:blank>
+PASS Origin parsing: <wss://foo:81/> against <about:blank>
+PASS Origin parsing: <wss://foo:443/> against <about:blank>
+PASS Origin parsing: <wss://foo:815/> against <about:blank>
+PASS Origin parsing: <http:/example.com/> against <about:blank>
+PASS Origin parsing: <ftp:/example.com/> against <about:blank>
+PASS Origin parsing: <https:/example.com/> against <about:blank>
+PASS Origin parsing: <madeupscheme:/example.com/> against <about:blank>
+PASS Origin parsing: <ftps:/example.com/> against <about:blank>
+PASS Origin parsing: <gopher:/example.com/> against <about:blank>
+PASS Origin parsing: <ws:/example.com/> against <about:blank>
+PASS Origin parsing: <wss:/example.com/> against <about:blank>
+PASS Origin parsing: <data:/example.com/> against <about:blank>
+PASS Origin parsing: <javascript:/example.com/> against <about:blank>
+PASS Origin parsing: <mailto:/example.com/> against <about:blank>
+PASS Origin parsing: <http:example.com/> against <about:blank>
+PASS Origin parsing: <ftp:example.com/> against <about:blank>
+PASS Origin parsing: <https:example.com/> against <about:blank>
+PASS Origin parsing: <madeupscheme:example.com/> against <about:blank>
+PASS Origin parsing: <ftps:example.com/> against <about:blank>
+PASS Origin parsing: <gopher:example.com/> against <about:blank>
+PASS Origin parsing: <ws:example.com/> against <about:blank>
+PASS Origin parsing: <wss:example.com/> against <about:blank>
+PASS Origin parsing: <data:example.com/> against <about:blank>
+PASS Origin parsing: <javascript:example.com/> against <about:blank>
+PASS Origin parsing: <mailto:example.com/> against <about:blank>
+PASS Origin parsing: <http:@www.example.com> against <about:blank>
+PASS Origin parsing: <http:/@www.example.com> against <about:blank>
+PASS Origin parsing: <http://@www.example.com> against <about:blank>
+PASS Origin parsing: <http:a:b@www.example.com> against <about:blank>
+PASS Origin parsing: <http:/a:b@www.example.com> against <about:blank>
+PASS Origin parsing: <http://a:b@www.example.com> against <about:blank>
+PASS Origin parsing: <http://@pple.com> against <about:blank>
+PASS Origin parsing: <http::b@www.example.com> against <about:blank>
+PASS Origin parsing: <http:/:b@www.example.com> against <about:blank>
+PASS Origin parsing: <http://:b@www.example.com> against <about:blank>
+PASS Origin parsing: <http:a:@www.example.com> against <about:blank>
+PASS Origin parsing: <http:/a:@www.example.com> against <about:blank>
+PASS Origin parsing: <http://a:@www.example.com> against <about:blank>
+PASS Origin parsing: <http://www.@pple.com> against <about:blank>
+PASS Origin parsing: <http://:@www.example.com> against <about:blank>
+PASS Origin parsing: </> against <http://www.example.com/test>
+PASS Origin parsing: </test.txt> against <http://www.example.com/test>
+PASS Origin parsing: <.> against <http://www.example.com/test>
+PASS Origin parsing: <..> against <http://www.example.com/test>
+PASS Origin parsing: <test.txt> against <http://www.example.com/test>
+PASS Origin parsing: <./test.txt> against <http://www.example.com/test>
+PASS Origin parsing: <../test.txt> against <http://www.example.com/test>
+PASS Origin parsing: <../aaa/test.txt> against <http://www.example.com/test>
+PASS Origin parsing: <../../test.txt> against <http://www.example.com/test>
+PASS Origin parsing: <中/test.txt> against <http://www.example.com/test>
+PASS Origin parsing: <http://www.example2.com> against <http://www.example.com/test>
+PASS Origin parsing: <//www.example2.com> against <http://www.example.com/test>
+PASS Origin parsing: <http://ExAmPlE.CoM> against <http://other.com/>
+PASS Origin parsing: <http://GOO​⁠goo.com> against <http://other.com/>
+PASS Origin parsing: <\0 http://example.com/ \r > against <about:blank>
+PASS Origin parsing: <http://www.foo。bar.com> against <http://other.com/>
+PASS Origin parsing: <https://x/�?�#�> against <about:blank>
+PASS Origin parsing: <http://Go.com> against <http://other.com/>
+PASS Origin parsing: <http://你好你好> against <http://other.com/>
+PASS Origin parsing: <https://faß.ExAmPlE/> against <about:blank>
+PASS Origin parsing: <sc://faß.ExAmPlE/> against <about:blank>
+PASS Origin parsing: <http://%30%78%63%30%2e%30%32%35%30.01> against <http://other.com/>
+PASS Origin parsing: <http://%30%78%63%30%2e%30%32%35%30.01%2e> against <http://other.com/>
+PASS Origin parsing: <http://0Xc0.0250.01> against <http://other.com/>
+PASS Origin parsing: <http://./> against <about:blank>
+PASS Origin parsing: <http://../> against <about:blank>
+PASS Origin parsing: <http://foo:💩@example.com/bar> against <http://other.com/>
+PASS Origin parsing: <#> against <test:test>
+PASS Origin parsing: <#x> against <mailto:x@x.com>
+PASS Origin parsing: <#x> against <data:,>
+PASS Origin parsing: <#x> against <about:blank>
+PASS Origin parsing: <#> against <test:test?test>
+PASS Origin parsing: <https://@test@test@example:800/> against <http://doesnotmatter/>
+PASS Origin parsing: <https://@@@example> against <http://doesnotmatter/>
+PASS Origin parsing: <http://`{}:`{}@h/`{}?`{}> against <http://doesnotmatter/>
+PASS Origin parsing: <http://host/?'> against <about:blank>
+PASS Origin parsing: <notspecial://host/?'> against <about:blank>
+PASS Origin parsing: </some/path> against <http://user@example.org/smth>
+PASS Origin parsing: <> against <http://user:pass@example.org:21/smth>
+PASS Origin parsing: </some/path> against <http://user:pass@example.org:21/smth>
+PASS Origin parsing: <i> against <sc:/pa/pa>
+PASS Origin parsing: <i> against <sc://ho/pa>
+PASS Origin parsing: <i> against <sc:///pa/pa>
+PASS Origin parsing: <../i> against <sc:/pa/pa>
+PASS Origin parsing: <../i> against <sc://ho/pa>
+PASS Origin parsing: <../i> against <sc:///pa/pa>
+PASS Origin parsing: </i> against <sc:/pa/pa>
+PASS Origin parsing: </i> against <sc://ho/pa>
+PASS Origin parsing: </i> against <sc:///pa/pa>
+PASS Origin parsing: <?i> against <sc:/pa/pa>
+PASS Origin parsing: <?i> against <sc://ho/pa>
+PASS Origin parsing: <?i> against <sc:///pa/pa>
+PASS Origin parsing: <#i> against <sc:sd>
+PASS Origin parsing: <#i> against <sc:sd/sd>
+PASS Origin parsing: <#i> against <sc:/pa/pa>
+PASS Origin parsing: <#i> against <sc://ho/pa>
+PASS Origin parsing: <#i> against <sc:///pa/pa>
+PASS Origin parsing: <about:/../> against <about:blank>
+PASS Origin parsing: <data:/../> against <about:blank>
+PASS Origin parsing: <javascript:/../> against <about:blank>
+PASS Origin parsing: <mailto:/../> against <about:blank>
+PASS Origin parsing: <sc://ñ.test/> against <about:blank>
+FAIL Origin parsing: <x> against <sc://ñ> Failed to construct 'URL': Invalid URL
+PASS Origin parsing: <sc:\../> against <about:blank>
+PASS Origin parsing: <sc::a@example.net> against <about:blank>
+PASS Origin parsing: <wow:%NBD> against <about:blank>
+PASS Origin parsing: <wow:%1G> against <about:blank>
+PASS Origin parsing: <wow:￿> against <about:blank>
+FAIL Origin parsing: <http://example.com/U+d800𐟾U+dfff﷐﷏﷯ﷰ￾￿?U+d800𐟾U+dfff﷐﷏﷯ﷰ￾￿> against <about:blank> Failed to construct 'URL': Invalid URL
+FAIL Origin parsing: <http://!"$&'()*+,-.;=_`{}~/> against <about:blank> Failed to construct 'URL': Invalid URL
+PASS Origin parsing: <sc://!"$%&'()*+,-.;=_`{}~/> against <about:blank>
+PASS Origin parsing: <ftp://%e2%98%83> against <about:blank>
+PASS Origin parsing: <https://%e2%98%83> against <about:blank>
+PASS Origin parsing: <http://127.0.0.1:10100/relative_import.html> against <about:blank>
+PASS Origin parsing: <http://facebook.com/?foo=%7B%22abc%22> against <about:blank>
+PASS Origin parsing: <https://localhost:3000/jqueryui@1.2.3> against <about:blank>
+PASS Origin parsing: <h	t
+t\rp://h	o
+s\rt:9	0
+0\r0/p	a
+t\rh?q	u
+e\rry#f	r
+a\rg> against <about:blank>
+PASS Origin parsing: <?a=b&c=d> against <http://example.org/foo/bar>
+PASS Origin parsing: <??a=b&c=d> against <http://example.org/foo/bar>
+PASS Origin parsing: <http:> against <http://example.org/foo/bar>
+PASS Origin parsing: <sc:> against <https://example.org/foo/bar>
+PASS Origin parsing: <http://foo.bar/baz?qux#foobar> against <about:blank>
+PASS Origin parsing: <http://foo.bar/baz?qux#foo"bar> against <about:blank>
+PASS Origin parsing: <http://foo.bar/baz?qux#foo<bar> against <about:blank>
+PASS Origin parsing: <http://foo.bar/baz?qux#foo>bar> against <about:blank>
+PASS Origin parsing: <http://foo.bar/baz?qux#foo`bar> against <about:blank>
+PASS Origin parsing: <http://1.2.3.4/> against <http://other.com/>
+PASS Origin parsing: <http://1.2.3.4./> against <http://other.com/>
+PASS Origin parsing: <http://192.168.257> against <http://other.com/>
+PASS Origin parsing: <http://192.168.257.> against <http://other.com/>
+PASS Origin parsing: <http://192.168.257.com> against <http://other.com/>
+PASS Origin parsing: <http://256> against <http://other.com/>
+PASS Origin parsing: <http://256.com> against <http://other.com/>
+PASS Origin parsing: <http://999999999> against <http://other.com/>
+PASS Origin parsing: <http://999999999.> against <http://other.com/>
+PASS Origin parsing: <http://999999999.com> against <http://other.com/>
+PASS Origin parsing: <http://10000000000.com> against <http://other.com/>
+PASS Origin parsing: <http://4294967295> against <http://other.com/>
+PASS Origin parsing: <http://0xffffffff> against <http://other.com/>
+PASS Origin parsing: <https://0x.0x.0> against <about:blank>
+PASS Origin parsing: <asdf://%43%7C/> against <about:blank>
+PASS Origin parsing: <http://[1:0::]> against <http://example.net/>
+PASS Origin parsing: <sc://ñ> against <about:blank>
+PASS Origin parsing: <sc://ñ?x> against <about:blank>
+PASS Origin parsing: <sc://ñ#x> against <about:blank>
+FAIL Origin parsing: <#x> against <sc://ñ> Failed to construct 'URL': Invalid URL
+FAIL Origin parsing: <?x> against <sc://ñ> Failed to construct 'URL': Invalid URL
+PASS Origin parsing: <tftp://foobar.com/someconfig;mode=netascii> against <about:blank>
+PASS Origin parsing: <telnet://user:pass@foobar.com:23/> against <about:blank>
+PASS Origin parsing: <ut2004://10.10.10.10:7777/Index.ut2> against <about:blank>
+PASS Origin parsing: <redis://foo:bar@somehost:6379/0?baz=bam&qux=baz> against <about:blank>
+PASS Origin parsing: <rsync://foo@host:911/sup> against <about:blank>
+PASS Origin parsing: <git://github.com/foo/bar.git> against <about:blank>
+PASS Origin parsing: <irc://myserver.com:6999/channel?passwd> against <about:blank>
+PASS Origin parsing: <dns://fw.example.org:9999/foo.bar.org?type=TXT> against <about:blank>
+PASS Origin parsing: <ldap://localhost:389/ou=People,o=JNDITutorial> against <about:blank>
+PASS Origin parsing: <git+https://github.com/foo/bar> against <about:blank>
+PASS Origin parsing: <urn:ietf:rfc:2648> against <about:blank>
+PASS Origin parsing: <tag:joe@example.org,2001:foo/bar> against <about:blank>
+PASS Origin parsing: <blob:https://example.com:443/> against <about:blank>
+PASS Origin parsing: <blob:d3958f5c-0777-0845-9dcf-2cb28783acaf> against <about:blank>
+PASS Origin parsing: <blob:> against <about:blank>
+PASS Origin parsing: <non-special:cannot-be-a-base-url-\0~€> against <about:blank>
+PASS Origin parsing: <https://www.example.com/path{path.html?query'=query#fragment<fragment> against <about:blank>
+PASS Origin parsing: <https://user:pass[@foo/bar> against <http://example.org>
+PASS Origin parsing: <foo:// !"$%&'()*+,-.;<=>@[\]^_`{|}~@host/> against <about:blank>
+PASS Origin parsing: <wss:// !"$%&'()*+,-.;<=>@[]^_`{|}~@host/> against <about:blank>
+PASS Origin parsing: <foo://joe: !"$%&'()*+,-.:;<=>@[\]^_`{|}~@host/> against <about:blank>
+PASS Origin parsing: <wss://joe: !"$%&'()*+,-.:;<=>@[]^_`{|}~@host/> against <about:blank>
+PASS Origin parsing: <foo://!"$%&'()*+,-.;=_`{}~/> against <about:blank>
+FAIL Origin parsing: <wss://!"$&'()*+,-.;=_`{}~/> against <about:blank> Failed to construct 'URL': Invalid URL
+PASS Origin parsing: <foo://host/ !"$%&'()*+,-./:;<=>@[\]^_`{|}~> against <about:blank>
+PASS Origin parsing: <wss://host/ !"$%&'()*+,-./:;<=>@[\]^_`{|}~> against <about:blank>
+PASS Origin parsing: <foo://host/dir/? !"$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
+PASS Origin parsing: <wss://host/dir/? !"$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
+PASS Origin parsing: <foo://host/dir/# !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
+PASS Origin parsing: <wss://host/dir/# !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~> against <about:blank>
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-setters-a-area.window-expected.txt b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-setters-a-area.window-expected.txt
new file mode 100644
index 0000000..2cd4f4f
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-setters-a-area.window-expected.txt
@@ -0,0 +1,453 @@
+This is a testharness.js-based test.
+Found 435 tests; 267 PASS, 168 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS Loading data…
+PASS <a>: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged.
+PASS <area>: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged.
+PASS <a>: Setting <a://example.net>.protocol = 'b'
+PASS <area>: Setting <a://example.net>.protocol = 'b'
+PASS <a>: Setting <javascript:alert(1)>.protocol = 'defuse'
+PASS <area>: Setting <javascript:alert(1)>.protocol = 'defuse'
+PASS <a>: Setting <a://example.net>.protocol = 'B' Upper-case ASCII is lower-cased
+PASS <area>: Setting <a://example.net>.protocol = 'B' Upper-case ASCII is lower-cased
+PASS <a>: Setting <a://example.net>.protocol = 'é' Non-ASCII is rejected
+PASS <area>: Setting <a://example.net>.protocol = 'é' Non-ASCII is rejected
+PASS <a>: Setting <a://example.net>.protocol = '0b' No leading digit
+PASS <area>: Setting <a://example.net>.protocol = '0b' No leading digit
+PASS <a>: Setting <a://example.net>.protocol = '+b' No leading punctuation
+PASS <area>: Setting <a://example.net>.protocol = '+b' No leading punctuation
+PASS <a>: Setting <a://example.net>.protocol = 'bC0+-.'
+PASS <area>: Setting <a://example.net>.protocol = 'bC0+-.'
+PASS <a>: Setting <a://example.net>.protocol = 'b,c' Only some punctuation is acceptable
+PASS <area>: Setting <a://example.net>.protocol = 'b,c' Only some punctuation is acceptable
+PASS <a>: Setting <a://example.net>.protocol = 'bé' Non-ASCII is rejected
+PASS <area>: Setting <a://example.net>.protocol = 'bé' Non-ASCII is rejected
+PASS <a>: Setting <http://test@example.net>.protocol = 'file' Can’t switch from URL containing username/password/port to file
+PASS <area>: Setting <http://test@example.net>.protocol = 'file' Can’t switch from URL containing username/password/port to file
+PASS <a>: Setting <https://example.net:1234>.protocol = 'file'
+PASS <area>: Setting <https://example.net:1234>.protocol = 'file'
+PASS <a>: Setting <wss://x:x@example.net:1234>.protocol = 'file'
+PASS <area>: Setting <wss://x:x@example.net:1234>.protocol = 'file'
+FAIL <a>: Setting <file://localhost/>.protocol = 'http' Can’t switch from file URL with no host assert_equals: expected "file:///" but got "http://localhost/"
+FAIL <area>: Setting <file://localhost/>.protocol = 'http' Can’t switch from file URL with no host assert_equals: expected "file:///" but got "http://localhost/"
+PASS <a>: Setting <file:///test>.protocol = 'https'
+PASS <area>: Setting <file:///test>.protocol = 'https'
+PASS <a>: Setting <file:>.protocol = 'wss'
+PASS <area>: Setting <file:>.protocol = 'wss'
+FAIL <a>: Setting <http://example.net>.protocol = 'b' Can’t switch from special scheme to non-special assert_equals: expected "http://example.net/" but got "b://example.net/"
+FAIL <area>: Setting <http://example.net>.protocol = 'b' Can’t switch from special scheme to non-special assert_equals: expected "http://example.net/" but got "b://example.net/"
+FAIL <a>: Setting <file://hi/path>.protocol = 's' assert_equals: expected "file://hi/path" but got "s://hi/path"
+FAIL <area>: Setting <file://hi/path>.protocol = 's' assert_equals: expected "file://hi/path" but got "s://hi/path"
+FAIL <a>: Setting <https://example.net>.protocol = 's' assert_equals: expected "https://example.net/" but got "s://example.net/"
+FAIL <area>: Setting <https://example.net>.protocol = 's' assert_equals: expected "https://example.net/" but got "s://example.net/"
+FAIL <a>: Setting <ftp://example.net>.protocol = 'test' assert_equals: expected "ftp://example.net/" but got "test://example.net/"
+FAIL <area>: Setting <ftp://example.net>.protocol = 'test' assert_equals: expected "ftp://example.net/" but got "test://example.net/"
+FAIL <a>: Setting <mailto:me@example.net>.protocol = 'http' Cannot-be-a-base URL doesn’t have a host, but URL in a special scheme must. assert_equals: expected "mailto:me@example.net" but got "http://me@example.net/"
+FAIL <area>: Setting <mailto:me@example.net>.protocol = 'http' Cannot-be-a-base URL doesn’t have a host, but URL in a special scheme must. assert_equals: expected "mailto:me@example.net" but got "http://me@example.net/"
+FAIL <a>: Setting <ssh://me@example.net>.protocol = 'http' Can’t switch from non-special scheme to special assert_equals: expected "ssh://me@example.net" but got "http://me@example.net/"
+FAIL <area>: Setting <ssh://me@example.net>.protocol = 'http' Can’t switch from non-special scheme to special assert_equals: expected "ssh://me@example.net" but got "http://me@example.net/"
+FAIL <a>: Setting <ssh://me@example.net>.protocol = 'https' assert_equals: expected "ssh://me@example.net" but got "https://me@example.net/"
+FAIL <area>: Setting <ssh://me@example.net>.protocol = 'https' assert_equals: expected "ssh://me@example.net" but got "https://me@example.net/"
+FAIL <a>: Setting <ssh://me@example.net>.protocol = 'file' assert_equals: expected "ssh://me@example.net" but got "file://me%40example.net/"
+FAIL <area>: Setting <ssh://me@example.net>.protocol = 'file' assert_equals: expected "ssh://me@example.net" but got "file://me%40example.net/"
+FAIL <a>: Setting <ssh://example.net>.protocol = 'file' assert_equals: expected "ssh://example.net" but got "file://example.net/"
+FAIL <area>: Setting <ssh://example.net>.protocol = 'file' assert_equals: expected "ssh://example.net" but got "file://example.net/"
+FAIL <a>: Setting <nonsense:///test>.protocol = 'https' assert_equals: expected "nonsense:///test" but got "https://test/"
+FAIL <area>: Setting <nonsense:///test>.protocol = 'https' assert_equals: expected "nonsense:///test" but got "https://test/"
+PASS <a>: Setting <http://example.net>.protocol = 'https:foo : bar' Stuff after the first ':' is ignored
+PASS <area>: Setting <http://example.net>.protocol = 'https:foo : bar' Stuff after the first ':' is ignored
+PASS <a>: Setting <data:text/html,<p>Test>.protocol = 'view-source+data:foo : bar' Stuff after the first ':' is ignored
+PASS <area>: Setting <data:text/html,<p>Test>.protocol = 'view-source+data:foo : bar' Stuff after the first ':' is ignored
+PASS <a>: Setting <http://foo.com:443/>.protocol = 'https' Port is set to null if it is the default for new scheme.
+PASS <area>: Setting <http://foo.com:443/>.protocol = 'https' Port is set to null if it is the default for new scheme.
+PASS <a>: Setting <file:///home/you/index.html>.username = 'me' No host means no username
+PASS <area>: Setting <file:///home/you/index.html>.username = 'me' No host means no username
+PASS <a>: Setting <unix:/run/foo.socket>.username = 'me' No host means no username
+PASS <area>: Setting <unix:/run/foo.socket>.username = 'me' No host means no username
+PASS <a>: Setting <mailto:you@example.net>.username = 'me' Cannot-be-a-base means no username
+PASS <area>: Setting <mailto:you@example.net>.username = 'me' Cannot-be-a-base means no username
+PASS <a>: Setting <javascript:alert(1)>.username = 'wario'
+PASS <area>: Setting <javascript:alert(1)>.username = 'wario'
+PASS <a>: Setting <http://example.net>.username = 'me'
+PASS <area>: Setting <http://example.net>.username = 'me'
+PASS <a>: Setting <http://:secret@example.net>.username = 'me'
+PASS <area>: Setting <http://:secret@example.net>.username = 'me'
+PASS <a>: Setting <http://me@example.net>.username = ''
+PASS <area>: Setting <http://me@example.net>.username = ''
+PASS <a>: Setting <http://me:secret@example.net>.username = ''
+PASS <area>: Setting <http://me:secret@example.net>.username = ''
+FAIL <a>: Setting <http://example.net>.username = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the userinfo encode set. assert_equals: expected "http://%00%01%09%0A%0D%1F%20!%22%23$%&'()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/" but got "http://%00%01%09%0A%0D%1F%20!%22%23$%&%27()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/"
+FAIL <area>: Setting <http://example.net>.username = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the userinfo encode set. assert_equals: expected "http://%00%01%09%0A%0D%1F%20!%22%23$%&'()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/" but got "http://%00%01%09%0A%0D%1F%20!%22%23$%&%27()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/"
+PASS <a>: Setting <http://example.net>.username = '%c3%89té' Bytes already percent-encoded are left as-is.
+PASS <area>: Setting <http://example.net>.username = '%c3%89té' Bytes already percent-encoded are left as-is.
+PASS <a>: Setting <sc:///>.username = 'x'
+PASS <area>: Setting <sc:///>.username = 'x'
+FAIL <a>: Setting <javascript://x/>.username = 'wario' assert_equals: expected "javascript://wario@x/" but got "javascript://x/"
+FAIL <area>: Setting <javascript://x/>.username = 'wario' assert_equals: expected "javascript://wario@x/" but got "javascript://x/"
+PASS <a>: Setting <file://test/>.username = 'test'
+PASS <area>: Setting <file://test/>.username = 'test'
+PASS <a>: Setting <file:///home/me/index.html>.password = 'secret' No host means no password
+PASS <area>: Setting <file:///home/me/index.html>.password = 'secret' No host means no password
+PASS <a>: Setting <unix:/run/foo.socket>.password = 'secret' No host means no password
+PASS <area>: Setting <unix:/run/foo.socket>.password = 'secret' No host means no password
+PASS <a>: Setting <mailto:me@example.net>.password = 'secret' Cannot-be-a-base means no password
+PASS <area>: Setting <mailto:me@example.net>.password = 'secret' Cannot-be-a-base means no password
+PASS <a>: Setting <http://example.net>.password = 'secret'
+PASS <area>: Setting <http://example.net>.password = 'secret'
+PASS <a>: Setting <http://me@example.net>.password = 'secret'
+PASS <area>: Setting <http://me@example.net>.password = 'secret'
+PASS <a>: Setting <http://:secret@example.net>.password = ''
+PASS <area>: Setting <http://:secret@example.net>.password = ''
+PASS <a>: Setting <http://me:secret@example.net>.password = ''
+PASS <area>: Setting <http://me:secret@example.net>.password = ''
+FAIL <a>: Setting <http://example.net>.password = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the userinfo encode set. assert_equals: expected "http://:%00%01%09%0A%0D%1F%20!%22%23$%&'()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/" but got "http://:%00%01%09%0A%0D%1F%20!%22%23$%&%27()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/"
+FAIL <area>: Setting <http://example.net>.password = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the userinfo encode set. assert_equals: expected "http://:%00%01%09%0A%0D%1F%20!%22%23$%&'()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/" but got "http://:%00%01%09%0A%0D%1F%20!%22%23$%&%27()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/"
+PASS <a>: Setting <http://example.net>.password = '%c3%89té' Bytes already percent-encoded are left as-is.
+PASS <area>: Setting <http://example.net>.password = '%c3%89té' Bytes already percent-encoded are left as-is.
+PASS <a>: Setting <sc:///>.password = 'x'
+PASS <area>: Setting <sc:///>.password = 'x'
+FAIL <a>: Setting <javascript://x/>.password = 'bowser' assert_equals: expected "javascript://:bowser@x/" but got "javascript://x/"
+FAIL <area>: Setting <javascript://x/>.password = 'bowser' assert_equals: expected "javascript://:bowser@x/" but got "javascript://x/"
+PASS <a>: Setting <file://test/>.password = 'test'
+PASS <area>: Setting <file://test/>.password = 'test'
+FAIL <a>: Setting <sc://x/>.host = '\0' Non-special scheme assert_equals: expected "x" but got ""
+FAIL <area>: Setting <sc://x/>.host = '\0' Non-special scheme assert_equals: expected "x" but got ""
+FAIL <a>: Setting <sc://x/>.host = '	' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <area>: Setting <sc://x/>.host = '	' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <a>: Setting <sc://x/>.host = '
+' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <area>: Setting <sc://x/>.host = '
+' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <a>: Setting <sc://x/>.host = '\r' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <area>: Setting <sc://x/>.host = '\r' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <a>: Setting <sc://x/>.host = ' ' assert_equals: expected "x" but got ""
+FAIL <area>: Setting <sc://x/>.host = ' ' assert_equals: expected "x" but got ""
+FAIL <a>: Setting <sc://x/>.host = '#' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <area>: Setting <sc://x/>.host = '#' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <a>: Setting <sc://x/>.host = '/' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <area>: Setting <sc://x/>.host = '/' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <a>: Setting <sc://x/>.host = '?' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <area>: Setting <sc://x/>.host = '?' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <a>: Setting <sc://x/>.host = '@' assert_equals: expected "x" but got ""
+FAIL <area>: Setting <sc://x/>.host = '@' assert_equals: expected "x" but got ""
+FAIL <a>: Setting <sc://x/>.host = 'ß' assert_equals: expected "sc://%C3%9F/" but got "sc://x/"
+FAIL <area>: Setting <sc://x/>.host = 'ß' assert_equals: expected "sc://%C3%9F/" but got "sc://x/"
+PASS <a>: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing
+PASS <area>: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing
+PASS <a>: Setting <mailto:me@example.net>.host = 'example.com' Cannot-be-a-base means no host
+PASS <area>: Setting <mailto:me@example.net>.host = 'example.com' Cannot-be-a-base means no host
+PASS <a>: Setting <data:text/plain,Stuff>.host = 'example.net' Cannot-be-a-base means no host
+PASS <area>: Setting <data:text/plain,Stuff>.host = 'example.net' Cannot-be-a-base means no host
+PASS <a>: Setting <http://example.net>.host = 'example.com:8080'
+PASS <area>: Setting <http://example.net>.host = 'example.com:8080'
+PASS <a>: Setting <http://example.net:8080>.host = 'example.com' Port number is unchanged if not specified in the new value
+PASS <area>: Setting <http://example.net:8080>.host = 'example.com' Port number is unchanged if not specified in the new value
+PASS <a>: Setting <http://example.net:8080>.host = 'example.com:' Port number is unchanged if not specified
+PASS <area>: Setting <http://example.net:8080>.host = 'example.com:' Port number is unchanged if not specified
+PASS <a>: Setting <http://example.net>.host = '' The empty host is not valid for special schemes
+PASS <area>: Setting <http://example.net>.host = '' The empty host is not valid for special schemes
+FAIL <a>: Setting <view-source+http://example.net/foo>.host = '' The empty host is OK for non-special schemes assert_equals: expected "view-source+http:///foo" but got "view-source+http://example.net/foo"
+FAIL <area>: Setting <view-source+http://example.net/foo>.host = '' The empty host is OK for non-special schemes assert_equals: expected "view-source+http:///foo" but got "view-source+http://example.net/foo"
+FAIL <a>: Setting <a:/foo>.host = 'example.net' Path-only URLs can gain a host assert_equals: expected "a://example.net/foo" but got "a:/foo"
+FAIL <area>: Setting <a:/foo>.host = 'example.net' Path-only URLs can gain a host assert_equals: expected "a://example.net/foo" but got "a:/foo"
+PASS <a>: Setting <http://example.net>.host = '0x7F000001:8080' IPv4 address syntax is normalized
+PASS <area>: Setting <http://example.net>.host = '0x7F000001:8080' IPv4 address syntax is normalized
+PASS <a>: Setting <http://example.net>.host = '[::0:01]:2' IPv6 address syntax is normalized
+PASS <area>: Setting <http://example.net>.host = '[::0:01]:2' IPv6 address syntax is normalized
+PASS <a>: Setting <http://example.net>.host = '[2001:db8::2]:4002' IPv6 literal address with port, crbug.com/1012416
+PASS <area>: Setting <http://example.net>.host = '[2001:db8::2]:4002' IPv6 literal address with port, crbug.com/1012416
+PASS <a>: Setting <http://example.net>.host = 'example.com:80' Default port number is removed
+PASS <area>: Setting <http://example.net>.host = 'example.com:80' Default port number is removed
+PASS <a>: Setting <https://example.net>.host = 'example.com:443' Default port number is removed
+PASS <area>: Setting <https://example.net>.host = 'example.com:443' Default port number is removed
+PASS <a>: Setting <https://example.net>.host = 'example.com:80' Default port number is only removed for the relevant scheme
+PASS <area>: Setting <https://example.net>.host = 'example.com:80' Default port number is only removed for the relevant scheme
+PASS <a>: Setting <http://example.net:8080>.host = 'example.com:80' Port number is removed if new port is scheme default and existing URL has a non-default port
+PASS <area>: Setting <http://example.net:8080>.host = 'example.com:80' Port number is removed if new port is scheme default and existing URL has a non-default port
+PASS <a>: Setting <http://example.net/path>.host = 'example.com/stuff' Stuff after a / delimiter is ignored
+PASS <area>: Setting <http://example.net/path>.host = 'example.com/stuff' Stuff after a / delimiter is ignored
+PASS <a>: Setting <http://example.net/path>.host = 'example.com:8080/stuff' Stuff after a / delimiter is ignored
+PASS <area>: Setting <http://example.net/path>.host = 'example.com:8080/stuff' Stuff after a / delimiter is ignored
+PASS <a>: Setting <http://example.net/path>.host = 'example.com?stuff' Stuff after a ? delimiter is ignored
+PASS <area>: Setting <http://example.net/path>.host = 'example.com?stuff' Stuff after a ? delimiter is ignored
+PASS <a>: Setting <http://example.net/path>.host = 'example.com:8080?stuff' Stuff after a ? delimiter is ignored
+PASS <area>: Setting <http://example.net/path>.host = 'example.com:8080?stuff' Stuff after a ? delimiter is ignored
+PASS <a>: Setting <http://example.net/path>.host = 'example.com#stuff' Stuff after a # delimiter is ignored
+PASS <area>: Setting <http://example.net/path>.host = 'example.com#stuff' Stuff after a # delimiter is ignored
+PASS <a>: Setting <http://example.net/path>.host = 'example.com:8080#stuff' Stuff after a # delimiter is ignored
+PASS <area>: Setting <http://example.net/path>.host = 'example.com:8080#stuff' Stuff after a # delimiter is ignored
+PASS <a>: Setting <http://example.net/path>.host = 'example.com\stuff' Stuff after a \ delimiter is ignored for special schemes
+PASS <area>: Setting <http://example.net/path>.host = 'example.com\stuff' Stuff after a \ delimiter is ignored for special schemes
+PASS <a>: Setting <http://example.net/path>.host = 'example.com:8080\stuff' Stuff after a \ delimiter is ignored for special schemes
+PASS <area>: Setting <http://example.net/path>.host = 'example.com:8080\stuff' Stuff after a \ delimiter is ignored for special schemes
+FAIL <a>: Setting <view-source+http://example.net/path>.host = 'example.com\stuff' \ is not a delimiter for non-special schemes, but still forbidden in hosts assert_equals: expected "example.net" but got ""
+FAIL <area>: Setting <view-source+http://example.net/path>.host = 'example.com\stuff' \ is not a delimiter for non-special schemes, but still forbidden in hosts assert_equals: expected "example.net" but got ""
+FAIL <a>: Setting <view-source+http://example.net/path>.host = 'example.com:8080stuff2' Anything other than ASCII digit stops the port parser in a setter but is not an error assert_equals: expected "view-source+http://example.com:8080/path" but got "view-source+http://example.net/path"
+FAIL <area>: Setting <view-source+http://example.net/path>.host = 'example.com:8080stuff2' Anything other than ASCII digit stops the port parser in a setter but is not an error assert_equals: expected "view-source+http://example.com:8080/path" but got "view-source+http://example.net/path"
+PASS <a>: Setting <http://example.net/path>.host = 'example.com:8080stuff2' Anything other than ASCII digit stops the port parser in a setter but is not an error
+PASS <area>: Setting <http://example.net/path>.host = 'example.com:8080stuff2' Anything other than ASCII digit stops the port parser in a setter but is not an error
+PASS <a>: Setting <http://example.net/path>.host = 'example.com:8080+2' Anything other than ASCII digit stops the port parser in a setter but is not an error
+PASS <area>: Setting <http://example.net/path>.host = 'example.com:8080+2' Anything other than ASCII digit stops the port parser in a setter but is not an error
+PASS <a>: Setting <http://example.net/path>.host = 'example.com:65535' Port numbers are 16 bit integers
+PASS <area>: Setting <http://example.net/path>.host = 'example.com:65535' Port numbers are 16 bit integers
+PASS <a>: Setting <http://example.net/path>.host = 'example.com:65536' Port numbers are 16 bit integers, overflowing is an error. Hostname is still set, though.
+PASS <area>: Setting <http://example.net/path>.host = 'example.com:65536' Port numbers are 16 bit integers, overflowing is an error. Hostname is still set, though.
+PASS <a>: Setting <http://example.net/>.host = '[google.com]' Broken IPv6
+PASS <area>: Setting <http://example.net/>.host = '[google.com]' Broken IPv6
+PASS <a>: Setting <http://example.net/>.host = '[::1.2.3.4x]'
+PASS <area>: Setting <http://example.net/>.host = '[::1.2.3.4x]'
+FAIL <a>: Setting <http://example.net/>.host = '[::1.2.3.]' assert_equals: expected "http://example.net/" but got "http://[::102:3]/"
+FAIL <area>: Setting <http://example.net/>.host = '[::1.2.3.]' assert_equals: expected "http://example.net/" but got "http://[::102:3]/"
+FAIL <a>: Setting <http://example.net/>.host = '[::1.2.]' assert_equals: expected "http://example.net/" but got "http://[::100:2]/"
+FAIL <area>: Setting <http://example.net/>.host = '[::1.2.]' assert_equals: expected "http://example.net/" but got "http://[::100:2]/"
+FAIL <a>: Setting <http://example.net/>.host = '[::1.]' assert_equals: expected "http://example.net/" but got "http://[::1]/"
+FAIL <area>: Setting <http://example.net/>.host = '[::1.]' assert_equals: expected "http://example.net/" but got "http://[::1]/"
+FAIL <a>: Setting <file://y/>.host = 'x:123' assert_equals: expected "file://y/" but got "file://x/"
+FAIL <area>: Setting <file://y/>.host = 'x:123' assert_equals: expected "file://y/" but got "file://x/"
+FAIL <a>: Setting <file://y/>.host = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/"
+FAIL <area>: Setting <file://y/>.host = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/"
+FAIL <a>: Setting <file://hi/x>.host = '' assert_equals: expected "file:///x" but got "file://hi/x"
+FAIL <area>: Setting <file://hi/x>.host = '' assert_equals: expected "file:///x" but got "file://hi/x"
+FAIL <a>: Setting <sc://test@test/>.host = '' assert_equals: expected "test" but got ""
+FAIL <area>: Setting <sc://test@test/>.host = '' assert_equals: expected "test" but got ""
+FAIL <a>: Setting <sc://test:12/>.host = '' assert_equals: expected "test:12" but got ""
+FAIL <area>: Setting <sc://test:12/>.host = '' assert_equals: expected "test:12" but got ""
+PASS <a>: Setting <http://example.com/>.host = '///bad.com' Leading / is not stripped
+PASS <area>: Setting <http://example.com/>.host = '///bad.com' Leading / is not stripped
+FAIL <a>: Setting <sc://example.com/>.host = '///bad.com' Leading / is not stripped assert_equals: expected "sc:///" but got "sc://example.com/"
+FAIL <area>: Setting <sc://example.com/>.host = '///bad.com' Leading / is not stripped assert_equals: expected "sc:///" but got "sc://example.com/"
+FAIL <a>: Setting <sc://x/>.hostname = '\0' Non-special scheme assert_equals: expected "x" but got ""
+FAIL <area>: Setting <sc://x/>.hostname = '\0' Non-special scheme assert_equals: expected "x" but got ""
+FAIL <a>: Setting <sc://x/>.hostname = '	' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <area>: Setting <sc://x/>.hostname = '	' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <a>: Setting <sc://x/>.hostname = '
+' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <area>: Setting <sc://x/>.hostname = '
+' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <a>: Setting <sc://x/>.hostname = '\r' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <area>: Setting <sc://x/>.hostname = '\r' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <a>: Setting <sc://x/>.hostname = ' ' assert_equals: expected "x" but got ""
+FAIL <area>: Setting <sc://x/>.hostname = ' ' assert_equals: expected "x" but got ""
+FAIL <a>: Setting <sc://x/>.hostname = '#' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <area>: Setting <sc://x/>.hostname = '#' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <a>: Setting <sc://x/>.hostname = '/' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <area>: Setting <sc://x/>.hostname = '/' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <a>: Setting <sc://x/>.hostname = '?' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <area>: Setting <sc://x/>.hostname = '?' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <a>: Setting <sc://x/>.hostname = '@' assert_equals: expected "x" but got ""
+FAIL <area>: Setting <sc://x/>.hostname = '@' assert_equals: expected "x" but got ""
+PASS <a>: Setting <mailto:me@example.net>.hostname = 'example.com' Cannot-be-a-base means no host
+PASS <area>: Setting <mailto:me@example.net>.hostname = 'example.com' Cannot-be-a-base means no host
+PASS <a>: Setting <data:text/plain,Stuff>.hostname = 'example.net' Cannot-be-a-base means no host
+PASS <area>: Setting <data:text/plain,Stuff>.hostname = 'example.net' Cannot-be-a-base means no host
+PASS <a>: Setting <http://example.net:8080>.hostname = 'example.com'
+PASS <area>: Setting <http://example.net:8080>.hostname = 'example.com'
+PASS <a>: Setting <http://example.net>.hostname = '' The empty host is not valid for special schemes
+PASS <area>: Setting <http://example.net>.hostname = '' The empty host is not valid for special schemes
+FAIL <a>: Setting <view-source+http://example.net/foo>.hostname = '' The empty host is OK for non-special schemes assert_equals: expected "view-source+http:///foo" but got "view-source+http://example.net/foo"
+FAIL <area>: Setting <view-source+http://example.net/foo>.hostname = '' The empty host is OK for non-special schemes assert_equals: expected "view-source+http:///foo" but got "view-source+http://example.net/foo"
+FAIL <a>: Setting <a:/foo>.hostname = 'example.net' Path-only URLs can gain a host assert_equals: expected "a://example.net/foo" but got "a:/foo"
+FAIL <area>: Setting <a:/foo>.hostname = 'example.net' Path-only URLs can gain a host assert_equals: expected "a://example.net/foo" but got "a:/foo"
+PASS <a>: Setting <http://example.net:8080>.hostname = '0x7F000001' IPv4 address syntax is normalized
+PASS <area>: Setting <http://example.net:8080>.hostname = '0x7F000001' IPv4 address syntax is normalized
+PASS <a>: Setting <http://example.net>.hostname = '[::0:01]' IPv6 address syntax is normalized
+PASS <area>: Setting <http://example.net>.hostname = '[::0:01]' IPv6 address syntax is normalized
+PASS <a>: Setting <http://example.net/path>.hostname = 'example.com:8080' : delimiter invalidates entire value
+PASS <area>: Setting <http://example.net/path>.hostname = 'example.com:8080' : delimiter invalidates entire value
+PASS <a>: Setting <http://example.net:8080/path>.hostname = 'example.com:' : delimiter invalidates entire value
+PASS <area>: Setting <http://example.net:8080/path>.hostname = 'example.com:' : delimiter invalidates entire value
+PASS <a>: Setting <http://example.net/path>.hostname = 'example.com/stuff' Stuff after a / delimiter is ignored
+PASS <area>: Setting <http://example.net/path>.hostname = 'example.com/stuff' Stuff after a / delimiter is ignored
+PASS <a>: Setting <http://example.net/path>.hostname = 'example.com?stuff' Stuff after a ? delimiter is ignored
+PASS <area>: Setting <http://example.net/path>.hostname = 'example.com?stuff' Stuff after a ? delimiter is ignored
+PASS <a>: Setting <http://example.net/path>.hostname = 'example.com#stuff' Stuff after a # delimiter is ignored
+PASS <area>: Setting <http://example.net/path>.hostname = 'example.com#stuff' Stuff after a # delimiter is ignored
+PASS <a>: Setting <http://example.net/path>.hostname = 'example.com\stuff' Stuff after a \ delimiter is ignored for special schemes
+PASS <area>: Setting <http://example.net/path>.hostname = 'example.com\stuff' Stuff after a \ delimiter is ignored for special schemes
+FAIL <a>: Setting <view-source+http://example.net/path>.hostname = 'example.com\stuff' \ is not a delimiter for non-special schemes, but still forbidden in hosts assert_equals: expected "example.net" but got ""
+FAIL <area>: Setting <view-source+http://example.net/path>.hostname = 'example.com\stuff' \ is not a delimiter for non-special schemes, but still forbidden in hosts assert_equals: expected "example.net" but got ""
+PASS <a>: Setting <http://example.net/>.hostname = '[google.com]' Broken IPv6
+PASS <area>: Setting <http://example.net/>.hostname = '[google.com]' Broken IPv6
+PASS <a>: Setting <http://example.net/>.hostname = '[::1.2.3.4x]'
+PASS <area>: Setting <http://example.net/>.hostname = '[::1.2.3.4x]'
+FAIL <a>: Setting <http://example.net/>.hostname = '[::1.2.3.]' assert_equals: expected "http://example.net/" but got "http://[::102:3]/"
+FAIL <area>: Setting <http://example.net/>.hostname = '[::1.2.3.]' assert_equals: expected "http://example.net/" but got "http://[::102:3]/"
+FAIL <a>: Setting <http://example.net/>.hostname = '[::1.2.]' assert_equals: expected "http://example.net/" but got "http://[::100:2]/"
+FAIL <area>: Setting <http://example.net/>.hostname = '[::1.2.]' assert_equals: expected "http://example.net/" but got "http://[::100:2]/"
+FAIL <a>: Setting <http://example.net/>.hostname = '[::1.]' assert_equals: expected "http://example.net/" but got "http://[::1]/"
+FAIL <area>: Setting <http://example.net/>.hostname = '[::1.]' assert_equals: expected "http://example.net/" but got "http://[::1]/"
+PASS <a>: Setting <file://y/>.hostname = 'x:123'
+PASS <area>: Setting <file://y/>.hostname = 'x:123'
+FAIL <a>: Setting <file://y/>.hostname = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/"
+FAIL <area>: Setting <file://y/>.hostname = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/"
+FAIL <a>: Setting <file://hi/x>.hostname = '' assert_equals: expected "file:///x" but got "file://hi/x"
+FAIL <area>: Setting <file://hi/x>.hostname = '' assert_equals: expected "file:///x" but got "file://hi/x"
+FAIL <a>: Setting <sc://test@test/>.hostname = '' assert_equals: expected "test" but got ""
+FAIL <area>: Setting <sc://test@test/>.hostname = '' assert_equals: expected "test" but got ""
+FAIL <a>: Setting <sc://test:12/>.hostname = '' assert_equals: expected "test:12" but got ""
+FAIL <area>: Setting <sc://test:12/>.hostname = '' assert_equals: expected "test:12" but got ""
+FAIL <a>: Setting <non-spec:/.//p>.hostname = 'h' Drop /. from path assert_equals: expected "non-spec://h//p" but got "non-spec:/.//p"
+FAIL <area>: Setting <non-spec:/.//p>.hostname = 'h' Drop /. from path assert_equals: expected "non-spec://h//p" but got "non-spec:/.//p"
+FAIL <a>: Setting <non-spec:/.//p>.hostname = '' assert_equals: expected "non-spec:////p" but got "non-spec:/.//p"
+FAIL <area>: Setting <non-spec:/.//p>.hostname = '' assert_equals: expected "non-spec:////p" but got "non-spec:/.//p"
+PASS <a>: Setting <http://example.com/>.hostname = '///bad.com' Leading / is not stripped
+PASS <area>: Setting <http://example.com/>.hostname = '///bad.com' Leading / is not stripped
+FAIL <a>: Setting <sc://example.com/>.hostname = '///bad.com' Leading / is not stripped assert_equals: expected "sc:///" but got "sc://example.com/"
+FAIL <area>: Setting <sc://example.com/>.hostname = '///bad.com' Leading / is not stripped assert_equals: expected "sc:///" but got "sc://example.com/"
+PASS <a>: Setting <http://example.net>.port = '8080'
+PASS <area>: Setting <http://example.net>.port = '8080'
+PASS <a>: Setting <http://example.net:8080>.port = '' Port number is removed if empty is the new value
+PASS <area>: Setting <http://example.net:8080>.port = '' Port number is removed if empty is the new value
+PASS <a>: Setting <http://example.net:8080>.port = '80' Default port number is removed
+PASS <area>: Setting <http://example.net:8080>.port = '80' Default port number is removed
+PASS <a>: Setting <https://example.net:4433>.port = '443' Default port number is removed
+PASS <area>: Setting <https://example.net:4433>.port = '443' Default port number is removed
+PASS <a>: Setting <https://example.net>.port = '80' Default port number is only removed for the relevant scheme
+PASS <area>: Setting <https://example.net>.port = '80' Default port number is only removed for the relevant scheme
+PASS <a>: Setting <http://example.net/path>.port = '8080/stuff' Stuff after a / delimiter is ignored
+PASS <area>: Setting <http://example.net/path>.port = '8080/stuff' Stuff after a / delimiter is ignored
+PASS <a>: Setting <http://example.net/path>.port = '8080?stuff' Stuff after a ? delimiter is ignored
+PASS <area>: Setting <http://example.net/path>.port = '8080?stuff' Stuff after a ? delimiter is ignored
+PASS <a>: Setting <http://example.net/path>.port = '8080#stuff' Stuff after a # delimiter is ignored
+PASS <area>: Setting <http://example.net/path>.port = '8080#stuff' Stuff after a # delimiter is ignored
+PASS <a>: Setting <http://example.net/path>.port = '8080\stuff' Stuff after a \ delimiter is ignored for special schemes
+PASS <area>: Setting <http://example.net/path>.port = '8080\stuff' Stuff after a \ delimiter is ignored for special schemes
+FAIL <a>: Setting <view-source+http://example.net/path>.port = '8080stuff2' Anything other than ASCII digit stops the port parser in a setter but is not an error assert_equals: expected "view-source+http://example.net:8080/path" but got "view-source+http://example.net/path"
+FAIL <area>: Setting <view-source+http://example.net/path>.port = '8080stuff2' Anything other than ASCII digit stops the port parser in a setter but is not an error assert_equals: expected "view-source+http://example.net:8080/path" but got "view-source+http://example.net/path"
+PASS <a>: Setting <http://example.net/path>.port = '8080stuff2' Anything other than ASCII digit stops the port parser in a setter but is not an error
+PASS <area>: Setting <http://example.net/path>.port = '8080stuff2' Anything other than ASCII digit stops the port parser in a setter but is not an error
+PASS <a>: Setting <http://example.net/path>.port = '8080+2' Anything other than ASCII digit stops the port parser in a setter but is not an error
+PASS <area>: Setting <http://example.net/path>.port = '8080+2' Anything other than ASCII digit stops the port parser in a setter but is not an error
+PASS <a>: Setting <http://example.net/path>.port = '65535' Port numbers are 16 bit integers
+PASS <area>: Setting <http://example.net/path>.port = '65535' Port numbers are 16 bit integers
+FAIL <a>: Setting <http://example.net:8080/path>.port = '65536' Port numbers are 16 bit integers, overflowing is an error assert_equals: expected "http://example.net:8080/path" but got "http://example.net:0/path"
+FAIL <area>: Setting <http://example.net:8080/path>.port = '65536' Port numbers are 16 bit integers, overflowing is an error assert_equals: expected "http://example.net:8080/path" but got "http://example.net:0/path"
+FAIL <a>: Setting <non-special://example.net:8080/path>.port = '65536' Port numbers are 16 bit integers, overflowing is an error assert_equals: expected "example.net:8080" but got ""
+FAIL <area>: Setting <non-special://example.net:8080/path>.port = '65536' Port numbers are 16 bit integers, overflowing is an error assert_equals: expected "example.net:8080" but got ""
+PASS <a>: Setting <file://test/>.port = '12'
+PASS <area>: Setting <file://test/>.port = '12'
+FAIL <a>: Setting <file://localhost/>.port = '12' assert_equals: expected "file:///" but got "file://localhost/"
+FAIL <area>: Setting <file://localhost/>.port = '12' assert_equals: expected "file:///" but got "file://localhost/"
+PASS <a>: Setting <non-base:value>.port = '12'
+PASS <area>: Setting <non-base:value>.port = '12'
+PASS <a>: Setting <sc:///>.port = '12'
+PASS <area>: Setting <sc:///>.port = '12'
+FAIL <a>: Setting <sc://x/>.port = '12' assert_equals: expected "sc://x:12/" but got "sc://x/"
+FAIL <area>: Setting <sc://x/>.port = '12' assert_equals: expected "sc://x:12/" but got "sc://x/"
+FAIL <a>: Setting <javascript://x/>.port = '12' assert_equals: expected "javascript://x:12/" but got "javascript://x/"
+FAIL <area>: Setting <javascript://x/>.port = '12' assert_equals: expected "javascript://x:12/" but got "javascript://x/"
+PASS <a>: Setting <mailto:me@example.net>.pathname = '/foo' Cannot-be-a-base don’t have a path
+PASS <area>: Setting <mailto:me@example.net>.pathname = '/foo' Cannot-be-a-base don’t have a path
+PASS <a>: Setting <file:///some/path>.pathname = '' Special URLs cannot have their paths erased
+PASS <area>: Setting <file:///some/path>.pathname = '' Special URLs cannot have their paths erased
+FAIL <a>: Setting <foo://somehost/some/path>.pathname = '' Non-special URLs can have their paths erased assert_equals: expected "foo://somehost" but got "foo://somehost/some/path"
+FAIL <area>: Setting <foo://somehost/some/path>.pathname = '' Non-special URLs can have their paths erased assert_equals: expected "foo://somehost" but got "foo://somehost/some/path"
+FAIL <a>: Setting <foo:///some/path>.pathname = '' Non-special URLs with an empty host can have their paths erased assert_equals: expected "foo://" but got "foo:///some/path"
+FAIL <area>: Setting <foo:///some/path>.pathname = '' Non-special URLs with an empty host can have their paths erased assert_equals: expected "foo://" but got "foo:///some/path"
+FAIL <a>: Setting <foo:/some/path>.pathname = '' Path-only URLs cannot have their paths erased assert_equals: expected "foo:/" but got "foo:/some/path"
+FAIL <area>: Setting <foo:/some/path>.pathname = '' Path-only URLs cannot have their paths erased assert_equals: expected "foo:/" but got "foo:/some/path"
+FAIL <a>: Setting <foo:/some/path>.pathname = 'test' Path-only URLs always have an initial slash assert_equals: expected "foo:/test" but got "foo:/some/path"
+FAIL <area>: Setting <foo:/some/path>.pathname = 'test' Path-only URLs always have an initial slash assert_equals: expected "foo:/test" but got "foo:/some/path"
+FAIL <a>: Setting <unix:/run/foo.socket?timeout=10>.pathname = '/var/log/../run/bar.socket' assert_equals: expected "unix:/var/run/bar.socket?timeout=10" but got "unix:/run/foo.socket?timeout=10"
+FAIL <area>: Setting <unix:/run/foo.socket?timeout=10>.pathname = '/var/log/../run/bar.socket' assert_equals: expected "unix:/var/run/bar.socket?timeout=10" but got "unix:/run/foo.socket?timeout=10"
+PASS <a>: Setting <https://example.net#nav>.pathname = 'home'
+PASS <area>: Setting <https://example.net#nav>.pathname = 'home'
+PASS <a>: Setting <https://example.net#nav>.pathname = '../home'
+PASS <area>: Setting <https://example.net#nav>.pathname = '../home'
+PASS <a>: Setting <http://example.net/home?lang=fr#nav>.pathname = '\a\%2E\b\%2e.\c' \ is a segment delimiter for 'special' URLs
+PASS <area>: Setting <http://example.net/home?lang=fr#nav>.pathname = '\a\%2E\b\%2e.\c' \ is a segment delimiter for 'special' URLs
+FAIL <a>: Setting <view-source+http://example.net/home?lang=fr#nav>.pathname = '\a\%2E\b\%2e.\c' \ is *not* a segment delimiter for non-'special' URLs assert_equals: expected "view-source+http://example.net/\\a\\%2E\\b\\%2e.\\c?lang=fr#nav" but got "view-source+http://example.net/home?lang=fr#nav"
+FAIL <area>: Setting <view-source+http://example.net/home?lang=fr#nav>.pathname = '\a\%2E\b\%2e.\c' \ is *not* a segment delimiter for non-'special' URLs assert_equals: expected "view-source+http://example.net/\\a\\%2E\\b\\%2e.\\c?lang=fr#nav" but got "view-source+http://example.net/home?lang=fr#nav"
+FAIL <a>: Setting <a:/>.pathname = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the default encode set. Tabs and newlines are removed. assert_equals: expected "a:/%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E%3F@AZ[\\]^_%60az%7B|%7D~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/"
+FAIL <area>: Setting <a:/>.pathname = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the default encode set. Tabs and newlines are removed. assert_equals: expected "a:/%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E%3F@AZ[\\]^_%60az%7B|%7D~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/"
+FAIL <a>: Setting <http://example.net>.pathname = '%2e%2E%c3%89té' Bytes already percent-encoded are left as-is, including %2E outside dotted segments. assert_equals: expected "http://example.net/%2e%2E%c3%89t%C3%A9" but got "http://example.net/..%c3%89t%C3%A9"
+FAIL <area>: Setting <http://example.net>.pathname = '%2e%2E%c3%89té' Bytes already percent-encoded are left as-is, including %2E outside dotted segments. assert_equals: expected "http://example.net/%2e%2E%c3%89t%C3%A9" but got "http://example.net/..%c3%89t%C3%A9"
+PASS <a>: Setting <http://example.net>.pathname = '?' ? needs to be encoded
+PASS <area>: Setting <http://example.net>.pathname = '?' ? needs to be encoded
+PASS <a>: Setting <http://example.net>.pathname = '#' # needs to be encoded
+PASS <area>: Setting <http://example.net>.pathname = '#' # needs to be encoded
+FAIL <a>: Setting <sc://example.net>.pathname = '?' ? needs to be encoded, non-special scheme assert_equals: expected "sc://example.net/%3F" but got "sc://example.net"
+FAIL <area>: Setting <sc://example.net>.pathname = '?' ? needs to be encoded, non-special scheme assert_equals: expected "sc://example.net/%3F" but got "sc://example.net"
+FAIL <a>: Setting <sc://example.net>.pathname = '#' # needs to be encoded, non-special scheme assert_equals: expected "sc://example.net/%23" but got "sc://example.net"
+FAIL <area>: Setting <sc://example.net>.pathname = '#' # needs to be encoded, non-special scheme assert_equals: expected "sc://example.net/%23" but got "sc://example.net"
+PASS <a>: Setting <http://example.net>.pathname = '/?é' ? doesn't mess up encoding
+PASS <area>: Setting <http://example.net>.pathname = '/?é' ? doesn't mess up encoding
+PASS <a>: Setting <http://example.net>.pathname = '/#é' # doesn't mess up encoding
+PASS <area>: Setting <http://example.net>.pathname = '/#é' # doesn't mess up encoding
+PASS <a>: Setting <file://monkey/>.pathname = '\\' File URLs and (back)slashes
+PASS <area>: Setting <file://monkey/>.pathname = '\\' File URLs and (back)slashes
+FAIL <a>: Setting <file:///unicorn>.pathname = '//\/' File URLs and (back)slashes assert_equals: expected "file://////" but got "file:///"
+FAIL <area>: Setting <file:///unicorn>.pathname = '//\/' File URLs and (back)slashes assert_equals: expected "file://////" but got "file:///"
+FAIL <a>: Setting <file:///unicorn>.pathname = '//monkey/..//' File URLs and (back)slashes assert_equals: expected "file://///" but got "file:///"
+FAIL <area>: Setting <file:///unicorn>.pathname = '//monkey/..//' File URLs and (back)slashes assert_equals: expected "file://///" but got "file:///"
+FAIL <a>: Setting <non-spec:/>.pathname = '/.//p' Serialize /. in path assert_equals: expected "non-spec:/.//p" but got "non-spec:/"
+FAIL <area>: Setting <non-spec:/>.pathname = '/.//p' Serialize /. in path assert_equals: expected "non-spec:/.//p" but got "non-spec:/"
+FAIL <a>: Setting <non-spec:/>.pathname = '/..//p' assert_equals: expected "non-spec:/.//p" but got "non-spec:/"
+FAIL <area>: Setting <non-spec:/>.pathname = '/..//p' assert_equals: expected "non-spec:/.//p" but got "non-spec:/"
+FAIL <a>: Setting <non-spec:/>.pathname = '//p' assert_equals: expected "non-spec:/.//p" but got "non-spec:/"
+FAIL <area>: Setting <non-spec:/>.pathname = '//p' assert_equals: expected "non-spec:/.//p" but got "non-spec:/"
+FAIL <a>: Setting <non-spec:/.//>.pathname = 'p' Drop /. from path assert_equals: expected "non-spec:/p" but got "non-spec:/.//"
+FAIL <area>: Setting <non-spec:/.//>.pathname = 'p' Drop /. from path assert_equals: expected "non-spec:/p" but got "non-spec:/.//"
+PASS <a>: Setting <https://example.net#nav>.search = 'lang=fr'
+PASS <area>: Setting <https://example.net#nav>.search = 'lang=fr'
+PASS <a>: Setting <https://example.net?lang=en-US#nav>.search = 'lang=fr'
+PASS <area>: Setting <https://example.net?lang=en-US#nav>.search = 'lang=fr'
+PASS <a>: Setting <https://example.net?lang=en-US#nav>.search = '?lang=fr'
+PASS <area>: Setting <https://example.net?lang=en-US#nav>.search = '?lang=fr'
+PASS <a>: Setting <https://example.net?lang=en-US#nav>.search = '??lang=fr'
+PASS <area>: Setting <https://example.net?lang=en-US#nav>.search = '??lang=fr'
+FAIL <a>: Setting <https://example.net?lang=en-US#nav>.search = '?' assert_equals: expected "https://example.net/?#nav" but got "https://example.net/#nav"
+FAIL <area>: Setting <https://example.net?lang=en-US#nav>.search = '?' assert_equals: expected "https://example.net/?#nav" but got "https://example.net/#nav"
+PASS <a>: Setting <https://example.net?lang=en-US#nav>.search = ''
+PASS <area>: Setting <https://example.net?lang=en-US#nav>.search = ''
+PASS <a>: Setting <https://example.net?lang=en-US>.search = ''
+PASS <area>: Setting <https://example.net?lang=en-US>.search = ''
+PASS <a>: Setting <https://example.net>.search = ''
+PASS <area>: Setting <https://example.net>.search = ''
+FAIL <a>: Setting <a:/>.search = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the query encode set. Tabs and newlines are removed. assert_equals: expected "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/?%00%01%1F%20!%22%23$%&%27()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+FAIL <area>: Setting <a:/>.search = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the query encode set. Tabs and newlines are removed. assert_equals: expected "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/?%00%01%1F%20!%22%23$%&%27()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+PASS <a>: Setting <http://example.net>.search = '%c3%89té' Bytes already percent-encoded are left as-is
+PASS <area>: Setting <http://example.net>.search = '%c3%89té' Bytes already percent-encoded are left as-is
+PASS <a>: Setting <https://example.net>.hash = 'main'
+PASS <area>: Setting <https://example.net>.hash = 'main'
+PASS <a>: Setting <https://example.net#nav>.hash = 'main'
+PASS <area>: Setting <https://example.net#nav>.hash = 'main'
+PASS <a>: Setting <https://example.net?lang=en-US>.hash = '##nav'
+PASS <area>: Setting <https://example.net?lang=en-US>.hash = '##nav'
+PASS <a>: Setting <https://example.net?lang=en-US#nav>.hash = '#main'
+PASS <area>: Setting <https://example.net?lang=en-US#nav>.hash = '#main'
+PASS <a>: Setting <https://example.net?lang=en-US#nav>.hash = '#'
+PASS <area>: Setting <https://example.net?lang=en-US#nav>.hash = '#'
+PASS <a>: Setting <https://example.net?lang=en-US#nav>.hash = ''
+PASS <area>: Setting <https://example.net?lang=en-US#nav>.hash = ''
+PASS <a>: Setting <http://example.net>.hash = '#foo bar'
+PASS <area>: Setting <http://example.net>.hash = '#foo bar'
+PASS <a>: Setting <http://example.net>.hash = '#foo"bar'
+PASS <area>: Setting <http://example.net>.hash = '#foo"bar'
+PASS <a>: Setting <http://example.net>.hash = '#foo<bar'
+PASS <area>: Setting <http://example.net>.hash = '#foo<bar'
+PASS <a>: Setting <http://example.net>.hash = '#foo>bar'
+PASS <area>: Setting <http://example.net>.hash = '#foo>bar'
+PASS <a>: Setting <http://example.net>.hash = '#foo`bar'
+PASS <area>: Setting <http://example.net>.hash = '#foo`bar'
+PASS <a>: Setting <a:/>.hash = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' Simple percent-encoding; tabs and newlines are removed
+PASS <area>: Setting <a:/>.hash = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' Simple percent-encoding; tabs and newlines are removed
+PASS <a>: Setting <http://example.net>.hash = 'a\0b' Percent-encode NULLs in fragment
+PASS <area>: Setting <http://example.net>.hash = 'a\0b' Percent-encode NULLs in fragment
+PASS <a>: Setting <non-spec:/>.hash = 'a\0b' Percent-encode NULLs in fragment
+PASS <area>: Setting <non-spec:/>.hash = 'a\0b' Percent-encode NULLs in fragment
+PASS <a>: Setting <http://example.net>.hash = '%c3%89té' Bytes already percent-encoded are left as-is
+PASS <area>: Setting <http://example.net>.hash = '%c3%89té' Bytes already percent-encoded are left as-is
+PASS <a>: Setting <javascript:alert(1)>.hash = 'castle'
+PASS <area>: Setting <javascript:alert(1)>.hash = 'castle'
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-setters.any-expected.txt b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-setters.any-expected.txt
new file mode 100644
index 0000000..68bf2c4e
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-setters.any-expected.txt
@@ -0,0 +1,229 @@
+This is a testharness.js-based test.
+Found 218 tests; 136 PASS, 82 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS Loading data…
+PASS URL: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged.
+PASS URL: Setting <a://example.net>.protocol = 'b'
+PASS URL: Setting <javascript:alert(1)>.protocol = 'defuse'
+PASS URL: Setting <a://example.net>.protocol = 'B' Upper-case ASCII is lower-cased
+PASS URL: Setting <a://example.net>.protocol = 'é' Non-ASCII is rejected
+PASS URL: Setting <a://example.net>.protocol = '0b' No leading digit
+PASS URL: Setting <a://example.net>.protocol = '+b' No leading punctuation
+PASS URL: Setting <a://example.net>.protocol = 'bC0+-.'
+PASS URL: Setting <a://example.net>.protocol = 'b,c' Only some punctuation is acceptable
+PASS URL: Setting <a://example.net>.protocol = 'bé' Non-ASCII is rejected
+PASS URL: Setting <http://test@example.net>.protocol = 'file' Can’t switch from URL containing username/password/port to file
+PASS URL: Setting <https://example.net:1234>.protocol = 'file'
+PASS URL: Setting <wss://x:x@example.net:1234>.protocol = 'file'
+FAIL URL: Setting <file://localhost/>.protocol = 'http' Can’t switch from file URL with no host assert_equals: expected "file:///" but got "http://localhost/"
+PASS URL: Setting <file:///test>.protocol = 'https'
+PASS URL: Setting <file:>.protocol = 'wss'
+FAIL URL: Setting <http://example.net>.protocol = 'b' Can’t switch from special scheme to non-special assert_equals: expected "http://example.net/" but got "b://example.net/"
+FAIL URL: Setting <file://hi/path>.protocol = 's' assert_equals: expected "file://hi/path" but got "s://hi/path"
+FAIL URL: Setting <https://example.net>.protocol = 's' assert_equals: expected "https://example.net/" but got "s://example.net/"
+FAIL URL: Setting <ftp://example.net>.protocol = 'test' assert_equals: expected "ftp://example.net/" but got "test://example.net/"
+FAIL URL: Setting <mailto:me@example.net>.protocol = 'http' Cannot-be-a-base URL doesn’t have a host, but URL in a special scheme must. assert_equals: expected "mailto:me@example.net" but got "http://me@example.net/"
+FAIL URL: Setting <ssh://me@example.net>.protocol = 'http' Can’t switch from non-special scheme to special assert_equals: expected "ssh://me@example.net" but got "http://me@example.net/"
+FAIL URL: Setting <ssh://me@example.net>.protocol = 'https' assert_equals: expected "ssh://me@example.net" but got "https://me@example.net/"
+FAIL URL: Setting <ssh://me@example.net>.protocol = 'file' assert_equals: expected "ssh://me@example.net" but got "file://me%40example.net/"
+FAIL URL: Setting <ssh://example.net>.protocol = 'file' assert_equals: expected "ssh://example.net" but got "file://example.net/"
+FAIL URL: Setting <nonsense:///test>.protocol = 'https' assert_equals: expected "nonsense:///test" but got "https://test/"
+PASS URL: Setting <http://example.net>.protocol = 'https:foo : bar' Stuff after the first ':' is ignored
+PASS URL: Setting <data:text/html,<p>Test>.protocol = 'view-source+data:foo : bar' Stuff after the first ':' is ignored
+PASS URL: Setting <http://foo.com:443/>.protocol = 'https' Port is set to null if it is the default for new scheme.
+PASS URL: Setting <file:///home/you/index.html>.username = 'me' No host means no username
+PASS URL: Setting <unix:/run/foo.socket>.username = 'me' No host means no username
+PASS URL: Setting <mailto:you@example.net>.username = 'me' Cannot-be-a-base means no username
+PASS URL: Setting <javascript:alert(1)>.username = 'wario'
+PASS URL: Setting <http://example.net>.username = 'me'
+PASS URL: Setting <http://:secret@example.net>.username = 'me'
+PASS URL: Setting <http://me@example.net>.username = ''
+PASS URL: Setting <http://me:secret@example.net>.username = ''
+FAIL URL: Setting <http://example.net>.username = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the userinfo encode set. assert_equals: expected "http://%00%01%09%0A%0D%1F%20!%22%23$%&'()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/" but got "http://%00%01%09%0A%0D%1F%20!%22%23$%&%27()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/"
+PASS URL: Setting <http://example.net>.username = '%c3%89té' Bytes already percent-encoded are left as-is.
+PASS URL: Setting <sc:///>.username = 'x'
+FAIL URL: Setting <javascript://x/>.username = 'wario' assert_equals: expected "javascript://wario@x/" but got "javascript://x/"
+PASS URL: Setting <file://test/>.username = 'test'
+PASS URL: Setting <file:///home/me/index.html>.password = 'secret' No host means no password
+PASS URL: Setting <unix:/run/foo.socket>.password = 'secret' No host means no password
+PASS URL: Setting <mailto:me@example.net>.password = 'secret' Cannot-be-a-base means no password
+PASS URL: Setting <http://example.net>.password = 'secret'
+PASS URL: Setting <http://me@example.net>.password = 'secret'
+PASS URL: Setting <http://:secret@example.net>.password = ''
+PASS URL: Setting <http://me:secret@example.net>.password = ''
+FAIL URL: Setting <http://example.net>.password = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the userinfo encode set. assert_equals: expected "http://:%00%01%09%0A%0D%1F%20!%22%23$%&'()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/" but got "http://:%00%01%09%0A%0D%1F%20!%22%23$%&%27()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/"
+PASS URL: Setting <http://example.net>.password = '%c3%89té' Bytes already percent-encoded are left as-is.
+PASS URL: Setting <sc:///>.password = 'x'
+FAIL URL: Setting <javascript://x/>.password = 'bowser' assert_equals: expected "javascript://:bowser@x/" but got "javascript://x/"
+PASS URL: Setting <file://test/>.password = 'test'
+FAIL URL: Setting <sc://x/>.host = '\0' Non-special scheme assert_equals: expected "x" but got ""
+FAIL URL: Setting <sc://x/>.host = '	' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.host = '
+' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.host = '\r' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.host = ' ' assert_equals: expected "x" but got ""
+FAIL URL: Setting <sc://x/>.host = '#' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.host = '/' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.host = '?' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.host = '@' assert_equals: expected "x" but got ""
+FAIL URL: Setting <sc://x/>.host = 'ß' assert_equals: expected "sc://%C3%9F/" but got "sc://x/"
+PASS URL: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing
+PASS URL: Setting <mailto:me@example.net>.host = 'example.com' Cannot-be-a-base means no host
+PASS URL: Setting <data:text/plain,Stuff>.host = 'example.net' Cannot-be-a-base means no host
+PASS URL: Setting <http://example.net>.host = 'example.com:8080'
+PASS URL: Setting <http://example.net:8080>.host = 'example.com' Port number is unchanged if not specified in the new value
+PASS URL: Setting <http://example.net:8080>.host = 'example.com:' Port number is unchanged if not specified
+PASS URL: Setting <http://example.net>.host = '' The empty host is not valid for special schemes
+FAIL URL: Setting <view-source+http://example.net/foo>.host = '' The empty host is OK for non-special schemes assert_equals: expected "view-source+http:///foo" but got "view-source+http://example.net/foo"
+FAIL URL: Setting <a:/foo>.host = 'example.net' Path-only URLs can gain a host assert_equals: expected "a://example.net/foo" but got "a:/foo"
+PASS URL: Setting <http://example.net>.host = '0x7F000001:8080' IPv4 address syntax is normalized
+PASS URL: Setting <http://example.net>.host = '[::0:01]:2' IPv6 address syntax is normalized
+PASS URL: Setting <http://example.net>.host = '[2001:db8::2]:4002' IPv6 literal address with port, crbug.com/1012416
+PASS URL: Setting <http://example.net>.host = 'example.com:80' Default port number is removed
+PASS URL: Setting <https://example.net>.host = 'example.com:443' Default port number is removed
+PASS URL: Setting <https://example.net>.host = 'example.com:80' Default port number is only removed for the relevant scheme
+PASS URL: Setting <http://example.net:8080>.host = 'example.com:80' Port number is removed if new port is scheme default and existing URL has a non-default port
+PASS URL: Setting <http://example.net/path>.host = 'example.com/stuff' Stuff after a / delimiter is ignored
+PASS URL: Setting <http://example.net/path>.host = 'example.com:8080/stuff' Stuff after a / delimiter is ignored
+PASS URL: Setting <http://example.net/path>.host = 'example.com?stuff' Stuff after a ? delimiter is ignored
+PASS URL: Setting <http://example.net/path>.host = 'example.com:8080?stuff' Stuff after a ? delimiter is ignored
+PASS URL: Setting <http://example.net/path>.host = 'example.com#stuff' Stuff after a # delimiter is ignored
+PASS URL: Setting <http://example.net/path>.host = 'example.com:8080#stuff' Stuff after a # delimiter is ignored
+PASS URL: Setting <http://example.net/path>.host = 'example.com\stuff' Stuff after a \ delimiter is ignored for special schemes
+PASS URL: Setting <http://example.net/path>.host = 'example.com:8080\stuff' Stuff after a \ delimiter is ignored for special schemes
+FAIL URL: Setting <view-source+http://example.net/path>.host = 'example.com\stuff' \ is not a delimiter for non-special schemes, but still forbidden in hosts assert_equals: expected "example.net" but got ""
+FAIL URL: Setting <view-source+http://example.net/path>.host = 'example.com:8080stuff2' Anything other than ASCII digit stops the port parser in a setter but is not an error assert_equals: expected "view-source+http://example.com:8080/path" but got "view-source+http://example.net/path"
+PASS URL: Setting <http://example.net/path>.host = 'example.com:8080stuff2' Anything other than ASCII digit stops the port parser in a setter but is not an error
+PASS URL: Setting <http://example.net/path>.host = 'example.com:8080+2' Anything other than ASCII digit stops the port parser in a setter but is not an error
+PASS URL: Setting <http://example.net/path>.host = 'example.com:65535' Port numbers are 16 bit integers
+PASS URL: Setting <http://example.net/path>.host = 'example.com:65536' Port numbers are 16 bit integers, overflowing is an error. Hostname is still set, though.
+PASS URL: Setting <http://example.net/>.host = '[google.com]' Broken IPv6
+PASS URL: Setting <http://example.net/>.host = '[::1.2.3.4x]'
+FAIL URL: Setting <http://example.net/>.host = '[::1.2.3.]' assert_equals: expected "http://example.net/" but got "http://[::102:3]/"
+FAIL URL: Setting <http://example.net/>.host = '[::1.2.]' assert_equals: expected "http://example.net/" but got "http://[::100:2]/"
+FAIL URL: Setting <http://example.net/>.host = '[::1.]' assert_equals: expected "http://example.net/" but got "http://[::1]/"
+FAIL URL: Setting <file://y/>.host = 'x:123' assert_equals: expected "file://y/" but got "file://x/"
+FAIL URL: Setting <file://y/>.host = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/"
+FAIL URL: Setting <file://hi/x>.host = '' assert_equals: expected "file:///x" but got "file://hi/x"
+FAIL URL: Setting <sc://test@test/>.host = '' assert_equals: expected "test" but got ""
+FAIL URL: Setting <sc://test:12/>.host = '' assert_equals: expected "test:12" but got ""
+PASS URL: Setting <http://example.com/>.host = '///bad.com' Leading / is not stripped
+FAIL URL: Setting <sc://example.com/>.host = '///bad.com' Leading / is not stripped assert_equals: expected "sc:///" but got "sc://example.com/"
+FAIL URL: Setting <sc://x/>.hostname = '\0' Non-special scheme assert_equals: expected "x" but got ""
+FAIL URL: Setting <sc://x/>.hostname = '	' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.hostname = '
+' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.hostname = '\r' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.hostname = ' ' assert_equals: expected "x" but got ""
+FAIL URL: Setting <sc://x/>.hostname = '#' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.hostname = '/' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.hostname = '?' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.hostname = '@' assert_equals: expected "x" but got ""
+PASS URL: Setting <mailto:me@example.net>.hostname = 'example.com' Cannot-be-a-base means no host
+PASS URL: Setting <data:text/plain,Stuff>.hostname = 'example.net' Cannot-be-a-base means no host
+PASS URL: Setting <http://example.net:8080>.hostname = 'example.com'
+PASS URL: Setting <http://example.net>.hostname = '' The empty host is not valid for special schemes
+FAIL URL: Setting <view-source+http://example.net/foo>.hostname = '' The empty host is OK for non-special schemes assert_equals: expected "view-source+http:///foo" but got "view-source+http://example.net/foo"
+FAIL URL: Setting <a:/foo>.hostname = 'example.net' Path-only URLs can gain a host assert_equals: expected "a://example.net/foo" but got "a:/foo"
+PASS URL: Setting <http://example.net:8080>.hostname = '0x7F000001' IPv4 address syntax is normalized
+PASS URL: Setting <http://example.net>.hostname = '[::0:01]' IPv6 address syntax is normalized
+PASS URL: Setting <http://example.net/path>.hostname = 'example.com:8080' : delimiter invalidates entire value
+PASS URL: Setting <http://example.net:8080/path>.hostname = 'example.com:' : delimiter invalidates entire value
+PASS URL: Setting <http://example.net/path>.hostname = 'example.com/stuff' Stuff after a / delimiter is ignored
+PASS URL: Setting <http://example.net/path>.hostname = 'example.com?stuff' Stuff after a ? delimiter is ignored
+PASS URL: Setting <http://example.net/path>.hostname = 'example.com#stuff' Stuff after a # delimiter is ignored
+PASS URL: Setting <http://example.net/path>.hostname = 'example.com\stuff' Stuff after a \ delimiter is ignored for special schemes
+FAIL URL: Setting <view-source+http://example.net/path>.hostname = 'example.com\stuff' \ is not a delimiter for non-special schemes, but still forbidden in hosts assert_equals: expected "example.net" but got ""
+PASS URL: Setting <http://example.net/>.hostname = '[google.com]' Broken IPv6
+PASS URL: Setting <http://example.net/>.hostname = '[::1.2.3.4x]'
+FAIL URL: Setting <http://example.net/>.hostname = '[::1.2.3.]' assert_equals: expected "http://example.net/" but got "http://[::102:3]/"
+FAIL URL: Setting <http://example.net/>.hostname = '[::1.2.]' assert_equals: expected "http://example.net/" but got "http://[::100:2]/"
+FAIL URL: Setting <http://example.net/>.hostname = '[::1.]' assert_equals: expected "http://example.net/" but got "http://[::1]/"
+PASS URL: Setting <file://y/>.hostname = 'x:123'
+FAIL URL: Setting <file://y/>.hostname = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/"
+FAIL URL: Setting <file://hi/x>.hostname = '' assert_equals: expected "file:///x" but got "file://hi/x"
+FAIL URL: Setting <sc://test@test/>.hostname = '' assert_equals: expected "test" but got ""
+FAIL URL: Setting <sc://test:12/>.hostname = '' assert_equals: expected "test:12" but got ""
+FAIL URL: Setting <non-spec:/.//p>.hostname = 'h' Drop /. from path assert_equals: expected "non-spec://h//p" but got "non-spec:/.//p"
+FAIL URL: Setting <non-spec:/.//p>.hostname = '' assert_equals: expected "non-spec:////p" but got "non-spec:/.//p"
+PASS URL: Setting <http://example.com/>.hostname = '///bad.com' Leading / is not stripped
+FAIL URL: Setting <sc://example.com/>.hostname = '///bad.com' Leading / is not stripped assert_equals: expected "sc:///" but got "sc://example.com/"
+PASS URL: Setting <http://example.net>.port = '8080'
+PASS URL: Setting <http://example.net:8080>.port = '' Port number is removed if empty is the new value
+PASS URL: Setting <http://example.net:8080>.port = '80' Default port number is removed
+PASS URL: Setting <https://example.net:4433>.port = '443' Default port number is removed
+PASS URL: Setting <https://example.net>.port = '80' Default port number is only removed for the relevant scheme
+PASS URL: Setting <http://example.net/path>.port = '8080/stuff' Stuff after a / delimiter is ignored
+PASS URL: Setting <http://example.net/path>.port = '8080?stuff' Stuff after a ? delimiter is ignored
+PASS URL: Setting <http://example.net/path>.port = '8080#stuff' Stuff after a # delimiter is ignored
+PASS URL: Setting <http://example.net/path>.port = '8080\stuff' Stuff after a \ delimiter is ignored for special schemes
+FAIL URL: Setting <view-source+http://example.net/path>.port = '8080stuff2' Anything other than ASCII digit stops the port parser in a setter but is not an error assert_equals: expected "view-source+http://example.net:8080/path" but got "view-source+http://example.net/path"
+PASS URL: Setting <http://example.net/path>.port = '8080stuff2' Anything other than ASCII digit stops the port parser in a setter but is not an error
+PASS URL: Setting <http://example.net/path>.port = '8080+2' Anything other than ASCII digit stops the port parser in a setter but is not an error
+PASS URL: Setting <http://example.net/path>.port = '65535' Port numbers are 16 bit integers
+FAIL URL: Setting <http://example.net:8080/path>.port = '65536' Port numbers are 16 bit integers, overflowing is an error assert_equals: expected "http://example.net:8080/path" but got "http://example.net:0/path"
+FAIL URL: Setting <non-special://example.net:8080/path>.port = '65536' Port numbers are 16 bit integers, overflowing is an error assert_equals: expected "example.net:8080" but got ""
+PASS URL: Setting <file://test/>.port = '12'
+FAIL URL: Setting <file://localhost/>.port = '12' assert_equals: expected "file:///" but got "file://localhost/"
+PASS URL: Setting <non-base:value>.port = '12'
+PASS URL: Setting <sc:///>.port = '12'
+FAIL URL: Setting <sc://x/>.port = '12' assert_equals: expected "sc://x:12/" but got "sc://x/"
+FAIL URL: Setting <javascript://x/>.port = '12' assert_equals: expected "javascript://x:12/" but got "javascript://x/"
+PASS URL: Setting <mailto:me@example.net>.pathname = '/foo' Cannot-be-a-base don’t have a path
+PASS URL: Setting <file:///some/path>.pathname = '' Special URLs cannot have their paths erased
+FAIL URL: Setting <foo://somehost/some/path>.pathname = '' Non-special URLs can have their paths erased assert_equals: expected "foo://somehost" but got "foo://somehost/some/path"
+FAIL URL: Setting <foo:///some/path>.pathname = '' Non-special URLs with an empty host can have their paths erased assert_equals: expected "foo://" but got "foo:///some/path"
+FAIL URL: Setting <foo:/some/path>.pathname = '' Path-only URLs cannot have their paths erased assert_equals: expected "foo:/" but got "foo:/some/path"
+FAIL URL: Setting <foo:/some/path>.pathname = 'test' Path-only URLs always have an initial slash assert_equals: expected "foo:/test" but got "foo:/some/path"
+FAIL URL: Setting <unix:/run/foo.socket?timeout=10>.pathname = '/var/log/../run/bar.socket' assert_equals: expected "unix:/var/run/bar.socket?timeout=10" but got "unix:/run/foo.socket?timeout=10"
+PASS URL: Setting <https://example.net#nav>.pathname = 'home'
+PASS URL: Setting <https://example.net#nav>.pathname = '../home'
+PASS URL: Setting <http://example.net/home?lang=fr#nav>.pathname = '\a\%2E\b\%2e.\c' \ is a segment delimiter for 'special' URLs
+FAIL URL: Setting <view-source+http://example.net/home?lang=fr#nav>.pathname = '\a\%2E\b\%2e.\c' \ is *not* a segment delimiter for non-'special' URLs assert_equals: expected "view-source+http://example.net/\\a\\%2E\\b\\%2e.\\c?lang=fr#nav" but got "view-source+http://example.net/home?lang=fr#nav"
+FAIL URL: Setting <a:/>.pathname = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the default encode set. Tabs and newlines are removed. assert_equals: expected "a:/%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E%3F@AZ[\\]^_%60az%7B|%7D~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/"
+FAIL URL: Setting <http://example.net>.pathname = '%2e%2E%c3%89té' Bytes already percent-encoded are left as-is, including %2E outside dotted segments. assert_equals: expected "http://example.net/%2e%2E%c3%89t%C3%A9" but got "http://example.net/..%c3%89t%C3%A9"
+PASS URL: Setting <http://example.net>.pathname = '?' ? needs to be encoded
+PASS URL: Setting <http://example.net>.pathname = '#' # needs to be encoded
+FAIL URL: Setting <sc://example.net>.pathname = '?' ? needs to be encoded, non-special scheme assert_equals: expected "sc://example.net/%3F" but got "sc://example.net"
+FAIL URL: Setting <sc://example.net>.pathname = '#' # needs to be encoded, non-special scheme assert_equals: expected "sc://example.net/%23" but got "sc://example.net"
+PASS URL: Setting <http://example.net>.pathname = '/?é' ? doesn't mess up encoding
+PASS URL: Setting <http://example.net>.pathname = '/#é' # doesn't mess up encoding
+PASS URL: Setting <file://monkey/>.pathname = '\\' File URLs and (back)slashes
+PASS URL: Setting <file:///unicorn>.pathname = '//\/' File URLs and (back)slashes
+PASS URL: Setting <file:///unicorn>.pathname = '//monkey/..//' File URLs and (back)slashes
+FAIL URL: Setting <non-spec:/>.pathname = '/.//p' Serialize /. in path assert_equals: expected "non-spec:/.//p" but got "non-spec:/"
+FAIL URL: Setting <non-spec:/>.pathname = '/..//p' assert_equals: expected "non-spec:/.//p" but got "non-spec:/"
+FAIL URL: Setting <non-spec:/>.pathname = '//p' assert_equals: expected "non-spec:/.//p" but got "non-spec:/"
+FAIL URL: Setting <non-spec:/.//>.pathname = 'p' Drop /. from path assert_equals: expected "non-spec:/p" but got "non-spec:/.//"
+PASS URL: Setting <https://example.net#nav>.search = 'lang=fr'
+PASS URL: Setting <https://example.net?lang=en-US#nav>.search = 'lang=fr'
+PASS URL: Setting <https://example.net?lang=en-US#nav>.search = '?lang=fr'
+PASS URL: Setting <https://example.net?lang=en-US#nav>.search = '??lang=fr'
+FAIL URL: Setting <https://example.net?lang=en-US#nav>.search = '?' assert_equals: expected "https://example.net/?#nav" but got "https://example.net/#nav"
+PASS URL: Setting <https://example.net?lang=en-US#nav>.search = ''
+PASS URL: Setting <https://example.net?lang=en-US>.search = ''
+PASS URL: Setting <https://example.net>.search = ''
+FAIL URL: Setting <a:/>.search = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the query encode set. Tabs and newlines are removed. assert_equals: expected "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/?%00%01%1F%20!%22%23$%&%27()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+PASS URL: Setting <http://example.net>.search = '%c3%89té' Bytes already percent-encoded are left as-is
+PASS URL: Setting <https://example.net>.hash = 'main'
+PASS URL: Setting <https://example.net#nav>.hash = 'main'
+PASS URL: Setting <https://example.net?lang=en-US>.hash = '##nav'
+PASS URL: Setting <https://example.net?lang=en-US#nav>.hash = '#main'
+PASS URL: Setting <https://example.net?lang=en-US#nav>.hash = '#'
+PASS URL: Setting <https://example.net?lang=en-US#nav>.hash = ''
+PASS URL: Setting <http://example.net>.hash = '#foo bar'
+PASS URL: Setting <http://example.net>.hash = '#foo"bar'
+PASS URL: Setting <http://example.net>.hash = '#foo<bar'
+PASS URL: Setting <http://example.net>.hash = '#foo>bar'
+PASS URL: Setting <http://example.net>.hash = '#foo`bar'
+PASS URL: Setting <a:/>.hash = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' Simple percent-encoding; tabs and newlines are removed
+PASS URL: Setting <http://example.net>.hash = 'a\0b' Percent-encode NULLs in fragment
+PASS URL: Setting <non-spec:/>.hash = 'a\0b' Percent-encode NULLs in fragment
+PASS URL: Setting <http://example.net>.hash = '%c3%89té' Bytes already percent-encoded are left as-is
+PASS URL: Setting <javascript:alert(1)>.hash = 'castle'
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-setters.any.worker-expected.txt b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-setters.any.worker-expected.txt
new file mode 100644
index 0000000..68bf2c4e
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/idna-2008/external/wpt/url/url-setters.any.worker-expected.txt
@@ -0,0 +1,229 @@
+This is a testharness.js-based test.
+Found 218 tests; 136 PASS, 82 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS Loading data…
+PASS URL: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged.
+PASS URL: Setting <a://example.net>.protocol = 'b'
+PASS URL: Setting <javascript:alert(1)>.protocol = 'defuse'
+PASS URL: Setting <a://example.net>.protocol = 'B' Upper-case ASCII is lower-cased
+PASS URL: Setting <a://example.net>.protocol = 'é' Non-ASCII is rejected
+PASS URL: Setting <a://example.net>.protocol = '0b' No leading digit
+PASS URL: Setting <a://example.net>.protocol = '+b' No leading punctuation
+PASS URL: Setting <a://example.net>.protocol = 'bC0+-.'
+PASS URL: Setting <a://example.net>.protocol = 'b,c' Only some punctuation is acceptable
+PASS URL: Setting <a://example.net>.protocol = 'bé' Non-ASCII is rejected
+PASS URL: Setting <http://test@example.net>.protocol = 'file' Can’t switch from URL containing username/password/port to file
+PASS URL: Setting <https://example.net:1234>.protocol = 'file'
+PASS URL: Setting <wss://x:x@example.net:1234>.protocol = 'file'
+FAIL URL: Setting <file://localhost/>.protocol = 'http' Can’t switch from file URL with no host assert_equals: expected "file:///" but got "http://localhost/"
+PASS URL: Setting <file:///test>.protocol = 'https'
+PASS URL: Setting <file:>.protocol = 'wss'
+FAIL URL: Setting <http://example.net>.protocol = 'b' Can’t switch from special scheme to non-special assert_equals: expected "http://example.net/" but got "b://example.net/"
+FAIL URL: Setting <file://hi/path>.protocol = 's' assert_equals: expected "file://hi/path" but got "s://hi/path"
+FAIL URL: Setting <https://example.net>.protocol = 's' assert_equals: expected "https://example.net/" but got "s://example.net/"
+FAIL URL: Setting <ftp://example.net>.protocol = 'test' assert_equals: expected "ftp://example.net/" but got "test://example.net/"
+FAIL URL: Setting <mailto:me@example.net>.protocol = 'http' Cannot-be-a-base URL doesn’t have a host, but URL in a special scheme must. assert_equals: expected "mailto:me@example.net" but got "http://me@example.net/"
+FAIL URL: Setting <ssh://me@example.net>.protocol = 'http' Can’t switch from non-special scheme to special assert_equals: expected "ssh://me@example.net" but got "http://me@example.net/"
+FAIL URL: Setting <ssh://me@example.net>.protocol = 'https' assert_equals: expected "ssh://me@example.net" but got "https://me@example.net/"
+FAIL URL: Setting <ssh://me@example.net>.protocol = 'file' assert_equals: expected "ssh://me@example.net" but got "file://me%40example.net/"
+FAIL URL: Setting <ssh://example.net>.protocol = 'file' assert_equals: expected "ssh://example.net" but got "file://example.net/"
+FAIL URL: Setting <nonsense:///test>.protocol = 'https' assert_equals: expected "nonsense:///test" but got "https://test/"
+PASS URL: Setting <http://example.net>.protocol = 'https:foo : bar' Stuff after the first ':' is ignored
+PASS URL: Setting <data:text/html,<p>Test>.protocol = 'view-source+data:foo : bar' Stuff after the first ':' is ignored
+PASS URL: Setting <http://foo.com:443/>.protocol = 'https' Port is set to null if it is the default for new scheme.
+PASS URL: Setting <file:///home/you/index.html>.username = 'me' No host means no username
+PASS URL: Setting <unix:/run/foo.socket>.username = 'me' No host means no username
+PASS URL: Setting <mailto:you@example.net>.username = 'me' Cannot-be-a-base means no username
+PASS URL: Setting <javascript:alert(1)>.username = 'wario'
+PASS URL: Setting <http://example.net>.username = 'me'
+PASS URL: Setting <http://:secret@example.net>.username = 'me'
+PASS URL: Setting <http://me@example.net>.username = ''
+PASS URL: Setting <http://me:secret@example.net>.username = ''
+FAIL URL: Setting <http://example.net>.username = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the userinfo encode set. assert_equals: expected "http://%00%01%09%0A%0D%1F%20!%22%23$%&'()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/" but got "http://%00%01%09%0A%0D%1F%20!%22%23$%&%27()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/"
+PASS URL: Setting <http://example.net>.username = '%c3%89té' Bytes already percent-encoded are left as-is.
+PASS URL: Setting <sc:///>.username = 'x'
+FAIL URL: Setting <javascript://x/>.username = 'wario' assert_equals: expected "javascript://wario@x/" but got "javascript://x/"
+PASS URL: Setting <file://test/>.username = 'test'
+PASS URL: Setting <file:///home/me/index.html>.password = 'secret' No host means no password
+PASS URL: Setting <unix:/run/foo.socket>.password = 'secret' No host means no password
+PASS URL: Setting <mailto:me@example.net>.password = 'secret' Cannot-be-a-base means no password
+PASS URL: Setting <http://example.net>.password = 'secret'
+PASS URL: Setting <http://me@example.net>.password = 'secret'
+PASS URL: Setting <http://:secret@example.net>.password = ''
+PASS URL: Setting <http://me:secret@example.net>.password = ''
+FAIL URL: Setting <http://example.net>.password = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the userinfo encode set. assert_equals: expected "http://:%00%01%09%0A%0D%1F%20!%22%23$%&'()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/" but got "http://:%00%01%09%0A%0D%1F%20!%22%23$%&%27()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/"
+PASS URL: Setting <http://example.net>.password = '%c3%89té' Bytes already percent-encoded are left as-is.
+PASS URL: Setting <sc:///>.password = 'x'
+FAIL URL: Setting <javascript://x/>.password = 'bowser' assert_equals: expected "javascript://:bowser@x/" but got "javascript://x/"
+PASS URL: Setting <file://test/>.password = 'test'
+FAIL URL: Setting <sc://x/>.host = '\0' Non-special scheme assert_equals: expected "x" but got ""
+FAIL URL: Setting <sc://x/>.host = '	' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.host = '
+' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.host = '\r' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.host = ' ' assert_equals: expected "x" but got ""
+FAIL URL: Setting <sc://x/>.host = '#' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.host = '/' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.host = '?' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.host = '@' assert_equals: expected "x" but got ""
+FAIL URL: Setting <sc://x/>.host = 'ß' assert_equals: expected "sc://%C3%9F/" but got "sc://x/"
+PASS URL: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing
+PASS URL: Setting <mailto:me@example.net>.host = 'example.com' Cannot-be-a-base means no host
+PASS URL: Setting <data:text/plain,Stuff>.host = 'example.net' Cannot-be-a-base means no host
+PASS URL: Setting <http://example.net>.host = 'example.com:8080'
+PASS URL: Setting <http://example.net:8080>.host = 'example.com' Port number is unchanged if not specified in the new value
+PASS URL: Setting <http://example.net:8080>.host = 'example.com:' Port number is unchanged if not specified
+PASS URL: Setting <http://example.net>.host = '' The empty host is not valid for special schemes
+FAIL URL: Setting <view-source+http://example.net/foo>.host = '' The empty host is OK for non-special schemes assert_equals: expected "view-source+http:///foo" but got "view-source+http://example.net/foo"
+FAIL URL: Setting <a:/foo>.host = 'example.net' Path-only URLs can gain a host assert_equals: expected "a://example.net/foo" but got "a:/foo"
+PASS URL: Setting <http://example.net>.host = '0x7F000001:8080' IPv4 address syntax is normalized
+PASS URL: Setting <http://example.net>.host = '[::0:01]:2' IPv6 address syntax is normalized
+PASS URL: Setting <http://example.net>.host = '[2001:db8::2]:4002' IPv6 literal address with port, crbug.com/1012416
+PASS URL: Setting <http://example.net>.host = 'example.com:80' Default port number is removed
+PASS URL: Setting <https://example.net>.host = 'example.com:443' Default port number is removed
+PASS URL: Setting <https://example.net>.host = 'example.com:80' Default port number is only removed for the relevant scheme
+PASS URL: Setting <http://example.net:8080>.host = 'example.com:80' Port number is removed if new port is scheme default and existing URL has a non-default port
+PASS URL: Setting <http://example.net/path>.host = 'example.com/stuff' Stuff after a / delimiter is ignored
+PASS URL: Setting <http://example.net/path>.host = 'example.com:8080/stuff' Stuff after a / delimiter is ignored
+PASS URL: Setting <http://example.net/path>.host = 'example.com?stuff' Stuff after a ? delimiter is ignored
+PASS URL: Setting <http://example.net/path>.host = 'example.com:8080?stuff' Stuff after a ? delimiter is ignored
+PASS URL: Setting <http://example.net/path>.host = 'example.com#stuff' Stuff after a # delimiter is ignored
+PASS URL: Setting <http://example.net/path>.host = 'example.com:8080#stuff' Stuff after a # delimiter is ignored
+PASS URL: Setting <http://example.net/path>.host = 'example.com\stuff' Stuff after a \ delimiter is ignored for special schemes
+PASS URL: Setting <http://example.net/path>.host = 'example.com:8080\stuff' Stuff after a \ delimiter is ignored for special schemes
+FAIL URL: Setting <view-source+http://example.net/path>.host = 'example.com\stuff' \ is not a delimiter for non-special schemes, but still forbidden in hosts assert_equals: expected "example.net" but got ""
+FAIL URL: Setting <view-source+http://example.net/path>.host = 'example.com:8080stuff2' Anything other than ASCII digit stops the port parser in a setter but is not an error assert_equals: expected "view-source+http://example.com:8080/path" but got "view-source+http://example.net/path"
+PASS URL: Setting <http://example.net/path>.host = 'example.com:8080stuff2' Anything other than ASCII digit stops the port parser in a setter but is not an error
+PASS URL: Setting <http://example.net/path>.host = 'example.com:8080+2' Anything other than ASCII digit stops the port parser in a setter but is not an error
+PASS URL: Setting <http://example.net/path>.host = 'example.com:65535' Port numbers are 16 bit integers
+PASS URL: Setting <http://example.net/path>.host = 'example.com:65536' Port numbers are 16 bit integers, overflowing is an error. Hostname is still set, though.
+PASS URL: Setting <http://example.net/>.host = '[google.com]' Broken IPv6
+PASS URL: Setting <http://example.net/>.host = '[::1.2.3.4x]'
+FAIL URL: Setting <http://example.net/>.host = '[::1.2.3.]' assert_equals: expected "http://example.net/" but got "http://[::102:3]/"
+FAIL URL: Setting <http://example.net/>.host = '[::1.2.]' assert_equals: expected "http://example.net/" but got "http://[::100:2]/"
+FAIL URL: Setting <http://example.net/>.host = '[::1.]' assert_equals: expected "http://example.net/" but got "http://[::1]/"
+FAIL URL: Setting <file://y/>.host = 'x:123' assert_equals: expected "file://y/" but got "file://x/"
+FAIL URL: Setting <file://y/>.host = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/"
+FAIL URL: Setting <file://hi/x>.host = '' assert_equals: expected "file:///x" but got "file://hi/x"
+FAIL URL: Setting <sc://test@test/>.host = '' assert_equals: expected "test" but got ""
+FAIL URL: Setting <sc://test:12/>.host = '' assert_equals: expected "test:12" but got ""
+PASS URL: Setting <http://example.com/>.host = '///bad.com' Leading / is not stripped
+FAIL URL: Setting <sc://example.com/>.host = '///bad.com' Leading / is not stripped assert_equals: expected "sc:///" but got "sc://example.com/"
+FAIL URL: Setting <sc://x/>.hostname = '\0' Non-special scheme assert_equals: expected "x" but got ""
+FAIL URL: Setting <sc://x/>.hostname = '	' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.hostname = '
+' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.hostname = '\r' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.hostname = ' ' assert_equals: expected "x" but got ""
+FAIL URL: Setting <sc://x/>.hostname = '#' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.hostname = '/' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.hostname = '?' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.hostname = '@' assert_equals: expected "x" but got ""
+PASS URL: Setting <mailto:me@example.net>.hostname = 'example.com' Cannot-be-a-base means no host
+PASS URL: Setting <data:text/plain,Stuff>.hostname = 'example.net' Cannot-be-a-base means no host
+PASS URL: Setting <http://example.net:8080>.hostname = 'example.com'
+PASS URL: Setting <http://example.net>.hostname = '' The empty host is not valid for special schemes
+FAIL URL: Setting <view-source+http://example.net/foo>.hostname = '' The empty host is OK for non-special schemes assert_equals: expected "view-source+http:///foo" but got "view-source+http://example.net/foo"
+FAIL URL: Setting <a:/foo>.hostname = 'example.net' Path-only URLs can gain a host assert_equals: expected "a://example.net/foo" but got "a:/foo"
+PASS URL: Setting <http://example.net:8080>.hostname = '0x7F000001' IPv4 address syntax is normalized
+PASS URL: Setting <http://example.net>.hostname = '[::0:01]' IPv6 address syntax is normalized
+PASS URL: Setting <http://example.net/path>.hostname = 'example.com:8080' : delimiter invalidates entire value
+PASS URL: Setting <http://example.net:8080/path>.hostname = 'example.com:' : delimiter invalidates entire value
+PASS URL: Setting <http://example.net/path>.hostname = 'example.com/stuff' Stuff after a / delimiter is ignored
+PASS URL: Setting <http://example.net/path>.hostname = 'example.com?stuff' Stuff after a ? delimiter is ignored
+PASS URL: Setting <http://example.net/path>.hostname = 'example.com#stuff' Stuff after a # delimiter is ignored
+PASS URL: Setting <http://example.net/path>.hostname = 'example.com\stuff' Stuff after a \ delimiter is ignored for special schemes
+FAIL URL: Setting <view-source+http://example.net/path>.hostname = 'example.com\stuff' \ is not a delimiter for non-special schemes, but still forbidden in hosts assert_equals: expected "example.net" but got ""
+PASS URL: Setting <http://example.net/>.hostname = '[google.com]' Broken IPv6
+PASS URL: Setting <http://example.net/>.hostname = '[::1.2.3.4x]'
+FAIL URL: Setting <http://example.net/>.hostname = '[::1.2.3.]' assert_equals: expected "http://example.net/" but got "http://[::102:3]/"
+FAIL URL: Setting <http://example.net/>.hostname = '[::1.2.]' assert_equals: expected "http://example.net/" but got "http://[::100:2]/"
+FAIL URL: Setting <http://example.net/>.hostname = '[::1.]' assert_equals: expected "http://example.net/" but got "http://[::1]/"
+PASS URL: Setting <file://y/>.hostname = 'x:123'
+FAIL URL: Setting <file://y/>.hostname = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/"
+FAIL URL: Setting <file://hi/x>.hostname = '' assert_equals: expected "file:///x" but got "file://hi/x"
+FAIL URL: Setting <sc://test@test/>.hostname = '' assert_equals: expected "test" but got ""
+FAIL URL: Setting <sc://test:12/>.hostname = '' assert_equals: expected "test:12" but got ""
+FAIL URL: Setting <non-spec:/.//p>.hostname = 'h' Drop /. from path assert_equals: expected "non-spec://h//p" but got "non-spec:/.//p"
+FAIL URL: Setting <non-spec:/.//p>.hostname = '' assert_equals: expected "non-spec:////p" but got "non-spec:/.//p"
+PASS URL: Setting <http://example.com/>.hostname = '///bad.com' Leading / is not stripped
+FAIL URL: Setting <sc://example.com/>.hostname = '///bad.com' Leading / is not stripped assert_equals: expected "sc:///" but got "sc://example.com/"
+PASS URL: Setting <http://example.net>.port = '8080'
+PASS URL: Setting <http://example.net:8080>.port = '' Port number is removed if empty is the new value
+PASS URL: Setting <http://example.net:8080>.port = '80' Default port number is removed
+PASS URL: Setting <https://example.net:4433>.port = '443' Default port number is removed
+PASS URL: Setting <https://example.net>.port = '80' Default port number is only removed for the relevant scheme
+PASS URL: Setting <http://example.net/path>.port = '8080/stuff' Stuff after a / delimiter is ignored
+PASS URL: Setting <http://example.net/path>.port = '8080?stuff' Stuff after a ? delimiter is ignored
+PASS URL: Setting <http://example.net/path>.port = '8080#stuff' Stuff after a # delimiter is ignored
+PASS URL: Setting <http://example.net/path>.port = '8080\stuff' Stuff after a \ delimiter is ignored for special schemes
+FAIL URL: Setting <view-source+http://example.net/path>.port = '8080stuff2' Anything other than ASCII digit stops the port parser in a setter but is not an error assert_equals: expected "view-source+http://example.net:8080/path" but got "view-source+http://example.net/path"
+PASS URL: Setting <http://example.net/path>.port = '8080stuff2' Anything other than ASCII digit stops the port parser in a setter but is not an error
+PASS URL: Setting <http://example.net/path>.port = '8080+2' Anything other than ASCII digit stops the port parser in a setter but is not an error
+PASS URL: Setting <http://example.net/path>.port = '65535' Port numbers are 16 bit integers
+FAIL URL: Setting <http://example.net:8080/path>.port = '65536' Port numbers are 16 bit integers, overflowing is an error assert_equals: expected "http://example.net:8080/path" but got "http://example.net:0/path"
+FAIL URL: Setting <non-special://example.net:8080/path>.port = '65536' Port numbers are 16 bit integers, overflowing is an error assert_equals: expected "example.net:8080" but got ""
+PASS URL: Setting <file://test/>.port = '12'
+FAIL URL: Setting <file://localhost/>.port = '12' assert_equals: expected "file:///" but got "file://localhost/"
+PASS URL: Setting <non-base:value>.port = '12'
+PASS URL: Setting <sc:///>.port = '12'
+FAIL URL: Setting <sc://x/>.port = '12' assert_equals: expected "sc://x:12/" but got "sc://x/"
+FAIL URL: Setting <javascript://x/>.port = '12' assert_equals: expected "javascript://x:12/" but got "javascript://x/"
+PASS URL: Setting <mailto:me@example.net>.pathname = '/foo' Cannot-be-a-base don’t have a path
+PASS URL: Setting <file:///some/path>.pathname = '' Special URLs cannot have their paths erased
+FAIL URL: Setting <foo://somehost/some/path>.pathname = '' Non-special URLs can have their paths erased assert_equals: expected "foo://somehost" but got "foo://somehost/some/path"
+FAIL URL: Setting <foo:///some/path>.pathname = '' Non-special URLs with an empty host can have their paths erased assert_equals: expected "foo://" but got "foo:///some/path"
+FAIL URL: Setting <foo:/some/path>.pathname = '' Path-only URLs cannot have their paths erased assert_equals: expected "foo:/" but got "foo:/some/path"
+FAIL URL: Setting <foo:/some/path>.pathname = 'test' Path-only URLs always have an initial slash assert_equals: expected "foo:/test" but got "foo:/some/path"
+FAIL URL: Setting <unix:/run/foo.socket?timeout=10>.pathname = '/var/log/../run/bar.socket' assert_equals: expected "unix:/var/run/bar.socket?timeout=10" but got "unix:/run/foo.socket?timeout=10"
+PASS URL: Setting <https://example.net#nav>.pathname = 'home'
+PASS URL: Setting <https://example.net#nav>.pathname = '../home'
+PASS URL: Setting <http://example.net/home?lang=fr#nav>.pathname = '\a\%2E\b\%2e.\c' \ is a segment delimiter for 'special' URLs
+FAIL URL: Setting <view-source+http://example.net/home?lang=fr#nav>.pathname = '\a\%2E\b\%2e.\c' \ is *not* a segment delimiter for non-'special' URLs assert_equals: expected "view-source+http://example.net/\\a\\%2E\\b\\%2e.\\c?lang=fr#nav" but got "view-source+http://example.net/home?lang=fr#nav"
+FAIL URL: Setting <a:/>.pathname = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the default encode set. Tabs and newlines are removed. assert_equals: expected "a:/%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E%3F@AZ[\\]^_%60az%7B|%7D~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/"
+FAIL URL: Setting <http://example.net>.pathname = '%2e%2E%c3%89té' Bytes already percent-encoded are left as-is, including %2E outside dotted segments. assert_equals: expected "http://example.net/%2e%2E%c3%89t%C3%A9" but got "http://example.net/..%c3%89t%C3%A9"
+PASS URL: Setting <http://example.net>.pathname = '?' ? needs to be encoded
+PASS URL: Setting <http://example.net>.pathname = '#' # needs to be encoded
+FAIL URL: Setting <sc://example.net>.pathname = '?' ? needs to be encoded, non-special scheme assert_equals: expected "sc://example.net/%3F" but got "sc://example.net"
+FAIL URL: Setting <sc://example.net>.pathname = '#' # needs to be encoded, non-special scheme assert_equals: expected "sc://example.net/%23" but got "sc://example.net"
+PASS URL: Setting <http://example.net>.pathname = '/?é' ? doesn't mess up encoding
+PASS URL: Setting <http://example.net>.pathname = '/#é' # doesn't mess up encoding
+PASS URL: Setting <file://monkey/>.pathname = '\\' File URLs and (back)slashes
+PASS URL: Setting <file:///unicorn>.pathname = '//\/' File URLs and (back)slashes
+PASS URL: Setting <file:///unicorn>.pathname = '//monkey/..//' File URLs and (back)slashes
+FAIL URL: Setting <non-spec:/>.pathname = '/.//p' Serialize /. in path assert_equals: expected "non-spec:/.//p" but got "non-spec:/"
+FAIL URL: Setting <non-spec:/>.pathname = '/..//p' assert_equals: expected "non-spec:/.//p" but got "non-spec:/"
+FAIL URL: Setting <non-spec:/>.pathname = '//p' assert_equals: expected "non-spec:/.//p" but got "non-spec:/"
+FAIL URL: Setting <non-spec:/.//>.pathname = 'p' Drop /. from path assert_equals: expected "non-spec:/p" but got "non-spec:/.//"
+PASS URL: Setting <https://example.net#nav>.search = 'lang=fr'
+PASS URL: Setting <https://example.net?lang=en-US#nav>.search = 'lang=fr'
+PASS URL: Setting <https://example.net?lang=en-US#nav>.search = '?lang=fr'
+PASS URL: Setting <https://example.net?lang=en-US#nav>.search = '??lang=fr'
+FAIL URL: Setting <https://example.net?lang=en-US#nav>.search = '?' assert_equals: expected "https://example.net/?#nav" but got "https://example.net/#nav"
+PASS URL: Setting <https://example.net?lang=en-US#nav>.search = ''
+PASS URL: Setting <https://example.net?lang=en-US>.search = ''
+PASS URL: Setting <https://example.net>.search = ''
+FAIL URL: Setting <a:/>.search = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the query encode set. Tabs and newlines are removed. assert_equals: expected "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/?%00%01%1F%20!%22%23$%&%27()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+PASS URL: Setting <http://example.net>.search = '%c3%89té' Bytes already percent-encoded are left as-is
+PASS URL: Setting <https://example.net>.hash = 'main'
+PASS URL: Setting <https://example.net#nav>.hash = 'main'
+PASS URL: Setting <https://example.net?lang=en-US>.hash = '##nav'
+PASS URL: Setting <https://example.net?lang=en-US#nav>.hash = '#main'
+PASS URL: Setting <https://example.net?lang=en-US#nav>.hash = '#'
+PASS URL: Setting <https://example.net?lang=en-US#nav>.hash = ''
+PASS URL: Setting <http://example.net>.hash = '#foo bar'
+PASS URL: Setting <http://example.net>.hash = '#foo"bar'
+PASS URL: Setting <http://example.net>.hash = '#foo<bar'
+PASS URL: Setting <http://example.net>.hash = '#foo>bar'
+PASS URL: Setting <http://example.net>.hash = '#foo`bar'
+PASS URL: Setting <a:/>.hash = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' Simple percent-encoding; tabs and newlines are removed
+PASS URL: Setting <http://example.net>.hash = 'a\0b' Percent-encode NULLs in fragment
+PASS URL: Setting <non-spec:/>.hash = 'a\0b' Percent-encode NULLs in fragment
+PASS URL: Setting <http://example.net>.hash = '%c3%89té' Bytes already percent-encoded are left as-is
+PASS URL: Setting <javascript:alert(1)>.hash = 'castle'
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/idna-2008/fast/url/idna2003-expected.txt b/third_party/blink/web_tests/virtual/idna-2008/fast/url/idna2003-expected.txt
new file mode 100644
index 0000000..b18baec
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/idna-2008/fast/url/idna2003-expected.txt
@@ -0,0 +1,38 @@
+IDNA2003 handling in domain name labels.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+The PASS/FAIL results of this test are set to the behavior in IDNA2003.
+FAIL canonicalize('http://faß.de/') should be http://fass.de/. Was http://xn--fa-hia.de/.
+FAIL canonicalize('http://βόλος.com/') should be http://xn--nxasmq6b.com/. Was http://xn--nxasmm1c.com/.
+FAIL canonicalize('http://ශ්‍රී.com/') should be http://xn--10cl1a0b.com/. Was http://xn--10cl1a0b660p.com/.
+FAIL canonicalize('http://نامه‌ای.com/') should be http://xn--mgba3gch31f.com/. Was http://xn--mgba3gch31f060k.com/.
+PASS canonicalize('http://www.looĸout.net/') is 'http://www.xn--looout-5bb.net/'
+PASS canonicalize('http://ᗯᗯᗯ.lookout.net/') is 'http://xn--1qeaa.lookout.net/'
+PASS canonicalize('http://www.lookout.сом/') is 'http://www.lookout.xn--l1adi/'
+PASS canonicalize('http://www.lookout.net:80/') is 'http://www.lookout.net:80/'
+FAIL canonicalize('http://www‥lookout.net/') should be http://www..lookout.net/. Was http://www%E2%80%A5lookout.net/.
+PASS canonicalize('http://www.lookout‧net/') is 'http://www.xn--lookoutnet-406e/'
+PASS canonicalize('http://www.looĸout.net/') is 'http://www.xn--looout-5bb.net/'
+PASS canonicalize('http://www.lookout.net⩴80/') is 'http://www.lookout.net::%3D80/'
+PASS canonicalize('http://lookout́.net/') is 'http://xn--lookout-zge.net/'
+PASS canonicalize('http://look⁠out.net/') is 'http://lookout.net/'
+PASS canonicalize('http://lookout.net/') is 'http://lookout.net/'
+PASS canonicalize('http://look︀out.net/') is 'http://lookout.net/'
+PASS canonicalize('http://www .lookout.net/') is 'http://www%20.lookout.net/'
+PASS canonicalize('http:// lookout.net/') is 'http://%E1%9A%80lookout.net/'
+PASS canonicalize('http://lookout.net/') is 'http://%1Flookout.net/'
+PASS canonicalize('http://look۝out.net/') is 'http://look%DB%9Dout.net/'
+PASS canonicalize('http://look᠎out.net/') is 'http://look%E1%A0%8Eout.net/'
+FAIL canonicalize('http://look🿾out.net/') should be http://look%F0%9F%BF%BEout.net/. Was http://look%EF%BF%BDout.net/.
+PASS canonicalize('http://lookout.net/') is 'http://look%EF%BF%BAout.net/'
+PASS canonicalize('http://look⿰out.net/') is 'http://look%E2%BF%B0out.net/'
+PASS canonicalize('http://look‮out.net/') is 'http://look%E2%80%AEout.net/'
+PASS canonicalize('http://lookout.net/') is 'http://look%E2%81%ABout.net/'
+PASS canonicalize('http://look󠀁out.net/') is 'http://look%F3%A0%80%81out.net/'
+PASS canonicalize('http://look󠀠out.net/') is 'http://look%F3%A0%80%A0out.net/'
+PASS canonicalize('http://look־out.net/') is 'http://look%D6%BEout.net/'
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/third_party/blink/web_tests/virtual/idna-2008/fast/url/idna2008-expected.txt b/third_party/blink/web_tests/virtual/idna-2008/fast/url/idna2008-expected.txt
new file mode 100644
index 0000000..33d4bcd2
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/idna-2008/fast/url/idna2008-expected.txt
@@ -0,0 +1,28 @@
+IDNA2008 handling in domain name labels.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+The PASS/FAIL results of this test are set to the behavior in IDNA2008.
+PASS canonicalize('http://Bücher.de/') is 'http://xn--bcher-kva.de/'
+PASS canonicalize('http://faß.de/') is 'http://xn--fa-hia.de/'
+PASS canonicalize('http://βόλος.com/') is 'http://xn--nxasmm1c.com/'
+PASS canonicalize('http://ශ්‍රී.com/') is 'http://xn--10cl1a0b660p.com/'
+PASS canonicalize('http://نامه‌ای.com/') is 'http://xn--mgba3gch31f060k.com/'
+FAIL canonicalize('http://♥.net/') should be http://�.net/. Was http://xn--g6h.net/.
+FAIL canonicalize('http://͸.net/') should be http://�.net/. Was http://%CD%B8.net/.
+FAIL canonicalize('http://Ӏ.com/') should be http://�.com/. Was http://%D3%80.com/.
+FAIL canonicalize('http://㛼.com/') should be http://�.com/. Was http://%F0%AF%A1%A8.com/.
+FAIL canonicalize('http://Ↄ.com/') should be http://�.com/. Was http://%E2%86%83.com/.
+PASS canonicalize('http://look͏out.net/') is 'http://lookout.net/'
+PASS canonicalize('http://gOoGle.com/') is 'http://google.com/'
+PASS canonicalize('http://ড়.com/') is 'http://xn--15b8c.com/'
+PASS canonicalize('http://ẞ.com/') is 'http://ss.com/'
+FAIL canonicalize('http://-foo.bar.com/') should be http:///. Was http://-foo.bar.com/.
+FAIL canonicalize('http://foo-.bar.com/') should be http:///. Was http://foo-.bar.com/.
+FAIL canonicalize('http://ab--cd.com/') should be http:///. Was http://ab--cd.com/.
+FAIL canonicalize('http://xn--0.com/') should be http:///. Was http://xn--0.com/.
+FAIL canonicalize('http://foò.bar.com/') should be http://foo%CC%80.bar.com/. Was http://xn--fo-3ja.bar.com/.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/third_party/closure_compiler/externs/runtime.js b/third_party/closure_compiler/externs/runtime.js
index e5770289..b541acd 100644
--- a/third_party/closure_compiler/externs/runtime.js
+++ b/third_party/closure_compiler/externs/runtime.js
@@ -280,7 +280,7 @@
 chrome.runtime.restartAfterDelay = function(seconds, callback) {};
 
 /**
- * Attempts to connect to connect listeners within an extension/app (such as the
+ * Attempts to connect listeners within an extension/app (such as the
  * background page), or other extensions/apps. This is useful for content
  * scripts connecting to their extension processes, inter-app/extension
  * communication, and <a href="manifest/externally_connectable.html">web
diff --git a/third_party/nearby/README.chromium b/third_party/nearby/README.chromium
index f95993a..6254108 100644
--- a/third_party/nearby/README.chromium
+++ b/third_party/nearby/README.chromium
@@ -1,7 +1,7 @@
 Name: Nearby Connections Library
 Short Name: Nearby
 URL: https://github.com/google/nearby
-Version: 4a0242472abc7913d0ce00d83d12284f0b3a0f6e
+Version: e58dd0a5977fa551c8985f913b389940846130a9
 License: Apache 2.0
 License File: LICENSE
 Security Critical: yes
diff --git a/third_party/r8/print_version.sh b/third_party/r8/print_version.sh
index df66377f..e936996e 100755
--- a/third_party/r8/print_version.sh
+++ b/third_party/r8/print_version.sh
@@ -10,6 +10,7 @@
   cipd_hash=$(gclient getdep -r src/third_party/r8:chromium/third_party/r8)
 fi
 echo "CIPD instance: $cipd_hash"
-r8_commit=$(cipd describe chromium/third_party/r8 -version "$cipd_hash" | grep "version:" | grep -P --only-matching '(?<=@).*(?=-)')
+# Multiple version tags can exist when 3pp bot runs again but R8.jar doesn't change.
+r8_commit=$(cipd describe chromium/third_party/r8 -version "$cipd_hash" | grep --max-count=1 "version:" | grep -P --only-matching '(?<=@).*(?=-)')
 echo "R8 commit: $r8_commit"
 echo "Recent commits: https://r8.googlesource.com/r8/+log/$r8_commit"
diff --git a/third_party/rust/Cargo.lock b/third_party/rust/Cargo.lock
index 2d43bcb..61d60f9 100644
--- a/third_party/rust/Cargo.lock
+++ b/third_party/rust/Cargo.lock
@@ -46,7 +46,7 @@
 
 [[package]]
 name = "autocxx"
-version = "0.23.0"
+version = "0.23.1"
 dependencies = [
  "aquamarine",
  "autocxx-macro",
@@ -77,7 +77,7 @@
 
 [[package]]
 name = "autocxx-engine"
-version = "0.23.0"
+version = "0.23.1"
 dependencies = [
  "aquamarine",
  "autocxx-bindgen",
@@ -104,7 +104,7 @@
 
 [[package]]
 name = "autocxx-gen"
-version = "0.23.0"
+version = "0.23.1"
 dependencies = [
  "autocxx-engine",
  "clap",
@@ -117,7 +117,7 @@
 
 [[package]]
 name = "autocxx-macro"
-version = "0.23.0"
+version = "0.23.1"
 dependencies = [
  "autocxx-parser",
  "proc-macro-error",
@@ -128,7 +128,7 @@
 
 [[package]]
 name = "autocxx-parser"
-version = "0.23.0"
+version = "0.23.1"
 dependencies = [
  "indexmap",
  "itertools 0.10.3",
@@ -223,17 +223,22 @@
  "autocxx-bindgen",
  "autocxx-gen",
  "bindgen",
+ "bitflags",
  "cargo-platform",
  "cargo_metadata",
  "clap",
  "cxx",
  "cxxbridge-cmd",
+ "lazy_static",
  "memoffset",
  "once_cell",
+ "proc-macro2",
+ "quote",
  "rstest",
  "rustversion",
  "semver",
  "serde",
+ "serde_json",
  "serde_json_lenient",
  "small_ctor",
  "static_assertions",
@@ -285,7 +290,6 @@
 name = "cxx"
 version = "1.0.81"
 dependencies = [
- "cxxbridge-flags",
  "cxxbridge-macro",
  "link-cplusplus",
 ]
@@ -312,10 +316,6 @@
 ]
 
 [[package]]
-name = "cxxbridge-flags"
-version = "1.0.81"
-
-[[package]]
 name = "cxxbridge-macro"
 version = "1.0.81"
 dependencies = [
diff --git a/third_party/rust/Cargo.toml b/third_party/rust/Cargo.toml
index 1b85d3a..a5962cff8 100644
--- a/third_party/rust/Cargo.toml
+++ b/third_party/rust/Cargo.toml
@@ -10,9 +10,11 @@
 
 [dependencies]
 autocxx = "0.23.1"
+bitflags = "1"
 cxx = "1"
 cxxbridge-cmd = "1"
-serde = "1"
+proc-macro2 = "1"
+quote = "1"
 static_assertions = "1"
 
 [dependencies.autocxx-bindgen]
@@ -41,6 +43,10 @@
 version = "0.1"
 features = ["unbounded_depth", "float_roundtrip"]
 
+[dependencies.syn]
+version = "1"
+features = ["full"]
+
 [dependencies.unicode-linebreak]
 version = "0.1"
 build-script-outputs = ["tables.rs"]
@@ -49,16 +55,15 @@
 cargo-platform = "0.1"
 cargo_metadata = "0.14"
 clap = "3"
+lazy_static = "1"
 once_cell = "1"
 rstest = "0.12"
 semver = "1"
+serde = "1"
+serde_json = "1"
 small_ctor = "0.1"
 tempfile = "3"
 
-[dev-dependencies.syn]
-version = "1"
-features = ["full"]
-
 [dev-dependencies.toml]
 version = "0.5"
 features = ["preserve_order"]
@@ -170,10 +175,6 @@
 path = "cxxbridge_cmd/v1/crate"
 package = "cxxbridge-cmd"
 
-[patch.crates-io.cxxbridge-flags_v1]
-path = "cxxbridge_flags/v1/crate"
-package = "cxxbridge-flags"
-
 [patch.crates-io.cxxbridge-macro_v1]
 path = "cxxbridge_macro/v1/crate"
 package = "cxxbridge-macro"
diff --git a/third_party/rust/autocxx/v0_23/BUILD.gn b/third_party/rust/autocxx/v0_23/BUILD.gn
index b0e7e4e..4950521d 100644
--- a/third_party/rust/autocxx/v0_23/BUILD.gn
+++ b/third_party/rust/autocxx/v0_23/BUILD.gn
@@ -14,7 +14,7 @@
   build_native_rust_unit_tests = false
   sources = [ "crate/src/lib.rs" ]
   edition = "2021"
-  cargo_pkg_version = "0.23.0"
+  cargo_pkg_version = "0.23.1"
   cargo_pkg_authors = "Adrian Taylor <adetaylor@chromium.org>"
   cargo_pkg_name = "autocxx"
   cargo_pkg_description = "Safe autogenerated interop between Rust and C++"
diff --git a/third_party/rust/autocxx_engine/v0_23/BUILD.gn b/third_party/rust/autocxx_engine/v0_23/BUILD.gn
index 2df07c8..0b546c1 100644
--- a/third_party/rust/autocxx_engine/v0_23/BUILD.gn
+++ b/third_party/rust/autocxx_engine/v0_23/BUILD.gn
@@ -18,7 +18,7 @@
   build_native_rust_unit_tests = false
   sources = [ "crate/src/lib.rs" ]
   edition = "2021"
-  cargo_pkg_version = "0.23.0"
+  cargo_pkg_version = "0.23.1"
   cargo_pkg_authors = "Adrian Taylor <adetaylor@chromium.org>"
   cargo_pkg_name = "autocxx-engine"
   cargo_pkg_description = "Safe autogenerated interop between Rust and C++"
diff --git a/third_party/rust/autocxx_gen/v0_23/BUILD.gn b/third_party/rust/autocxx_gen/v0_23/BUILD.gn
index 3dab23b..ad4f1724 100644
--- a/third_party/rust/autocxx_gen/v0_23/BUILD.gn
+++ b/third_party/rust/autocxx_gen/v0_23/BUILD.gn
@@ -12,7 +12,7 @@
   build_native_rust_unit_tests = false
   sources = [ "crate/src/main.rs" ]
   edition = "2021"
-  cargo_pkg_version = "0.23.0"
+  cargo_pkg_version = "0.23.1"
   cargo_pkg_authors = "Adrian Taylor <adetaylor@chromium.org>"
   cargo_pkg_name = "autocxx-gen"
   cargo_pkg_description = "Safe autogenerated interop between Rust and C++"
diff --git a/third_party/rust/autocxx_macro/v0_23/BUILD.gn b/third_party/rust/autocxx_macro/v0_23/BUILD.gn
index 3ec52a8..65b7dfc 100644
--- a/third_party/rust/autocxx_macro/v0_23/BUILD.gn
+++ b/third_party/rust/autocxx_macro/v0_23/BUILD.gn
@@ -18,7 +18,7 @@
   build_native_rust_unit_tests = false
   sources = [ "crate/src/lib.rs" ]
   edition = "2021"
-  cargo_pkg_version = "0.23.0"
+  cargo_pkg_version = "0.23.1"
   cargo_pkg_authors = "Adrian Taylor <adetaylor@chromium.org>"
   cargo_pkg_name = "autocxx-macro"
   cargo_pkg_description = "Safe autogenerated interop between Rust and C++"
diff --git a/third_party/rust/autocxx_parser/v0_23/BUILD.gn b/third_party/rust/autocxx_parser/v0_23/BUILD.gn
index 146b612..0c6e533 100644
--- a/third_party/rust/autocxx_parser/v0_23/BUILD.gn
+++ b/third_party/rust/autocxx_parser/v0_23/BUILD.gn
@@ -18,7 +18,7 @@
   build_native_rust_unit_tests = false
   sources = [ "crate/src/lib.rs" ]
   edition = "2021"
-  cargo_pkg_version = "0.23.0"
+  cargo_pkg_version = "0.23.1"
   cargo_pkg_authors = "Adrian Taylor <adetaylor@chromium.org>"
   cargo_pkg_name = "autocxx-parser"
   cargo_pkg_description = "Safe autogenerated interop between Rust and C++"
diff --git a/third_party/rust/bitflags/v1/BUILD.gn b/third_party/rust/bitflags/v1/BUILD.gn
index 5abad5b..ea51e41 100644
--- a/third_party/rust/bitflags/v1/BUILD.gn
+++ b/third_party/rust/bitflags/v1/BUILD.gn
@@ -8,10 +8,6 @@
   crate_name = "bitflags"
   epoch = "1"
   crate_type = "rlib"
-
-  # Only for usage from third-party crates. Add the crate to
-  # third_party.toml to use it from first-party code.
-  visibility = [ "//third_party/rust/*" ]
   crate_root = "crate/src/lib.rs"
 
   # Unit tests skipped. Generate with --with-tests to include them.
diff --git a/third_party/rust/lazy_static/v1/BUILD.gn b/third_party/rust/lazy_static/v1/BUILD.gn
index f35d858..8e56ef7 100644
--- a/third_party/rust/lazy_static/v1/BUILD.gn
+++ b/third_party/rust/lazy_static/v1/BUILD.gn
@@ -8,10 +8,23 @@
   crate_name = "lazy_static"
   epoch = "1"
   crate_type = "rlib"
+  crate_root = "crate/src/lib.rs"
 
-  # Only for usage from third-party crates. Add the crate to
-  # third_party.toml to use it from first-party code.
-  visibility = [ "//third_party/rust/*" ]
+  # Unit tests skipped. Generate with --with-tests to include them.
+  build_native_rust_unit_tests = false
+  sources = [ "crate/src/lib.rs" ]
+  edition = "2015"
+  cargo_pkg_version = "1.4.0"
+  cargo_pkg_authors = "Marvin Löbel <loebel.marvin@gmail.com>"
+  cargo_pkg_name = "lazy_static"
+  cargo_pkg_description =
+      "A macro for declaring lazily evaluated statics in Rust."
+}
+cargo_crate("test_support") {
+  crate_name = "lazy_static"
+  epoch = "1"
+  crate_type = "rlib"
+  testonly = true
   crate_root = "crate/src/lib.rs"
 
   # Unit tests skipped. Generate with --with-tests to include them.
diff --git a/third_party/rust/proc_macro2/v1/BUILD.gn b/third_party/rust/proc_macro2/v1/BUILD.gn
index 92e003b2..3aba2aa 100644
--- a/third_party/rust/proc_macro2/v1/BUILD.gn
+++ b/third_party/rust/proc_macro2/v1/BUILD.gn
@@ -8,10 +8,6 @@
   crate_name = "proc_macro2"
   epoch = "1"
   crate_type = "rlib"
-
-  # Only for usage from third-party crates. Add the crate to
-  # third_party.toml to use it from first-party code.
-  visibility = [ "//third_party/rust/*" ]
   crate_root = "crate/src/lib.rs"
 
   # Unit tests skipped. Generate with --with-tests to include them.
diff --git a/third_party/rust/quote/v1/BUILD.gn b/third_party/rust/quote/v1/BUILD.gn
index 69b810c..68f3b9ea 100644
--- a/third_party/rust/quote/v1/BUILD.gn
+++ b/third_party/rust/quote/v1/BUILD.gn
@@ -8,10 +8,6 @@
   crate_name = "quote"
   epoch = "1"
   crate_type = "rlib"
-
-  # Only for usage from third-party crates. Add the crate to
-  # third_party.toml to use it from first-party code.
-  visibility = [ "//third_party/rust/*" ]
   crate_root = "crate/src/lib.rs"
 
   # Unit tests skipped. Generate with --with-tests to include them.
diff --git a/third_party/rust/serde/v1/BUILD.gn b/third_party/rust/serde/v1/BUILD.gn
index 9a7b0aa..4a71dfc 100644
--- a/third_party/rust/serde/v1/BUILD.gn
+++ b/third_party/rust/serde/v1/BUILD.gn
@@ -27,3 +27,27 @@
   build_root = "crate/build.rs"
   build_sources = [ "crate/build.rs" ]
 }
+cargo_crate("test_support") {
+  crate_name = "serde"
+  epoch = "1"
+  crate_type = "rlib"
+  testonly = true
+  crate_root = "crate/src/lib.rs"
+
+  # Unit tests skipped. Generate with --with-tests to include them.
+  build_native_rust_unit_tests = false
+  sources = [ "crate/src/lib.rs" ]
+  edition = "2015"
+  cargo_pkg_version = "1.0.147"
+  cargo_pkg_authors = "Erick Tryzelaar <erick.tryzelaar@gmail.com>, David Tolnay <dtolnay@gmail.com>"
+  cargo_pkg_name = "serde"
+  cargo_pkg_description = "A generic serialization/deserialization framework"
+  deps = [ "//third_party/rust/serde_derive/v1:lib" ]
+  features = [
+    "derive",
+    "serde_derive",
+    "std",
+  ]
+  build_root = "crate/build.rs"
+  build_sources = [ "crate/build.rs" ]
+}
diff --git a/third_party/rust/serde_json/v1/BUILD.gn b/third_party/rust/serde_json/v1/BUILD.gn
index e184036..47a755d 100644
--- a/third_party/rust/serde_json/v1/BUILD.gn
+++ b/third_party/rust/serde_json/v1/BUILD.gn
@@ -8,10 +8,33 @@
   crate_name = "serde_json"
   epoch = "1"
   crate_type = "rlib"
+  crate_root = "crate/src/lib.rs"
 
-  # Only for usage from third-party crates. Add the crate to
-  # third_party.toml to use it from first-party code.
-  visibility = [ "//third_party/rust/*" ]
+  # Unit tests skipped. Generate with --with-tests to include them.
+  build_native_rust_unit_tests = false
+  sources = [ "crate/src/lib.rs" ]
+  edition = "2018"
+  cargo_pkg_version = "1.0.78"
+  cargo_pkg_authors = "Erick Tryzelaar <erick.tryzelaar@gmail.com>, David Tolnay <dtolnay@gmail.com>"
+  cargo_pkg_name = "serde_json"
+  cargo_pkg_description = "A JSON serialization file format"
+  deps = [
+    "//third_party/rust/itoa/v1:lib",
+    "//third_party/rust/ryu/v1:lib",
+    "//third_party/rust/serde/v1:lib",
+  ]
+  features = [
+    "std",
+    "unbounded_depth",
+  ]
+  build_root = "crate/build.rs"
+  build_sources = [ "crate/build.rs" ]
+}
+cargo_crate("test_support") {
+  crate_name = "serde_json"
+  epoch = "1"
+  crate_type = "rlib"
+  testonly = true
   crate_root = "crate/src/lib.rs"
 
   # Unit tests skipped. Generate with --with-tests to include them.
diff --git a/third_party/rust/serde_json_lenient/v0_1/wrapper/BUILD.gn b/third_party/rust/serde_json_lenient/v0_1/wrapper/BUILD.gn
new file mode 100644
index 0000000..ecb927c3
--- /dev/null
+++ b/third_party/rust/serde_json_lenient/v0_1/wrapper/BUILD.gn
@@ -0,0 +1,27 @@
+# Copyright 2022 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/rust/rust_static_library.gni")
+
+rust_static_library("wrapper") {
+  crate_root = "lib.rs"
+  sources = [
+    "lib.rs",
+    "visitor.rs",
+  ]
+
+  cxx_bindings = [ "lib.rs" ]
+
+  public_deps = [ ":wrapper_functions" ]
+  deps = [
+    "//third_party/rust/serde/v1:lib",
+    "//third_party/rust/serde_json_lenient/v0_1:lib",
+  ]
+}
+
+source_set("wrapper_functions") {
+  visibility = [ ":*" ]
+  deps = [ "//build/rust:cxx_cppdeps" ]
+  sources = [ "functions.h" ]
+}
diff --git a/third_party/rust/serde_json_lenient/v0_1/wrapper/functions.h b/third_party/rust/serde_json_lenient/v0_1/wrapper/functions.h
new file mode 100644
index 0000000..458ce73e
--- /dev/null
+++ b/third_party/rust/serde_json_lenient/v0_1/wrapper/functions.h
@@ -0,0 +1,98 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_RUST_SERDE_JSON_LENIENT_V0_1_WRAPPER_SERDE_JSON_LENIENT_H_
+#define THIRD_PARTY_RUST_SERDE_JSON_LENIENT_V0_1_WRAPPER_SERDE_JSON_LENIENT_H_
+
+#include <stdint.h>
+
+#include "third_party/rust/cxx/v1/crate/include/cxx.h"
+
+namespace serde_json_lenient {
+
+// An opaque pointer provided by the caller to decode_json() which is passed
+// through to the visitor functions on `Functions`.
+struct ContextPointer;
+
+// C++ functions that provide the functionality for each stop by the JSON
+// deserializer in order to construct the output in C++ from the JSON
+// deserialization.
+//
+// TODO(danakj): CXX does not support function pointers, so we need to use a
+// struct of methods (this struct) for it to call instead of a structure of
+// function pointers which could be defined directly in the Rust code.
+struct Functions {
+  void (*list_append_none_fn)(ContextPointer&);
+  void (*list_append_bool_fn)(ContextPointer&, bool);
+  void (*list_append_i32_fn)(ContextPointer&, int32_t);
+  void (*list_append_f64_fn)(ContextPointer&, double);
+  void (*list_append_str_fn)(ContextPointer&, rust::Str);
+  // The returned ContextPointer reference is given to the visitor functions as
+  // an argument for all nodes visited in this list.
+  ContextPointer& (*list_append_list_fn)(ContextPointer&, size_t);
+  // The returned ContextPointer reference is given to the visitor functions as
+  // an argument for all nodes visited in this dict.
+  ContextPointer& (*list_append_dict_fn)(ContextPointer&);
+
+  void (*dict_set_none_fn)(ContextPointer&, rust::Str);
+  void (*dict_set_bool_fn)(ContextPointer&, rust::Str, bool);
+  void (*dict_set_i32_fn)(ContextPointer&, rust::Str, int32_t);
+  void (*dict_set_f64_fn)(ContextPointer&, rust::Str, double);
+  void (*dict_set_str_fn)(ContextPointer&, rust::Str, rust::Str);
+  // The returned ContextPointer reference is given to the visitor functions as
+  // an argument for all nodes visited in this list.
+  ContextPointer& (*dict_set_list_fn)(ContextPointer&, rust::Str, size_t);
+  // The returned ContextPointer reference is given to the visitor functions as
+  // an argument for all nodes visited in this dict.
+  ContextPointer& (*dict_set_dict_fn)(ContextPointer&, rust::Str);
+
+  void list_append_none(ContextPointer& c) const {
+    list_append_none_fn(c);
+  }
+  void list_append_bool(ContextPointer& c, bool val) const {
+    list_append_bool_fn(c, val);
+  }
+  void list_append_i32(ContextPointer& c, int32_t val) const {
+    list_append_i32_fn(c,val);
+  }
+  void list_append_f64(ContextPointer& c, double val) const {
+    list_append_f64_fn(c, val);
+  }
+  void list_append_str(ContextPointer& c, rust::Str val) const {
+    list_append_str_fn(c, val);
+  }
+  ContextPointer& list_append_list(ContextPointer& c, size_t reserve) const {
+    return list_append_list_fn(c, reserve);
+  }
+  ContextPointer& list_append_dict(ContextPointer& c) const {
+    return list_append_dict_fn(c);
+  }
+
+  void dict_set_none(ContextPointer& c, rust::Str key) const {
+    dict_set_none_fn(c, key);
+  }
+  void dict_set_bool(ContextPointer& c, rust::Str key, bool val) const {
+    dict_set_bool_fn(c, key, val);
+  }
+  void dict_set_i32(ContextPointer& c, rust::Str key, int32_t val) const {
+    dict_set_i32_fn(c, key,val);
+  }
+  void dict_set_f64(ContextPointer& c, rust::Str key, double val) const {
+    dict_set_f64_fn(c, key, val);
+  }
+  void dict_set_str(ContextPointer& c, rust::Str key, rust::Str val) const {
+    dict_set_str_fn(c, key, val);
+  }
+  ContextPointer& dict_set_list(ContextPointer& c, rust::Str key,
+                                size_t reserve) const {
+    return dict_set_list_fn(c, key, reserve);
+  }
+  ContextPointer& dict_set_dict(ContextPointer& c, rust::Str key) const {
+    return dict_set_dict_fn(c, key);
+  }
+};
+
+}  // namespace serde_json_lenient
+
+#endif  // THIRD_PARTY_RUST_SERDE_JSON_LENIENT_V0_1_WRAPPER_SERDE_JSON_LENIENT_H_
diff --git a/third_party/rust/serde_json_lenient/v0_1/wrapper/lib.rs b/third_party/rust/serde_json_lenient/v0_1/wrapper/lib.rs
new file mode 100644
index 0000000..2b41bc8
--- /dev/null
+++ b/third_party/rust/serde_json_lenient/v0_1/wrapper/lib.rs
@@ -0,0 +1,167 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+mod visitor;
+
+use crate::visitor::ValueVisitor;
+
+use serde::de::Deserializer;
+use serde_json_lenient::de::SliceRead;
+use std::pin::Pin;
+
+/// UTF8 byte order mark.
+const UTF8_BOM: [u8; 3] = [0xef, 0xbb, 0xbf];
+
+/// C++ bindings.
+#[cxx::bridge(namespace=serde_json_lenient)]
+mod ffi {
+    // From the `wrapper_functions` target.
+    unsafe extern "C++" {
+        include!("third_party/rust/serde_json_lenient/v0_1/wrapper/functions.h");
+
+        type ContextPointer;
+
+        type Functions;
+        fn list_append_none(self: &Functions, ctx: Pin<&mut ContextPointer>);
+        fn list_append_bool(self: &Functions, ctx: Pin<&mut ContextPointer>, val: bool);
+        fn list_append_i32(self: &Functions, ctx: Pin<&mut ContextPointer>, val: i32);
+        fn list_append_f64(self: &Functions, ctx: Pin<&mut ContextPointer>, val: f64);
+        fn list_append_str(self: &Functions, ctx: Pin<&mut ContextPointer>, val: &str);
+        fn list_append_list<'a>(
+            self: &Functions,
+            ctx: Pin<&'a mut ContextPointer>,
+            reserve: usize,
+        ) -> Pin<&'a mut ContextPointer>;
+        fn list_append_dict<'a>(
+            self: &Functions,
+            ctx: Pin<&'a mut ContextPointer>,
+        ) -> Pin<&'a mut ContextPointer>;
+
+        fn dict_set_none(self: &Functions, ctx: Pin<&mut ContextPointer>, key: &str);
+        fn dict_set_bool(self: &Functions, ctx: Pin<&mut ContextPointer>, key: &str, val: bool);
+        fn dict_set_i32(self: &Functions, ctx: Pin<&mut ContextPointer>, key: &str, val: i32);
+        fn dict_set_f64(self: &Functions, ctx: Pin<&mut ContextPointer>, key: &str, val: f64);
+        fn dict_set_str(self: &Functions, ctx: Pin<&mut ContextPointer>, key: &str, val: &str);
+        fn dict_set_list<'f, 'a>(
+            self: &Functions,
+            ctx: Pin<&'a mut ContextPointer>,
+            key: &'f str,
+            reserve: usize,
+        ) -> Pin<&'a mut ContextPointer>;
+        fn dict_set_dict<'f, 'a>(
+            self: &Functions,
+            ctx: Pin<&'a mut ContextPointer>,
+            key: &'f str,
+        ) -> Pin<&'a mut ContextPointer>;
+    }
+
+    extern "Rust" {
+        fn decode_json(
+            json: &[u8],
+            options: &JsonOptions,
+            functions: &Functions,
+            ctx: Pin<&mut ContextPointer>,
+            error: Pin<&mut DecodeError>,
+        ) -> bool;
+    }
+
+    struct DecodeError {
+        line: i32,
+        column: i32,
+        message: String,
+    }
+
+    /// Options for parsing JSON inputs. A mirror of the C++ `base::JSONParserOptions` bitflags,
+    /// represented as a friendlier struct-of-bools instead, and with additional fields
+    struct JsonOptions {
+        /// Allows commas to exist after the last element in structures.
+        allow_trailing_commas: bool,
+        /// If set the parser replaces invalid code points (i.e. lone surrogates) with the Unicode
+        /// replacement character (U+FFFD). If not set, invalid code points trigger a hard error and
+        /// parsing fails.
+        replace_invalid_characters: bool,
+        /// Allows both C (/* */) and C++ (//) style comments.
+        allow_comments: bool,
+        /// Permits unescaped ASCII control characters (such as unescaped \r and \n) in the range
+        /// [0x00,0x1F].
+        allow_control_chars: bool,
+        /// Permits \\v vertical tab escapes.
+        allow_vert_tab: bool,
+        /// Permits \\xNN escapes as described above.
+        allow_x_escapes: bool,
+
+        /// The maximum recursion depth to walk while parsing nested JSON objects. JSON beyond the
+        /// specified depth will be ignored.
+        max_depth: usize,
+    }
+}
+
+pub type DecodeError = ffi::DecodeError;
+pub type JsonOptions = ffi::JsonOptions;
+pub type Functions = ffi::Functions;
+pub type ContextPointer = ffi::ContextPointer;
+
+/// Decode a JSON input from `json` and call back out to functions defined in `options` when
+/// visiting each node in order for the caller to construct an output.
+///
+/// The first item visited will be appened to the `ctx` as if the `ctx` were a list. This means the
+/// `ContextPointer` in `ctx` must already be a list aggregate type, unless the caller has extra
+/// logic to handle the first element visited.
+///
+/// The `error` is only written to when there is an error decoding and `false` is returned.
+///
+/// # Returns
+///
+/// Whether the decode succeeded.
+pub fn decode_json(
+    json: &[u8],
+    options: &JsonOptions,
+    functions: &Functions,
+    // TODO(danakj): Use std::ptr::NonNull when the binding generator supports it.
+    ctx: Pin<&mut ContextPointer>,
+    // TODO(danakj): Return `Result<(), DecodeError>` once the binding generator supports it.
+    mut error: Pin<&mut DecodeError>,
+) -> bool {
+    let mut deserializer = serde_json_lenient::Deserializer::new(SliceRead::new(
+        if json.starts_with(&UTF8_BOM) { &json[3..] } else { json },
+        options.replace_invalid_characters,
+        options.allow_control_chars,
+        options.allow_vert_tab,
+        options.allow_x_escapes,
+    ));
+    deserializer.set_ignore_trailing_commas(options.allow_trailing_commas);
+    deserializer.set_allow_comments(options.allow_comments);
+
+    // We track recursion depth ourselves to limit it to `max_depth` option.
+    deserializer.disable_recursion_limit();
+
+    // The first element visited will be treated as if being appended to a list, as is specified in
+    // the contract of `decode_json()`.
+    //
+    // SAFETY: We have only a single ContextPointer around at a time, so this reference will not
+    // alias. The lifetime of the ContextPointer exceeds this function's lifetime, so we are okay to
+    // tie it to the `target`'s lifetime which is shorter.
+    //
+    // Dereferencing the ContextPointer in C++ would be Undefined Behaviour since it's not a similar
+    // type to the actual type it's pointing to, but Rust allows us to make a reference to it
+    // regardless.
+    let target = visitor::DeserializationTarget::List { ctx };
+
+    let result =
+        deserializer.deserialize_any(ValueVisitor::new(&functions, target, options.max_depth));
+    match result.and(deserializer.end()) {
+        Ok(()) => true,
+        Err(err) => {
+            error.as_mut().line = err.line().try_into().unwrap_or(-1);
+            error.as_mut().column = err.column().try_into().unwrap_or(-1);
+            error.as_mut().message.clear();
+            // The following line pulls in a lot of binary bloat, due to all the formatter
+            // implementations required to stringify error messages. This error message is used in
+            // only a couple of places outside unit tests so we could consider trying
+            // to eliminate.
+            error.as_mut().message.push_str(&err.to_string());
+            false
+        }
+    }
+}
diff --git a/third_party/rust/serde_json_lenient/v0_1/wrapper/visitor.rs b/third_party/rust/serde_json_lenient/v0_1/wrapper/visitor.rs
new file mode 100644
index 0000000..856f08a
--- /dev/null
+++ b/third_party/rust/serde_json_lenient/v0_1/wrapper/visitor.rs
@@ -0,0 +1,192 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use crate::{ContextPointer, Functions};
+use serde::de::{DeserializeSeed, Deserializer, Error, MapAccess, SeqAccess, Visitor};
+use std::convert::TryFrom;
+use std::fmt;
+use std::pin::Pin;
+
+/// Watches to ensure recursion does not go too deep during deserialization.
+struct RecursionDepthCheck(usize);
+
+impl RecursionDepthCheck {
+    /// Recurse a level and return an error if we've recursed too far.
+    fn recurse<E: Error>(&self) -> Result<RecursionDepthCheck, E> {
+        match self.0.checked_sub(1) {
+            Some(recursion_limit) => Ok(RecursionDepthCheck(recursion_limit)),
+            None => Err(Error::custom("recursion limit exceeded")),
+        }
+    }
+}
+
+/// What type of aggregate JSON type is being deserialized.
+pub enum DeserializationTarget<'c> {
+    /// Deserialize by appending to a list.
+    List { ctx: Pin<&'c mut ContextPointer> },
+    /// Deserialize by setting a dictionary key.
+    Dict { ctx: Pin<&'c mut ContextPointer>, key: String },
+}
+
+/// A deserializer and visitor type that is used to visit each value in the JSON input when it is
+/// deserialized.
+///
+/// Normally serde deserialization instantiates a new object, but this visitor is designed to call
+/// back into C++ for creating the deserialized objects. To achieve this we use a feature of serde
+/// called "stateful deserialization" (https://docs.serde.rs/serde/de/trait.DeserializeSeed.html).
+pub struct ValueVisitor<'f, 'c> {
+    fns: &'f Functions,
+    aggregate: DeserializationTarget<'c>,
+    recursion_depth_check: RecursionDepthCheck,
+}
+
+impl<'f, 'c> ValueVisitor<'f, 'c> {
+    pub fn new(fns: &'f Functions, target: DeserializationTarget<'c>, max_depth: usize) -> Self {
+        Self {
+            fns,
+            aggregate: target,
+            // The `max_depth` includes the top level of the JSON input, which is where parsing
+            // starts. We subtract 1 to count the top level now.
+            recursion_depth_check: RecursionDepthCheck(max_depth - 1),
+        }
+    }
+}
+
+impl<'de, 'f, 'c> Visitor<'de> for ValueVisitor<'f, 'c> {
+    // We call out to C++ to construct the deserialized type, so no output from the visitor.
+    type Value = ();
+
+    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        formatter.write_str("any valid JSON")
+    }
+
+    fn visit_i32<E: serde::de::Error>(self, value: i32) -> Result<Self::Value, E> {
+        match self.aggregate {
+            DeserializationTarget::List { ctx } => self.fns.list_append_i32(ctx, value),
+            DeserializationTarget::Dict { ctx, key } => self.fns.dict_set_i32(ctx, &key, value),
+        };
+        Ok(())
+    }
+
+    fn visit_i8<E: serde::de::Error>(self, value: i8) -> Result<Self::Value, E> {
+        self.visit_i32(value as i32)
+    }
+
+    fn visit_bool<E: serde::de::Error>(self, value: bool) -> Result<Self::Value, E> {
+        match self.aggregate {
+            DeserializationTarget::List { ctx } => self.fns.list_append_bool(ctx, value),
+            DeserializationTarget::Dict { ctx, key } => self.fns.dict_set_bool(ctx, &key, value),
+        };
+        Ok(())
+    }
+
+    fn visit_i64<E: serde::de::Error>(self, value: i64) -> Result<Self::Value, E> {
+        // Integer values that are > 32 bits large are returned as doubles instead. See
+        // JSONReaderTest.LargerIntIsLossy for a related test.
+        match i32::try_from(value) {
+            Ok(value) => self.visit_i32(value),
+            Err(_) => self.visit_f64(value as f64),
+        }
+    }
+
+    fn visit_u64<E: serde::de::Error>(self, value: u64) -> Result<Self::Value, E> {
+        // See visit_i64 comment.
+        match i32::try_from(value) {
+            Ok(value) => self.visit_i32(value),
+            Err(_) => self.visit_f64(value as f64),
+        }
+    }
+
+    fn visit_f64<E: serde::de::Error>(self, value: f64) -> Result<Self::Value, E> {
+        match self.aggregate {
+            DeserializationTarget::List { ctx } => self.fns.list_append_f64(ctx, value),
+            DeserializationTarget::Dict { ctx, key } => self.fns.dict_set_f64(ctx, &key, value),
+        };
+        Ok(())
+    }
+
+    fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
+        match self.aggregate {
+            DeserializationTarget::List { ctx } => self.fns.list_append_str(ctx, value),
+            DeserializationTarget::Dict { ctx, key } => self.fns.dict_set_str(ctx, &key, value),
+        };
+        Ok(())
+    }
+
+    fn visit_borrowed_str<E: serde::de::Error>(self, value: &'de str) -> Result<Self::Value, E> {
+        match self.aggregate {
+            DeserializationTarget::List { ctx } => self.fns.list_append_str(ctx, value),
+            DeserializationTarget::Dict { ctx, key } => self.fns.dict_set_str(ctx, &key, value),
+        };
+        Ok(())
+    }
+
+    fn visit_string<E: serde::de::Error>(self, value: String) -> Result<Self::Value, E> {
+        self.visit_str(&value)
+    }
+
+    fn visit_none<E: serde::de::Error>(self) -> Result<Self::Value, E> {
+        match self.aggregate {
+            DeserializationTarget::List { ctx } => self.fns.list_append_none(ctx),
+            DeserializationTarget::Dict { ctx, key } => self.fns.dict_set_none(ctx, &key),
+        };
+        Ok(())
+    }
+
+    fn visit_unit<E: serde::de::Error>(self) -> Result<Self::Value, E> {
+        self.visit_none()
+    }
+
+    fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
+    where
+        M: MapAccess<'de>,
+    {
+        // TODO(danakj): base::Value::Dict doesn't expose a way to reserve space, so we don't bother
+        // using `access.size_hint()` here, unlike when creating a list.
+        let mut inner_ctx = match self.aggregate {
+            DeserializationTarget::List { ctx } => self.fns.list_append_dict(ctx),
+            DeserializationTarget::Dict { ctx, key } => self.fns.dict_set_dict(ctx, &key),
+        };
+        while let Some(key) = access.next_key::<String>()? {
+            access.next_value_seed(ValueVisitor {
+                fns: self.fns,
+                aggregate: DeserializationTarget::Dict { ctx: inner_ctx.as_mut(), key },
+                recursion_depth_check: self.recursion_depth_check.recurse()?,
+            })?;
+        }
+        Ok(())
+    }
+
+    fn visit_seq<S>(self, mut access: S) -> Result<Self::Value, S::Error>
+    where
+        S: SeqAccess<'de>,
+    {
+        let mut inner_ctx = match self.aggregate {
+            DeserializationTarget::List { ctx } => {
+                self.fns.list_append_list(ctx, access.size_hint().unwrap_or(0))
+            }
+            DeserializationTarget::Dict { ctx, key } => {
+                self.fns.dict_set_list(ctx, &key, access.size_hint().unwrap_or(0))
+            }
+        };
+        while let Some(_) = access.next_element_seed(ValueVisitor {
+            fns: self.fns,
+            aggregate: DeserializationTarget::List { ctx: inner_ctx.as_mut() },
+            recursion_depth_check: self.recursion_depth_check.recurse()?,
+        })? {}
+        Ok(())
+    }
+}
+
+impl<'de, 'f, 'c> DeserializeSeed<'de> for ValueVisitor<'f, 'c> {
+    // We call out to C++ to construct the deserialized type, so no output from here.
+    type Value = ();
+
+    fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        deserializer.deserialize_any(self)
+    }
+}
diff --git a/third_party/rust/syn/v1/BUILD.gn b/third_party/rust/syn/v1/BUILD.gn
index d38841b..dd87f41 100644
--- a/third_party/rust/syn/v1/BUILD.gn
+++ b/third_party/rust/syn/v1/BUILD.gn
@@ -38,38 +38,3 @@
   build_root = "crate/build.rs"
   build_sources = [ "crate/build.rs" ]
 }
-cargo_crate("test_support") {
-  crate_name = "syn"
-  epoch = "1"
-  crate_type = "rlib"
-  testonly = true
-  crate_root = "crate/src/lib.rs"
-
-  # Unit tests skipped. Generate with --with-tests to include them.
-  build_native_rust_unit_tests = false
-  sources = [ "crate/src/lib.rs" ]
-  edition = "2018"
-  cargo_pkg_version = "1.0.99"
-  cargo_pkg_authors = "David Tolnay <dtolnay@gmail.com>"
-  cargo_pkg_name = "syn"
-  cargo_pkg_description = "Parser for Rust source code"
-  deps = [
-    "//third_party/rust/proc_macro2/v1:lib",
-    "//third_party/rust/quote/v1:lib",
-    "//third_party/rust/unicode_ident/v1:lib",
-  ]
-  features = [
-    "clone-impls",
-    "derive",
-    "extra-traits",
-    "full",
-    "parsing",
-    "printing",
-    "proc-macro",
-    "quote",
-    "visit",
-    "visit-mut",
-  ]
-  build_root = "crate/build.rs"
-  build_sources = [ "crate/build.rs" ]
-}
diff --git a/third_party/rust/third_party.toml b/third_party/rust/third_party.toml
index 6275819..b8d015e 100644
--- a/third_party/rust/third_party.toml
+++ b/third_party/rust/third_party.toml
@@ -43,12 +43,15 @@
 ]
 
 [dependencies]
+autocxx = "0.23.1"
+bitflags = "1"
 cxxbridge-cmd = "1"
 cxx = "1"
-serde = "1"
-autocxx = "0.23.1"
 memoffset = { version = "0.6", features = [ "unstable_const" ] }
+proc-macro2 = "1"
+quote = "1"
 static_assertions = "1"
+syn = {version = "1", features = ["full"]}
 
 [dependencies.autocxx-gen]
 version = "0.23"
@@ -83,9 +86,9 @@
 # This allows them built in a configuration that can be used from Chromium
 # tests, but not used from production.
 [dev-dependencies]
+lazy_static = "1"
 rstest = "0.12"
 small_ctor = "0.1"
-syn = {version = "1", features = ["full"]}
 
 # Used in Rust tool for BUILD.gn generation.
 cargo_metadata = "0.14"
@@ -93,5 +96,7 @@
 clap = "3"
 once_cell = "1"
 semver = "1"
+serde = "1"
+serde_json = "1"
 tempfile = "3"
 toml = {version = "0.5", features = ["preserve_order"]}
diff --git a/third_party/sqlite/sqlite3.h b/third_party/sqlite/sqlite3.h
index 1e87506..6713bf1 100644
--- a/third_party/sqlite/sqlite3.h
+++ b/third_party/sqlite/sqlite3.h
@@ -14,30 +14,6 @@
 // dynamic library loader.
 #include "third_party/sqlite/src/amalgamation/rename_exports.h"
 
-#if defined(SQLITE_OMIT_COMPLETE)
-// When SQLITE_OMIT_COMPLETE is defined, sqlite3.h does not emit a declaration
-// for sqlite3_complete(). SQLite's shell.c stubs out the function by #defining
-// a macro.
-//
-// In order to avoid a macro redefinition warning, we must undo the #define in
-// rename_exports.h.
-//
-// Historical note: SQLite's shell.c initially did not support building against
-// a libary with SQLITE_OMIT_COMPLETE at all. The first attempt at adding
-// support was https://www.sqlite.org/src/info/c3e816cca4ddf096 which defined
-// sqlite_complete() as a stub function in shell.c. This worked on UNIX systems,
-// but caused a compilation error on Windows, where sqlite3.h declares
-// sqlite3_complete() as a __declspec(dllimport). The Windows build error was
-// fixed in https://www.sqlite.org/src/info/d584a0cb51281594 at our request.
-// While the current approach of using a macro requires the workaround here, it
-// is preferable to the previous version, which did not build at all on Windows.
-#if defined(sqlite3_complete)
-#undef sqlite3_complete
-#else
-#error "This workaround is no longer needed."
-#endif  // !defined(sqlite3_complete)
-#endif  // defined(SQLITE_OMIT_COMPLETE)
-
 #if defined(SQLITE_OMIT_COMPILEOPTION_DIAGS)
 // When SQLITE_OMIT_COMPILEOPTION_DIAGS is defined, sqlite3.h emits macros
 // instead of declarations for sqlite3_compileoption_{get,used}().
diff --git a/third_party/sqlite/sqlite_chromium_configuration_flags.gni b/third_party/sqlite/sqlite_chromium_configuration_flags.gni
index 008679f..c9feed0 100644
--- a/third_party/sqlite/sqlite_chromium_configuration_flags.gni
+++ b/third_party/sqlite/sqlite_chromium_configuration_flags.gni
@@ -52,12 +52,6 @@
   # Chrome doesn't use sqlite3_compileoption_{used,get}().
   "SQLITE_OMIT_COMPILEOPTION_DIAGS",
 
-  # Chrome doesn't ship the SQLite shell, so command auto-completion is not
-  # needed. Chrome developers who build the SQLite shell living at
-  # //third_party/sqlite:sqlite_shell for diagnostic purposes will have to
-  # live without auto-completion.
-  "SQLITE_OMIT_COMPLETE",
-
   # EXPLAIN's output is not stable across releases [1], so it should not be
   # used in Chrome. Skipping the EXPLAIN machinery also results in
   # non-trivial binary savings.
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index 9579d1e6..c8645383 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -421,7 +421,6 @@
       'linux-blink-animation-use-time-delta': 'debug_bot_enable_blink_animation_use_time_delta_reclient',
       'linux-blink-heap-concurrent-marking-tsan-rel': 'release_trybot_minimal_symbols_tsan',
       'linux-blink-heap-verification': 'release_bot_enable_blink_heap_verification_dcheck_always_on_reclient',
-      'linux-blink-v8-sandbox-future-rel': 'release_bot_enable_v8_sandbox_future_dcheck_always_on_reclient',
       'linux-blink-wpt-reset-rel': 'release_bot_blink_minimal_symbols_reclient',
       'linux-chromeos-annotator-rel': 'chromeos_with_codecs_release_bot_reclient',
       'linux-chromeos-code-coverage': 'chromeos_with_codecs_release_bot_coverage_reclient',
@@ -3500,10 +3499,6 @@
       'release_bot_blink_reclient', 'enable_blink_heap_verification', 'dcheck_always_on',
     ],
 
-    'release_bot_enable_v8_sandbox_future_dcheck_always_on_reclient': [
-      'release_bot_blink_reclient', 'v8_sandbox_future', 'dcheck_always_on',
-    ],
-
     'release_bot_extended_tracing_reclient': [
       'release_bot_reclient', 'extended_tracing',
     ],
@@ -4814,10 +4809,6 @@
       'gn_args': 'v8_enable_pointer_compression=false',
     },
 
-    'v8_sandbox_future': {
-      'gn_args': 'v8_enable_sandbox_future=true',
-    },
-
     'v8_simulate_arm': {
       'gn_args': 'target_cpu="x86" v8_target_cpu="arm"',
     },
diff --git a/tools/mb/mb_config_expectations/chromium.fyi.json b/tools/mb/mb_config_expectations/chromium.fyi.json
index d5e63ea..2fdc89db 100644
--- a/tools/mb/mb_config_expectations/chromium.fyi.json
+++ b/tools/mb/mb_config_expectations/chromium.fyi.json
@@ -1118,17 +1118,6 @@
       "use_remoteexec": true
     }
   },
-  "linux-blink-v8-sandbox-future-rel": {
-    "gn_args": {
-      "dcheck_always_on": true,
-      "ffmpeg_branding": "Chrome",
-      "is_component_build": false,
-      "is_debug": false,
-      "proprietary_codecs": true,
-      "use_remoteexec": true,
-      "v8_enable_sandbox_future": true
-    }
-  },
   "linux-blink-wpt-reset-rel": {
     "gn_args": {
       "dcheck_always_on": false,
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index b33a4f5..50fef681 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -2444,6 +2444,8 @@
   <int value="23"
       label="Invisible renderer in foreground app with not perceptible
              binding oom"/>
+  <int value="24"
+      label="Invisible renderer in foreground app with waived binding oom"/>
 </enum>
 
 <enum name="AndroidProcessedMinidumps">
@@ -3918,6 +3920,12 @@
   <int value="4" label="Outside of NTP (e.g. bookmarks bar)"/>
 </enum>
 
+<enum name="AppStreamLaunchEntryPoint">
+  <int value="0" label="Launched from full apps list"/>
+  <int value="1" label="Launched from a notification"/>
+  <int value="2" label="Launched from recent apps in Phone Hub"/>
+</enum>
+
 <enum name="AppsWindowType">
   <int value="0" label="Default"/>
   <int value="1" label="Panel"/>
@@ -35957,7 +35965,7 @@
   <int value="1117" label="DELETED_WEBRTCLOGGINGPRIVATE_STOPRTCEVENTLOGGING"/>
   <int value="1118" label="PASSWORDSPRIVATE_GETSAVEDPASSWORDLIST"/>
   <int value="1119" label="PASSWORDSPRIVATE_GETPASSWORDEXCEPTIONLIST"/>
-  <int value="1120" label="DELETED_INPUTMETHODPRIVATE_OPENOPTIONSPAGE"/>
+  <int value="1120" label="INPUTMETHODPRIVATE_OPENOPTIONSPAGE"/>
   <int value="1121" label="DELETED_FEEDBACKPRIVATE_LOGSRTPROMPTRESULT"/>
   <int value="1122" label="BLUETOOTHLOWENERGY_CREATESERVICE"/>
   <int value="1123" label="BLUETOOTHLOWENERGY_CREATECHARACTERISTIC"/>
@@ -53931,6 +53939,12 @@
              locked for 9+ seconds and the app was quit by OS or the user)."/>
 </enum>
 
+<enum name="IOSTabSwitcherDragDropTabs">
+  <int value="0" label="A tab is dragged."/>
+  <int value="1" label="A tab is dropped at the same index position."/>
+  <int value="2" label="A tab is dropped at a new index position."/>
+</enum>
+
 <enum name="IOSTabSwitcherPageChangeInteraction">
   <int value="0" label="Unknown Interaction"/>
   <int value="1" label="Scroll View Drag"/>
@@ -77012,6 +77026,103 @@
   <int value="9" label="Entering overview in tests"/>
 </enum>
 
+<enum name="OwnerKeyUmaEvent">
+  <int value="0" label="DeviceSettingsServiceIsNull">
+    DeviceSettingsService was null, owner key was not loaded.
+  </int>
+  <int value="1" label="ManagedDeviceSuccess">
+    Managed device successfully loaded the public owner key.
+  </int>
+  <int value="2" label="ManagedDeviceFail">
+    Managed device failed to load the public owner key.
+  </int>
+  <int value="3" label="OwnerHasKeysSuccess">
+    Consumer owner user successfully loaded both public and private keys.
+  </int>
+  <int value="4" label="OwnerHasKeysFail">
+    Consumer owner received both public and private keys, but at least one of
+    them wasn't actually loaded.
+  </int>
+  <int value="5" label="EstablishingConsumerOwnershipSuccess">
+    ChromeOS decided to establish consumer ownership when there was no existing
+    public key.
+  </int>
+  <int value="6" label="EstablishingConsumerOwnershipFail">
+    ChromeOS decided to establish consumer ownership when there was an existing
+    public key.
+  </int>
+  <int value="7" label="RegeneratingOwnerKeyBasedOnPolicySuccess">
+    ChromeOS decided to re-generate the lost owner key based on the data from
+    device policies after the public key was found (the private part is what was
+    lost).
+  </int>
+  <int value="8" label="RegeneratingOwnerKeyBasedOnPolicyFail">
+    ChromeOS decided to re-generate the lost owner key based on the data from
+    device policies and the public key was also not found. (Strictly speaking
+    not a failure, but still an unusual situation).
+  </int>
+  <int value="9" label="UserNotAnOwnerBasedOnPolicySuccess">
+    A user was categorized as not an owner based on the data from device
+    policies, the public key was successfully loaded.
+  </int>
+  <int value="10" label="UserNotAnOwnerBasedOnPolicyFail">
+    A user was categorized as not an owner based on the data from device
+    policies, the public key failed to load.
+  </int>
+  <int value="11" label="RegeneratingOwnerKeyBasedOnLocalStateSuccess">
+    ChromeOS decided to re-generate the lost owner key based on the data from
+    local state and the public key was not present.
+  </int>
+  <int value="12" label="RegeneratingOwnerKeyBasedOnLocalStateFail">
+    ChromeOS decided to re-generate the lost owner key based on the data from
+    local state after the public key was found (in such a case device policies
+    should be used, relying on local state is unexpected).
+  </int>
+  <int value="13" label="UserNotAnOwnerBasedOnLocalStateSuccess">
+    A user was categorized as not an owner based on the data from local state,
+    the public key was successfully loaded.
+  </int>
+  <int value="14" label="UserNotAnOwnerBasedOnLocalStateFail">
+    A user was categorized as not an owner based on the data from local state,
+    the public key failed to load.
+  </int>
+  <int value="15" label="UnsureUserNotAnOwnerSuccess">
+    ChromeOS assumed that a user is not an owner based on the lack of data, the
+    public key was successfully loaded.
+  </int>
+  <int value="16" label="UnsureUserNotAnOwnerFail">
+    ChromeOS assumed that a user is not an owner based on the lack of data, the
+    public key failed to load.
+  </int>
+  <int value="17" label="OwnerKeyGeneratedSuccess">
+    New owner key was generated on the first attempt.
+  </int>
+  <int value="18" label="OwnerKeyGeneratedFail">
+    New owner key was generated after 1+ failures.
+  </int>
+  <int value="19" label="FailedToGenerateOwnerKeySuccess">
+    Failed to generate new owner key, at least the old public key was returned.
+  </int>
+  <int value="20" label="FailedToGenerateOwnerKeyFail">
+    Failed to generate new owner key, the old public key also failed to load (or
+    was not present).
+  </int>
+  <int value="21" label="StartSigningPolicySuccess">
+    Successfully started signing policies.
+  </int>
+  <int value="22" label="StartSigningPolicyFail">
+    Failed to start signing policies.
+  </int>
+  <int value="23" label="SignedPolicySuccess">
+    Successfully signed policies.
+  </int>
+  <int value="24" label="SignedPolicyFail">Failed to sign policies.</int>
+  <int value="25" label="StoredPolicySuccess">
+    Successfully stored policies.
+  </int>
+  <int value="26" label="StoredPolicyFail">Failed to store policies.</int>
+</enum>
+
 <enum name="P2PLookupResult">
   <int value="0" label="Found"/>
   <int value="1" label="Not Found"/>
diff --git a/tools/metrics/histograms/metadata/android/histograms.xml b/tools/metrics/histograms/metadata/android/histograms.xml
index 311f238cd..b4c8a4f 100644
--- a/tools/metrics/histograms/metadata/android/histograms.xml
+++ b/tools/metrics/histograms/metadata/android/histograms.xml
@@ -825,6 +825,18 @@
   </summary>
 </histogram>
 
+<histogram name="Android.BindingManger.ConnectionsDroppedDueToMaxSize"
+    units="connections" expires_after="2023-04-16">
+  <owner>ckitagawa@chromium.org</owner>
+  <owner>yfriedman@chromium.org</owner>
+  <summary>
+    Records the total number of BindingManager bindings to
+    ChildProcessConnections that were dropped due to the BindingManager reaching
+    the connection limit. This value accumulates while the app is running and is
+    emitted upong the app being backgrounded at which time the counter is reset.
+  </summary>
+</histogram>
+
 <histogram name="Android.BiometricAuth.AuthRequester"
     enum="BiometricAuthRequester" expires_after="M112">
   <owner>ioanap@chromium.org</owner>
@@ -1932,16 +1944,6 @@
   </summary>
 </histogram>
 
-<histogram name="Android.Intent.LaunchExternalAppFormSubmitHasUserGesture"
-    enum="Boolean" expires_after="M74">
-  <owner>tedchoc@chromium.org</owner>
-  <owner>thildebr@chromium.org</owner>
-  <summary>
-    When an external application was launched as a result of a form submit, this
-    tracks whether there was a user gesture associated with the submit.
-  </summary>
-</histogram>
-
 <histogram name="Android.Intent.MainFrameIntentLaunch"
     enum="MainFrameIntentLaunch" expires_after="2023-08-16">
   <owner>mthiesse@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/blink/histograms.xml b/tools/metrics/histograms/metadata/blink/histograms.xml
index 76104f9..611d260 100644
--- a/tools/metrics/histograms/metadata/blink/histograms.xml
+++ b/tools/metrics/histograms/metadata/blink/histograms.xml
@@ -1992,6 +1992,17 @@
   </summary>
 </histogram>
 
+<histogram name="Blink.LocalFrameRoot.DidReachFirstContentfulPaint.MainFrame"
+    units="Boolean" expires_after="2023-03-19">
+  <owner>pdr@chromium.org</owner>
+  <owner>paint-dev@chromium.org</owner>
+  <summary>
+    Reports if the main frame ever receives a FirstContentfulPaint signal. Only
+    recorded for main frame local frame roots that run at least one main frame
+    update.
+  </summary>
+</histogram>
+
 <histogram base="true" name="Blink.MainFrame.UpdateTime" units="microseconds"
     expires_after="2023-03-19">
 <!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
diff --git a/tools/metrics/histograms/metadata/browser/histograms.xml b/tools/metrics/histograms/metadata/browser/histograms.xml
index 7e0d8931..13e73ada 100644
--- a/tools/metrics/histograms/metadata/browser/histograms.xml
+++ b/tools/metrics/histograms/metadata/browser/histograms.xml
@@ -159,6 +159,22 @@
   </token>
 </histogram>
 
+<histogram name="Browser.ERP.{Param}PayloadSize" units="bytes"
+    expires_after="2023-11-17">
+  <owner>xuhong@chromium.org</owner>
+  <owner>lbaraz@chromium.org</owner>
+  <owner>cros-reporting-team@google.com</owner>
+  <summary>
+    The size of ERP's {Param} payload as a JSON string when upload is
+    successful. Updated every time when an upload succeeds and at least one hour
+    has passed since the last update.
+  </summary>
+  <token key="Param">
+    <variant name="Request" summary="request"/>
+    <variant name="Response" summary="response"/>
+  </token>
+</histogram>
+
 <histogram name="Browser.MainThreadsCongestion{Phase}" units="janks"
     expires_after="2023-03-15">
 <!-- on probation: expected to graduate as a go/chrome-browser-guiding-metrics -->
diff --git a/tools/metrics/histograms/metadata/chromeos/histograms.xml b/tools/metrics/histograms/metadata/chromeos/histograms.xml
index b98c1b8..d2ab7d22 100644
--- a/tools/metrics/histograms/metadata/chromeos/histograms.xml
+++ b/tools/metrics/histograms/metadata/chromeos/histograms.xml
@@ -1610,6 +1610,16 @@
   </summary>
 </histogram>
 
+<histogram name="ChromeOS.Ownership.OwnerKeyUmaEvent" enum="OwnerKeyUmaEvent"
+    expires_after="2023-05-30">
+  <owner>miersh@google.com</owner>
+  <owner>pmarko@google.com</owner>
+  <owner>chromeos-commercial-networking@google.com</owner>
+  <summary>
+    Records events related to the owner key generation, loading and usage.
+  </summary>
+</histogram>
+
 <histogram name="ChromeOS.PlatformVerification.Available"
     enum="BooleanAvailable" expires_after="2021-12-21">
   <owner>apronin@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/custom_tabs/histograms.xml b/tools/metrics/histograms/metadata/custom_tabs/histograms.xml
index a3eab9e4..294bca1b 100644
--- a/tools/metrics/histograms/metadata/custom_tabs/histograms.xml
+++ b/tools/metrics/histograms/metadata/custom_tabs/histograms.xml
@@ -492,31 +492,6 @@
   </summary>
 </histogram>
 
-<histogram name="CustomTabs.SpareWebContents.Status2"
-    enum="SpareWebContentsStatus2" expires_after="2020-12-20">
-  <owner>lizeb@chromium.org</owner>
-  <owner>mthiesse@chromium.org</owner>
-  <summary>
-    Android: When a spare WebContents is created from Custom Tabs, record
-    creation, and whether it was used by CCT, stolen by another Chrome Activity,
-    killed or destroyed. Creation is recorded to get the &quot;abandoned&quot;
-    case, that is when Chrome is killed before the renderer. Starting with M84
-    this is is also recorded for webapps/WebAPKs. Filter the platform for just
-    CCT/TWA data.
-  </summary>
-</histogram>
-
-<histogram name="CustomTabs.SpareWebContents.TimeBeforeDeath" units="ms"
-    expires_after="2020-05-10">
-  <owner>lizeb@chromium.org</owner>
-  <summary>
-    Android: When a spare WebContents is created from Custom Tabs, record how
-    long is took before the render process was killed. Starting with M84 this is
-    is also recorded for webapps/WebAPKs. Filter the platform for just CCT/TWA
-    data.
-  </summary>
-</histogram>
-
 <histogram name="CustomTabs.SpeculationStatusOnStart"
     enum="CustomTabsSpeculationStatusOnStart" expires_after="2021-02-14">
   <owner>lizeb@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/geolocation/histograms.xml b/tools/metrics/histograms/metadata/geolocation/histograms.xml
index 261fe27a..5fa42f8 100644
--- a/tools/metrics/histograms/metadata/geolocation/histograms.xml
+++ b/tools/metrics/histograms/metadata/geolocation/histograms.xml
@@ -191,28 +191,6 @@
   </summary>
 </histogram>
 
-<histogram name="Geolocation.Timeout" units="ms" expires_after="M85">
-  <owner>mattreynolds@chromium.org</owner>
-  <owner>deviceapi-team@google.com</owner>
-  <summary>
-    Counts Geolocation request timeout values, bucketing by timeout duration.
-    This is recorded for all requests upon creation, see
-    Geolocation.TimeoutExpired for timeouts that actually expired.
-  </summary>
-</histogram>
-
-<histogram name="Geolocation.TimeoutExpired" units="ms"
-    expires_after="2020-02-01">
-  <owner>mattreynolds@chromium.org</owner>
-  <owner>deviceapi-team@google.com</owner>
-  <summary>
-    Counts Geolocation request timeout expirations, bucketing by timeout
-    duration. This means no position was received within the allowed time from
-    the browser process due to e.g. a slow network or an unresponsive system
-    location provider.
-  </summary>
-</histogram>
-
 <histogram name="Geolocation.WifiDataProviderCommon.WifiScanTaskTime"
     units="ms" expires_after="2023-07-15">
   <owner>reillyg@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/histogram_suffixes_list.xml b/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
index 1b3c512..ce1ac91 100644
--- a/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
+++ b/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
@@ -3742,8 +3742,6 @@
       name="PageLoad.DocumentTiming.NavigationToLoadEventFired"/>
   <affected-histogram name="PageLoad.Experimental.Bytes.Network"/>
   <affected-histogram
-      name="PageLoad.Experimental.PaintTiming.NavigationToFirstMeaningfulPaint"/>
-  <affected-histogram
       name="PageLoad.PaintTiming.NavigationToFirstContentfulPaint"/>
   <affected-histogram name="PageLoad.PaintTiming.NavigationToFirstImagePaint"/>
   <affected-histogram name="PageLoad.PaintTiming.NavigationToFirstPaint"/>
@@ -3806,8 +3804,6 @@
              prerendered, the recorded value is modified as activation
              origined."/>
   <affected-histogram
-      name="PageLoad.Experimental.PaintTiming.NavigationToFirstMeaningfulPaint"/>
-  <affected-histogram
       name="PageLoad.PaintTiming.NavigationToFirstContentfulPaint"/>
 </histogram_suffixes>
 
@@ -3817,8 +3813,6 @@
       label="The predictor database contained origins for preconnecting for a
              page"/>
   <affected-histogram
-      name="PageLoad.Clients.LoadingPredictor2.Experimental.PaintTiming.NavigationToFirstMeaningfulPaint"/>
-  <affected-histogram
       name="PageLoad.Clients.LoadingPredictor2.PaintTiming.NavigationToFirstContentfulPaint"/>
 </histogram_suffixes>
 
@@ -3848,8 +3842,6 @@
   <affected-histogram
       name="PageLoad.DocumentTiming.NavigationToLoadEventFired"/>
   <affected-histogram
-      name="PageLoad.Experimental.PaintTiming.NavigationToFirstMeaningfulPaint"/>
-  <affected-histogram
       name="PageLoad.PaintTiming.ForegroundToFirstContentfulPaint"/>
   <affected-histogram
       name="PageLoad.PaintTiming.NavigationToFirstContentfulPaint"/>
@@ -3865,8 +3857,6 @@
   <affected-histogram
       name="PageLoad.Experimental.Bytes.NetworkIncludingHeaders"/>
   <affected-histogram
-      name="PageLoad.Experimental.PaintTiming.NavigationToFirstMeaningfulPaint"/>
-  <affected-histogram
       name="PageLoad.PaintTiming.NavigationToFirstContentfulPaint"/>
   <affected-histogram name="PageLoad.ParseTiming.ParseBlockedOnScriptLoad"/>
   <affected-histogram name="PageLoad.ParseTiming.ParseDuration"/>
@@ -3888,8 +3878,6 @@
   <affected-histogram
       name="PageLoad.DocumentTiming.NavigationToLoadEventFired"/>
   <affected-histogram
-      name="PageLoad.Experimental.PaintTiming.NavigationToFirstMeaningfulPaint"/>
-  <affected-histogram
       name="PageLoad.PaintTiming.NavigationToFirstContentfulPaint"/>
   <affected-histogram
       name="PageLoad.PaintTiming.ParseStartToFirstContentfulPaint"/>
@@ -3906,8 +3894,6 @@
   <affected-histogram
       name="PageLoad.Experimental.Bytes.NetworkIncludingHeaders"/>
   <affected-histogram
-      name="PageLoad.Experimental.PaintTiming.NavigationToFirstMeaningfulPaint"/>
-  <affected-histogram
       name="PageLoad.PaintTiming.NavigationToFirstContentfulPaint"/>
   <affected-histogram name="PageLoad.ParseTiming.ParseBlockedOnScriptLoad"/>
   <affected-histogram name="PageLoad.ParseTiming.ParseDuration"/>
@@ -3921,8 +3907,6 @@
   <suffix name="Clients.Scheme.HTTPS"
       label="PageLoadMetrics that are a result of a navigation to a main
              resource where the committed URL is HTTPS."/>
-  <affected-histogram
-      name="PageLoad.Experimental.PaintTiming.NavigationToFirstMeaningfulPaint"/>
   <affected-histogram name="PageLoad.Internal.NavigationStartedInForeground"/>
   <affected-histogram
       name="PageLoad.PaintTiming.NavigationToFirstContentfulPaint"/>
@@ -4057,8 +4041,6 @@
   <affected-histogram
       name="PageLoad.DocumentTiming.NavigationToLoadEventFired"/>
   <affected-histogram
-      name="PageLoad.Experimental.PaintTiming.NavigationToFirstMeaningfulPaint"/>
-  <affected-histogram
       name="PageLoad.PaintTiming.NavigationToFirstContentfulPaint"/>
   <affected-histogram name="PageLoad.PaintTiming.NavigationToFirstPaint"/>
   <affected-histogram
diff --git a/tools/metrics/histograms/metadata/ios/histograms.xml b/tools/metrics/histograms/metadata/ios/histograms.xml
index d9cbbac..ed0aa55d 100644
--- a/tools/metrics/histograms/metadata/ios/histograms.xml
+++ b/tools/metrics/histograms/metadata/ios/histograms.xml
@@ -1033,6 +1033,7 @@
     This histogram is of special interest to the chrome-analysis-team@. Do not
     change its semantics or retire it without talking to them first.
   </summary>
+  <token key="MXVersion" variants="MXVersion"/>
 </histogram>
 
 <histogram name="IOS.MetricKit.{MXVersion}ApplicationResumeTime" units="ms"
@@ -1046,6 +1047,7 @@
     Recorded only if user opted in for sharing diagnostic data on iOS device.
     Note: The date the data is reported is later than the day it account for.
   </summary>
+  <token key="MXVersion" variants="MXVersion"/>
 </histogram>
 
 <histogram name="IOS.MetricKit.{MXVersion}AverageSuspendedMemory" units="MB"
@@ -1060,6 +1062,7 @@
     Recorded only if user opted in for sharing diagnostic data on iOS device.
     Note: The date the data is reported is later than the day it account for.
   </summary>
+  <token key="MXVersion" variants="MXVersion"/>
 </histogram>
 
 <histogram name="IOS.MetricKit.{MXVersion}BackgroundExitData"
@@ -1086,6 +1089,7 @@
     This histogram is of special interest to the chrome-analysis-team@. Do not
     change its semantics or retire it without talking to them first.
   </summary>
+  <token key="MXVersion" variants="MXVersion"/>
 </histogram>
 
 <histogram name="IOS.MetricKit.{MXVersion}BackgroundTimePerDay" units="ms"
@@ -1101,6 +1105,7 @@
     Recorded only if user opted in for sharing diagnostic data on iOS device.
     Note: The date the data is reported is later than the day it account for.
   </summary>
+  <token key="MXVersion" variants="MXVersion"/>
 </histogram>
 
 <histogram name="IOS.MetricKit.{MXVersion}ForegroundExitData"
@@ -1129,6 +1134,7 @@
     This histogram is of special interest to the chrome-analysis-team@. Do not
     change its semantics or retire it without talking to them first.
   </summary>
+  <token key="MXVersion" variants="MXVersion"/>
 </histogram>
 
 <histogram name="IOS.MetricKit.{MXVersion}ForegroundTimePerDay" units="s"
@@ -1147,6 +1153,7 @@
     Recorded only if user opted in for sharing diagnostic data on iOS device.
     Note: The date the data is reported is later than the day it account for.
   </summary>
+  <token key="MXVersion" variants="MXVersion"/>
 </histogram>
 
 <histogram name="IOS.MetricKit.{MXVersion}PeakMemoryUsage" units="MB"
@@ -1166,6 +1173,7 @@
     Recorded only if user opted in for sharing diagnostic data on iOS device.
     Note: The date the data is reported is later than the day it account for.
   </summary>
+  <token key="MXVersion" variants="MXVersion"/>
 </histogram>
 
 <histogram name="IOS.MetricKit.{MXVersion}TimeToFirstDraw" units="ms"
@@ -1179,6 +1187,7 @@
     Recorded only if user opted in for sharing diagnostic data on iOS device.
     Note: The date the data is reported is later than the day it account for.
   </summary>
+  <token key="MXVersion" variants="MXVersion"/>
 </histogram>
 
 <histogram name="IOS.MultiWindow.Configuration"
@@ -1875,6 +1884,17 @@
   </summary>
 </histogram>
 
+<histogram name="IOS.TabSwitcher.DragDropTabs"
+    enum="IOSTabSwitcherDragDropTabs" expires_after="2023-11-16">
+  <owner>ewannpv@chromium.org</owner>
+  <owner>gambard@chromium.org</owner>
+  <owner>bling-team@google.com</owner>
+  <summary>
+    Records tab drag and drop interactions on the tab switcher. An intercation
+    is recorded twice (one drag and one drop).
+  </summary>
+</histogram>
+
 <histogram name="IOS.TabSwitcher.PageChangeInteraction"
     enum="IOSTabSwitcherPageChangeInteraction" expires_after="2023-05-14">
   <owner>marq@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/network/histograms.xml b/tools/metrics/histograms/metadata/network/histograms.xml
index 920dd25..14be043 100644
--- a/tools/metrics/histograms/metadata/network/histograms.xml
+++ b/tools/metrics/histograms/metadata/network/histograms.xml
@@ -49,6 +49,13 @@
   <variant name="L2tpIpsec" summary="L2TP/IPsec"/>
 </variants>
 
+<histogram name="Network.Ash.Cellular.Apn.CreateCustomApn.Result"
+    enum="BooleanSuccess" expires_after="2023-10-18">
+  <owner>gordonseto@google.com</owner>
+  <owner>cros-connectivity@google.com</owner>
+  <summary>Records the result of an attempt to create a custom APN.</summary>
+</histogram>
+
 <histogram name="Network.Ash.Cellular.Apn.CustomApns.Count" units="count"
     expires_after="2023-10-18">
   <owner>gordonseto@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index 99ed39c9..c7959aa 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -5777,6 +5777,17 @@
   </summary>
 </histogram>
 
+<histogram name="Eche.AppStream.LaunchAttempt" enum="AppStreamLaunchEntryPoint"
+    expires_after="2023-05-18">
+  <owner>crisrael@google.com</owner>
+  <owner>jonmann@google.com</owner>
+  <owner>chromeos-cross-device-eng@google.com</owner>
+  <summary>
+    This metric logs where the Eche app was launched from. The metric is emmited
+    when the eche app is launched.
+  </summary>
+</histogram>
+
 <histogram name="Eche.Connection.Duration" units="ms"
     expires_after="2022-10-04">
   <owner>samchiu@chromium.org</owner>
@@ -5823,6 +5834,9 @@
 
 <histogram name="Eche.NotificationClicked" enum="EcheNotificationInteraction"
     expires_after="2023-04-16">
+  <obsolete>
+    Deprecated as of 11/2022. Replaced with Eche.AppStream.LaunchAttempt
+  </obsolete>
   <owner>samchiu@chromium.org</owner>
   <owner>andychou@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/page/histograms.xml b/tools/metrics/histograms/metadata/page/histograms.xml
index cf79819..2d22ede 100644
--- a/tools/metrics/histograms/metadata/page/histograms.xml
+++ b/tools/metrics/histograms/metadata/page/histograms.xml
@@ -1175,17 +1175,6 @@
 </histogram>
 
 <histogram
-    name="PageLoad.Clients.SideSearch.SidePanel.Experimental.PaintTiming.NavigationToFirstMeaningfulPaint"
-    units="ms" expires_after="2023-08-21">
-  <owner>tluk@chromium.org</owner>
-  <owner>chrome-cros@chromium.org</owner>
-  <summary>
-    Measures the time from navigation start to first meaningful paint. Only
-    recorded for side search navigations from the side panel.
-  </summary>
-</histogram>
-
-<histogram
     name="PageLoad.Clients.SideSearch.SidePanel.InteractiveTiming.FirstInputDelay4"
     units="ms" expires_after="2023-08-21">
   <owner>tluk@chromium.org</owner>
@@ -1925,17 +1914,6 @@
   </summary>
 </histogram>
 
-<histogram name="PageLoad.Experimental.PaintTiming.FirstMeaningfulPaintStatus"
-    enum="FirstMeaningfulPaintStatus" expires_after="2021-01-31">
-  <owner>ksakamoto@chromium.org</owner>
-  <owner>speed-metrics-dev@chromium.org</owner>
-  <summary>
-    Records whether the First Meaningful Paint metric was reported for the page
-    load, or why it wasn't if not. See http://bit.ly/ttfmp-doc for the
-    definition of First Meaningful Paint.
-  </summary>
-</histogram>
-
 <histogram name="PageLoad.Experimental.PaintTiming.InputToFirstContentfulPaint"
     units="ms" expires_after="2023-03-19">
   <owner>sullivan@chromium.org</owner>
@@ -1987,20 +1965,6 @@
   </summary>
 </histogram>
 
-<histogram
-    name="PageLoad.Experimental.PaintTiming.NavigationToFirstMeaningfulPaint"
-    units="ms" expires_after="2023-04-30">
-  <owner>ksakamoto@chromium.org</owner>
-  <owner>speed-metrics-dev@chromium.org</owner>
-  <summary>
-    Measures the time from navigation timing's navigation start to the first
-    meaningful paint (http://bit.ly/ttfmp-doc), for main frame documents.
-
-    Do not modify this metric in any way without contacting
-    speed-metrics-dev@chromium.org.
-  </summary>
-</histogram>
-
 <histogram name="PageLoad.Experimental.TotalForegroundDuration" units="ms"
     expires_after="2023-04-16">
   <owner>npm@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/power/histograms.xml b/tools/metrics/histograms/metadata/power/histograms.xml
index 6471604b..b2c2f743 100644
--- a/tools/metrics/histograms/metadata/power/histograms.xml
+++ b/tools/metrics/histograms/metadata/power/histograms.xml
@@ -813,6 +813,18 @@
   </summary>
 </histogram>
 
+<histogram name="Power.BatteryDischargeMode2{UsageScenario}"
+    enum="BatteryDischargeMode" expires_after="2023-03-31">
+  <owner>etiennep@chromium.org</owner>
+  <owner>olivierli@chromium.org</owner>
+  <summary>
+    Battery discharge mode describing whether BatteryDischargeRate2 could be
+    reported or not, and why. This is recorded every 2 minutes for
+    {UsageScenario} (see go/chrome_power_use_per_scenario).
+  </summary>
+  <token key="UsageScenario" variants="UsageScenario"/>
+</histogram>
+
 <histogram name="Power.BatteryDischargeMode5{UsageScenario}{IntervalType}"
     enum="BatteryDischargeMode" expires_after="2023-03-31">
   <owner>etiennep@chromium.org</owner>
@@ -829,6 +841,11 @@
     This is recorded for {UsageScenario}.
 
     This contains {IntervalType}.
+
+    There are 2 differences with Power.BatteryDischargeRate2. 1: When possible,
+    the intervals are aligned with battery discharge notifications from the OS
+    (MacOS only for now), and 2: Intervals represents only 1 minute of Chrome
+    runtime (as opposed to 2 minutes).
   </summary>
   <token key="UsageScenario" variants="UsageScenario"/>
   <token key="IntervalType" variants="IntervalType"/>
@@ -844,6 +861,23 @@
   </summary>
 </histogram>
 
+<histogram name="Power.BatteryDischargeRate2{UsageScenario}"
+    units="hundredth of percent" expires_after="2023-03-31">
+  <owner>etiennep@chromium.org</owner>
+  <owner>olivierli@chromium.org</owner>
+  <owner>lgrey@chromium.org</owner>
+  <summary>
+    Battery discharge rate per minute, with 1/10000 of full charge resolution,
+    example: - Battery capacity = 4000 mAh; - Battery charge at the beginning of
+    the interval: 3900 mAh; - Battery charge at the end of the interval: 3700
+    mAh; - Discharge proportion: (3900-3700) / 4000 = 0.05 - Reported value:
+    500. This metric is only recorded when on battery power. This is reported on
+    Mac and Windows. This is recorded every 2 minutes for {UsageScenario} (see
+    go/chrome_power_use_per_scenario).
+  </summary>
+  <token key="UsageScenario" variants="UsageScenario"/>
+</histogram>
+
 <histogram
     name="Power.BatteryDischargeRateMilliwatts5{UsageScenario}{IntervalType}"
     units="milliwatts" expires_after="2023-03-31">
@@ -863,6 +897,12 @@
     This is recorded for {UsageScenario}.
 
     This contains {IntervalType}.
+
+    There are 3 differences with Power.BatteryDischargeRate2. 1: When possible,
+    the intervals are aligned with battery discharge notifications from the OS
+    (MacOS only for now), 2: Intervals represents only 1 minute of Chrome
+    runtime (as opposed to 2 minutes), and 3: The discharge rate is emitted in
+    milliwatts instead of hundredth of a percent.
   </summary>
   <token key="UsageScenario" variants="UsageScenario"/>
   <token key="IntervalType" variants="IntervalType"/>
@@ -889,6 +929,11 @@
     This is recorded for {UsageScenario}.
 
     This contains {IntervalType}.
+
+    There are 2 differences with Power.BatteryDischargeRate2. 1: When possible,
+    the intervals are aligned with battery discharge notifications from the OS
+    (MacOS only for now), and 2: Intervals represents only 1 minute of Chrome
+    runtime (as opposed to 2 minutes).
   </summary>
   <token key="UsageScenario" variants="UsageScenario"/>
   <token key="IntervalType" variants="IntervalType"/>
diff --git a/tools/metrics/histograms/metadata/sync/histograms.xml b/tools/metrics/histograms/metadata/sync/histograms.xml
index e2c2dbe..925fd391 100644
--- a/tools/metrics/histograms/metadata/sync/histograms.xml
+++ b/tools/metrics/histograms/metadata/sync/histograms.xml
@@ -405,8 +405,7 @@
     taken every time the Sync data types are (re)configured, which typically
     happens during startup and when the user changes any Sync settings.
 
-    NOTE: this doesn't include OS datatypes once the SplitSettingsSync feature
-    is enabled. Use Sync.CustomOSSync for those.
+    NOTE: this does NOT include OS datatypes. Use Sync.CustomOSSync for those.
   </summary>
 </histogram>
 
diff --git a/tools/metrics/histograms/metadata/v8/histograms.xml b/tools/metrics/histograms/metadata/v8/histograms.xml
index c37b63fa..cc0e6aa 100644
--- a/tools/metrics/histograms/metadata/v8/histograms.xml
+++ b/tools/metrics/histograms/metadata/v8/histograms.xml
@@ -23,7 +23,7 @@
 <histograms>
 
 <histogram name="V8.ArrayBufferLargeAllocations" units="MB"
-    expires_after="2022-09-11">
+    expires_after="M120">
   <owner>gdeepti@chromium.org</owner>
   <owner>ecmziegler@chromium.org</owner>
   <owner>clemensb@chromium.org</owner>
@@ -33,8 +33,7 @@
   </summary>
 </histogram>
 
-<histogram name="V8.ArrayBufferNewSizeFailures" units="MB"
-    expires_after="2022-09-11">
+<histogram name="V8.ArrayBufferNewSizeFailures" units="MB" expires_after="M120">
   <owner>gdeepti@chromium.org</owner>
   <owner>ecmziegler@chromium.org</owner>
   <owner>clemensb@chromium.org</owner>
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index 15b05b1..deb06e4 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -14317,6 +14317,9 @@
     </summary>
   </metric>
   <metric name="Experimental.PaintTiming.NavigationToFirstMeaningfulPaint">
+    <obsolete>
+      Removed on 11/2022
+    </obsolete>
     <summary>
       Measures the time in milliseconds from navigation timing's navigation
       start to the first meaningful paint (http://bit.ly/ttfmp-doc), for main
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index c16b35c..21818bd 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -6,7 +6,7 @@
         },
         "win": {
             "hash": "bb2cc045bbee68ad3cd3daed827075285572fe49",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/a3e39a91c2d5d61394a314c8b042d07660e8743d/trace_processor_shell.exe"
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/a379922c811554e5e779ab4609e63cbeff592974/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "58893933be305d3bfe0a72ebebcacde2ac3ca893",
@@ -22,7 +22,7 @@
         },
         "linux": {
             "hash": "7e4cc10d0a4b47b170771988b6800e9438ad73cf",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/9fcb4a22e876f62e6e197d03000ee955d26c4bf4/trace_processor_shell"
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/a379922c811554e5e779ab4609e63cbeff592974/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/style_variable_generator/PRESUBMIT.py b/tools/style_variable_generator/PRESUBMIT.py
index 2bf6443..4d6155dd 100644
--- a/tools/style_variable_generator/PRESUBMIT.py
+++ b/tools/style_variable_generator/PRESUBMIT.py
@@ -12,7 +12,7 @@
 
 TEST_PATTERNS = [r'.+_test.py$']
 STYLE_VAR_GEN_INPUTS = [
-    r'^tools[\\\/]style_variable_generator[\\\/].+\.json5$'
+    r'^tools[\\\/]style_variable_generator[\\\/][^\/]+\.json5$'
 ]
 
 def _CommonChecks(input_api, output_api):
diff --git a/tools/style_variable_generator/presubmit_support.py b/tools/style_variable_generator/presubmit_support.py
index 20d5dc6d..54e2c721f 100644
--- a/tools/style_variable_generator/presubmit_support.py
+++ b/tools/style_variable_generator/presubmit_support.py
@@ -25,8 +25,6 @@
 
 
 def FindDeletedCSSVariables(input_api, output_api, input_file_filter):
-    # TODO(1312192): reenable after fixing presubmit exceptions
-    return []
     files = input_api.AffectedFiles(
         file_filter=lambda f: input_api.FilterSourceFile(
             f, files_to_check=input_file_filter))
diff --git a/ui/base/cocoa/nsmenuitem_additions.mm b/ui/base/cocoa/nsmenuitem_additions.mm
index 0ba26b9..19d41e8 100644
--- a/ui/base/cocoa/nsmenuitem_additions.mm
+++ b/ui/base/cocoa/nsmenuitem_additions.mm
@@ -173,9 +173,14 @@
   if (useEventCharacters) {
     eventString = eventCharacters;
 
-    // Process the shift if necessary.
-    if (eventModifiers & NSEventModifierFlagShift)
+    // If the user is pressing the Shift key, force the shortcut string to
+    // uppercase. Otherwise, if only Caps Lock is down, ensure the shortcut
+    // string is lowercase.
+    if (eventModifiers & NSEventModifierFlagShift) {
       eventString = [eventString uppercaseString];
+    } else if (eventModifiers & NSEventModifierFlagCapsLock) {
+      eventString = [eventString lowercaseString];
+    }
   }
 
   if ([eventString length] == 0 || [[self keyEquivalent] length] == 0)
diff --git a/ui/base/cocoa/nsmenuitem_additions_unittest.mm b/ui/base/cocoa/nsmenuitem_additions_unittest.mm
index 53ade602..4fab497 100644
--- a/ui/base/cocoa/nsmenuitem_additions_unittest.mm
+++ b/ui/base/cocoa/nsmenuitem_additions_unittest.mm
@@ -8,11 +8,32 @@
 
 #include <ostream>
 
+#include "base/mac/foundation_util.h"
 #include "base/mac/scoped_nsobject.h"
 #include "base/strings/sys_string_conversions.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/events/keycodes/keyboard_code_conversion_mac.h"
 
+@interface NSEventForTesting : NSEvent
+@property(copy, nonatomic) NSString* characters;
+@end
+
+@implementation NSEventForTesting
+
+@synthesize characters = _characters;
+
+- (void)setCharacters:(NSString*)aString {
+  _characters = [aString copy];
+  if (aString.length)
+    ASSERT_TRUE([[self characters] isEqualToString:aString]);
+}
+
+- (NSString*)characters {
+  return _characters != nil ? _characters : [super characters];
+}
+
+@end
+
 std::ostream& operator<<(std::ostream& out, NSObject* obj) {
   return out << base::SysNSStringToUTF8([obj description]);
 }
@@ -29,16 +50,16 @@
                   NSString* chars,
                   NSString* charsNoMods,
                   const NSUInteger keyCode = 0) {
-  return [NSEvent keyEventWithType:NSEventTypeKeyDown
-                          location:NSZeroPoint
-                     modifierFlags:modifierFlags
-                         timestamp:0.0
-                      windowNumber:0
-                           context:nil
-                        characters:chars
-       charactersIgnoringModifiers:charsNoMods
-                         isARepeat:NO
-                           keyCode:keyCode];
+  return [NSEventForTesting keyEventWithType:NSEventTypeKeyDown
+                                    location:NSZeroPoint
+                               modifierFlags:modifierFlags
+                                   timestamp:0.0
+                                windowNumber:0
+                                     context:nil
+                                  characters:chars
+                 charactersIgnoringModifiers:charsNoMods
+                                   isARepeat:NO
+                                     keyCode:keyCode];
 }
 
 NSMenuItem* MenuItem(NSString* equiv, NSUInteger mask = 0) {
@@ -509,6 +530,45 @@
   SetIsInputSourceCommandHebrewForTesting(false);
 }
 
+// With the Persian - Standard layout, pressing Cmd W without Shift but with
+// Caps Lock on generates a key event with a capital W. Make sure we treat
+// the W as lower case so that we don't match Shift Cmd W, unless the user
+// is also holding down the Shift key.
+TEST(NSMenuItemAdditionsTest, TestCmdCapsLockOnPersianStandardLayout) {
+  NSString* capitalW = @"W";
+  NSMenuItem* closeTabItem = MenuItem(@"w", NSEventModifierFlagCommand);
+  NSMenuItem* closeWindowItem = MenuItem(capitalW, NSEventModifierFlagCommand);
+
+  // Simulate pressing Cmd W with Caps Lock on.
+  NSEvent* cmdWWithCapsLock =
+      KeyEvent(NSEventModifierFlagCommand | NSEventModifierFlagCapsLock,
+               capitalW, @"\u0635", kVK_ANSI_W);
+  // The layout generates an event with a capital W. We have to force the
+  // characters because the regular NSEvent machinery insists on converting
+  // the string to lower case.
+  [base::mac::ObjCCastStrict<NSEventForTesting>(cmdWWithCapsLock)
+      setCharacters:capitalW];
+  ExpectKeyFiresItem(cmdWWithCapsLock, closeTabItem, /*compareCocoa=*/false);
+
+  // Make sure Shift-Cmd W triggers Close Window.
+  NSEvent* shiftCmdW =
+      KeyEvent(NSEventModifierFlagCommand | NSEventModifierFlagShift, capitalW,
+               @"\u1612", kVK_ANSI_W);
+  [base::mac::ObjCCastStrict<NSEventForTesting>(shiftCmdW)
+      setCharacters:capitalW];
+  ExpectKeyFiresItem(shiftCmdW, closeWindowItem, /*compareCocoa=*/false);
+
+  // And also Shift-Cmd W with Caps Lock down.
+  NSEvent* shiftCmdWWithCapsLock =
+      KeyEvent(NSEventModifierFlagCommand | NSEventModifierFlagShift |
+                   NSEventModifierFlagCapsLock,
+               capitalW, @"\u1612", kVK_ANSI_W);
+  [base::mac::ObjCCastStrict<NSEventForTesting>(shiftCmdWWithCapsLock)
+      setCharacters:capitalW];
+  ExpectKeyFiresItem(shiftCmdWWithCapsLock, closeWindowItem,
+                     /*compareCocoa=*/false);
+}
+
 NSString* keyCodeToCharacter(NSUInteger keyCode,
                              EventModifiers modifiers,
                              TISInputSourceRef layout) {
diff --git a/ui/base/ime/ash/input_method_manager.h b/ui/base/ime/ash/input_method_manager.h
index 0384f041..3a38c1f 100644
--- a/ui/base/ime/ash/input_method_manager.h
+++ b/ui/base/ime/ash/input_method_manager.h
@@ -201,6 +201,12 @@
     // methods.
     virtual size_t GetNumEnabledInputMethods() const = 0;
 
+    // Returns the input method descriptor from the given input method id
+    // string.
+    // If the given input method id is invalid, returns nullptr.
+    virtual const InputMethodDescriptor* GetInputMethodFromId(
+        const std::string& input_method_id) const = 0;
+
     // Sets the list of extension IME ids which should be enabled.
     virtual void SetEnabledExtensionImes(std::vector<std::string>* ids) = 0;
 
diff --git a/ui/base/ime/ash/mock_input_method_manager.cc b/ui/base/ime/ash/mock_input_method_manager.cc
index ec54d5f..ad526ed 100644
--- a/ui/base/ime/ash/mock_input_method_manager.cc
+++ b/ui/base/ime/ash/mock_input_method_manager.cc
@@ -63,6 +63,12 @@
   return enabled_input_method_ids;
 }
 
+const InputMethodDescriptor*
+MockInputMethodManager::State::GetInputMethodFromId(
+    const std::string& input_method_id) const {
+  return nullptr;
+}
+
 size_t MockInputMethodManager::State::GetNumEnabledInputMethods() const {
   return enabled_input_method_ids.size();
 }
diff --git a/ui/base/ime/ash/mock_input_method_manager.h b/ui/base/ime/ash/mock_input_method_manager.h
index 60bbcef..55b411d8 100644
--- a/ui/base/ime/ash/mock_input_method_manager.h
+++ b/ui/base/ime/ash/mock_input_method_manager.h
@@ -51,6 +51,8 @@
     std::unique_ptr<InputMethodDescriptors> GetEnabledInputMethods()
         const override;
     const std::vector<std::string>& GetEnabledInputMethodIds() const override;
+    const InputMethodDescriptor* GetInputMethodFromId(
+        const std::string& input_method_id) const override;
     size_t GetNumEnabledInputMethods() const override;
     void SetEnabledExtensionImes(std::vector<std::string>* ids) override;
     void SetInputMethodLoginDefault() override;
diff --git a/ui/base/interaction/interactive_test.cc b/ui/base/interaction/interactive_test.cc
index d6ed8ea..26a9bcbc 100644
--- a/ui/base/interaction/interactive_test.cc
+++ b/ui/base/interaction/interactive_test.cc
@@ -15,10 +15,6 @@
 
 namespace ui::test {
 
-namespace {
-DEFINE_LOCAL_CUSTOM_ELEMENT_EVENT_TYPE(kEnsureNotPresentCheckEvent);
-}
-
 using internal::kInteractiveTestPivotElementId;
 
 InteractiveTestApi::InteractiveTestApi(
@@ -174,28 +170,7 @@
 InteractiveTestApi::MultiStep InteractiveTestApi::EnsureNotPresent(
     ElementIdentifier element_to_check,
     bool in_any_context) {
-  MultiStep steps;
-  steps.emplace_back(WithElement(
-      kInteractiveTestPivotElementId,
-      base::BindOnce([](TrackedElement* element) {
-        base::ThreadTaskRunnerHandle::Get()->PostTask(
-            FROM_HERE,
-            base::BindOnce(
-                [](ElementIdentifier id, ElementContext context) {
-                  auto* const element =
-                      ElementTracker::GetElementTracker()
-                          ->GetFirstMatchingElement(id, context);
-                  if (element) {
-                    ElementTracker::GetFrameworkDelegate()->NotifyCustomEvent(
-                        element, kEnsureNotPresentCheckEvent);
-                  }
-                  // Note: if the element is no longer present, the sequence was
-                  // already aborted; there is no need to send further errors.
-                },
-                element->identifier(), element->context()));
-      })));
-  steps.emplace_back(AfterEvent(
-      kInteractiveTestPivotElementId, kEnsureNotPresentCheckEvent,
+  return internal::InteractiveTestPrivate::PostTask(
       base::BindOnce(
           [](ElementIdentifier element_to_check, bool in_any_context,
              InteractionSequence* seq, TrackedElement* reference) {
@@ -212,8 +187,12 @@
               seq->FailForTesting();
             }
           },
-          element_to_check, in_any_context)));
-  return steps;
+          element_to_check, in_any_context));
+}
+
+// static
+InteractiveTestApi::MultiStep InteractiveTestApi::FlushEvents() {
+  return internal::InteractiveTestPrivate::PostTask(base::DoNothing());
 }
 
 // static
diff --git a/ui/base/interaction/interactive_test.h b/ui/base/interaction/interactive_test.h
index f2ef7c5d..8d64df3 100644
--- a/ui/base/interaction/interactive_test.h
+++ b/ui/base/interaction/interactive_test.h
@@ -48,7 +48,7 @@
 
  protected:
   using InputType = InteractionTestUtil::InputType;
-  using MultiStep = std::vector<InteractionSequence::StepBuilder>;
+  using MultiStep = internal::InteractiveTestPrivate::MultiStep;
   using StepBuilder = InteractionSequence::StepBuilder;
 
   // Construct a MultiStep from one or more StepBuilders and/or MultiSteps.
@@ -173,6 +173,14 @@
       ElementIdentifier element_to_check,
       bool in_any_context = false);
 
+  // Ensures that the next step does not piggyback on the previous step(s), but
+  // rather, executes on a fresh message loop. Normally, steps will continue to
+  // trigger on the same call stack until a start condition is not met.
+  //
+  // Use sparingly, and only when e.g. re-entrancy issues prevent the test from
+  // otherwise working properly.
+  [[nodiscard]] static MultiStep FlushEvents();
+
   // Provides syntactic sugar so you can put "in any context" before an action
   // or test step rather than after. For example the following are equivalent:
   //
diff --git a/ui/base/interaction/interactive_test_internal.cc b/ui/base/interaction/interactive_test_internal.cc
index e6f1af5..5b300b47 100644
--- a/ui/base/interaction/interactive_test_internal.cc
+++ b/ui/base/interaction/interactive_test_internal.cc
@@ -13,6 +13,7 @@
 namespace ui::test::internal {
 
 DEFINE_ELEMENT_IDENTIFIER_VALUE(kInteractiveTestPivotElementId);
+DEFINE_CUSTOM_ELEMENT_EVENT_TYPE(kInteractiveTestPivotEventType);
 
 InteractiveTestPrivate::InteractiveTestPrivate(
     std::unique_ptr<InteractionTestUtil> test_util)
diff --git a/ui/base/interaction/interactive_test_internal.h b/ui/base/interaction/interactive_test_internal.h
index 7fb14de3..01e6c4ef 100644
--- a/ui/base/interaction/interactive_test_internal.h
+++ b/ui/base/interaction/interactive_test_internal.h
@@ -9,6 +9,8 @@
 
 #include "base/logging.h"
 #include "base/strings/string_piece_forward.h"
+#include "base/test/rectify_callback.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/variant.h"
@@ -26,11 +28,14 @@
 // Element that is present during interactive tests that actions can bounce
 // events off of.
 DECLARE_ELEMENT_IDENTIFIER_VALUE(kInteractiveTestPivotElementId);
+DECLARE_CUSTOM_ELEMENT_EVENT_TYPE(kInteractiveTestPivotEventType);
 
 // Class that implements functionality for InteractiveTest* that should be
 // hidden from tests that inherit the API.
 class InteractiveTestPrivate {
  public:
+  using MultiStep = std::vector<InteractionSequence::StepBuilder>;
+
   explicit InteractiveTestPrivate(
       std::unique_ptr<InteractionTestUtil> test_util);
   virtual ~InteractiveTestPrivate();
@@ -66,6 +71,11 @@
     aborted_callback_for_testing_ = std::move(aborted_callback_for_testing);
   }
 
+  // Places a callback in the message queue to bounce an event off of the pivot
+  // element, then responds by executing `task`.
+  template <typename T>
+  static MultiStep PostTask(T&& task);
+
  private:
   friend class ui::test::InteractiveTestApi;
 
@@ -105,6 +115,43 @@
   return false;
 }
 
+// static
+template <typename T>
+InteractiveTestPrivate::MultiStep InteractiveTestPrivate::PostTask(T&& task) {
+  MultiStep result;
+  result.emplace_back(std::move(
+      InteractionSequence::StepBuilder()
+          .SetElementID(kInteractiveTestPivotElementId)
+          .SetStartCallback(base::BindOnce([](ui::TrackedElement* el) {
+            base::ThreadTaskRunnerHandle::Get()->PostTask(
+                FROM_HERE,
+                base::BindOnce(
+                    [](ElementIdentifier id, ElementContext context) {
+                      auto* const el =
+                          ui::ElementTracker::GetElementTracker()
+                              ->GetFirstMatchingElement(id, context);
+                      if (el) {
+                        ui::ElementTracker::GetFrameworkDelegate()
+                            ->NotifyCustomEvent(el,
+                                                kInteractiveTestPivotEventType);
+                      }
+                      // If there is no pivot element, the test sequence has
+                      // been aborted and there's no need to send an additional
+                      // error.
+                    },
+                    el->identifier(), el->context()));
+          }))));
+  result.emplace_back(std::move(
+      InteractionSequence::StepBuilder()
+          .SetElementID(kInteractiveTestPivotElementId)
+          .SetType(InteractionSequence::StepType::kCustomEvent,
+                   kInteractiveTestPivotEventType)
+          .SetStartCallback(
+              base::RectifyCallback<InteractionSequence::StepStartCallback>(
+                  std::move(task)))));
+  return result;
+}
+
 // Converts an ElementSpecifier to an element ID or name and sets it onto
 // `builder`.
 void SpecifyElement(ui::InteractionSequence::StepBuilder& builder,
diff --git a/ui/compositor/paint_recorder.cc b/ui/compositor/paint_recorder.cc
index 07b0b7ad..baabb44 100644
--- a/ui/compositor/paint_recorder.cc
+++ b/ui/compositor/paint_recorder.cc
@@ -29,7 +29,7 @@
                               cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer)
                         : nullptr),
       record_canvas_(cache ? local_list_.get() : context_->list_.get(),
-                     gfx::RectToSkRect(gfx::Rect(recording_size))),
+                     recording_size),
       canvas_(&record_canvas_, context.device_scale_factor_),
       cache_(cache),
       recording_size_(recording_size) {
diff --git a/ui/compositor/paint_recorder.h b/ui/compositor/paint_recorder.h
index 3ea209f..881cd106 100644
--- a/ui/compositor/paint_recorder.h
+++ b/ui/compositor/paint_recorder.h
@@ -47,7 +47,7 @@
  private:
   const raw_ref<const PaintContext> context_;
   scoped_refptr<cc::DisplayItemList> local_list_;
-  cc::RecordPaintCanvas record_canvas_;
+  cc::InspectableRecordPaintCanvas record_canvas_;
   gfx::Canvas canvas_;
   raw_ptr<PaintCache> cache_;
   gfx::Size recording_size_;
diff --git a/ui/display/win/screen_win.cc b/ui/display/win/screen_win.cc
index a66d4ce9..5af4aea3 100644
--- a/ui/display/win/screen_win.cc
+++ b/ui/display/win/screen_win.cc
@@ -731,6 +731,12 @@
       device_name);
 }
 
+// static
+void ScreenWin::UpdateDisplayInfos() {
+  if (g_instance)
+    g_instance->UpdateAllDisplaysAndNotify();
+}
+
 HWND ScreenWin::GetHWNDFromNativeWindow(gfx::NativeWindow window) const {
   NOTREACHED();
   return nullptr;
diff --git a/ui/display/win/screen_win.h b/ui/display/win/screen_win.h
index 33eac37b..b8746f5 100644
--- a/ui/display/win/screen_win.h
+++ b/ui/display/win/screen_win.h
@@ -158,6 +158,11 @@
   // Returns the device id for the given `device_name`.
   static int64_t DeviceIdFromDeviceName(const wchar_t* device_name);
 
+  // Updates the display infos to make sure they have the right scale factors.
+  // This is called before handling WM_DPICHANGED messages, to be sure that we
+  // have the right scale factors for the screens.
+  static void UpdateDisplayInfos();
+
   // Returns the HWND associated with the NativeWindow.
   virtual HWND GetHWNDFromNativeWindow(gfx::NativeWindow view) const;
 
diff --git a/ui/file_manager/file_manager/containers/search_container.ts b/ui/file_manager/file_manager/containers/search_container.ts
index 8a932fbc..8d7271d 100644
--- a/ui/file_manager/file_manager/containers/search_container.ts
+++ b/ui/file_manager/file_manager/containers/search_container.ts
@@ -5,7 +5,12 @@
 import {CrInputElement} from 'chrome://resources/cr_elements/cr_input/cr_input.js';
 
 import {queryRequiredElement} from '../common/js/dom_utils.js';
+import {util} from '../common/js/util.js';
+import {PropStatus, SearchData, SearchFileType, SearchLocation, SearchOptions, SearchRecency, SearchStatus, State} from '../externs/ts/state.js';
 import {SearchAutocompleteList} from '../foreground/js/ui/search_autocomplete_list.js';
+import {searchAction} from '../state/actions.js';
+import {getStore, Store} from '../state/store.js';
+import {OptionKind, SEARCH_OPTIONS_CHANGED, SearchOptionsChangedEvent, XfSearchOptionsElement} from '../widgets/xf_search_options.js';
 
 /**
  * @fileoverview
@@ -48,17 +53,38 @@
   // A component that shows potential matches for the text the user typed so
   // far.
   private autocompleteList_: SearchAutocompleteList;
+  // The current value of search options, initialized to some sensible default.
+  private currentOptions_: SearchOptions = {
+    location: SearchLocation.THIS_FOLDER,
+    recency: SearchRecency.ANYTIME,
+    type: SearchFileType.ALL_TYPES,
+  };
+  // The store which updates us about state changes.
+  private store_: Store;
+  // The cached state of the store; store may post events if other parts of the
+  // state change. However, we just want to react to changes related to search.
+  // We use the cached search state to check if the state change was related to
+  // search state change or some other part of the state.
+  private searchState_: SearchData|undefined = undefined;
+  // The container with search options widget.
+  private optionsContainer_: HTMLElement;
+  // The UI widget that allows users to manipulate search options. This is used
+  // mostly to cache the access to the actual element, rather than accessing it
+  // via querySelector.
+  private searchOptions_: XfSearchOptionsElement|null = null;
+
 
   /**
    * Builds a search container that creates and manages UI elements. This
-   * container receives a reference to a container, |searchWrapper| that
-   * contains other UI elements. It uses the container to fetch them by IDs.
-   * Once the UI elements are found, this container makes itself the listener of
-   * the events that are posted to by the UI elements and converts them to
-   * business logic. This includes notifying listeners when the the search query
-   * changes or the auto-complete item is selected.
+   * container receives a reference to `searchWrapper` that contains query
+   * UI elements and `optionsContainer` that has search options UI element.
+   * It uses `searchWrapper` to fetch them by IDs. Once the UI elements are
+   * found, this container makes itself the listener of the events that are
+   * posted to by the UI elements and converts them to business logic. This
+   * includes notifying listeners when the the search query changes or the
+   * auto-complete item is selected.
    */
-  constructor(searchWrapper: HTMLElement) {
+  constructor(searchWrapper: HTMLElement, optionsContainer: HTMLElement) {
     super();
 
     // The "box" around the search button, query input, and clear button.
@@ -86,6 +112,12 @@
         new SearchAutocompleteList(this.searchBox_.ownerDocument);
     this.searchWrapper_.parentNode!.appendChild(this.autocompleteList_);
 
+    this.optionsContainer_ = optionsContainer;
+    this.store_ = getStore();
+    if (util.isSearchV2Enabled()) {
+      this.store_.subscribe(this);
+    }
+
     this.setupEventHandlers();
   }
 
@@ -175,6 +207,126 @@
   }
 
   /**
+   * A method invoked every time the store state changes.
+   */
+  onStateChanged(state: State) {
+    const {search} = state;
+    if (this.searchState_ === search) {
+      // Bail out early if the search part of the state has not changed.
+      return;
+    }
+    this.searchState_ = search;
+    if (search && search.status) {
+      const status = search.status;
+      if (status === PropStatus.STARTED && search.query) {
+        this.showOptions_();
+      } else if (status === SearchStatus.INACTIVE) {
+        this.hideOptions_();
+      }
+    }
+  }
+
+  private hideOptions_() {
+    const element = this.getSearchOptionsElement_();
+    if (element) {
+      element.hidden = true;
+    }
+  }
+
+  private showOptions_() {
+    let element = this.getSearchOptionsElement_();
+    if (!element) {
+      element = this.createSearchOptionsElement_();
+    }
+    element.hidden = false;
+  }
+
+  /**
+   * Returns the search options element by either retuning the cached instance,
+   * or fetching it by its tag. May return null.
+   */
+  private getSearchOptionsElement_(): XfSearchOptionsElement|null {
+    if (!this.searchOptions_) {
+      this.searchOptions_ = document.querySelector('xf-search-options');
+    }
+    return this.searchOptions_;
+  }
+
+  private createSearchOptionsElement_(): XfSearchOptionsElement {
+    const element = document.createElement('xf-search-options');
+    this.optionsContainer_.appendChild(element);
+
+    element.id = 'search-options';
+    // TODO(majewski): Get strings from loadTimeData.
+    element.getLocationSelector().setOptions([
+      {value: SearchLocation.EVERYWHERE, text: 'Everywhere'},
+      {value: SearchLocation.THIS_CHROMEBOOK, text: 'This Chromebook'},
+      {value: SearchLocation.THIS_FOLDER, text: 'This folder', default: true},
+    ]);
+    element.getRecencySelector().setOptions([
+      {value: SearchRecency.ANYTIME, text: 'Anytime'},
+      {value: SearchRecency.TODAY, text: 'Today'},
+      {value: SearchRecency.YESTERDAY, text: 'Yesterday'},
+      {value: SearchRecency.LAST_WEEK, text: 'Last week'},
+      {value: SearchRecency.LAST_MONTH, text: 'Last month'},
+      {value: SearchRecency.LAST_YEAR, text: 'Last year'},
+    ]);
+    element.getFileTypeSelector().setOptions([
+      {value: SearchFileType.ALL_TYPES, text: 'Any'},
+      {value: SearchFileType.AUDIO, text: 'Audio'},
+      {value: SearchFileType.DOCUMENTS, text: 'Documents'},
+      {value: SearchFileType.IMAGES, text: 'Images'},
+      {value: SearchFileType.VIDEOS, text: 'Videos'},
+    ]);
+    element.addEventListener(
+        SEARCH_OPTIONS_CHANGED, this.onOptionsChanged_.bind(this));
+    this.searchOptions_ = element;
+    return element;
+  }
+
+  private onOptionsChanged_(event: SearchOptionsChangedEvent) {
+    const kind = event.detail.kind;
+    const value = event.detail.value;
+    switch (kind) {
+      case OptionKind.LOCATION: {
+        const location = value as unknown as SearchLocation;
+        if (location !== this.currentOptions_.location) {
+          this.currentOptions_.location = location;
+          this.updateState_();
+        }
+        break;
+      }
+      case OptionKind.RECENCY: {
+        const recency = value as unknown as SearchRecency;
+        if (recency !== this.currentOptions_.recency) {
+          this.currentOptions_.recency = recency;
+          this.updateState_();
+        }
+        break;
+      }
+      case OptionKind.FILE_TYPE: {
+        const type = value as unknown as SearchFileType;
+        if (type !== this.currentOptions_.type) {
+          this.currentOptions_.type = type;
+          this.updateState_();
+        }
+        break;
+      }
+      default:
+        console.error(`Unhandled search option kind: ${kind}`);
+        break;
+    }
+  }
+
+  /**
+   * Updates the store state.
+   */
+  private updateState_() {
+    // TODO(majewski): Update the store.
+    console.debug('Search options event', JSON.stringify(this.currentOptions_));
+  }
+
+  /**
    * Attaches all necessary event listeners to the UI elements that make the
    * search interface. This method must be called as the last statement of the
    * constructor.
@@ -262,6 +414,9 @@
     // Do not initiate close transition if we are not open. This would leave us
     // in the CLOSING state, without ever getting to CLOSED state.
     if (this.inputState_ === SearchInputState.OPEN) {
+      if (util.isSearchV2Enabled()) {
+        this.store_.dispatch(searchAction({status: SearchStatus.INACTIVE}));
+      }
       this.inputState_ = SearchInputState.CLOSING;
       this.inputElement_.tabIndex = -1;
       this.inputElement_.disabled = true;
diff --git a/ui/file_manager/file_manager/externs/ts/state.js b/ui/file_manager/file_manager/externs/ts/state.js
index f46e4a6..1edec26f 100644
--- a/ui/file_manager/file_manager/externs/ts/state.js
+++ b/ui/file_manager/file_manager/externs/ts/state.js
@@ -39,6 +39,16 @@
 };
 
 /**
+ * The additional property states understood by the search container. When the
+ * user actions cause search elements to be hidden, the search state in the
+ * store becomes INACTIVE.
+ * @enum {string}
+ */
+export const SearchStatus = {
+  INACTIVE: 'INACTIVE',
+};
+
+/**
  * The current directory.
  * The directory is only effectively active when the `status` is SUCCESS.
  * @typedef {{
@@ -50,10 +60,58 @@
 export let CurrentDirectory;
 
 /**
+ * Enumeration of all supported search locations. If new location is added,
+ * please update this enum.
+ * @enum {string}
+ */
+export const SearchLocation = {
+  EVERYWHERE: 'everywhere',
+  THIS_CHROMEBOOK: 'this_chromebook',
+  THIS_FOLDER: 'this_folder',
+};
+
+/**
+ * Enumeration of all supported how-recent time spans.
+ * @enum{string}
+ */
+export const SearchRecency = {
+  ANYTIME: 'anytime',
+  TODAY: 'today',
+  YESTERDAY: 'yesterday',
+  LAST_WEEK: 'last_week',
+  LAST_MONTH: 'last_month',
+  LAST_YEAR: 'last_year',
+};
+
+/**
+ * Enumeration of all supported file types. We use generic buckets such as
+ * Images, to denote all "*.jpg", "*.gif", "*.png", etc., file types.
+ * @enum{string}
+ */
+export const SearchFileType = {
+  ALL_TYPES: 'all_types',
+  AUDIO: 'audio',
+  DOCUMENTS: 'documents',
+  IMAGES: 'images',
+  VIDEOS: 'videos',
+};
+
+/**
+ * The options used by the file search operation.
+ * @typedef {{
+ *   location: SearchLocation,
+ *   recency: SearchRecency,
+ *   type: SearchFileType,
+ * }}
+ */
+export let SearchOptions;
+
+/**
  * Data for search. It should be empty `{}` when the user isn't searching.
  * @typedef {{
- *   status: (PropStatus|undefined),
+ *   status: (PropStatus|SearchStatus|undefined),
  *   query: (string|undefined),
+ *   options: (!SearchOptions|undefined),
  * }}
  */
 export let SearchData;
diff --git a/ui/file_manager/file_manager/foreground/js/directory_model.js b/ui/file_manager/file_manager/foreground/js/directory_model.js
index 42823b7..a056757 100644
--- a/ui/file_manager/file_manager/foreground/js/directory_model.js
+++ b/ui/file_manager/file_manager/foreground/js/directory_model.js
@@ -1608,6 +1608,8 @@
         return;
       }
 
+      this.store_.dispatch(
+          searchAction({query: query, status: PropStatus.STARTED}));
       this.onSearchCompleted_ = (...args) => {
         // Notify the caller via callback, for non-store based callers.
         onSearchRescan(...args);
diff --git a/ui/file_manager/file_manager/foreground/js/ui/file_manager_ui.js b/ui/file_manager/file_manager/foreground/js/ui/file_manager_ui.js
index 08679fb..d365507 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/file_manager_ui.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/file_manager_ui.js
@@ -239,7 +239,8 @@
      * @const
      */
     this.searchContainer = new SearchContainer(
-        queryRequiredElement('#search-wrapper', this.element));
+        queryRequiredElement('#search-wrapper', this.element),
+        queryRequiredElement('#search-options-container', this.element));
 
     /**
      * Toggle-view button.
diff --git a/ui/file_manager/file_manager/main.html b/ui/file_manager/file_manager/main.html
index eb1a24e..80dd3f75 100644
--- a/ui/file_manager/file_manager/main.html
+++ b/ui/file_manager/file_manager/main.html
@@ -427,6 +427,7 @@
             </cr-button>
           </div>
         </div>
+        <div id="search-options-container"></div>
         <div class="dialog-body">
           <div id="a11y-msg" aria-live="polite" style="display: inline-block; position: fixed; clip: rect(0, 0, 0, 0);"></div>
           <div class="main-panel">
diff --git a/ui/file_manager/file_manager/state/actions.ts b/ui/file_manager/file_manager/state/actions.ts
index f22a543..4005bde 100644
--- a/ui/file_manager/file_manager/state/actions.ts
+++ b/ui/file_manager/file_manager/state/actions.ts
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {PropStatus} from '../externs/ts/state.js';
+import {PropStatus, SearchOptions} from '../externs/ts/state.js';
 import {BaseAction} from '../lib/base_store.js';
 
 import {FileKey} from './file_key.js';
@@ -44,6 +44,7 @@
   payload: {
     query?: string,
     status?: PropStatus,
+    options?: SearchOptions,
   };
 }
 
@@ -62,13 +63,15 @@
 }
 
 export function searchAction(
-    {query: query, status}: {query?: string, status?: PropStatus}):
+    {query, status, options}:
+        {query?: string, status?: PropStatus, options?: SearchOptions}):
     SearchAction {
   return {
     type: ActionType.SEARCH,
     payload: {
       query: query,
       status,
+      options,
     },
   };
 }
diff --git a/ui/file_manager/file_manager/state/reducers/search.ts b/ui/file_manager/file_manager/state/reducers/search.ts
index 6036ecb..1080753b 100644
--- a/ui/file_manager/file_manager/state/reducers/search.ts
+++ b/ui/file_manager/file_manager/state/reducers/search.ts
@@ -9,6 +9,7 @@
   const search = {
     query: action.payload.query,
     status: action.payload.status,
+    options: action.payload.options,
   };
   return {...state, search};
 }
diff --git a/ui/file_manager/file_manager/state/store.ts b/ui/file_manager/file_manager/state/store.ts
index 9bc7282..6448292 100644
--- a/ui/file_manager/file_manager/state/store.ts
+++ b/ui/file_manager/file_manager/state/store.ts
@@ -44,6 +44,7 @@
     search: {
       query: undefined,
       status: undefined,
+      options: undefined,
     },
   };
 }
diff --git a/ui/file_manager/file_manager/widgets/xf_search_options.html b/ui/file_manager/file_manager/widgets/xf_search_options.html
new file mode 100644
index 0000000..b13da63
--- /dev/null
+++ b/ui/file_manager/file_manager/widgets/xf_search_options.html
@@ -0,0 +1,15 @@
+<style>
+  :host([hidden]) {
+    display: none !important;
+  }
+
+  :host {
+    display: flex;
+    flex-direction: row;
+    margin: 5px 2px;
+  }
+</style>
+
+<xf-select id="location-selector"></xf-select>
+<xf-select id="recency-selector"></xf-select>
+<xf-select id="type-selector"></xf-select>
diff --git a/ui/file_manager/file_manager/widgets/xf_search_options.ts b/ui/file_manager/file_manager/widgets/xf_search_options.ts
new file mode 100644
index 0000000..3b1485f
--- /dev/null
+++ b/ui/file_manager/file_manager/widgets/xf_search_options.ts
@@ -0,0 +1,156 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview A search widget that allows users to choose search options.
+ * Disable type checking for closure, as it is done by the typescript compiler.
+ * @suppress {checkTypes}
+ */
+
+import {getTemplate} from './xf_search_options.html.js';
+import {SELECTION_CHANGED, XfSelect} from './xf_select.js';
+
+/**
+ * The enumeration of strings used to identify the kind of option that changed.
+ * @enum {string}
+ */
+export enum OptionKind {
+  LOCATION = 'location',
+  RECENCY = 'recenty',
+  FILE_TYPE = 'file-type',
+}
+
+/**
+ * The interface that specifies the type of the detail field in the custom
+ * SEARCH_OPTIONS_CHANGED event.
+ */
+export interface OptionChange {
+  // The kind of option that has changed (LOCATION, RECENCY, FILE_TYPE).
+  kind: OptionKind;
+  // The current value of the changed option.
+  value: string;
+}
+
+/**
+ * Helper function for constructing a SEARCH_OPTIONS_CHANGED event.
+ */
+function newSearchOptionsChangedEvent(
+    kind: OptionKind, value: string): CustomEvent {
+  return new CustomEvent(SEARCH_OPTIONS_CHANGED, {
+    bubbles: true,
+    composed: true,
+    detail: {
+      kind: kind,
+      value: value,
+    },
+  });
+}
+
+/**
+ * Search options is a composite element that allows users to tweak specifics of
+ * the file search operation. For example, it presents an UI for selecting the
+ * desired file type.
+ *
+ * It emits the `SEARCH_OPTIONS_CHANGED` event when the options change. The
+ * detail filed of the event carries information what option changes and to what
+ * value. It is up to the creator of this element to set values that are
+ * meaningful. Typical use:
+ *
+ * const element = document.createElement('xf-search-options');
+ * element.setLocationOptions(new Map<string, string>([
+ *   ['value-a', 'Text of value A'],
+ *   ...
+ * ]);
+ * element.addEventListener(
+ *     SEARCH_OPTIONS_CHANGED, (event) => {
+ *       if (event.detail.kind === OptionKind.LOCATION) {
+ *         if (event.detail.value === 'value-a') {
+ *           // adjust search options, based on value-a being selected.
+ *         }
+ *       }
+ *     });
+ */
+export class XfSearchOptionsElement extends HTMLElement {
+  constructor() {
+    super();
+
+    // Create element content.
+    const template = document.createElement('template');
+    template.innerHTML = getTemplate() as unknown as string;
+    const fragment = template.content.cloneNode(true);
+    this.attachShadow({mode: 'open'}).appendChild(fragment);
+  }
+
+  /**
+   * On DOM connected initialize all event listeners. We do not disconnect them,
+   * as the only time we get DOM disconnected is when the entire files app is
+   * closed.
+   */
+  connectedCallback(): void {
+    this.getLocationSelector().addEventListener(SELECTION_CHANGED, (event) => {
+      this.dispatchEvent(newSearchOptionsChangedEvent(
+          OptionKind.LOCATION, event.detail.value));
+    });
+    this.getRecencySelector().addEventListener(SELECTION_CHANGED, (event) => {
+      this.dispatchEvent(
+          newSearchOptionsChangedEvent(OptionKind.RECENCY, event.detail.value));
+    });
+    this.getFileTypeSelector().addEventListener(SELECTION_CHANGED, (event) => {
+      this.dispatchEvent(newSearchOptionsChangedEvent(
+          OptionKind.FILE_TYPE, event.detail.value));
+    });
+  }
+
+  /**
+   * Provides access to the location select element.
+   */
+  getLocationSelector(): XfSelect {
+    return this.getSelectElement_('location-selector');
+  }
+
+  /**
+   * Provides access to the recency select element.
+   */
+  getRecencySelector(): XfSelect {
+    return this.getSelectElement_('recency-selector');
+  }
+
+  /**
+   * Provides access to the file type select element.
+   */
+  getFileTypeSelector(): XfSelect {
+    return this.getSelectElement_('type-selector');
+  }
+
+  private getSelectElement_(id: string): XfSelect {
+    const e = this.shadowRoot!.querySelector<XfSelect>(`#${id}`);
+    if (e) {
+      return e;
+    }
+    throw new Error(`Element "${id}" not found`);
+  }
+}
+
+/**
+ * The name of the even generated by this widget.
+ */
+export const SEARCH_OPTIONS_CHANGED = 'search_options_changed';
+
+/**
+ * A custom event that informs the container which option kind change to what
+ * value. It is up to the container to interpret these.
+ */
+export type SearchOptionsChangedEvent = CustomEvent<OptionChange>;
+
+declare global {
+  interface HTMLElementEventMap {
+    [SEARCH_OPTIONS_CHANGED]: SearchOptionsChangedEvent;
+  }
+
+  interface HTMLElementTagNameMap {
+    'xf-search-options': XfSearchOptionsElement;
+  }
+}
+
+customElements.define('xf-search-options', XfSearchOptionsElement);
diff --git a/ui/file_manager/file_manager/widgets/xf_search_options_unittest.ts b/ui/file_manager/file_manager/widgets/xf_search_options_unittest.ts
new file mode 100644
index 0000000..9a502f6
--- /dev/null
+++ b/ui/file_manager/file_manager/widgets/xf_search_options_unittest.ts
@@ -0,0 +1,72 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {assertDeepEquals} from 'chrome://webui-test/chai_assert.js';
+
+import {SearchFileType, SearchLocation, SearchRecency} from '../externs/ts/state.js';
+
+import {OptionKind, SEARCH_OPTIONS_CHANGED, XfSearchOptionsElement} from './xf_search_options.js';
+
+/**
+ * Creates new <xf-search-options> element for each test.
+ */
+export function setUp() {
+  document.body.innerHTML = '<xf-search-options></xf-search-options>';
+}
+
+/**
+ * Returns the <xf-search-options> element.
+ */
+function getSearchOptionsElement(): XfSearchOptionsElement {
+  const element = document.querySelector('xf-search-options');
+  return element!;
+}
+
+export async function testChangeLocation(done: () => void) {
+  const element = getSearchOptionsElement();
+  const locationSelector = element.getLocationSelector();
+  locationSelector.setOptions([
+    {value: SearchLocation.EVERYWHERE, text: 'Everywhere'},
+    {value: SearchLocation.THIS_FOLDER, text: 'This folder', default: true},
+  ]);
+
+  element.addEventListener(SEARCH_OPTIONS_CHANGED, (event) => {
+    const want = {kind: OptionKind.LOCATION, value: SearchLocation.EVERYWHERE};
+    assertDeepEquals(want, event.detail);
+    done();
+  });
+  locationSelector.value = SearchLocation.EVERYWHERE;
+}
+
+export async function testChangeRecency(done: () => void) {
+  const element = getSearchOptionsElement();
+  const recencySelector = element.getRecencySelector();
+  recencySelector.setOptions([
+    {value: SearchRecency.ANYTIME, text: 'Any time'},
+    {value: SearchRecency.YESTERDAY, text: 'Yesterday'},
+  ]);
+
+  element.addEventListener(SEARCH_OPTIONS_CHANGED, (event) => {
+    const want = {kind: OptionKind.RECENCY, value: SearchRecency.YESTERDAY};
+    assertDeepEquals(want, event.detail);
+    done();
+  });
+  recencySelector.value = SearchRecency.YESTERDAY;
+}
+
+export async function testChangeFileType(done: () => void) {
+  const element = getSearchOptionsElement();
+  const fileTypeSelector = element.getFileTypeSelector();
+  fileTypeSelector.setOptions([
+    {value: SearchFileType.ALL_TYPES, text: 'All types'},
+    {value: SearchFileType.IMAGES, text: 'Images'},
+  ]);
+
+  element.addEventListener(SEARCH_OPTIONS_CHANGED, (event) => {
+    const want = {kind: OptionKind.FILE_TYPE, value: SearchFileType.IMAGES};
+    assertDeepEquals(want, event.detail);
+    done();
+  });
+  fileTypeSelector.value = SearchFileType.IMAGES;
+}
diff --git a/ui/file_manager/file_names.gni b/ui/file_manager/file_names.gni
index 27a9666..f1db801 100644
--- a/ui/file_manager/file_names.gni
+++ b/ui/file_manager/file_names.gni
@@ -269,6 +269,7 @@
   "file_manager/widgets/xf_conflict_dialog.ts",
   "file_manager/widgets/xf_dlp_restriction_details_dialog.ts",
   "file_manager/widgets/xf_nudge.ts",
+  "file_manager/widgets/xf_search_options.ts",
   "file_manager/widgets/xf_select.ts",
   "file_manager/foreground/js/ui/search_autocomplete_list.ts",
 
@@ -287,6 +288,7 @@
   "file_manager/widgets/xf_conflict_dialog.html",
   "file_manager/widgets/xf_dlp_restriction_details_dialog.html",
   "file_manager/widgets/xf_nudge.html",
+  "file_manager/widgets/xf_search_options.html",
   "file_manager/widgets/xf_select.html",
 ]
 
@@ -307,6 +309,7 @@
   "file_manager/widgets/xf_conflict_dialog_unittest.ts",
   "file_manager/widgets/xf_dlp_restriction_details_dialog_unittest.ts",
   "file_manager/widgets/xf_nudge_unittest.ts",
+  "file_manager/widgets/xf_search_options_unittest.ts",
   "file_manager/widgets/xf_select_unittest.ts",
 
   # Foreground:
diff --git a/ui/file_manager/integration_tests/file_manager/search.js b/ui/file_manager/integration_tests/file_manager/search.js
index ad4787b..5883ab3 100644
--- a/ui/file_manager/integration_tests/file_manager/search.js
+++ b/ui/file_manager/integration_tests/file_manager/search.js
@@ -310,3 +310,17 @@
     }
   });
 };
+
+/**
+ * Checks that the search options are shown as expected.
+ */
+testcase.searchOptions = async () => {
+  // Open Files app on Downloads.
+  const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS);
+
+  // Enter some text in the search box. Minimum one character is needed.
+  await remoteCall.typeSearchText(appId, 'x');
+
+  // Verify that the search options are visible.
+  await remoteCall.waitForElement(appId, 'xf-search-options:not([hidden])');
+};
diff --git a/ui/gfx/image/canvas_image_source.cc b/ui/gfx/image/canvas_image_source.cc
index ff6fd0a..4337ae7 100644
--- a/ui/gfx/image/canvas_image_source.cc
+++ b/ui/gfx/image/canvas_image_source.cc
@@ -58,16 +58,14 @@
           cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer);
   display_item_list->StartPaint();
 
-  SizeF size_in_pixel = ScaleSize(SizeF(size_), scale);
-  cc::RecordPaintCanvas record_canvas(
-      display_item_list.get(),
-      SkRect::MakeWH(SkFloatToScalar(size_in_pixel.width()),
-                     SkFloatToScalar(size_in_pixel.height())));
+  Size size_in_pixel = ScaleToCeiledSize(size_, scale);
+  cc::InspectableRecordPaintCanvas record_canvas(display_item_list.get(),
+                                                 size_in_pixel);
   gfx::Canvas canvas(&record_canvas, scale);
 #if DCHECK_IS_ON()
   Rect clip_rect;
   DCHECK(canvas.GetClipBounds(&clip_rect));
-  DCHECK(clip_rect.Contains(gfx::Rect(ToCeiledSize(size_in_pixel))));
+  DCHECK(clip_rect.Contains(gfx::Rect(size_in_pixel)));
 #endif
   canvas.Scale(scale, scale);
   Draw(&canvas);
diff --git a/ui/gfx/image/image_skia_rep_default.cc b/ui/gfx/image/image_skia_rep_default.cc
index ca11e16..905ca27 100644
--- a/ui/gfx/image/image_skia_rep_default.cc
+++ b/ui/gfx/image/image_skia_rep_default.cc
@@ -80,8 +80,7 @@
       base::MakeRefCounted<cc::DisplayItemList>(
           cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer);
 
-  cc::RecordPaintCanvas record_canvas(
-      display_item_list.get(), SkRect::MakeIWH(pixel_width(), pixel_height()));
+  cc::RecordPaintCanvas record_canvas(display_item_list.get());
 
   display_item_list->StartPaint();
   record_canvas.drawImage(paint_image(), 0, 0);
diff --git a/ui/gfx/overlay_transform_utils.cc b/ui/gfx/overlay_transform_utils.cc
index 1d31678..5d23bb53 100644
--- a/ui/gfx/overlay_transform_utils.cc
+++ b/ui/gfx/overlay_transform_utils.cc
@@ -6,7 +6,6 @@
 
 #include "base/notreached.h"
 #include "ui/gfx/geometry/rect_conversions.h"
-#include "ui/gfx/geometry/skia_conversions.h"
 
 namespace gfx {
 
@@ -19,21 +18,16 @@
     case OVERLAY_TRANSFORM_NONE:
       return Transform();
     case OVERLAY_TRANSFORM_FLIP_HORIZONTAL:
-      return SkMatrixToTransform(
-          SkMatrix::MakeAll(-1, 0, viewport_bounds.width(), 0, 1, 0, 0, 0, 1));
+      return Transform::Affine(-1, 0, 0, 1, viewport_bounds.width(), 0);
     case OVERLAY_TRANSFORM_FLIP_VERTICAL:
-      return SkMatrixToTransform(
-          SkMatrix::MakeAll(1, 0, 0, 0, -1, viewport_bounds.height(), 0, 0, 1));
+      return Transform::Affine(1, 0, 0, -1, 0, viewport_bounds.height());
     case OVERLAY_TRANSFORM_ROTATE_90:
-      return SkMatrixToTransform(
-          SkMatrix::MakeAll(0, -1, viewport_bounds.height(), 1, 0, 0, 0, 0, 1));
+      return Transform::Affine(0, 1, -1, 0, viewport_bounds.height(), 0);
     case OVERLAY_TRANSFORM_ROTATE_180:
-      return SkMatrixToTransform(
-          SkMatrix::MakeAll(-1, 0, viewport_bounds.width(), 0, -1,
-                            viewport_bounds.height(), 0, 0, 1));
+      return Transform::Affine(-1, 0, 0, -1, viewport_bounds.width(),
+                               viewport_bounds.height());
     case OVERLAY_TRANSFORM_ROTATE_270:
-      return SkMatrixToTransform(
-          SkMatrix::MakeAll(0, 1, 0, -1, 0, viewport_bounds.width(), 0, 0, 1));
+      return Transform::Affine(0, -1, 1, 0, 0, viewport_bounds.width());
   }
 
   NOTREACHED();
diff --git a/ui/gfx/paint_vector_icon_unittest.cc b/ui/gfx/paint_vector_icon_unittest.cc
index ebf6bc5..3c7b954 100644
--- a/ui/gfx/paint_vector_icon_unittest.cc
+++ b/ui/gfx/paint_vector_icon_unittest.cc
@@ -46,7 +46,7 @@
 // (CLOSE) uses the correct starting point. See crbug.com/697497
 TEST(VectorIconTest, RelativeMoveToAfterClose) {
   cc::PaintRecorder recorder;
-  Canvas canvas(recorder.beginRecording(100, 100), 1.0f);
+  Canvas canvas(recorder.beginRecording(), 1.0f);
 
   const PathElement elements[] = {
       MOVE_TO, 4, 5, LINE_TO, 10, 11, CLOSE,
diff --git a/ui/gfx/render_text_harfbuzz.cc b/ui/gfx/render_text_harfbuzz.cc
index 9bc39c3..9f3ca19 100644
--- a/ui/gfx/render_text_harfbuzz.cc
+++ b/ui/gfx/render_text_harfbuzz.cc
@@ -11,6 +11,7 @@
 #include "base/containers/contains.h"
 #include "base/containers/lru_cache.h"
 #include "base/containers/span.h"
+#include "base/debug/dump_without_crashing.h"
 #include "base/feature_list.h"
 #include "base/hash/hash.h"
 #include "base/i18n/base_i18n_switches.h"
@@ -2136,6 +2137,16 @@
                            TRACE_STR_COPY(font_name.c_str()),
                            "primary_font_name", primary_font.GetFontName());
       RecordShapeRunsFallback(ShapeRunFallback::FALLBACKS);
+#if BUILDFLAG(IS_WIN)
+      // Resolving fallback fonts using the registry keys on windows will be
+      // deprecated and removed (see: http://crbug.com/995789). The crashes
+      // reported here should be fixed before deprecating the code.
+      static bool is_first_crash = true;
+      if (is_first_crash) {
+        is_first_crash = false;
+        base::debug::DumpWithoutCrashing();
+      }
+#endif
       return;
     }
   }
diff --git a/ui/gfx/render_text_unittest.cc b/ui/gfx/render_text_unittest.cc
index d9e0646..be7ede3 100644
--- a/ui/gfx/render_text_unittest.cc
+++ b/ui/gfx/render_text_unittest.cc
@@ -454,7 +454,7 @@
     constexpr int kCanvasHeight = 400;
 
     cc::PaintRecorder recorder;
-    Canvas canvas(recorder.beginRecording(kCanvasWidth, kCanvasHeight), 1.0f);
+    Canvas canvas(recorder.beginRecording(), 1.0f);
     test_api_->Draw(&canvas, select_all);
     sk_sp<cc::PaintRecord> record = recorder.finishRecordingAsPicture();
 
diff --git a/ui/gl/gl_surface_format.h b/ui/gl/gl_surface_format.h
index cd862034..b51f3a6 100644
--- a/ui/gl/gl_surface_format.h
+++ b/ui/gl/gl_surface_format.h
@@ -33,8 +33,10 @@
   bool IsCompatible(GLSurfaceFormat other_format) const;
 
   // Default pixel format is RGBA8888. Use this method to select
-  // a preference of RGB565. TODO(klausw): use individual setter
-  // methods if there's a use case for them.
+  // a preference of RGB565. This could be refactored to use individual
+  // setter methods for more fine-grained control, but so far there hasn't
+  // been a use case for that. See also GLES2CommandBufferStub::Initialize
+  // which distinguishes between RGBA8888 and RGB565 modes.
   void SetRGB565();
 
   // Other properties for future use.
diff --git a/ui/lottie/animation_unittest.cc b/ui/lottie/animation_unittest.cc
index 947015b..b078af5 100644
--- a/ui/lottie/animation_unittest.cc
+++ b/ui/lottie/animation_unittest.cc
@@ -306,9 +306,7 @@
   AnimationWithImageAssetsTest()
       : display_list_(base::MakeRefCounted<cc::DisplayItemList>(
             cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer)),
-        record_canvas_(display_list_.get(),
-                       SkRect::MakeIWH(cc::kLottieDataWith2AssetsWidth,
-                                       cc::kLottieDataWith2AssetsHeight)) {}
+        record_canvas_(display_list_.get()) {}
 
   void SetUp() override {
     canvas_ = std::make_unique<gfx::Canvas>(&record_canvas_, kCanvasImageScale);
diff --git a/ui/lottie/resource.cc b/ui/lottie/resource.cc
index 4b9de6a5a..43571ba 100644
--- a/ui/lottie/resource.cc
+++ b/ui/lottie/resource.cc
@@ -58,9 +58,7 @@
           cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer);
   display_item_list->StartPaint();
 
-  cc::RecordPaintCanvas record_canvas(
-      display_item_list.get(), SkRect::MakeWH(SkFloatToScalar(size.width()),
-                                              SkFloatToScalar(size.height())));
+  cc::InspectableRecordPaintCanvas record_canvas(display_item_list.get(), size);
   gfx::Canvas canvas(&record_canvas, 1.0);
 #if DCHECK_IS_ON()
   gfx::Rect clip_rect;
diff --git a/ui/views/border_unittest.cc b/ui/views/border_unittest.cc
index 0cf484f..17fc93d 100644
--- a/ui/views/border_unittest.cc
+++ b/ui/views/border_unittest.cc
@@ -160,9 +160,7 @@
     view_ = std::make_unique<views::View>();
     view_->SetSize(gfx::Size(100, 50));
     recorder_ = std::make_unique<cc::PaintRecorder>();
-    canvas_ = std::make_unique<gfx::Canvas>(
-        recorder_->beginRecording(SkRect::MakeWH(kCanvasWidth, kCanvasHeight)),
-        1.0f);
+    canvas_ = std::make_unique<gfx::Canvas>(recorder_->beginRecording(), 1.0f);
   }
 
   void TearDown() override {
diff --git a/ui/views/controls/button/checkbox.cc b/ui/views/controls/button/checkbox.cc
index 3db3661..5491988 100644
--- a/ui/views/controls/button/checkbox.cc
+++ b/ui/views/controls/button/checkbox.cc
@@ -177,7 +177,6 @@
 
 void Checkbox::OnThemeChanged() {
   LabelButton::OnThemeChanged();
-  UpdateImage();
 }
 
 SkPath Checkbox::GetFocusRingPath() const {
diff --git a/ui/views/win/hwnd_message_handler.cc b/ui/views/win/hwnd_message_handler.cc
index ec39cb0..382f68c 100644
--- a/ui/views/win/hwnd_message_handler.cc
+++ b/ui/views/win/hwnd_message_handler.cc
@@ -1845,6 +1845,12 @@
   }
 
   dpi_ = dpi;
+  // Make sure the displays have up to date scale factors before updating the
+  // window's scale factor. Otherwise, we can be in an inconsistent state,
+  // in which the display a window is on has a different scale factor than the
+  // window, when the window handles the scale factor change.
+  // See https://crbug.com/1368455 for more info.
+  display::win::ScreenWin::UpdateDisplayInfos();
   SetBoundsInternal(gfx::Rect(*reinterpret_cast<RECT*>(l_param)), false);
   delegate_->HandleWindowScaleFactorChanged(scaling_factor);
   return 0;
diff --git a/url/BUILD.gn b/url/BUILD.gn
index eedb03f7..ce2f088f 100644
--- a/url/BUILD.gn
+++ b/url/BUILD.gn
@@ -47,6 +47,8 @@
     "url_canon_stdurl.cc",
     "url_constants.cc",
     "url_constants.h",
+    "url_features.cc",
+    "url_features.h",
     "url_file.h",
     "url_parse_file.cc",
     "url_parse_internal.h",
diff --git a/url/url_canon_unittest.cc b/url/url_canon_unittest.cc
index c20e5025..ab422ef 100644
--- a/url/url_canon_unittest.cc
+++ b/url/url_canon_unittest.cc
@@ -10,10 +10,12 @@
 #include "base/strings/string_piece.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/gtest_util.h"
+#include "base/test/scoped_feature_list.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/third_party/mozilla/url_parse.h"
 #include "url/url_canon_internal.h"
 #include "url/url_canon_stdstring.h"
+#include "url/url_features.h"
 #include "url/url_test_utils.h"
 
 namespace url {
@@ -285,38 +287,78 @@
   EXPECT_EQ(0, out_comp.len);
 }
 
-TEST(URLCanonTest, Host) {
+// IDNA mode to use in CanonHost tests.
+enum class IDNAMode { kTransitional, kNonTransitional };
+
+class URLCanonHostTest : public ::testing::Test,
+                         public ::testing::WithParamInterface<IDNAMode> {
+ public:
+  URLCanonHostTest() {
+    if (GetParam() == IDNAMode::kNonTransitional) {
+      scoped_feature_list_.InitAndEnableFeature(kUseIDNA2008NonTransitional);
+    } else {
+      scoped_feature_list_.InitAndDisableFeature(kUseIDNA2008NonTransitional);
+    }
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         URLCanonHostTest,
+                         ::testing::Values(IDNAMode::kTransitional,
+                                           IDNAMode::kNonTransitional));
+
+TEST_P(URLCanonHostTest, Host) {
+  bool use_idna_non_transitional = IsUsingIDNA2008NonTransitional();
+
   IPAddressCase host_cases[] = {
-       // Basic canonicalization, uppercase should be converted to lowercase.
-    {"GoOgLe.CoM", L"GoOgLe.CoM", "google.com", Component(0, 10), CanonHostInfo::NEUTRAL, -1, ""},
+      // Basic canonicalization, uppercase should be converted to lowercase.
+      {"GoOgLe.CoM", L"GoOgLe.CoM", "google.com", Component(0, 10),
+       CanonHostInfo::NEUTRAL, -1, ""},
       // Spaces and some other characters should be escaped.
-    {"Goo%20 goo%7C|.com", L"Goo%20 goo%7C|.com", "goo%20%20goo%7C%7C.com", Component(0, 22), CanonHostInfo::NEUTRAL, -1, ""},
+      {"Goo%20 goo%7C|.com", L"Goo%20 goo%7C|.com", "goo%20%20goo%7C%7C.com",
+       Component(0, 22), CanonHostInfo::NEUTRAL, -1, ""},
       // Exciting different types of spaces!
-    {NULL, L"GOO\x00a0\x3000goo.com", "goo%20%20goo.com", Component(0, 16), CanonHostInfo::NEUTRAL, -1, ""},
+      {NULL, L"GOO\x00a0\x3000goo.com", "goo%20%20goo.com", Component(0, 16),
+       CanonHostInfo::NEUTRAL, -1, ""},
       // Other types of space (no-break, zero-width, zero-width-no-break) are
       // name-prepped away to nothing.
-    {NULL, L"GOO\x200b\x2060\xfeffgoo.com", "googoo.com", Component(0, 10), CanonHostInfo::NEUTRAL, -1, ""},
+      {NULL, L"GOO\x200b\x2060\xfeffgoo.com", "googoo.com", Component(0, 10),
+       CanonHostInfo::NEUTRAL, -1, ""},
       // Ideographic full stop (full-width period for Chinese, etc.) should be
       // treated as a dot.
-    {NULL, L"www.foo\x3002" L"bar.com", "www.foo.bar.com", Component(0, 15), CanonHostInfo::NEUTRAL, -1, ""},
+      {NULL,
+       L"www.foo\x3002"
+       L"bar.com",
+       "www.foo.bar.com", Component(0, 15), CanonHostInfo::NEUTRAL, -1, ""},
       // Invalid unicode characters should fail...
       // ...In wide input, ICU will barf and we'll end up with the input as
       //    escaped UTF-8 (the invalid character should be replaced with the
       //    replacement character).
-    {"\xef\xb7\x90zyx.com", L"\xfdd0zyx.com", "%EF%BF%BDzyx.com", Component(0, 16), CanonHostInfo::BROKEN, -1, ""},
+      {"\xef\xb7\x90zyx.com", L"\xfdd0zyx.com", "%EF%BF%BDzyx.com",
+       Component(0, 16), CanonHostInfo::BROKEN, -1, ""},
       // ...This is the same as previous but with with escaped.
-    {"%ef%b7%90zyx.com", L"%ef%b7%90zyx.com", "%EF%BF%BDzyx.com", Component(0, 16), CanonHostInfo::BROKEN, -1, ""},
-      // Test name prepping, fullwidth input should be converted to ASCII and NOT
+      {"%ef%b7%90zyx.com", L"%ef%b7%90zyx.com", "%EF%BF%BDzyx.com",
+       Component(0, 16), CanonHostInfo::BROKEN, -1, ""},
+      // Test name prepping, fullwidth input should be converted to ASCII and
+      // NOT
       // IDN-ized. This is "Go" in fullwidth UTF-8/UTF-16.
-    {"\xef\xbc\xa7\xef\xbd\x8f.com", L"\xff27\xff4f.com", "go.com", Component(0, 6), CanonHostInfo::NEUTRAL, -1, ""},
+      {"\xef\xbc\xa7\xef\xbd\x8f.com", L"\xff27\xff4f.com", "go.com",
+       Component(0, 6), CanonHostInfo::NEUTRAL, -1, ""},
       // Test that fullwidth escaped values are properly name-prepped,
       // then converted or rejected.
       // ...%41 in fullwidth = 'A' (also as escaped UTF-8 input)
-    {"\xef\xbc\x85\xef\xbc\x94\xef\xbc\x91.com", L"\xff05\xff14\xff11.com", "a.com", Component(0, 5), CanonHostInfo::NEUTRAL, -1, ""},
-    {"%ef%bc%85%ef%bc%94%ef%bc%91.com", L"%ef%bc%85%ef%bc%94%ef%bc%91.com", "a.com", Component(0, 5), CanonHostInfo::NEUTRAL, -1, ""},
+      {"\xef\xbc\x85\xef\xbc\x94\xef\xbc\x91.com", L"\xff05\xff14\xff11.com",
+       "a.com", Component(0, 5), CanonHostInfo::NEUTRAL, -1, ""},
+      {"%ef%bc%85%ef%bc%94%ef%bc%91.com", L"%ef%bc%85%ef%bc%94%ef%bc%91.com",
+       "a.com", Component(0, 5), CanonHostInfo::NEUTRAL, -1, ""},
       // ...%00 in fullwidth should fail (also as escaped UTF-8 input)
-    {"\xef\xbc\x85\xef\xbc\x90\xef\xbc\x90.com", L"\xff05\xff10\xff10.com", "%00.com", Component(0, 7), CanonHostInfo::BROKEN, -1, ""},
-    {"%ef%bc%85%ef%bc%90%ef%bc%90.com", L"%ef%bc%85%ef%bc%90%ef%bc%90.com", "%00.com", Component(0, 7), CanonHostInfo::BROKEN, -1, ""},
+      {"\xef\xbc\x85\xef\xbc\x90\xef\xbc\x90.com", L"\xff05\xff10\xff10.com",
+       "%00.com", Component(0, 7), CanonHostInfo::BROKEN, -1, ""},
+      {"%ef%bc%85%ef%bc%90%ef%bc%90.com", L"%ef%bc%85%ef%bc%90%ef%bc%90.com",
+       "%00.com", Component(0, 7), CanonHostInfo::BROKEN, -1, ""},
       // ICU will convert weird percents into ASCII percents, but not unescape
       // further. A weird percent is U+FE6A (EF B9 AA in UTF-8) which is a
       // "small percent". At this point we should be within our rights to mark
@@ -324,12 +366,30 @@
       // happens to allow ASCII characters (%41 = "A" -> 'a') to be unescaped
       // and kept as valid, so we validate that behavior here, but this level
       // of fixing the input shouldn't be seen as required. "%81" is invalid.
-    {"\xef\xb9\xaa" "41.com", L"\xfe6a" L"41.com", "a.com", Component(0, 5), CanonHostInfo::NEUTRAL, -1, ""},
-    {"%ef%b9%aa" "41.com", L"\xfe6a" L"41.com", "a.com", Component(0, 5), CanonHostInfo::NEUTRAL, -1, ""},
-    {"\xef\xb9\xaa" "81.com", L"\xfe6a" L"81.com", "%81.com", Component(0, 7), CanonHostInfo::BROKEN, -1, ""},
-    {"%ef%b9%aa" "81.com", L"\xfe6a" L"81.com", "%81.com", Component(0, 7), CanonHostInfo::BROKEN, -1, ""},
+      {"\xef\xb9\xaa"
+       "41.com",
+       L"\xfe6a"
+       L"41.com",
+       "a.com", Component(0, 5), CanonHostInfo::NEUTRAL, -1, ""},
+      {"%ef%b9%aa"
+       "41.com",
+       L"\xfe6a"
+       L"41.com",
+       "a.com", Component(0, 5), CanonHostInfo::NEUTRAL, -1, ""},
+      {"\xef\xb9\xaa"
+       "81.com",
+       L"\xfe6a"
+       L"81.com",
+       "%81.com", Component(0, 7), CanonHostInfo::BROKEN, -1, ""},
+      {"%ef%b9%aa"
+       "81.com",
+       L"\xfe6a"
+       L"81.com",
+       "%81.com", Component(0, 7), CanonHostInfo::BROKEN, -1, ""},
       // Basic IDN support, UTF-8 and UTF-16 input should be converted to IDN
-    {"\xe4\xbd\xa0\xe5\xa5\xbd\xe4\xbd\xa0\xe5\xa5\xbd", L"\x4f60\x597d\x4f60\x597d", "xn--6qqa088eba", Component(0, 14), CanonHostInfo::NEUTRAL, -1, ""},
+      {"\xe4\xbd\xa0\xe5\xa5\xbd\xe4\xbd\xa0\xe5\xa5\xbd",
+       L"\x4f60\x597d\x4f60\x597d", "xn--6qqa088eba", Component(0, 14),
+       CanonHostInfo::NEUTRAL, -1, ""},
       // See http://unicode.org/cldr/utility/idna.jsp for other
       // examples/experiments and http://goo.gl/7yG11o
       // for the full list of characters handled differently by
@@ -337,169 +397,206 @@
 
       // 4 Deviation characters are mapped/ignored in UTS 46 transitional
       // mechansm. UTS 46, table 4 row (g).
-      // Sharp-s is mapped to 'ss' in UTS 46 and IDNA 2003.
-      // Otherwise, it'd be "xn--fuball-cta.de".
-    {"fu\xc3\x9f" "ball.de", L"fu\x00df" L"ball.de", "fussball.de",
-      Component(0, 11), CanonHostInfo::NEUTRAL, -1, ""},
-      // Final-sigma (U+03C3) is mapped to regular sigma (U+03C2).
-      // Otherwise, it'd be "xn--wxaijb9b".
-    {"\xcf\x83\xcf\x8c\xce\xbb\xce\xbf\xcf\x82", L"\x3c3\x3cc\x3bb\x3bf\x3c2",
-      "xn--wxaikc6b", Component(0, 12),
-      CanonHostInfo::NEUTRAL, -1, ""},
+      // Sharp-s is mapped to 'ss' in IDNA 2003, not in IDNA 2008 or UTF 46
+      // after transitional period.
+      // Previously, it'd be "fussball.de".
+      {"fu\xc3\x9f"
+       "ball.de",
+       L"fu\x00df"
+       L"ball.de",
+       use_idna_non_transitional ? "xn--fuball-cta.de" : "fussball.de",
+       use_idna_non_transitional ? Component(0, 17) : Component(0, 11),
+       CanonHostInfo::NEUTRAL, -1, ""},
+
+      // Final-sigma (U+03C3) was mapped to regular sigma (U+03C2).
+      // Previously, it'd be "xn--wxaikc9b".
+      {"\xcf\x83\xcf\x8c\xce\xbb\xce\xbf\xcf\x82", L"\x3c3\x3cc\x3bb\x3bf\x3c2",
+       use_idna_non_transitional ? "xn--wxaijb9b" : "xn--wxaikc6b",
+       Component(0, 12), CanonHostInfo::NEUTRAL, -1, ""},
+
       // ZWNJ (U+200C) and ZWJ (U+200D) are mapped away in UTS 46 transitional
-      // handling as well as in IDNA 2003.
-    {"a\xe2\x80\x8c" "b\xe2\x80\x8d" "c", L"a\x200c" L"b\x200d" L"c", "abc",
-      Component(0, 3), CanonHostInfo::NEUTRAL, -1, ""},
-      // ZWJ between Devanagari characters is still mapped away in UTS 46
-      // transitional handling. IDNA 2008 would give xn--11bo0mv54g.
-    {"\xe0\xa4\x95\xe0\xa5\x8d\xe2\x80\x8d\xe0\xa4\x9c",
-     L"\x915\x94d\x200d\x91c", "xn--11bo0m",
-     Component(0, 10), CanonHostInfo::NEUTRAL, -1, ""},
+      // handling as well as in IDNA 2003, but not thereafter.
+      {"a\xe2\x80\x8c"
+       "b\xe2\x80\x8d"
+       "c",
+       L"a\x200c"
+       L"b\x200d"
+       L"c",
+       use_idna_non_transitional ? "xn--abc-9m0ag" : "abc",
+       use_idna_non_transitional ? Component(0, 13) : Component(0, 3),
+       CanonHostInfo::NEUTRAL, -1, ""},
+
+      // ZWJ between Devanagari characters was still mapped away in UTS 46
+      // transitional handling. IDNA 2008 gives xn--11bo0mv54g.
+      // Previously "xn--11bo0m".
+      {"\xe0\xa4\x95\xe0\xa5\x8d\xe2\x80\x8d\xe0\xa4\x9c",
+       L"\x915\x94d\x200d\x91c",
+       use_idna_non_transitional ? "xn--11bo0mv54g" : "xn--11bo0m",
+       use_idna_non_transitional ? Component(0, 14) : Component(0, 10),
+       CanonHostInfo::NEUTRAL, -1, ""},
+
       // Fullwidth exclamation mark is disallowed. UTS 46, table 4, row (b)
       // However, we do allow this at the moment because we don't use
       // STD3 rules and canonicalize full-width ASCII to ASCII.
-    {"wow\xef\xbc\x81", L"wow\xff01", "wow%21",
-      Component(0, 6), CanonHostInfo::NEUTRAL, -1, ""},
+      {"wow\xef\xbc\x81", L"wow\xff01", "wow%21", Component(0, 6),
+       CanonHostInfo::NEUTRAL, -1, ""},
       // U+2132 (turned capital F) is disallowed. UTS 46, table 4, row (c)
       // Allowed in IDNA 2003, but the mapping changed after Unicode 3.2
-    {"\xe2\x84\xb2oo", L"\x2132oo", "%E2%84%B2oo",
-      Component(0, 11), CanonHostInfo::BROKEN, -1, ""},
+      {"\xe2\x84\xb2oo", L"\x2132oo", "%E2%84%B2oo", Component(0, 11),
+       CanonHostInfo::BROKEN, -1, ""},
       // U+2F868 (CJK Comp) is disallowed. UTS 46, table 4, row (d)
       // Allowed in IDNA 2003, but the mapping changed after Unicode 3.2
-    {"\xf0\xaf\xa1\xa8\xe5\xa7\xbb.cn", L"\xd87e\xdc68\x59fb.cn",
-      "%F0%AF%A1%A8%E5%A7%BB.cn",
-      Component(0, 24), CanonHostInfo::BROKEN, -1, ""},
+      {"\xf0\xaf\xa1\xa8\xe5\xa7\xbb.cn", L"\xd87e\xdc68\x59fb.cn",
+       "%F0%AF%A1%A8%E5%A7%BB.cn", Component(0, 24), CanonHostInfo::BROKEN, -1,
+       ""},
       // Maps uppercase letters to lower case letters. UTS 46 table 4 row (e)
-    {"M\xc3\x9cNCHEN", L"M\xdcNCHEN", "xn--mnchen-3ya",
-      Component(0, 14), CanonHostInfo::NEUTRAL, -1, ""},
+      {"M\xc3\x9cNCHEN", L"M\xdcNCHEN", "xn--mnchen-3ya", Component(0, 14),
+       CanonHostInfo::NEUTRAL, -1, ""},
       // An already-IDNA host is not modified.
-    {"xn--mnchen-3ya", L"xn--mnchen-3ya", "xn--mnchen-3ya",
-      Component(0, 14), CanonHostInfo::NEUTRAL, -1, ""},
+      {"xn--mnchen-3ya", L"xn--mnchen-3ya", "xn--mnchen-3ya", Component(0, 14),
+       CanonHostInfo::NEUTRAL, -1, ""},
       // Symbol/punctuations are allowed in IDNA 2003/UTS46.
       // Not allowed in IDNA 2008. UTS 46 table 4 row (f).
-    {"\xe2\x99\xa5ny.us", L"\x2665ny.us", "xn--ny-s0x.us",
-      Component(0, 13), CanonHostInfo::NEUTRAL, -1, ""},
+      {"\xe2\x99\xa5ny.us", L"\x2665ny.us", "xn--ny-s0x.us", Component(0, 13),
+       CanonHostInfo::NEUTRAL, -1, ""},
       // U+11013 is new in Unicode 6.0 and is allowed. UTS 46 table 4, row (h)
       // We used to allow it because we passed through unassigned code points.
-    {"\xf0\x91\x80\x93.com", L"\xd804\xdc13.com", "xn--n00d.com",
-      Component(0, 12), CanonHostInfo::NEUTRAL, -1, ""},
+      {"\xf0\x91\x80\x93.com", L"\xd804\xdc13.com", "xn--n00d.com",
+       Component(0, 12), CanonHostInfo::NEUTRAL, -1, ""},
       // U+0602 is disallowed in UTS46/IDNA 2008. UTS 46 table 4, row(i)
       // Used to be allowed in INDA 2003.
-    {"\xd8\x82.eg", L"\x602.eg", "%D8%82.eg",
-      Component(0, 9), CanonHostInfo::BROKEN, -1, ""},
+      {"\xd8\x82.eg", L"\x602.eg", "%D8%82.eg", Component(0, 9),
+       CanonHostInfo::BROKEN, -1, ""},
       // U+20B7 is new in Unicode 5.2 (not a part of IDNA 2003 based
       // on Unicode 3.2). We did allow it in the past because we let unassigned
       // code point pass. We continue to allow it even though it's a
       // "punctuation and symbol" blocked in IDNA 2008.
       // UTS 46 table 4, row (j)
-    {"\xe2\x82\xb7.com", L"\x20b7.com", "xn--wzg.com",
-      Component(0, 11), CanonHostInfo::NEUTRAL, -1, ""},
+      {"\xe2\x82\xb7.com", L"\x20b7.com", "xn--wzg.com", Component(0, 11),
+       CanonHostInfo::NEUTRAL, -1, ""},
       // Maps uppercase letters to lower case letters.
       // In IDNA 2003, it's allowed without case-folding
       // ( xn--bc-7cb.com ) because it's not defined in Unicode 3.2
       // (added in Unicode 4.1). UTS 46 table 4 row (k)
-    {"bc\xc8\xba.com", L"bc\x23a.com", "xn--bc-is1a.com",
-      Component(0, 15), CanonHostInfo::NEUTRAL, -1, ""},
+      {"bc\xc8\xba.com", L"bc\x23a.com", "xn--bc-is1a.com", Component(0, 15),
+       CanonHostInfo::NEUTRAL, -1, ""},
       // Maps U+FF43 (Full Width Small Letter C) to 'c'.
-    {"ab\xef\xbd\x83.xyz", L"ab\xff43.xyz", "abc.xyz",
-      Component(0, 7), CanonHostInfo::NEUTRAL, -1, ""},
+      {"ab\xef\xbd\x83.xyz", L"ab\xff43.xyz", "abc.xyz", Component(0, 7),
+       CanonHostInfo::NEUTRAL, -1, ""},
       // Maps U+1D68C (Math Monospace Small C) to 'c'.
       // U+1D68C = \xD835\xDE8C in UTF-16
-    {"ab\xf0\x9d\x9a\x8c.xyz", L"ab\xd835\xde8c.xyz", "abc.xyz",
-      Component(0, 7), CanonHostInfo::NEUTRAL, -1, ""},
+      {"ab\xf0\x9d\x9a\x8c.xyz", L"ab\xd835\xde8c.xyz", "abc.xyz",
+       Component(0, 7), CanonHostInfo::NEUTRAL, -1, ""},
       // BiDi check test
       // "Divehi" in Divehi (Thaana script) ends with BidiClass=NSM.
       // Disallowed in IDNA 2003 but now allowed in UTS 46/IDNA 2008.
-    {"\xde\x8b\xde\xa8\xde\x88\xde\xac\xde\x80\xde\xa8",
-     L"\x78b\x7a8\x788\x7ac\x780\x7a8", "xn--hqbpi0jcw",
-     Component(0, 13), CanonHostInfo::NEUTRAL, -1, ""},
+      {"\xde\x8b\xde\xa8\xde\x88\xde\xac\xde\x80\xde\xa8",
+       L"\x78b\x7a8\x788\x7ac\x780\x7a8", "xn--hqbpi0jcw", Component(0, 13),
+       CanonHostInfo::NEUTRAL, -1, ""},
       // Disallowed in both IDNA 2003 and 2008 with BiDi check.
       // Labels starting with a RTL character cannot end with a LTR character.
-    {"\xd8\xac\xd8\xa7\xd8\xb1xyz", L"\x62c\x627\x631xyz",
-     "%D8%AC%D8%A7%D8%B1xyz", Component(0, 21),
-     CanonHostInfo::BROKEN, -1, ""},
+      {"\xd8\xac\xd8\xa7\xd8\xb1xyz", L"\x62c\x627\x631xyz",
+       "%D8%AC%D8%A7%D8%B1xyz", Component(0, 21), CanonHostInfo::BROKEN, -1,
+       ""},
       // Labels starting with a RTL character can end with BC=EN (European
       // number). Disallowed in IDNA 2003 but now allowed.
-    {"\xd8\xac\xd8\xa7\xd8\xb1" "2", L"\x62c\x627\x631" L"2",
-     "xn--2-ymcov", Component(0, 11),
-     CanonHostInfo::NEUTRAL, -1, ""},
+      {"\xd8\xac\xd8\xa7\xd8\xb1"
+       "2",
+       L"\x62c\x627\x631"
+       L"2",
+       "xn--2-ymcov", Component(0, 11), CanonHostInfo::NEUTRAL, -1, ""},
       // Labels starting with a RTL character cannot have "L" characters
       // even if it ends with an BC=EN. Disallowed in both IDNA 2003/2008.
-    {"\xd8\xac\xd8\xa7\xd8\xb1xy2", L"\x62c\x627\x631xy2",
-     "%D8%AC%D8%A7%D8%B1xy2", Component(0, 21),
-     CanonHostInfo::BROKEN, -1, ""},
+      {"\xd8\xac\xd8\xa7\xd8\xb1xy2", L"\x62c\x627\x631xy2",
+       "%D8%AC%D8%A7%D8%B1xy2", Component(0, 21), CanonHostInfo::BROKEN, -1,
+       ""},
       // Labels starting with a RTL character can end with BC=AN (Arabic number)
       // Disallowed in IDNA 2003, but now allowed.
-    {"\xd8\xac\xd8\xa7\xd8\xb1\xd9\xa2", L"\x62c\x627\x631\x662",
-     "xn--mgbjq0r", Component(0, 11),
-     CanonHostInfo::NEUTRAL, -1, ""},
+      {"\xd8\xac\xd8\xa7\xd8\xb1\xd9\xa2", L"\x62c\x627\x631\x662",
+       "xn--mgbjq0r", Component(0, 11), CanonHostInfo::NEUTRAL, -1, ""},
       // Labels starting with a RTL character cannot have "L" characters
       // even if it ends with an BC=AN (Arabic number).
       // Disallowed in both IDNA 2003/2008.
-    {"\xd8\xac\xd8\xa7\xd8\xb1xy\xd9\xa2", L"\x62c\x627\x631xy\x662",
-     "%D8%AC%D8%A7%D8%B1xy%D9%A2", Component(0, 26),
-     CanonHostInfo::BROKEN, -1, ""},
+      {"\xd8\xac\xd8\xa7\xd8\xb1xy\xd9\xa2", L"\x62c\x627\x631xy\x662",
+       "%D8%AC%D8%A7%D8%B1xy%D9%A2", Component(0, 26), CanonHostInfo::BROKEN,
+       -1, ""},
       // Labels starting with a RTL character cannot mix BC=EN and BC=AN
-    {"\xd8\xac\xd8\xa7\xd8\xb1xy2\xd9\xa2", L"\x62c\x627\x631xy2\x662",
-     "%D8%AC%D8%A7%D8%B1xy2%D9%A2", Component(0, 27),
-     CanonHostInfo::BROKEN, -1, ""},
+      {"\xd8\xac\xd8\xa7\xd8\xb1xy2\xd9\xa2", L"\x62c\x627\x631xy2\x662",
+       "%D8%AC%D8%A7%D8%B1xy2%D9%A2", Component(0, 27), CanonHostInfo::BROKEN,
+       -1, ""},
       // As of Unicode 6.2, U+20CF is not assigned. We do not allow it.
-    {"\xe2\x83\x8f.com", L"\x20cf.com", "%E2%83%8F.com",
-      Component(0, 13), CanonHostInfo::BROKEN, -1, ""},
+      {"\xe2\x83\x8f.com", L"\x20cf.com", "%E2%83%8F.com", Component(0, 13),
+       CanonHostInfo::BROKEN, -1, ""},
       // U+0080 is not allowed.
-    {"\xc2\x80.com", L"\x80.com", "%C2%80.com",
-      Component(0, 10), CanonHostInfo::BROKEN, -1, ""},
+      {"\xc2\x80.com", L"\x80.com", "%C2%80.com", Component(0, 10),
+       CanonHostInfo::BROKEN, -1, ""},
       // Mixed UTF-8 and escaped UTF-8 (narrow case) and UTF-16 and escaped
       // Mixed UTF-8 and escaped UTF-8 (narrow case) and UTF-16 and escaped
       // UTF-8 (wide case). The output should be equivalent to the true wide
       // character input above).
-    {"%E4%BD%A0%E5%A5%BD\xe4\xbd\xa0\xe5\xa5\xbd",
-      L"%E4%BD%A0%E5%A5%BD\x4f60\x597d", "xn--6qqa088eba",
-      Component(0, 14), CanonHostInfo::NEUTRAL, -1, ""},
+      {"%E4%BD%A0%E5%A5%BD\xe4\xbd\xa0\xe5\xa5\xbd",
+       L"%E4%BD%A0%E5%A5%BD\x4f60\x597d", "xn--6qqa088eba", Component(0, 14),
+       CanonHostInfo::NEUTRAL, -1, ""},
       // Invalid escaped characters should fail and the percents should be
       // escaped.
-    {"%zz%66%a", L"%zz%66%a", "%25zzf%25a", Component(0, 10),
-      CanonHostInfo::BROKEN, -1, ""},
+      {"%zz%66%a", L"%zz%66%a", "%25zzf%25a", Component(0, 10),
+       CanonHostInfo::BROKEN, -1, ""},
       // If we get an invalid character that has been escaped.
-    {"%25", L"%25", "%25", Component(0, 3),
-      CanonHostInfo::BROKEN, -1, ""},
-    {"hello%00", L"hello%00", "hello%00", Component(0, 8),
-      CanonHostInfo::BROKEN, -1, ""},
+      {"%25", L"%25", "%25", Component(0, 3), CanonHostInfo::BROKEN, -1, ""},
+      {"hello%00", L"hello%00", "hello%00", Component(0, 8),
+       CanonHostInfo::BROKEN, -1, ""},
       // Escaped numbers should be treated like IP addresses if they are.
-    {"%30%78%63%30%2e%30%32%35%30.01", L"%30%78%63%30%2e%30%32%35%30.01",
-      "192.168.0.1", Component(0, 11), CanonHostInfo::IPV4, 3,
-      "C0A80001"},
-    {"%30%78%63%30%2e%30%32%35%30.01%2e", L"%30%78%63%30%2e%30%32%35%30.01%2e",
-      "192.168.0.1", Component(0, 11), CanonHostInfo::IPV4, 3,
-      "C0A80001"},
+      {"%30%78%63%30%2e%30%32%35%30.01", L"%30%78%63%30%2e%30%32%35%30.01",
+       "192.168.0.1", Component(0, 11), CanonHostInfo::IPV4, 3, "C0A80001"},
+      {"%30%78%63%30%2e%30%32%35%30.01%2e",
+       L"%30%78%63%30%2e%30%32%35%30.01%2e", "192.168.0.1", Component(0, 11),
+       CanonHostInfo::IPV4, 3, "C0A80001"},
       // Invalid escaping should trigger the regular host error handling.
-    {"%3g%78%63%30%2e%30%32%35%30%2E.01", L"%3g%78%63%30%2e%30%32%35%30%2E.01", "%253gxc0.0250..01", Component(0, 17), CanonHostInfo::BROKEN, -1, ""},
+      {"%3g%78%63%30%2e%30%32%35%30%2E.01",
+       L"%3g%78%63%30%2e%30%32%35%30%2E.01", "%253gxc0.0250..01",
+       Component(0, 17), CanonHostInfo::BROKEN, -1, ""},
       // Something that isn't exactly an IP should get treated as a host and
       // spaces escaped.
-    {"192.168.0.1 hello", L"192.168.0.1 hello", "192.168.0.1%20hello", Component(0, 19), CanonHostInfo::NEUTRAL, -1, ""},
+      {"192.168.0.1 hello", L"192.168.0.1 hello", "192.168.0.1%20hello",
+       Component(0, 19), CanonHostInfo::NEUTRAL, -1, ""},
       // Fullwidth and escaped UTF-8 fullwidth should still be treated as IP.
       // These are "0Xc0.0250.01" in fullwidth.
-    {"\xef\xbc\x90%Ef%bc\xb8%ef%Bd%83\xef\xbc\x90%EF%BC%8E\xef\xbc\x90\xef\xbc\x92\xef\xbc\x95\xef\xbc\x90\xef\xbc%8E\xef\xbc\x90\xef\xbc\x91", L"\xff10\xff38\xff43\xff10\xff0e\xff10\xff12\xff15\xff10\xff0e\xff10\xff11", "192.168.0.1", Component(0, 11), CanonHostInfo::IPV4, 3, "C0A80001"},
+      {"\xef\xbc\x90%Ef%bc\xb8%ef%Bd%83\xef\xbc\x90%EF%BC%"
+       "8E\xef\xbc\x90\xef\xbc\x92\xef\xbc\x95\xef\xbc\x90\xef\xbc%"
+       "8E\xef\xbc\x90\xef\xbc\x91",
+       L"\xff10\xff38\xff43\xff10\xff0e\xff10\xff12\xff15\xff10\xff0e\xff10"
+       L"\xff11",
+       "192.168.0.1", Component(0, 11), CanonHostInfo::IPV4, 3, "C0A80001"},
       // Broken IP addresses get marked as such.
-    {"192.168.0.257", L"192.168.0.257", "192.168.0.257", Component(0, 13), CanonHostInfo::BROKEN, -1, ""},
-    {"[google.com]", L"[google.com]", "[google.com]", Component(0, 12), CanonHostInfo::BROKEN, -1, ""},
+      {"192.168.0.257", L"192.168.0.257", "192.168.0.257", Component(0, 13),
+       CanonHostInfo::BROKEN, -1, ""},
+      {"[google.com]", L"[google.com]", "[google.com]", Component(0, 12),
+       CanonHostInfo::BROKEN, -1, ""},
       // Cyrillic letter followed by '(' should return punycode for '(' escaped
       // before punycode string was created. I.e.
       // if '(' is escaped after punycode is created we would get xn--%28-8tb
       // (incorrect).
-    {"\xd1\x82(", L"\x0442(", "xn--%28-7ed", Component(0, 11),
-      CanonHostInfo::NEUTRAL, -1, ""},
-      // Address with all hexidecimal characters with leading number of 1<<32
+      {"\xd1\x82(", L"\x0442(", "xn--%28-7ed", Component(0, 11),
+       CanonHostInfo::NEUTRAL, -1, ""},
+      // Address with all hexadecimal characters with leading number of 1<<32
       // or greater and should return NEUTRAL rather than BROKEN if not all
       // components are numbers.
-    {"12345678912345.de", L"12345678912345.de", "12345678912345.de", Component(0, 17), CanonHostInfo::NEUTRAL, -1, ""},
-    {"1.12345678912345.de", L"1.12345678912345.de", "1.12345678912345.de", Component(0, 19), CanonHostInfo::NEUTRAL, -1, ""},
-    {"12345678912345.12345678912345.de", L"12345678912345.12345678912345.de", "12345678912345.12345678912345.de", Component(0, 32), CanonHostInfo::NEUTRAL, -1, ""},
-    {"1.2.0xB3A73CE5B59.de", L"1.2.0xB3A73CE5B59.de", "1.2.0xb3a73ce5b59.de", Component(0, 20), CanonHostInfo::NEUTRAL, -1, ""},
-    {"12345678912345.0xde", L"12345678912345.0xde", "12345678912345.0xde", Component(0, 19), CanonHostInfo::BROKEN, -1, ""},
-    // A label that starts with "xn--" but contains non-ASCII characters should
-    // be an error. Escape the invalid characters.
-    {"xn--m\xc3\xbcnchen", L"xn--m\xfcnchen", "xn--m%C3%BCnchen", Component(0, 16), CanonHostInfo::BROKEN, -1, ""},
+      {"12345678912345.de", L"12345678912345.de", "12345678912345.de",
+       Component(0, 17), CanonHostInfo::NEUTRAL, -1, ""},
+      {"1.12345678912345.de", L"1.12345678912345.de", "1.12345678912345.de",
+       Component(0, 19), CanonHostInfo::NEUTRAL, -1, ""},
+      {"12345678912345.12345678912345.de", L"12345678912345.12345678912345.de",
+       "12345678912345.12345678912345.de", Component(0, 32),
+       CanonHostInfo::NEUTRAL, -1, ""},
+      {"1.2.0xB3A73CE5B59.de", L"1.2.0xB3A73CE5B59.de", "1.2.0xb3a73ce5b59.de",
+       Component(0, 20), CanonHostInfo::NEUTRAL, -1, ""},
+      {"12345678912345.0xde", L"12345678912345.0xde", "12345678912345.0xde",
+       Component(0, 19), CanonHostInfo::BROKEN, -1, ""},
+      // A label that starts with "xn--" but contains non-ASCII characters
+      // should
+      // be an error. Escape the invalid characters.
+      {"xn--m\xc3\xbcnchen", L"xn--m\xfcnchen", "xn--m%C3%BCnchen",
+       Component(0, 16), CanonHostInfo::BROKEN, -1, ""},
   };
 
   // CanonicalizeHost() non-verbose.
diff --git a/url/url_features.cc b/url/url_features.cc
new file mode 100644
index 0000000..62e68d3
--- /dev/null
+++ b/url/url_features.cc
@@ -0,0 +1,16 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "url/url_features.h"
+
+namespace url {
+
+BASE_FEATURE(kUseIDNA2008NonTransitional,
+             "UseIDNA2008NonTransitional",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
+bool IsUsingIDNA2008NonTransitional() {
+  return base::FeatureList::IsEnabled(kUseIDNA2008NonTransitional);
+}
+}  // namespace url
diff --git a/url/url_features.h b/url/url_features.h
new file mode 100644
index 0000000..3bd283f3
--- /dev/null
+++ b/url/url_features.h
@@ -0,0 +1,19 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef URL_URL_FEATURES_H_
+#define URL_URL_FEATURES_H_
+
+#include "base/component_export.h"
+#include "base/feature_list.h"
+
+namespace url {
+
+COMPONENT_EXPORT(URL) BASE_DECLARE_FEATURE(kUseIDNA2008NonTransitional);
+
+// Returns true if Chrome is using IDNA 2008 in Non-Transitional mode.
+COMPONENT_EXPORT(URL) bool IsUsingIDNA2008NonTransitional();
+}  // namespace url
+
+#endif  // URL_URL_FEATURES_H_
diff --git a/url/url_idna_icu.cc b/url/url_idna_icu.cc
index f83b92d..0a552a8 100644
--- a/url/url_idna_icu.cc
+++ b/url/url_idna_icu.cc
@@ -15,21 +15,25 @@
 #include "third_party/icu/source/common/unicode/utypes.h"
 #include "url/url_canon_icu.h"
 #include "url/url_canon_internal.h"  // for _itoa_s
+#include "url/url_features.h"
 
 namespace url {
 
+namespace {
+
 // Use UIDNA, a C pointer to a UTS46/IDNA 2008 handling object opened with
 // uidna_openUTS46().
 //
 // We use UTS46 with BiDiCheck to migrate from IDNA 2003 (with unassigned
-// code points allowed) to IDNA 2008 with
-// the backward compatibility in mind. What it does:
+// code points allowed) to IDNA 2008 with the backward compatibility in mind.
+// What it does:
 //
 // 1. Use the up-to-date Unicode data.
 // 2. Define a case folding/mapping with the up-to-date Unicode data as
 //    in IDNA 2003.
-// 3. Use transitional mechanism for 4 deviation characters (sharp-s,
-//    final sigma, ZWJ and ZWNJ) for now.
+// 3. If `use_idna_non_transitional` is true, use non-transitional mechanism for
+//    4 deviation characters (sharp-s, final sigma, ZWJ and ZWNJ) per
+//    url.spec.whatwg.org.
 // 4. Continue to allow symbols and punctuations.
 // 5. Apply new BiDi check rules more permissive than the IDNA 2003 BiDI rules.
 // 6. Do not apply STD3 rules
@@ -39,25 +43,39 @@
 // http://goo.gl/3XBhqw ).
 // See http://http://unicode.org/reports/tr46/ and references therein
 // for more details.
-UIDNA* GetUIDNA() {
-  static UIDNA* uidna = [] {
-    UErrorCode err = U_ZERO_ERROR;
-    // TODO(jungshik): Change options as different parties (browsers,
-    // registrars, search engines) converge toward a consensus.
-    UIDNA* value = uidna_openUTS46(UIDNA_CHECK_BIDI, &err);
-    if (U_FAILURE(err)) {
-      CHECK(false) << "failed to open UTS46 data with error: "
-                   << u_errorName(err)
-                   << ". If you see this error message in a test environment "
-                   << "your test environment likely lacks the required data "
-                   << "tables for libicu. See https://crbug.com/778929.";
-      value = nullptr;
-    }
-    return value;
-  }();
-  return uidna;
+UIDNA* CreateIDNA(bool use_idna_non_transitional) {
+  uint32_t options = UIDNA_CHECK_BIDI;
+  if (use_idna_non_transitional) {
+    // Use non-transitional processing if enabled. See
+    // https://url.spec.whatwg.org/#idna for details.
+    options |=
+        UIDNA_NONTRANSITIONAL_TO_ASCII | UIDNA_NONTRANSITIONAL_TO_UNICODE;
+  }
+  UErrorCode err = U_ZERO_ERROR;
+  UIDNA* idna = uidna_openUTS46(options, &err);
+  if (U_FAILURE(err)) {
+    CHECK(false) << "failed to open UTS46 data with error: " << u_errorName(err)
+                 << ". If you see this error message in a test environment "
+                 << "your test environment likely lacks the required data "
+                 << "tables for libicu. See https://crbug.com/778929.";
+    idna = nullptr;
+  }
+  return idna;
 }
 
+UIDNA* GetUIDNA() {
+  // This logic results in having two UIDNA instances in tests. This is okay.
+  if (IsUsingIDNA2008NonTransitional()) {
+    static UIDNA* uidna = CreateIDNA(/*use_idna_non_transitional=*/true);
+    return uidna;
+  } else {
+    static UIDNA* uidna = CreateIDNA(/*use_idna_non_transitional=*/false);
+    return uidna;
+  }
+}
+
+}  // namespace
+
 // Converts the Unicode input representing a hostname to ASCII using IDN rules.
 // The output must be ASCII, but is represented as wide characters.
 //
diff --git a/weblayer/browser/resources/weblayer_internals/weblayer_internals.js b/weblayer/browser/resources/weblayer_internals/weblayer_internals.js
index 33c1aa7..16e9402 100644
--- a/weblayer/browser/resources/weblayer_internals/weblayer_internals.js
+++ b/weblayer/browser/resources/weblayer_internals/weblayer_internals.js
@@ -4,7 +4,7 @@
 
 /* Javascript module for chrome://weblayer. */
 
-import {isAndroid} from 'chrome://resources/js/cr.m.js';
+import {isAndroid} from 'chrome://resources/js/platform.js';
 import {$} from 'chrome://resources/js/util.js';
 
 import {PageHandler} from './weblayer_internals.mojom-webui.js';