diff --git a/DEPS b/DEPS
index 6161d57cf..0f0b2c8 100644
--- a/DEPS
+++ b/DEPS
@@ -195,11 +195,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '81eabce6a3af0d83ec7bbe6b9decac7f484ecde5',
+  'skia_revision': '356838b9f8c00b6d2643bd5605f5f66fcad99a47',
   # 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': '6a9925418cf749b315c477a6a6e0e880b040b3b7',
+  'v8_revision': 'b1eb63de88dca4f8b91ec9793779f92c48d9a735',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
@@ -215,7 +215,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': '5988d0f33501ab96e661377f204f11b36e58276a',
+  'pdfium_revision': '193cfbd675299e49c04b323e7f6ba402a9306da9',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -266,7 +266,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': 'fb33bb85a9f76785f3edc12bcaae3c57d075da29',
+  'devtools_frontend_revision': '6177a5ec5b8baf95f40b2ab851bffdf64f2568b6',
   # 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.
@@ -302,7 +302,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.
-  'spv_tools_revision': 'e28436f2b8a2f0705a07de908178d2ea2682c6d3',
+  'spv_tools_revision': '3434cb0b006d973b18f5abcdbf3ff17488c6a825',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -342,7 +342,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libavif
   # and whatever else without interference from each other.
-  'libavif_revision': 'fdc636b4ce090f235e84404b6161fa140eb86d38',
+  'libavif_revision': '3fd2db22eef9fbd20bc5f768b50f13102ec77f7c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling nearby
   # and whatever else without interference from each other.
@@ -890,7 +890,7 @@
 
   # For Linux and Chromium OS.
   'src/third_party/cros_system_api': {
-      'url': Var('chromium_git') + '/chromiumos/platform2/system_api.git' + '@' + 'bbd71acf57db11ae096f69a44a3da6b1003a348f',
+      'url': Var('chromium_git') + '/chromiumos/platform2/system_api.git' + '@' + '8125d936f9e06a24857ebbfa8936796bc7b21ffd',
       'condition': 'checkout_linux',
   },
 
@@ -915,7 +915,7 @@
   },
 
   'src/third_party/ffmpeg':
-    Var('chromium_git') + '/chromium/third_party/ffmpeg.git' + '@' + '45b753b2d1d27b673a14419070ccc9558774efc2',
+    Var('chromium_git') + '/chromium/third_party/ffmpeg.git' + '@' + '48b037ba0de5eecc97baf6e1d0133c4cc58485b1',
 
   'src/third_party/flac':
     Var('chromium_git') + '/chromium/deps/flac.git' + '@' + 'af862024c8c8fa0ae07ced05e89013d881b00596',
@@ -1248,7 +1248,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '0795384a799723b5b52f8b1adef953710d3eb94b',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '72e31331da037f1e410c183484d086b71257a80e',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1457,7 +1457,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'c1d5cef72529f787f6d83f9da26b1cec7194d665',
 
   'src/third_party/webgpu-cts/src':
-    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '6371fe6f1acf4b614155acfb468dec4c55c50d27',
+    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'a60596ec57527c989f1beaa37fc4bf1aa22bf084',
 
   'src/third_party/blink/web_tests/wpt_internal/webgpu/third_party/glslang_js': {
     'packages': [
@@ -1470,7 +1470,7 @@
   },
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '577fc0c395d876159c5d45a0940d3d1dd025c35f',
+    Var('webrtc_git') + '/src.git' + '@' + 'bcdfc8975ea28f256f777d841e25f2e749090d96',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1542,7 +1542,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@1d251f6f077f4b41768a1d9160d62b8859470c22',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@e90094abdc0ae0e01200e185c863dd67f23f3cb4',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/java/src/org/chromium/android_webview/AwPacProcessor.java b/android_webview/java/src/org/chromium/android_webview/AwPacProcessor.java
index f2b27ab..1cbe934b 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwPacProcessor.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwPacProcessor.java
@@ -23,42 +23,46 @@
 @TargetApi(28)
 public class AwPacProcessor {
     private long mNativePacProcessor;
-    private long mNetworkHandle;
+    private Network mNetwork;
 
     public static final long NETWORK_UNSPECIFIED = 0;
 
     private static class LazyHolder {
-        static final AwPacProcessor sInstance = new AwPacProcessor(NETWORK_UNSPECIFIED);
+        static final AwPacProcessor sInstance = new AwPacProcessor(null);
     }
 
     public static AwPacProcessor getInstance() {
         return LazyHolder.sInstance;
     }
 
-    public AwPacProcessor(long networkHandle) {
-        mNetworkHandle = networkHandle;
-        mNativePacProcessor = AwPacProcessorJni.get().createNativePacProcessor(networkHandle);
-
-        if (mNetworkHandle != NETWORK_UNSPECIFIED) {
-            Context context = ContextUtils.getApplicationContext();
-            ConnectivityManager connectivityManager =
-                    (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
-            NetworkRequest.Builder builder = new NetworkRequest.Builder();
-
-            connectivityManager.registerNetworkCallback(
-                    builder.build(), new ConnectivityManager.NetworkCallback() {
-                        @Override
-                        public void onLinkPropertiesChanged(
-                                Network network, LinkProperties linkProperties) {
-                            if (network.getNetworkHandle() == mNetworkHandle) {
-                                updateNetworkLinkAddress(linkProperties);
-                            }
-                        }
-                    });
-
-            updateNetworkLinkAddress(connectivityManager.getLinkProperties(
-                    Network.fromNetworkHandle(mNetworkHandle)));
+    public AwPacProcessor(Network network) {
+        if (network == null) {
+            mNativePacProcessor =
+                    AwPacProcessorJni.get().createNativePacProcessor(NETWORK_UNSPECIFIED);
+            return;
         }
+
+        mNetwork = network;
+        mNativePacProcessor =
+                AwPacProcessorJni.get().createNativePacProcessor(mNetwork.getNetworkHandle());
+
+        Context context = ContextUtils.getApplicationContext();
+        ConnectivityManager connectivityManager =
+                (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        NetworkRequest.Builder builder = new NetworkRequest.Builder();
+
+        connectivityManager.registerNetworkCallback(
+                builder.build(), new ConnectivityManager.NetworkCallback() {
+                    @Override
+                    public void onLinkPropertiesChanged(
+                            Network network, LinkProperties linkProperties) {
+                        if (network.equals(mNetwork)) {
+                            updateNetworkLinkAddress(linkProperties);
+                        }
+                    }
+                });
+
+        updateNetworkLinkAddress(connectivityManager.getLinkProperties(mNetwork));
     }
 
     private void updateNetworkLinkAddress(LinkProperties linkProperties) {
@@ -82,6 +86,10 @@
         return AwPacProcessorJni.get().makeProxyRequest(mNativePacProcessor, this, url);
     }
 
+    public Network getNetwork() {
+        return mNetwork;
+    }
+
     public static void initializeEnvironment() {
         AwPacProcessorJni.get().initializeEnvironment();
     }
diff --git a/android_webview/tools/system_webview_shell/layout_tests/src/org/chromium/webview_shell/test/WebViewThreadTest.java b/android_webview/tools/system_webview_shell/layout_tests/src/org/chromium/webview_shell/test/WebViewThreadTest.java
index 0108d27..87e3f54 100644
--- a/android_webview/tools/system_webview_shell/layout_tests/src/org/chromium/webview_shell/test/WebViewThreadTest.java
+++ b/android_webview/tools/system_webview_shell/layout_tests/src/org/chromium/webview_shell/test/WebViewThreadTest.java
@@ -22,7 +22,6 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.test.BaseJUnit4ClassRunner;
-import org.chromium.base.test.util.DisabledTest;
 import org.chromium.webview_shell.WebViewThreadTestActivity;
 
 /**
@@ -97,7 +96,6 @@
      */
     @Test
     @SmallTest
-    @DisabledTest(message = "https://crubg.com/1117683")
     public void testCookieManagerBeforeCreateWebView() throws InterruptedException {
         CookieManager.getInstance();
         mActivity.createWebViewOnUiThread(TIMEOUT);
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index c39b789..fa08c4c 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -460,10 +460,16 @@
     "host/root_window_transformer.h",
     "host/transformer_helper.cc",
     "host/transformer_helper.h",
+    "hud_display/cpu_graph_page_view.cc",
+    "hud_display/cpu_graph_page_view.h",
+    "hud_display/cpu_status.cc",
+    "hud_display/cpu_status.h",
     "hud_display/data_source.cc",
     "hud_display/data_source.h",
     "hud_display/graph.cc",
     "hud_display/graph.h",
+    "hud_display/graph_page_view_base.cc",
+    "hud_display/graph_page_view_base.h",
     "hud_display/graphs_container_view.cc",
     "hud_display/graphs_container_view.h",
     "hud_display/hud_constants.h",
@@ -475,6 +481,8 @@
     "hud_display/hud_properties.h",
     "hud_display/hud_settings_view.cc",
     "hud_display/hud_settings_view.h",
+    "hud_display/memory_graph_page_view.cc",
+    "hud_display/memory_graph_page_view.h",
     "hud_display/memory_status.cc",
     "hud_display/memory_status.h",
     "hud_display/tab_strip.cc",
diff --git a/ash/ambient/ambient_constants.h b/ash/ambient/ambient_constants.h
index 993a0dc..8709c28 100644
--- a/ash/ambient/ambient_constants.h
+++ b/ash/ambient/ambient_constants.h
@@ -14,11 +14,33 @@
 constexpr base::TimeDelta kAnimationDuration =
     base::TimeDelta::FromMilliseconds(500);
 
+// Topic related numbers.
+
+// The default interval to fetch Topics.
+constexpr base::TimeDelta kTopicFetchInterval =
+    base::TimeDelta::FromSeconds(30);
+
 // The default interval to refresh photos.
-// TODO(b/139953713): Change to a correct time interval.
 constexpr base::TimeDelta kPhotoRefreshInterval =
     base::TimeDelta::FromSeconds(60);
 
+// The number of requests to fetch topics.
+constexpr int kNumberOfRequests = 50;
+
+// The batch size of topics to fetch in one request.
+// Magic number 2 is based on experiments that no curation on Google Photos.
+constexpr int kTopicsBatchSize = 2;
+
+// Max cached images.
+constexpr int kMaxNumberOfCachedImages = 100;
+
+constexpr int kMaxImageSizeInBytes = 5 * 1024 * 1024;
+
+constexpr int kMaxReservedAvailableDiskSpaceByte = 200 * 1024 * 1024;
+
+constexpr char kPhotoFileExt[] = ".img";
+constexpr char kPhotoDetailsFileExt[] = ".txt";
+
 // Directory name of ambient mode.
 constexpr char kAmbientModeDirectoryName[] = "ambient-mode";
 
diff --git a/ash/ambient/ambient_controller.h b/ash/ambient/ambient_controller.h
index 0c12deb..3be2b27 100644
--- a/ash/ambient/ambient_controller.h
+++ b/ash/ambient/ambient_controller.h
@@ -94,6 +94,10 @@
     return ambient_backend_controller_.get();
   }
 
+  AmbientPhotoController* ambient_photo_controller() {
+    return &ambient_photo_controller_;
+  }
+
   AmbientUiModel* ambient_ui_model() { return &ambient_ui_model_; }
 
  private:
@@ -131,10 +135,6 @@
 
   void CloseWidget(bool immediately);
 
-  AmbientPhotoController* get_ambient_photo_controller_for_testing() {
-    return &ambient_photo_controller_;
-  }
-
   AmbientContainerView* get_container_view_for_testing() {
     return container_view_;
   }
diff --git a/ash/ambient/ambient_photo_controller.cc b/ash/ambient/ambient_photo_controller.cc
index 5001e79..5b00b627 100644
--- a/ash/ambient/ambient_photo_controller.cc
+++ b/ash/ambient/ambient_photo_controller.cc
@@ -10,6 +10,7 @@
 #include "ash/ambient/ambient_constants.h"
 #include "ash/ambient/ambient_controller.h"
 #include "ash/ambient/model/ambient_backend_model.h"
+#include "ash/public/cpp/ambient/ambient_backend_controller.h"
 #include "ash/public/cpp/ambient/ambient_client.h"
 #include "ash/public/cpp/image_downloader.h"
 #include "ash/shell.h"
@@ -24,6 +25,7 @@
 #include "base/optional.h"
 #include "base/path_service.h"
 #include "base/rand_util.h"
+#include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/system/sys_info.h"
 #include "base/task/post_task.h"
@@ -45,25 +47,25 @@
 
 // TODO(b/161357364): refactor utility functions and constants
 
-// Topic related numbers.
+constexpr net::BackoffEntry::Policy kFetchTopicRetryBackoffPolicy = {
+    0,              // Number of initial errors to ignore.
+    500,            // Initial delay in ms.
+    2.0,            // Factor by which the waiting time will be multiplied.
+    0.2,            // Fuzzing percentage.
+    2 * 60 * 1000,  // Maximum delay in ms.
+    -1,             // Never discard the entry.
+    true,           // Use initial delay.
+};
 
-// The number of requests to fetch topics.
-constexpr int kNumberOfRequests = 50;
-
-// The batch size of topics to fetch in one request.
-// Magic number 2 is based on experiments that no curation on Google Photos.
-constexpr int kTopicsBatchSize = 2;
-
-// The upper bound of delay to the fetch topics. An random value will be
-// generated in the range of |kTopicFetchDelayMax|/2 to |kTopicFetchDelayMax|.
-constexpr base::TimeDelta kTopicFetchDelayMax =
-    base::TimeDelta::FromSeconds(36);
-
-constexpr int kMaxImageSizeInBytes = 5 * 1024 * 1024;
-
-constexpr int kMaxReservedAvailableDiskSpaceByte = 200 * 1024 * 1024;
-
-constexpr char kPhotoFileExt[] = ".img";
+constexpr net::BackoffEntry::Policy kResumeFetchImageBackoffPolicy = {
+    0,              // Number of initial errors to ignore.
+    500,            // Initial delay in ms.
+    2.0,            // Factor by which the waiting time will be multiplied.
+    0.2,            // Fuzzing percentage.
+    8 * 60 * 1000,  // Maximum delay in ms.
+    -1,             // Never discard the entry.
+    true,           // Use initial delay.
+};
 
 using DownloadCallback = base::OnceCallback<void(const gfx::ImageSkia&)>;
 
@@ -89,14 +91,6 @@
   base::DeletePathRecursively(path);
 }
 
-std::string ToPhotoFileName(const std::string& url) {
-  std::string hash_tag;
-  base::Base64Encode(base::SHA1HashString(url), &hash_tag);
-  // Replace path divider.
-  base::ReplaceSubstringsAfterOffset(&hash_tag, 0, "/", "_");
-  return hash_tag + std::string(kPhotoFileExt);
-}
-
 void ToImageSkia(DownloadCallback callback, const SkBitmap& image) {
   if (image.isNull()) {
     std::move(callback).Run(gfx::ImageSkia());
@@ -127,12 +121,6 @@
     return;
   }
 
-  if (!base::PathExists(path.DirName()) &&
-      !base::CreateDirectory(path.DirName())) {
-    LOG(ERROR) << "Cannot create a new session directory.";
-    return;
-  }
-
   // Create a temp file.
   base::FilePath temp_file;
   if (!base::CreateTemporaryFileInDir(path.DirName(), &temp_file)) {
@@ -219,7 +207,7 @@
       const std::vector<uint8_t>& encoded_bytes,
       base::OnceCallback<void(const gfx::ImageSkia&)> callback) override {
     data_decoder::DecodeImageIsolated(
-        std::move(encoded_bytes), data_decoder::mojom::ImageCodec::DEFAULT,
+        encoded_bytes, data_decoder::mojom::ImageCodec::DEFAULT,
         /*shrink_to_fit=*/true, data_decoder::kDefaultMaxSizeInBytes,
         /*desired_image_frame_size=*/gfx::Size(),
         base::BindOnce(&ToImageSkia, std::move(callback)));
@@ -227,7 +215,9 @@
 };
 
 AmbientPhotoController::AmbientPhotoController()
-    : url_loader_(std::make_unique<AmbientURLLoaderImpl>()),
+    : fetch_topic_retry_backoff_(&kFetchTopicRetryBackoffPolicy),
+      resume_fetch_image_backoff_(&kResumeFetchImageBackoffPolicy),
+      url_loader_(std::make_unique<AmbientURLLoaderImpl>()),
       image_decoder_(std::make_unique<AmbientImageDecoderImpl>()),
       task_runner_(
           base::ThreadPool::CreateSequencedTaskRunner(GetTaskTraits())) {
@@ -237,33 +227,30 @@
 AmbientPhotoController::~AmbientPhotoController() = default;
 
 void AmbientPhotoController::StartScreenUpdate() {
-  photo_path_ = GetRootPath().Append(FILE_PATH_LITERAL(base::GenerateGUID()));
-  task_runner_->PostTaskAndReply(
-      FROM_HERE, base::BindOnce(&DeletePathRecursively, GetRootPath()),
-      base::BindOnce(&AmbientPhotoController::FetchTopics,
-                     weak_factory_.GetWeakPtr()));
+  FetchTopics();
 }
 
 void AmbientPhotoController::StopScreenUpdate() {
   photo_refresh_timer_.Stop();
   topic_index_ = 0;
   topics_batch_fetched_ = 0;
+  image_refresh_started_ = false;
+  retries_to_read_from_cache_ = kMaxNumberOfCachedImages;
+  fetch_topic_retry_backoff_.Reset();
+  resume_fetch_image_backoff_.Reset();
   ambient_backend_model_.Clear();
   weak_factory_.InvalidateWeakPtrs();
-
-  task_runner_->PostTask(FROM_HERE,
-                         base::BindOnce(&DeletePathRecursively, photo_path_));
-  photo_path_.clear();
 }
 
 void AmbientPhotoController::OnTopicsChanged() {
   ++topics_batch_fetched_;
   if (topics_batch_fetched_ < kNumberOfRequests)
-    ScheduleFetchTopics();
+    ScheduleFetchTopics(/*backoff=*/false);
 
-  // The first OnTopicsChanged event triggers the photo refresh.
-  if (topics_batch_fetched_ == 1)
+  if (!image_refresh_started_) {
+    image_refresh_started_ = true;
     ScheduleRefreshImage();
+  }
 }
 
 void AmbientPhotoController::FetchTopics() {
@@ -276,9 +263,16 @@
                          weak_factory_.GetWeakPtr()));
 }
 
-void AmbientPhotoController::ScheduleFetchTopics() {
+void AmbientPhotoController::ClearCache() {
+  task_runner_->PostTask(FROM_HERE,
+                         base::BindOnce(&DeletePathRecursively, GetRootPath()));
+}
+
+void AmbientPhotoController::ScheduleFetchTopics(bool backoff) {
+  // If retry, using the backoff delay, otherwise the default delay.
   const base::TimeDelta kDelay =
-      (base::RandDouble() * kTopicFetchDelayMax) / 2 + kTopicFetchDelayMax / 2;
+      backoff ? fetch_topic_retry_backoff_.GetTimeUntilRelease()
+              : kTopicFetchInterval;
   base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
       FROM_HERE,
       base::BindOnce(&AmbientPhotoController::FetchTopics,
@@ -295,80 +289,68 @@
   // is true.
   photo_refresh_timer_.Start(
       FROM_HERE, refresh_interval,
-      base::BindOnce(&AmbientPhotoController::TryReadPhotoRawData,
+      base::BindOnce(&AmbientPhotoController::FetchPhotoRawData,
                      weak_factory_.GetWeakPtr()));
 }
 
-const AmbientModeTopic& AmbientPhotoController::GetNextTopic() {
+const AmbientModeTopic* AmbientPhotoController::GetNextTopic() {
   const auto& topics = ambient_backend_model_.topics();
-  DCHECK(!topics.empty());
-
-  // We prefetch the first two photos, which will increase the |topic_index_| to
-  // 2 in the first batch with size of 2. Then it will reset to 0 if we put this
-  // block after the increment of |topic_index_|.
+  // If no more topics, will read from cache.
   if (topic_index_ == topics.size())
-    topic_index_ = 0;
+    return nullptr;
 
-  return topics[topic_index_++];
+  return &topics[topic_index_++];
 }
 
 void AmbientPhotoController::OnScreenUpdateInfoFetched(
     const ash::ScreenUpdate& screen_update) {
   // It is possible that |screen_update| is an empty instance if fatal errors
   // happened during the fetch.
-  // TODO(b/148485116): Implement retry logic.
   if (screen_update.next_topics.empty() &&
       !screen_update.weather_info.has_value()) {
     LOG(ERROR) << "The screen update info fetch has failed.";
+
+    fetch_topic_retry_backoff_.InformOfRequest(/*succeeded=*/false);
+    ScheduleFetchTopics(/*backoff=*/true);
+    if (!image_refresh_started_) {
+      image_refresh_started_ = true;
+      ScheduleRefreshImage();
+    }
     return;
   }
 
+  fetch_topic_retry_backoff_.InformOfRequest(/*succeeded=*/true);
   ambient_backend_model_.AppendTopics(screen_update.next_topics);
   StartDownloadingWeatherConditionIcon(screen_update.weather_info);
 }
 
-void AmbientPhotoController::TryReadPhotoRawData() {
-  const AmbientModeTopic& topic = GetNextTopic();
-
-  base::FilePath path = photo_path_.Append(ToPhotoFileName(topic.GetUrl()));
-  task_runner_->PostTaskAndReplyWithResult(
-      FROM_HERE,
-      base::BindOnce(
-          [](const base::FilePath& path) {
-            auto data = std::make_unique<std::string>();
-            if (!base::ReadFileToString(path, data.get()))
-              data = nullptr;
-            return data;
-          },
-          path),
-      base::BindOnce(&AmbientPhotoController::OnPhotoRawDataRead,
-                     weak_factory_.GetWeakPtr(), topic));
-}
-
-void AmbientPhotoController::OnPhotoRawDataRead(
-    const AmbientModeTopic& topic,
-    std::unique_ptr<std::string> data) {
-  if (!data || data->empty()) {
+void AmbientPhotoController::FetchPhotoRawData() {
+  const AmbientModeTopic* topic = GetNextTopic();
+  if (topic) {
     url_loader_->Download(
-        topic.GetUrl(),
+        topic->GetUrl(),
         base::BindOnce(&AmbientPhotoController::OnPhotoRawDataAvailable,
-                       weak_factory_.GetWeakPtr(), topic,
-                       /*need_to_save=*/true));
-  } else {
-    OnPhotoRawDataAvailable(topic, /*need_to_save=*/false, std::move(data));
+                       weak_factory_.GetWeakPtr(),
+                       /*from_downloading=*/true,
+                       std::make_unique<std::string>(topic->details)));
+    return;
   }
+
+  // If |topic| is nullptr, will try to read from disk cache.
+  TryReadPhotoRawData();
 }
 
-void AmbientPhotoController::OnPhotoRawDataAvailable(
-    const AmbientModeTopic& topic,
-    bool need_to_save,
-    std::unique_ptr<std::string> response_body) {
-  if (!response_body) {
-    LOG(ERROR) << "Failed to download image";
+void AmbientPhotoController::TryReadPhotoRawData() {
+  // Stop reading from cache after the max number of retries.
+  if (retries_to_read_from_cache_ == 0) {
+    if (topic_index_ == ambient_backend_model_.topics().size()) {
+      image_refresh_started_ = false;
+      return;
+    }
 
-    // Continue to get next photo on error.
-    // TODO(b/148485116): Add exponential backoff retry logic.
-    const base::TimeDelta kDelay = base::TimeDelta::FromMilliseconds(100);
+    // Try to resume normal workflow with backoff.
+    const base::TimeDelta kDelay =
+        resume_fetch_image_backoff_.GetTimeUntilRelease();
     base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
         FROM_HERE,
         base::BindOnce(&AmbientPhotoController::ScheduleRefreshImage,
@@ -377,49 +359,113 @@
     return;
   }
 
-  const base::FilePath path =
-      photo_path_.Append(ToPhotoFileName(topic.GetUrl()));
+  --retries_to_read_from_cache_;
+  std::string file_name = base::NumberToString(cache_index_for_display_);
+  ++cache_index_for_display_;
+  if (cache_index_for_display_ == kMaxNumberOfCachedImages)
+    cache_index_for_display_ = 0;
+
+  auto photo_data = std::make_unique<std::string>();
+  auto photo_details = std::make_unique<std::string>();
   task_runner_->PostTaskAndReply(
       FROM_HERE,
       base::BindOnce(
-          [](const base::FilePath& path, bool need_to_save,
-             const std::string& data) {
-            if (need_to_save)
-              WriteFile(path, data);
+          [](const std::string& file_name, std::string* photo_data,
+             std::string* photo_details) {
+            if (!base::ReadFileToString(
+                    GetRootPath().Append(file_name + kPhotoFileExt),
+                    photo_data)) {
+              photo_data->clear();
+            }
+            if (!base::ReadFileToString(
+                    GetRootPath().Append(file_name + kPhotoDetailsFileExt),
+                    photo_details)) {
+              photo_details->clear();
+            }
           },
-          path, need_to_save, *response_body),
+          file_name, photo_data.get(), photo_details.get()),
+      base::BindOnce(&AmbientPhotoController::OnPhotoRawDataAvailable,
+                     weak_factory_.GetWeakPtr(), /*from_downloading=*/false,
+                     std::move(photo_details), std::move(photo_data)));
+}
+
+void AmbientPhotoController::OnPhotoRawDataAvailable(
+    bool from_downloading,
+    std::unique_ptr<std::string> details,
+    std::unique_ptr<std::string> data) {
+  if (!data || data->empty()) {
+    if (from_downloading) {
+      LOG(ERROR) << "Failed to download image";
+      resume_fetch_image_backoff_.InformOfRequest(/*succeeded=*/false);
+    } else {
+      LOG(WARNING) << "Failed to read image";
+    }
+
+    // Try to read from cache when failure happens.
+    TryReadPhotoRawData();
+    return;
+  }
+
+  const std::string file_name = base::NumberToString(cache_index_for_store_);
+  // If the data is fetched from downloading, write to disk.
+  // Note: WriteFile() could fail. The saved file name may not be continuous.
+  if (from_downloading)
+    ++cache_index_for_store_;
+  if (cache_index_for_store_ == kMaxNumberOfCachedImages)
+    cache_index_for_store_ = 0;
+
+  task_runner_->PostTaskAndReply(
+      FROM_HERE,
+      base::BindOnce(
+          [](const std::string& file_name, bool need_to_save,
+             const std::string& data, const std::string& details) {
+            if (need_to_save) {
+              WriteFile(GetRootPath().Append(file_name + kPhotoFileExt), data);
+              WriteFile(GetRootPath().Append(file_name + kPhotoDetailsFileExt),
+                        details);
+            }
+          },
+          file_name, from_downloading, *data, *details),
       base::BindOnce(&AmbientPhotoController::DecodePhotoRawData,
-                     weak_factory_.GetWeakPtr(), topic,
-                     std::move(response_body)));
+                     weak_factory_.GetWeakPtr(), from_downloading,
+                     std::move(details), std::move(data)));
 }
 
 void AmbientPhotoController::DecodePhotoRawData(
-    const AmbientModeTopic& topic,
+    bool from_downloading,
+    std::unique_ptr<std::string> details,
     std::unique_ptr<std::string> data) {
   std::vector<uint8_t> image_bytes(data->begin(), data->end());
-  image_decoder_->Decode(image_bytes,
-                         base::BindOnce(&AmbientPhotoController::OnPhotoDecoded,
-                                        weak_factory_.GetWeakPtr(), topic));
+  image_decoder_->Decode(
+      image_bytes, base::BindOnce(&AmbientPhotoController::OnPhotoDecoded,
+                                  weak_factory_.GetWeakPtr(), from_downloading,
+                                  std::move(details)));
 }
 
-void AmbientPhotoController::OnPhotoDecoded(const AmbientModeTopic& topic,
-                                            const gfx::ImageSkia& image) {
-  base::TimeDelta kDelay;
+void AmbientPhotoController::OnPhotoDecoded(
+    bool from_downloading,
+    std::unique_ptr<std::string> details,
+    const gfx::ImageSkia& image) {
   if (image.isNull()) {
     LOG(WARNING) << "Image is null";
-    kDelay = base::TimeDelta::FromMilliseconds(100);
-  } else {
-    PhotoWithDetails detailed_photo;
-    detailed_photo.photo = image;
-    detailed_photo.details = topic.details;
-    ambient_backend_model_.AddNextImage(std::move(detailed_photo));
+    if (from_downloading)
+      resume_fetch_image_backoff_.InformOfRequest(/*succeeded=*/false);
+
+    // Try to read from cache when failure happens.
+    TryReadPhotoRawData();
+    return;
   }
 
-  base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
-      FROM_HERE,
-      base::BindOnce(&AmbientPhotoController::ScheduleRefreshImage,
-                     weak_factory_.GetWeakPtr()),
-      kDelay);
+  retries_to_read_from_cache_ = kMaxNumberOfCachedImages;
+  if (from_downloading)
+    resume_fetch_image_backoff_.InformOfRequest(/*succeeded=*/true);
+
+  PhotoWithDetails detailed_photo;
+  detailed_photo.photo = image;
+  detailed_photo.details = *details;
+  ambient_backend_model_.AddNextImage(std::move(detailed_photo));
+
+  ScheduleRefreshImage();
 }
 
 void AmbientPhotoController::StartDownloadingWeatherConditionIcon(
@@ -464,4 +510,12 @@
   ambient_backend_model_.UpdateWeatherInfo(icon, temp_f, show_celsius);
 }
 
+void AmbientPhotoController::FetchTopicsForTesting() {
+  FetchTopics();
+}
+
+void AmbientPhotoController::FetchImageForTesting() {
+  FetchPhotoRawData();
+}
+
 }  // namespace ash
diff --git a/ash/ambient/ambient_photo_controller.h b/ash/ambient/ambient_photo_controller.h
index 5b751e8b..85abd91 100644
--- a/ash/ambient/ambient_photo_controller.h
+++ b/ash/ambient/ambient_photo_controller.h
@@ -10,6 +10,7 @@
 #include <utility>
 #include <vector>
 
+#include "ash/ambient/ambient_constants.h"
 #include "ash/ambient/model/ambient_backend_model.h"
 #include "ash/ambient/model/ambient_backend_model_observer.h"
 #include "ash/ash_export.h"
@@ -21,6 +22,7 @@
 #include "base/optional.h"
 #include "base/scoped_observer.h"
 #include "base/timer/timer.h"
+#include "net/base/backoff_entry.h"
 #include "services/network/public/cpp/simple_url_loader.h"
 
 namespace gfx {
@@ -96,38 +98,42 @@
   // AmbientBackendModelObserver:
   void OnTopicsChanged() override;
 
+  // Clear cache when Settings changes.
+  void ClearCache();
+
  private:
   friend class AmbientAshTestBase;
 
   void FetchTopics();
 
-  void ScheduleFetchTopics();
+  void ScheduleFetchTopics(bool backoff);
 
   void ScheduleRefreshImage();
 
   void GetScreenUpdateInfo();
 
   // Return a topic to download the image.
-  const AmbientModeTopic& GetNextTopic();
+  // Return nullptr when need to read from disk cache.
+  const AmbientModeTopic* GetNextTopic();
 
   void OnScreenUpdateInfoFetched(const ash::ScreenUpdate& screen_update);
 
-  // Try to read photo raw data from disk.
+  // Fetch photo raw data by downloading or reading from cache.
+  void FetchPhotoRawData();
+
+  // Try to read photo raw data from cache.
   void TryReadPhotoRawData();
 
-  // If photo raw data is read successfully, call OnPhotoRawDataAvailable() to
-  // decode data. Otherwise, download the raw data and save to disk.
-  void OnPhotoRawDataRead(const AmbientModeTopic& topic,
+  void OnPhotoRawDataAvailable(bool from_downloading,
+                               std::unique_ptr<std::string> details,
+                               std::unique_ptr<std::string> data);
+
+  void DecodePhotoRawData(bool from_downloading,
+                          std::unique_ptr<std::string> details,
                           std::unique_ptr<std::string> data);
 
-  void OnPhotoRawDataAvailable(const AmbientModeTopic& topic,
-                               bool need_to_save,
-                               std::unique_ptr<std::string> response_body);
-
-  void DecodePhotoRawData(const AmbientModeTopic& topic,
-                          std::unique_ptr<std::string> data);
-
-  void OnPhotoDecoded(const AmbientModeTopic& topic,
+  void OnPhotoDecoded(bool from_downloading,
+                      std::unique_ptr<std::string> details,
                       const gfx::ImageSkia& image);
 
   void StartDownloadingWeatherConditionIcon(
@@ -144,6 +150,8 @@
     url_loader_ = std::move(url_loader);
   }
 
+  AmbientURLLoader* get_url_loader_for_testing() { return url_loader_.get(); }
+
   void set_image_decoder_for_testing(
       std::unique_ptr<AmbientImageDecoder> image_decoder) {
     image_decoder_ = std::move(image_decoder);
@@ -153,6 +161,10 @@
     return image_decoder_.get();
   }
 
+  void FetchTopicsForTesting();
+
+  void FetchImageForTesting();
+
   AmbientBackendModel ambient_backend_model_;
 
   // The timer to refresh photos.
@@ -164,11 +176,33 @@
   // Tracking how many batches of topics have been fetched.
   int topics_batch_fetched_ = 0;
 
+  // Current index of cached image to read and display when failure happens.
+  // The image file of this index may not exist or may not be valid. It will try
+  // to read from the next cached file by increasing this index by 1.
+  int cache_index_for_display_ = 0;
+
+  // Current index of cached image to save for the latest downloaded photo.
+  // The write command could fail. This index will increase 1 no matter writing
+  // successfully or not. But theoretically we could not to change this index if
+  // failures happen.
+  int cache_index_for_store_ = 0;
+
+  // Whether the image refresh started or not.
+  bool image_refresh_started_ = false;
+
+  // Cached image may not exist or valid. This is the max times of attempts to
+  // read cached images.
+  int retries_to_read_from_cache_ = kMaxNumberOfCachedImages;
+
+  // Backoff for fetch topics retries.
+  net::BackoffEntry fetch_topic_retry_backoff_;
+
+  // Backoff to resume fetch images.
+  net::BackoffEntry resume_fetch_image_backoff_;
+
   ScopedObserver<AmbientBackendModel, AmbientBackendModelObserver>
       ambient_backend_model_observer_{this};
 
-  base::FilePath photo_path_;
-
   std::unique_ptr<AmbientURLLoader> url_loader_;
 
   std::unique_ptr<AmbientImageDecoder> image_decoder_;
diff --git a/ash/ambient/ambient_photo_controller_unittest.cc b/ash/ambient/ambient_photo_controller_unittest.cc
index 00b6c3603..c3d4716 100644
--- a/ash/ambient/ambient_photo_controller_unittest.cc
+++ b/ash/ambient/ambient_photo_controller_unittest.cc
@@ -25,7 +25,6 @@
 #include "base/run_loop.h"
 #include "base/system/sys_info.h"
 #include "base/test/bind_test_util.h"
-#include "base/test/task_environment.h"
 #include "base/timer/timer.h"
 #include "ui/gfx/image/image_skia.h"
 
@@ -43,7 +42,7 @@
   topics = photo_controller()->ambient_backend_model()->topics();
   EXPECT_TRUE(topics.empty());
 
-  task_environment()->FastForwardBy(kPhotoRefreshInterval);
+  FastForwardToNextImage();
   topics = photo_controller()->ambient_backend_model()->topics();
   EXPECT_FALSE(topics.empty());
 
@@ -60,7 +59,7 @@
 
   // Start to refresh images.
   photo_controller()->StartScreenUpdate();
-  task_environment()->FastForwardBy(1.2 * kPhotoRefreshInterval);
+  FastForwardToNextImage();
   image = photo_controller()->ambient_backend_model()->GetNextImage();
   EXPECT_FALSE(image.IsNull());
 
@@ -78,20 +77,18 @@
 
   // Start to refresh images.
   photo_controller()->StartScreenUpdate();
-  task_environment()->FastForwardBy(1.2 * kPhotoRefreshInterval);
+  FastForwardToNextImage();
   image1 = photo_controller()->ambient_backend_model()->GetNextImage();
   EXPECT_FALSE(image1.IsNull());
   EXPECT_TRUE(image2.IsNull());
 
-  // Fastforward enough time to update the photo.
-  task_environment()->FastForwardBy(1.2 * kPhotoRefreshInterval);
+  FastForwardToNextImage();
   image2 = photo_controller()->ambient_backend_model()->GetNextImage();
   EXPECT_FALSE(image2.IsNull());
   EXPECT_FALSE(image1.photo.BackedBySameObjectAs(image2.photo));
   EXPECT_TRUE(image3.IsNull());
 
-  // Fastforward enough time to update another photo.
-  task_environment()->FastForwardBy(1.2 * kPhotoRefreshInterval);
+  FastForwardToNextImage();
   image3 = photo_controller()->ambient_backend_model()->GetNextImage();
   EXPECT_FALSE(image3.IsNull());
   EXPECT_FALSE(image1.photo.BackedBySameObjectAs(image3.photo));
@@ -101,56 +98,251 @@
   photo_controller()->StopScreenUpdate();
 }
 
-// Test that image is saved and deleted when starting/stopping screen update.
-TEST_F(AmbientPhotoControllerTest, ShouldSaveAndDeleteImagesOnDisk) {
+// Test that image is saved.
+TEST_F(AmbientPhotoControllerTest, ShouldSaveImagesOnDisk) {
   base::FilePath home_dir;
   base::PathService::Get(base::DIR_HOME, &home_dir);
 
   base::FilePath ambient_image_path =
       home_dir.Append(FILE_PATH_LITERAL(kAmbientModeDirectoryName));
 
-  // Save a file to check if it gets deleted by StartScreenUpdate.
-  auto file_to_delete = ambient_image_path.Append("file_to_delete");
-  base::WriteFile(file_to_delete, "delete_me");
+  // Clean up.
+  base::DeletePathRecursively(ambient_image_path);
 
-  // Start to refresh images. Kicks off tasks that cleans |ambient_image_path|,
-  // then downloads a test image and writes it to a subdirectory of
+  // Start to refresh images. It will download a test image and write it in
   // |ambient_image_path| in a delayed task.
   photo_controller()->StartScreenUpdate();
-  task_environment()->FastForwardBy(1.2 * kPhotoRefreshInterval);
+  FastForwardToNextImage();
 
   EXPECT_TRUE(base::PathExists(ambient_image_path));
-  EXPECT_FALSE(base::PathExists(file_to_delete));
 
   {
-    // Count files and directories in root_path. There should only be one
-    // subdirectory that was just created to save image files for this ambient
-    // mode session.
+    // Count files and directories in root_path. There should only be one file
+    // that was just created to save image files for this ambient mode session.
     base::FileEnumerator files(
         ambient_image_path, /*recursive=*/false,
         base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
     int count = 0;
     for (base::FilePath current = files.Next(); !current.empty();
          current = files.Next()) {
-      EXPECT_TRUE(files.GetInfo().IsDirectory());
+      EXPECT_FALSE(files.GetInfo().IsDirectory());
       count++;
     }
 
-    EXPECT_EQ(count, 1);
+    // Two image files and two attribution files.
+    EXPECT_EQ(count, 4);
   }
 
+  // Clean up.
+  base::DeletePathRecursively(ambient_image_path);
+}
+
+// Test that image is save and will be deleted when stopping ambient mode.
+TEST_F(AmbientPhotoControllerTest, ShouldNotDeleteImagesOnDisk) {
+  base::FilePath home_dir;
+  base::PathService::Get(base::DIR_HOME, &home_dir);
+
+  base::FilePath ambient_image_path =
+      home_dir.Append(FILE_PATH_LITERAL(kAmbientModeDirectoryName));
+
+  // Clean up.
+  base::DeletePathRecursively(ambient_image_path);
+
+  // Start to refresh images. It will download a test image and write it in
+  // |ambient_image_path| in a delayed task.
+  photo_controller()->StartScreenUpdate();
+  FastForwardToNextImage();
+
+  EXPECT_TRUE(base::PathExists(ambient_image_path));
+
   auto image = photo_controller()->ambient_backend_model()->GetNextImage();
   EXPECT_FALSE(image.IsNull());
 
   // Stop to refresh images.
   photo_controller()->StopScreenUpdate();
-  task_environment()->FastForwardBy(1.2 * kPhotoRefreshInterval);
+  FastForwardToNextImage();
 
   EXPECT_TRUE(base::PathExists(ambient_image_path));
-  EXPECT_TRUE(base::IsDirectoryEmpty(ambient_image_path));
+  EXPECT_FALSE(base::IsDirectoryEmpty(ambient_image_path));
 
   image = photo_controller()->ambient_backend_model()->GetNextImage();
   EXPECT_TRUE(image.IsNull());
+
+  {
+    // Count files and directories in root_path. There should only be one file
+    // that was just created to save image files for this ambient mode session.
+    base::FileEnumerator files(
+        ambient_image_path, /*recursive=*/false,
+        base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
+    int count = 0;
+    for (base::FilePath current = files.Next(); !current.empty();
+         current = files.Next()) {
+      EXPECT_FALSE(files.GetInfo().IsDirectory());
+      count++;
+    }
+
+    // Two image files and two attribution files.
+    EXPECT_EQ(count, 4);
+  }
+
+  // Clean up.
+  base::DeletePathRecursively(ambient_image_path);
+}
+
+// Test that image is read from disk when no more topics.
+TEST_F(AmbientPhotoControllerTest, ShouldReadCacheWhenNoMoreTopics) {
+  base::FilePath home_dir;
+  base::PathService::Get(base::DIR_HOME, &home_dir);
+
+  base::FilePath ambient_image_path =
+      home_dir.Append(FILE_PATH_LITERAL(kAmbientModeDirectoryName));
+
+  // Clean up.
+  base::DeletePathRecursively(ambient_image_path);
+
+  FetchImage();
+  FastForwardToNextImage();
+  // Topics is empty. Will read from cache, which is empty.
+  auto image = photo_controller()->ambient_backend_model()->GetNextImage();
+  EXPECT_TRUE(image.IsNull());
+
+  // Save a file to check if it gets read for display.
+  auto cached_image = ambient_image_path.Append("0.img");
+  base::CreateDirectory(ambient_image_path);
+  base::WriteFile(cached_image, "cached image");
+
+  // Reset variables in photo controller.
+  photo_controller()->StopScreenUpdate();
+  FetchImage();
+  FastForwardToNextImage();
+  image = photo_controller()->ambient_backend_model()->GetNextImage();
+  EXPECT_FALSE(image.IsNull());
+
+  // Clean up.
+  base::DeletePathRecursively(ambient_image_path);
+}
+
+// Test that will try 100 times to read image from disk when no more topics.
+TEST_F(AmbientPhotoControllerTest,
+       ShouldTry100TimesToReadCacheWhenNoMoreTopics) {
+  base::FilePath home_dir;
+  base::PathService::Get(base::DIR_HOME, &home_dir);
+
+  base::FilePath ambient_image_path =
+      home_dir.Append(FILE_PATH_LITERAL(kAmbientModeDirectoryName));
+
+  // Clean up.
+  base::DeletePathRecursively(ambient_image_path);
+
+  FetchImage();
+  FastForwardToNextImage();
+  // Topics is empty. Will read from cache, which is empty.
+  auto image = photo_controller()->ambient_backend_model()->GetNextImage();
+  EXPECT_TRUE(image.IsNull());
+
+  // The initial file name to be read is 0. Save a file with 99.img to check if
+  // it gets read for display.
+  auto cached_image = ambient_image_path.Append("99.img");
+  base::CreateDirectory(ambient_image_path);
+  base::WriteFile(cached_image, "cached image");
+
+  // Reset variables in photo controller.
+  photo_controller()->StopScreenUpdate();
+  FetchImage();
+  FastForwardToNextImage();
+  image = photo_controller()->ambient_backend_model()->GetNextImage();
+  EXPECT_FALSE(image.IsNull());
+
+  // Clean up.
+  base::DeletePathRecursively(ambient_image_path);
+}
+
+// Test that image is read from disk when image downloading failed.
+TEST_F(AmbientPhotoControllerTest, ShouldReadCacheWhenImageDownloadingFailed) {
+  base::FilePath home_dir;
+  base::PathService::Get(base::DIR_HOME, &home_dir);
+
+  base::FilePath ambient_image_path =
+      home_dir.Append(FILE_PATH_LITERAL(kAmbientModeDirectoryName));
+
+  // Clean up.
+  base::DeletePathRecursively(ambient_image_path);
+
+  SetUrlLoaderData(std::make_unique<std::string>());
+  FetchTopics();
+  // Forward a little bit time. FetchTopics() will succeed. Downloading should
+  // fail. Will read from cache, which is empty.
+  task_environment()->FastForwardBy(0.2 * kTopicFetchInterval);
+  auto image = photo_controller()->ambient_backend_model()->GetNextImage();
+  EXPECT_TRUE(image.IsNull());
+
+  // Save a file to check if it gets read for display.
+  auto cached_image = ambient_image_path.Append("0.img");
+  base::CreateDirectory(ambient_image_path);
+  base::WriteFile(cached_image, "cached image");
+
+  // Reset variables in photo controller.
+  photo_controller()->StopScreenUpdate();
+  FetchTopics();
+  // Forward a little bit time. FetchTopics() will succeed. Downloading should
+  // fail. Will read from cache.
+  task_environment()->FastForwardBy(0.2 * kTopicFetchInterval);
+  image = photo_controller()->ambient_backend_model()->GetNextImage();
+  EXPECT_FALSE(image.IsNull());
+
+  // Clean up.
+  base::DeletePathRecursively(ambient_image_path);
+}
+
+// Test that image is read from disk when image decoding failed.
+TEST_F(AmbientPhotoControllerTest, ShouldReadCacheWhenImageDecodingFailed) {
+  base::FilePath home_dir;
+  base::PathService::Get(base::DIR_HOME, &home_dir);
+
+  base::FilePath ambient_image_path =
+      home_dir.Append(FILE_PATH_LITERAL(kAmbientModeDirectoryName));
+
+  // Clean up.
+  base::DeletePathRecursively(ambient_image_path);
+
+  SeteImageDecoderImage(gfx::ImageSkia());
+  FetchTopics();
+  // Forward a little bit time. FetchTopics() will succeed.
+  // Downloading succeed and save the data to disk.
+  // First decoding should fail. Will read from cache, and then succeed.
+  task_environment()->FastForwardBy(0.2 * kTopicFetchInterval);
+  auto image = photo_controller()->ambient_backend_model()->GetNextImage();
+  EXPECT_FALSE(image.IsNull());
+
+  // Clean up.
+  base::DeletePathRecursively(ambient_image_path);
+}
+
+// Test that image will refresh when have more topics.
+TEST_F(AmbientPhotoControllerTest, ShouldResumWhenHaveMoreTopics) {
+  base::FilePath home_dir;
+  base::PathService::Get(base::DIR_HOME, &home_dir);
+
+  base::FilePath ambient_image_path =
+      home_dir.Append(FILE_PATH_LITERAL(kAmbientModeDirectoryName));
+
+  // Clean up.
+  base::DeletePathRecursively(ambient_image_path);
+
+  FetchImage();
+  FastForwardToNextImage();
+  // Topics is empty. Will read from cache, which is empty.
+  auto image = photo_controller()->ambient_backend_model()->GetNextImage();
+  EXPECT_TRUE(image.IsNull());
+
+  FetchTopics();
+  // Forward a little bit time. FetchTopics() will succeed and refresh image.
+  task_environment()->FastForwardBy(0.2 * kTopicFetchInterval);
+  image = photo_controller()->ambient_backend_model()->GetNextImage();
+  EXPECT_FALSE(image.IsNull());
+
+  // Clean up.
+  base::DeletePathRecursively(ambient_image_path);
 }
 
 }  // namespace ash
diff --git a/ash/ambient/backdrop/ambient_backend_controller_impl.cc b/ash/ambient/backdrop/ambient_backend_controller_impl.cc
index f6cf190..b068fad4 100644
--- a/ash/ambient/backdrop/ambient_backend_controller_impl.cc
+++ b/ash/ambient/backdrop/ambient_backend_controller_impl.cc
@@ -218,10 +218,7 @@
   scoped_refptr<network::SharedURLLoaderFactory> loader_factory_;
 };
 
-AmbientBackendControllerImpl::AmbientBackendControllerImpl()
-    : backdrop_client_config_(ash::AmbientClient::Get()->ShouldUseProdServer()
-                                  ? BackdropClientConfig::ServerType::kProd
-                                  : BackdropClientConfig::ServerType::kDev) {}
+AmbientBackendControllerImpl::AmbientBackendControllerImpl() = default;
 
 AmbientBackendControllerImpl::~AmbientBackendControllerImpl() = default;
 
@@ -393,6 +390,17 @@
   const bool success =
       BackdropClientConfig::ParseUpdateSettingsResponse(*response);
   std::move(callback).Run(success);
+
+  // Clear disk cache when Settings changes.
+  // TODO(wutao): Use observer pattern. Need to future narrow down
+  // the clear up only on albums changes, not on temperature unit
+  // changes.
+  if (success) {
+    Shell::Get()
+        ->ambient_controller()
+        ->ambient_photo_controller()
+        ->ClearCache();
+  }
 }
 
 void AmbientBackendControllerImpl::FetchSettingPreviewInternal(
diff --git a/ash/ambient/test/ambient_ash_test_base.cc b/ash/ambient/test/ambient_ash_test_base.cc
index 985855e..e06e20a 100644
--- a/ash/ambient/test/ambient_ash_test_base.cc
+++ b/ash/ambient/test/ambient_ash_test_base.cc
@@ -41,13 +41,20 @@
   void Download(
       const std::string& url,
       network::SimpleURLLoader::BodyAsStringCallback callback) override {
-    auto data = std::make_unique<std::string>();
-    *data = "test";
+    std::string data = data_ ? *data_ : "test";
     // Pretend to respond asynchronously.
     base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
-        FROM_HERE, base::BindOnce(std::move(callback), std::move(data)),
+        FROM_HERE,
+        base::BindOnce(std::move(callback),
+                       std::make_unique<std::string>(data)),
         base::TimeDelta::FromMilliseconds(1));
   }
+
+  void SetData(std::unique_ptr<std::string> data) { data_ = std::move(data); }
+
+ private:
+  // If not null, will return this data.
+  std::unique_ptr<std::string> data_;
 };
 
 class TestAmbientImageDecoderImpl : public AmbientImageDecoder {
@@ -59,10 +66,14 @@
   void Decode(
       const std::vector<uint8_t>& encoded_bytes,
       base::OnceCallback<void(const gfx::ImageSkia&)> callback) override {
+    gfx::ImageSkia image =
+        image_ ? *image_ : gfx::test::CreateImageSkia(width_, height_);
+    // Only use once.
+    image_.reset();
+
     // Pretend to respond asynchronously.
     base::SequencedTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE, base::BindOnce(std::move(callback),
-                                  gfx::test::CreateImageSkia(width_, height_)));
+        FROM_HERE, base::BindOnce(std::move(callback), image));
   }
 
   void SetImageSize(int width, int height) {
@@ -70,10 +81,15 @@
     height_ = height;
   }
 
+  void SetImage(const gfx::ImageSkia& image) { image_ = image; }
+
  private:
   // Width and height of test images.
   int width_ = 10;
   int height_ = 20;
+
+  // If set, will replay this image.
+  base::Optional<gfx::ImageSkia> image_;
 };
 
 AmbientAshTestBase::AmbientAshTestBase()
@@ -186,9 +202,7 @@
 
 void AmbientAshTestBase::SetPhotoViewImageSize(int width, int height) {
   auto* image_decoder = static_cast<TestAmbientImageDecoderImpl*>(
-      ambient_controller()
-          ->get_ambient_photo_controller_for_testing()
-          ->get_image_decoder_for_testing());
+      photo_controller()->get_image_decoder_for_testing());
 
   image_decoder->SetImageSize(width, height);
 }
@@ -242,11 +256,33 @@
 }
 
 AmbientPhotoController* AmbientAshTestBase::photo_controller() {
-  return ambient_controller()->get_ambient_photo_controller_for_testing();
+  return ambient_controller()->ambient_photo_controller();
 }
 
 AmbientContainerView* AmbientAshTestBase::container_view() {
   return ambient_controller()->get_container_view_for_testing();
 }
 
+void AmbientAshTestBase::FetchTopics() {
+  photo_controller()->FetchTopicsForTesting();
+}
+
+void AmbientAshTestBase::FetchImage() {
+  photo_controller()->FetchImageForTesting();
+}
+
+void AmbientAshTestBase::SetUrlLoaderData(std::unique_ptr<std::string> data) {
+  auto* url_loader_ = static_cast<TestAmbientURLLoaderImpl*>(
+      photo_controller()->get_url_loader_for_testing());
+
+  url_loader_->SetData(std::move(data));
+}
+
+void AmbientAshTestBase::SeteImageDecoderImage(const gfx::ImageSkia& image) {
+  auto* image_decoder = static_cast<TestAmbientImageDecoderImpl*>(
+      photo_controller()->get_image_decoder_for_testing());
+
+  image_decoder->SetImage(image);
+}
+
 }  // namespace ash
diff --git a/ash/ambient/test/ambient_ash_test_base.h b/ash/ambient/test/ambient_ash_test_base.h
index d3ac14b..1a76d84 100644
--- a/ash/ambient/test/ambient_ash_test_base.h
+++ b/ash/ambient/test/ambient_ash_test_base.h
@@ -103,6 +103,14 @@
   // Returns the top-level view which contains all the ambient components.
   AmbientContainerView* container_view();
 
+  void FetchTopics();
+
+  void FetchImage();
+
+  void SetUrlLoaderData(std::unique_ptr<std::string> data);
+
+  void SeteImageDecoderImage(const gfx::ImageSkia& image);
+
  private:
   base::test::ScopedFeatureList scoped_feature_list_;
   std::unique_ptr<TestImageDownloader> image_downloader_;
diff --git a/ash/hud_display/cpu_graph_page_view.cc b/ash/hud_display/cpu_graph_page_view.cc
new file mode 100644
index 0000000..dd29819e
--- /dev/null
+++ b/ash/hud_display/cpu_graph_page_view.cc
@@ -0,0 +1,75 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/hud_display/cpu_graph_page_view.h"
+
+#include <numeric>
+
+#include "ash/hud_display/hud_constants.h"
+#include "ui/gfx/canvas.h"
+
+namespace ash {
+namespace hud_display {
+
+////////////////////////////////////////////////////////////////////////////////
+// CpuGraphPageView, public:
+
+BEGIN_METADATA(CpuGraphPageView)
+METADATA_PARENT_CLASS(GraphPageViewBase)
+END_METADATA()
+
+CpuGraphPageView::CpuGraphPageView()
+    : cpu_other_(Graph::Baseline::BASELINE_BOTTOM,
+                 Graph::Fill::SOLID,
+                 SkColorSetA(SK_ColorMAGENTA, kHUDAlpha)),
+      cpu_system_(Graph::Baseline::BASELINE_BOTTOM,
+                  Graph::Fill::SOLID,
+                  SkColorSetA(SK_ColorRED, kHUDAlpha)),
+      cpu_user_(Graph::Baseline::BASELINE_BOTTOM,
+                Graph::Fill::SOLID,
+                SkColorSetA(SK_ColorBLUE, kHUDAlpha)),
+      cpu_idle_(Graph::Baseline::BASELINE_BOTTOM,
+                Graph::Fill::SOLID,
+                SkColorSetA(SK_ColorDKGRAY, kHUDAlpha)) {}
+
+CpuGraphPageView::~CpuGraphPageView() = default;
+
+////////////////////////////////////////////////////////////////////////////////
+
+void CpuGraphPageView::OnPaint(gfx::Canvas* canvas) {
+  // TODO: Should probably update last graph point more often than shift graph.
+
+  // Layout graphs.
+  const gfx::Rect rect = GetContentsBounds();
+  cpu_other_.Layout(rect, nullptr /* base*/);
+  cpu_system_.Layout(rect, &cpu_other_);
+  cpu_user_.Layout(rect, &cpu_system_);
+  cpu_idle_.Layout(rect, &cpu_user_);
+
+  // Paint damaged area now that all parameters have been determined.
+  cpu_other_.Draw(canvas);
+  cpu_system_.Draw(canvas);
+  cpu_user_.Draw(canvas);
+  cpu_idle_.Draw(canvas);
+}
+
+void CpuGraphPageView::UpdateData(const DataSource::Snapshot& snapshot) {
+  // TODO: Should probably update last graph point more often than shift graph.
+  const float total = snapshot.cpu_idle_part + snapshot.cpu_user_part +
+                      snapshot.cpu_system_part + snapshot.cpu_other_part;
+  // Nothing to do if data is not available yet (sum < 1%).
+  if (total < 0.01)
+    return;
+
+  // Assume total already equals 1, no need to re-weight.
+
+  // Update graph data.
+  cpu_other_.AddValue(snapshot.cpu_other_part);
+  cpu_system_.AddValue(snapshot.cpu_system_part);
+  cpu_user_.AddValue(snapshot.cpu_user_part);
+  cpu_idle_.AddValue(snapshot.cpu_idle_part);
+}
+
+}  // namespace hud_display
+}  // namespace ash
diff --git a/ash/hud_display/cpu_graph_page_view.h b/ash/hud_display/cpu_graph_page_view.h
new file mode 100644
index 0000000..90e5f9d92
--- /dev/null
+++ b/ash/hud_display/cpu_graph_page_view.h
@@ -0,0 +1,41 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_HUD_DISPLAY_CPU_GRAPH_PAGE_VIEW_H_
+#define ASH_HUD_DISPLAY_CPU_GRAPH_PAGE_VIEW_H_
+
+#include "ash/hud_display/graph.h"
+#include "ash/hud_display/graph_page_view_base.h"
+
+namespace ash {
+namespace hud_display {
+
+// Draws CPU graphs;
+class CpuGraphPageView : public GraphPageViewBase {
+ public:
+  METADATA_HEADER(CpuGraphPageView);
+
+  CpuGraphPageView();
+  CpuGraphPageView(const CpuGraphPageView&) = delete;
+  CpuGraphPageView& operator=(const CpuGraphPageView&) = delete;
+  ~CpuGraphPageView() override;
+
+  // view::
+  void OnPaint(gfx::Canvas* canvas) override;
+
+  // Update page data from the new snapshot.
+  void UpdateData(const DataSource::Snapshot& snapshot) override;
+
+ private:
+  // Stacked, percent of CPU ticks per interval:
+  Graph cpu_other_;
+  Graph cpu_system_;
+  Graph cpu_user_;
+  Graph cpu_idle_;
+};
+
+}  // namespace hud_display
+}  // namespace ash
+
+#endif  // ASH_HUD_DISPLAY_CPU_GRAPH_PAGE_VIEW_H_
diff --git a/ash/hud_display/cpu_status.cc b/ash/hud_display/cpu_status.cc
new file mode 100644
index 0000000..cec1858
--- /dev/null
+++ b/ash/hud_display/cpu_status.cc
@@ -0,0 +1,63 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/hud_display/cpu_status.h"
+
+#include <cinttypes>
+#include <cstdio>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/notreached.h"
+#include "base/strings/string_util.h"
+#include "base/threading/thread_restrictions.h"
+
+namespace ash {
+namespace hud_display {
+namespace {
+
+constexpr char kProcStatFile[] = "/proc/stat";
+
+std::string ReadProcFile(const base::FilePath& path) {
+  // Synchronously reading files in /proc and /sys are safe.
+  base::ThreadRestrictions::ScopedAllowIO allow_io;
+
+  std::string result;
+  base::ReadFileToString(path, &result);
+  return result;
+}
+
+}  // namespace
+
+CpuStats GetProcStatCPU() {
+  const std::string stat = ReadProcFile(base::FilePath(kProcStatFile));
+  // First string should be total Cpu statistics.
+  if (!base::StartsWith(stat, "cpu ", base::CompareCase::SENSITIVE)) {
+    NOTREACHED();
+    return CpuStats();
+  }
+  const size_t newline_pos = stat.find('\n');
+  if (newline_pos == std::string::npos) {
+    NOTREACHED();
+    return CpuStats();
+  }
+
+  // Parse first line only.
+  // Format is described in [man 5 proc] and in kernel source proc/stat.c .
+  // https://github.com/torvalds/linux/blob/master/fs/proc/stat.c#L150-L160
+
+  CpuStats stats;
+  int assigned =
+      sscanf(stat.c_str(),
+             "cpu %" SCNu64 " %" SCNu64 " %" SCNu64 " %" SCNu64 " %" SCNu64
+             " %" SCNu64 " %" SCNu64 " %" SCNu64 " %" SCNu64 " %" SCNu64 "",
+             &stats.user, &stats.nice, &stats.system, &stats.idle,
+             &stats.iowait, &stats.irq, &stats.softirq, &stats.steal,
+             &stats.guest, &stats.guest_nice);
+  DCHECK_EQ(assigned, 10);
+  return stats;
+}
+
+}  // namespace hud_display
+}  // namespace ash
diff --git a/ash/hud_display/cpu_status.h b/ash/hud_display/cpu_status.h
new file mode 100644
index 0000000..efa57a9
--- /dev/null
+++ b/ash/hud_display/cpu_status.h
@@ -0,0 +1,43 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_HUD_DISPLAY_CPU_STATUS_H_
+#define ASH_HUD_DISPLAY_CPU_STATUS_H_
+
+#include <cstdint>
+
+namespace ash {
+namespace hud_display {
+
+// All CPU entries from /proc/stat.
+struct CpuStats {
+  // These are the raw values read from /proc/stat, so as noted below,
+  // their interpretation depends on the architechture. But we are using them
+  // to plot relative values (0% - 100%) and thus the absolute values are not
+  // important.
+  // [man 5 proc]: Time, measured in units of USER_HZ (1/100ths of a second on
+  // most architectures, use sysconf(_SC_CLK_TCK) to obtain the right value).
+  uint64_t user;     // Time spent in user mode.
+  uint64_t nice;     // Time spent in user mode with low priority (nice)
+  uint64_t system;   // Time spent in system mode.
+  uint64_t idle;     // Time spent in the idle task. (follows /proc/uptme)
+  uint64_t iowait;   // Time waiting for I/O to complete. Not reliable.
+  uint64_t irq;      // Time servicing interrupts.
+  uint64_t softirq;  // Time servicing softirqs.
+  uint64_t steal;    // Stolen time, which is the time spent in other operating
+                     // systems when running in a virtualized environment.
+  uint64_t guest;    // Time spent running a virtual CPU for guest operating
+                     // systems under the control of the Linux kernel.
+  uint64_t guest_nice;  // Time spent running a niced guest (virtual CPU for
+                        // guest operating systems under the control of the
+                        // Linux kernel).
+};
+
+// Parses current /proc/stat and restuns current values.
+CpuStats GetProcStatCPU();
+
+}  // namespace hud_display
+}  // namespace ash
+
+#endif  // ASH_HUD_DISPLAY_CPU_STATUS_H_
diff --git a/ash/hud_display/data_source.cc b/ash/hud_display/data_source.cc
index 0cef5bd..dfb9a62 100644
--- a/ash/hud_display/data_source.cc
+++ b/ash/hud_display/data_source.cc
@@ -25,6 +25,44 @@
   }
   return total_ram;
 }
+
+// Calculates counter difference with respect to overflow.
+CpuStats Delta(const CpuStats& newer, const CpuStats& older) {
+  static_assert(sizeof(CpuStats) == sizeof(uint64_t) * 10,
+                "This method should be updated when CpuStats is changed.");
+
+  // Calculates (left - right) assuming |left| and |right| are increasing
+  // unsigned counters with respect to possible counter overflow.
+  auto minus = [](const size_t& left, const size_t right) {
+    return left > right ? (left - right)
+                        : (left + (std::numeric_limits<size_t>::max() - right));
+  };
+
+  CpuStats result;
+  result.user = minus(newer.user, older.user);
+  result.nice = minus(newer.nice, older.nice);
+  result.system = minus(newer.system, older.system);
+  result.idle = minus(newer.idle, older.idle);
+  result.iowait = minus(newer.iowait, older.iowait);
+  result.irq = minus(newer.irq, older.irq);
+  result.softirq = minus(newer.softirq, older.softirq);
+  result.steal = minus(newer.steal, older.steal);
+  result.guest = minus(newer.guest, older.guest);
+  result.guest_nice = minus(newer.guest_nice, older.guest_nice);
+  return result;
+}
+
+// Returns sum of all entries. This is useful for deltas to calculate
+// percentage.
+size_t Sum(const CpuStats& stats) {
+  static_assert(sizeof(CpuStats) == sizeof(uint64_t) * 10,
+                "This method should be updated when CpuStats is changed.");
+
+  return stats.user + stats.nice + stats.system + stats.idle + stats.iowait +
+         stats.irq + stats.softirq + stats.steal + stats.guest +
+         stats.guest_nice;
+}
+
 }  // anonymous namespace
 
 // --------------------------------
@@ -35,7 +73,11 @@
 DataSource::Snapshot::Snapshot() = default;
 DataSource::Snapshot::Snapshot(const Snapshot&) = default;
 
-DataSource::DataSource() = default;
+DataSource::DataSource() {
+  cpu_stats_base_ = {0};
+  cpu_stats_latest_ = {0};
+}
+
 DataSource::~DataSource() = default;
 
 DataSource::Snapshot DataSource::GetSnapshotAndReset() {
@@ -43,6 +85,27 @@
   Refresh();
 
   Snapshot snapshot = GetSnapshot();
+
+  if (cpu_stats_base_.user > 0) {
+    // Calculate CPU graph values for the last interval.
+    CpuStats cpu_stats_delta = Delta(cpu_stats_latest_, cpu_stats_base_);
+    const double cpu_ticks_total = Sum(cpu_stats_delta);
+
+    // Makes sure that the given value is between 0 and 1 and converts to
+    // float.
+    auto to_0_1 = [](const double& value) -> float {
+      return std::min(1.0f, std::max(0.0f, static_cast<float>(value)));
+    };
+
+    snapshot.cpu_idle_part = cpu_stats_delta.idle / cpu_ticks_total;
+    snapshot.cpu_user_part =
+        (cpu_stats_delta.user + cpu_stats_delta.nice) / cpu_ticks_total;
+    snapshot.cpu_system_part = cpu_stats_delta.system / cpu_ticks_total;
+    // The remaining part is "other".
+    snapshot.cpu_other_part =
+        to_0_1(1 - snapshot.cpu_idle_part - snapshot.cpu_user_part -
+               snapshot.cpu_system_part);
+  }
   ResetCounters();
   return snapshot;
 }
@@ -53,6 +116,9 @@
 
 void DataSource::ResetCounters() {
   snapshot_ = Snapshot();
+
+  cpu_stats_base_ = cpu_stats_latest_;
+  cpu_stats_latest_ = {0};
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -83,6 +149,8 @@
   snapshot_.gpu_rss = std::max(snapshot_.gpu_rss, memory_status.gpu_rss());
   snapshot_.gpu_kernel =
       std::max(snapshot_.gpu_kernel, memory_status.gpu_kernel());
+
+  cpu_stats_latest_ = GetProcStatCPU();
 }
 
 }  // namespace hud_display
diff --git a/ash/hud_display/data_source.h b/ash/hud_display/data_source.h
index 07337e3..0844250 100644
--- a/ash/hud_display/data_source.h
+++ b/ash/hud_display/data_source.h
@@ -6,8 +6,8 @@
 #define ASH_HUD_DISPLAY_DATA_SOURCE_H_
 
 #include <cstdint>
-
 #include <limits>
+#include "ash/hud_display/cpu_status.h"
 
 namespace ash {
 namespace hud_display {
@@ -44,13 +44,19 @@
     int64_t renderers_rss = 0;
     // Amount of RSS Shared memory used by Chrome type=renderer processes.
     int64_t renderers_rss_shared = 0;
+
+    // CPU stats are calculated only in GetSnapshotAndReset().
+    // CPU usage values should sum to 1.
+    float cpu_idle_part = 0;    // Amount spent in idle state.
+    float cpu_user_part = 0;    // Amount spent in user + nice mode.
+    float cpu_system_part = 0;  // Amount spent in system mode.
+    float cpu_other_part = 0;   // Other states: irq, etc.
   };
 
   DataSource();
-  ~DataSource();
-
   DataSource(const DataSource&) = delete;
   DataSource& operator=(const DataSource&) = delete;
+  ~DataSource();
 
   Snapshot GetSnapshotAndReset();
 
@@ -62,6 +68,12 @@
 
   // Current system snapshot.
   Snapshot snapshot_;
+
+  // Last CPU stats snapshot.
+  CpuStats cpu_stats_latest_;
+
+  // Last stats before Reset() to calculate delta.
+  CpuStats cpu_stats_base_;
 };
 
 }  // namespace hud_display
diff --git a/ash/hud_display/graph.h b/ash/hud_display/graph.h
index 32f592f7..3fb321f 100644
--- a/ash/hud_display/graph.h
+++ b/ash/hud_display/graph.h
@@ -7,6 +7,7 @@
 
 #include <vector>
 
+#include "ash/hud_display/hud_constants.h"
 #include "base/containers/ring_buffer.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/gfx/geometry/point.h"
@@ -21,11 +22,10 @@
 
 class Graph {
  public:
-  // Graph screen size (that is passed in Layout()) should match (ring buffer
+  // Graph screen size (that is used in Layout()) should match (ring buffer
   // size - 1) to prevent scaling, because RingBuffer always keeps one element
   // unused.
-  static constexpr size_t kDefaultWidth = 190;
-  using Data = base::RingBuffer<float, kDefaultWidth + 1>;
+  using Data = base::RingBuffer<float, kDefaultGraphWidth + 1>;
 
   enum class Baseline {
     BASELINE_BOTTOM,  // Positive values will be drawn from the bottom border
diff --git a/ash/hud_display/graph_page_view_base.cc b/ash/hud_display/graph_page_view_base.cc
new file mode 100644
index 0000000..042770b0
--- /dev/null
+++ b/ash/hud_display/graph_page_view_base.cc
@@ -0,0 +1,23 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/hud_display/graph_page_view_base.h"
+
+namespace ash {
+namespace hud_display {
+
+BEGIN_METADATA(GraphPageViewBase)
+METADATA_PARENT_CLASS(View)
+END_METADATA()
+
+GraphPageViewBase::GraphPageViewBase() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(ui_sequence_checker_);
+}
+
+GraphPageViewBase::~GraphPageViewBase() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(ui_sequence_checker_);
+}
+
+}  // namespace hud_display
+}  // namespace ash
diff --git a/ash/hud_display/graph_page_view_base.h b/ash/hud_display/graph_page_view_base.h
new file mode 100644
index 0000000..cef3281
--- /dev/null
+++ b/ash/hud_display/graph_page_view_base.h
@@ -0,0 +1,35 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_HUD_DISPLAY_GRAPH_PAGE_VIEW_BASE_H_
+#define ASH_HUD_DISPLAY_GRAPH_PAGE_VIEW_BASE_H_
+
+#include "ash/hud_display/data_source.h"
+#include "base/sequence_checker.h"
+#include "ui/views/view.h"
+
+namespace ash {
+namespace hud_display {
+
+// Interface for a single graph page.
+class GraphPageViewBase : public views::View {
+ public:
+  METADATA_HEADER(GraphPageViewBase);
+
+  GraphPageViewBase();
+  GraphPageViewBase(const GraphPageViewBase&) = delete;
+  GraphPageViewBase& operator=(const GraphPageViewBase&) = delete;
+  ~GraphPageViewBase() override;
+
+  // Update page data from the new snapshot.
+  virtual void UpdateData(const DataSource::Snapshot& snapshot) = 0;
+
+ private:
+  SEQUENCE_CHECKER(ui_sequence_checker_);
+};
+
+}  // namespace hud_display
+}  // namespace ash
+
+#endif  // ASH_HUD_DISPLAY_GRAPH_PAGE_VIEW_BASE_H_
diff --git a/ash/hud_display/graphs_container_view.cc b/ash/hud_display/graphs_container_view.cc
index a4063ef..c3b6397 100644
--- a/ash/hud_display/graphs_container_view.cc
+++ b/ash/hud_display/graphs_container_view.cc
@@ -6,10 +6,12 @@
 
 #include <numeric>
 
+#include "ash/hud_display/cpu_graph_page_view.h"
 #include "ash/hud_display/hud_constants.h"
+#include "ash/hud_display/memory_graph_page_view.h"
 #include "base/bind.h"
 #include "base/task/post_task.h"
-#include "ui/gfx/canvas.h"
+#include "ui/views/layout/fill_layout.h"
 
 namespace ash {
 namespace hud_display {
@@ -28,33 +30,19 @@
 METADATA_PARENT_CLASS(View)
 END_METADATA()
 
-GraphsContainerView::GraphsContainerView()
-    : graph_chrome_rss_private_(Graph::Baseline::BASELINE_BOTTOM,
-                                Graph::Fill::SOLID,
-                                SkColorSetA(SK_ColorRED, kHUDAlpha)),
-      graph_mem_free_(Graph::Baseline::BASELINE_BOTTOM,
-                      Graph::Fill::NONE,
-                      SkColorSetA(SK_ColorDKGRAY, kHUDAlpha)),
-      graph_mem_used_unknown_(Graph::Baseline::BASELINE_BOTTOM,
-                              Graph::Fill::SOLID,
-                              SkColorSetA(SK_ColorLTGRAY, kHUDAlpha)),
-      graph_renderers_rss_private_(Graph::Baseline::BASELINE_BOTTOM,
-                                   Graph::Fill::SOLID,
-                                   SkColorSetA(SK_ColorCYAN, kHUDAlpha)),
-      graph_arc_rss_private_(Graph::Baseline::BASELINE_BOTTOM,
-                             Graph::Fill::SOLID,
-                             SkColorSetA(SK_ColorMAGENTA, kHUDAlpha)),
-      graph_gpu_rss_private_(Graph::Baseline::BASELINE_BOTTOM,
-                             Graph::Fill::SOLID,
-                             SkColorSetA(SK_ColorRED, kHUDAlpha)),
-      graph_gpu_kernel_(Graph::Baseline::BASELINE_BOTTOM,
-                        Graph::Fill::SOLID,
-                        SkColorSetA(SK_ColorYELLOW, kHUDAlpha)),
-      graph_chrome_rss_shared_(Graph::Baseline::BASELINE_BOTTOM,
-                               Graph::Fill::NONE,
-                               SkColorSetA(SK_ColorBLUE, kHUDAlpha)) {
+GraphsContainerView::GraphsContainerView() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(ui_sequence_checker_);
 
+  // Make all graph pages take the whole view and make sure that only one
+  // is shown at a time.
+  SetLayoutManager(std::make_unique<views::FillLayout>());
+
+  // Adds another graphs page.
+  AddChildView(std::make_unique<MemoryGraphPageView>())
+      ->SetID(static_cast<int>(DisplayMode::MEMORY_DISPLAY));
+  AddChildView(std::make_unique<CpuGraphPageView>())
+      ->SetID(static_cast<int>(DisplayMode::CPU_DISPLAY));
+
   refresh_timer_.Start(FROM_HERE, kGraphsDataRefreshInterval, this,
                        &GraphsContainerView::UpdateData);
 }
@@ -63,86 +51,24 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(ui_sequence_checker_);
 }
 
-////////////////////////////////////////////////////////////////////////////////
+void GraphsContainerView::UpdateData() {
+  const DataSource::Snapshot snapshot = data_source_.GetSnapshotAndReset();
+  for (auto* child : children())
+    static_cast<GraphPageViewBase*>(child)->UpdateData(snapshot);
 
-void GraphsContainerView::OnPaint(gfx::Canvas* canvas) {
-  // TODO: Should probably update last graph point more often than shift graph.
-
-  // Layout graphs.
-  const gfx::Rect rect = GetContentsBounds();
-  graph_chrome_rss_private_.Layout(rect, nullptr /* base*/);
-  graph_mem_free_.Layout(rect, &graph_chrome_rss_private_);
-  graph_mem_used_unknown_.Layout(rect, &graph_mem_free_);
-  graph_renderers_rss_private_.Layout(rect, &graph_mem_used_unknown_);
-  graph_arc_rss_private_.Layout(rect, &graph_renderers_rss_private_);
-  graph_gpu_rss_private_.Layout(rect, &graph_arc_rss_private_);
-  graph_gpu_kernel_.Layout(rect, &graph_gpu_rss_private_);
-  // Not stacked.
-  graph_chrome_rss_shared_.Layout(rect, nullptr /* base*/);
-
-  // Paint damaged area now that all parameters have been determined.
-  graph_chrome_rss_private_.Draw(canvas);
-  graph_mem_free_.Draw(canvas);
-  graph_mem_used_unknown_.Draw(canvas);
-  graph_renderers_rss_private_.Draw(canvas);
-  graph_arc_rss_private_.Draw(canvas);
-  graph_gpu_rss_private_.Draw(canvas);
-  graph_gpu_kernel_.Draw(canvas);
-
-  graph_chrome_rss_shared_.Draw(canvas);
+  SchedulePaint();
 }
 
-void GraphsContainerView::UpdateData() {
-  // TODO: Should probably update last graph point more often than shift graph.
-  const DataSource::Snapshot snapshot = data_source_.GetSnapshotAndReset();
-
-  const double total = snapshot.total_ram;
-  // Nothing to do if data is not available yet.
-  if (total < 1)
+void GraphsContainerView::SetMode(DisplayMode mode) {
+  auto* selected = GetViewByID(static_cast<int>(mode));
+  if (!selected) {
+    DCHECK(selected);
     return;
+  }
+  for (auto* child : children())
+    child->SetVisible(false);
 
-  const float chrome_rss_private =
-      (snapshot.browser_rss - snapshot.browser_rss_shared) / total;
-  const float mem_free = snapshot.free_ram / total;
-  // mem_used_unknown is calculated below.
-  const float renderers_rss_private =
-      (snapshot.renderers_rss - snapshot.renderers_rss_shared) / total;
-  const float arc_rss_private =
-      (snapshot.arc_rss - snapshot.arc_rss_shared) / total;
-  const float gpu_rss_private =
-      (snapshot.gpu_rss - snapshot.gpu_rss_shared) / total;
-  const float gpu_kernel = snapshot.gpu_kernel / total;
-
-  // not stacked.
-  const float chrome_rss_shared = snapshot.browser_rss_shared / total;
-
-  std::vector<float> used_buckets;
-  used_buckets.push_back(chrome_rss_private);
-  used_buckets.push_back(mem_free);
-  used_buckets.push_back(renderers_rss_private);
-  used_buckets.push_back(arc_rss_private);
-  used_buckets.push_back(gpu_rss_private);
-  used_buckets.push_back(gpu_kernel);
-
-  float mem_used_unknown =
-      1 - std::accumulate(used_buckets.begin(), used_buckets.end(), 0.0f);
-
-  if (mem_used_unknown < 0)
-    LOG(WARNING) << "mem_used_unknown=" << mem_used_unknown << " < 0 !";
-
-  // Update graph data.
-  graph_chrome_rss_private_.AddValue(chrome_rss_private);
-  graph_mem_free_.AddValue(mem_free);
-  graph_mem_used_unknown_.AddValue(std::max(mem_used_unknown, 0.0f));
-  graph_renderers_rss_private_.AddValue(renderers_rss_private);
-  graph_arc_rss_private_.AddValue(arc_rss_private);
-  graph_gpu_rss_private_.AddValue(gpu_rss_private);
-  graph_gpu_kernel_.AddValue(gpu_kernel);
-  // Not stacked.
-  graph_chrome_rss_shared_.AddValue(chrome_rss_shared);
-
-  if (GetVisible())
-    SchedulePaint();
+  selected->SetVisible(true);
 }
 
 }  // namespace hud_display
diff --git a/ash/hud_display/graphs_container_view.h b/ash/hud_display/graphs_container_view.h
index 930a6e27..faad9a7 100644
--- a/ash/hud_display/graphs_container_view.h
+++ b/ash/hud_display/graphs_container_view.h
@@ -6,7 +6,7 @@
 #define ASH_HUD_DISPLAY_GRAPHS_CONTAINER_VIEW_H_
 
 #include "ash/hud_display/data_source.h"
-#include "ash/hud_display/graph.h"
+#include "ash/hud_display/graph_page_view_base.h"
 #include "base/sequence_checker.h"
 #include "base/timer/timer.h"
 #include "ui/views/view.h"
@@ -14,48 +14,29 @@
 namespace ash {
 namespace hud_display {
 
+enum class DisplayMode;
+
 // GraphsContainerView class draws a bunch of graphs.
 class GraphsContainerView : public views::View {
  public:
   METADATA_HEADER(GraphsContainerView);
 
   GraphsContainerView();
-  ~GraphsContainerView() override;
-
   GraphsContainerView(const GraphsContainerView&) = delete;
   GraphsContainerView& operator=(const GraphsContainerView&) = delete;
-
-  // view::
-  void OnPaint(gfx::Canvas* canvas) override;
+  ~GraphsContainerView() override;
 
   // Synchrnously reads system counters and updates data.
   void UpdateData();
 
+  // Updates graphs display to match given mode.
+  void SetMode(DisplayMode mode);
+
  private:
   // HUD is updatd with new data every tick.
   base::RepeatingTimer refresh_timer_;
 
-  // --- Stacked:
-  // Share of the total RAM occupied by Chrome browser private RSS.
-  Graph graph_chrome_rss_private_;
-  // Share of the total RAM reported as Free memory be kernel.
-  Graph graph_mem_free_;
-  // Total RAM - other graphs in this stack.
-  Graph graph_mem_used_unknown_;
-  // Share of the total RAM occupied by Chrome type=renderer processes private
-  // RSS.
-  Graph graph_renderers_rss_private_;
-  // Share of the total RAM occupied by ARC++ processes private RSS.
-  Graph graph_arc_rss_private_;
-  // Share of the total RAM occupied by Chrome type=gpu process private RSS.
-  Graph graph_gpu_rss_private_;
-  // Share of the total RAM used by kernel GPU driver.
-  Graph graph_gpu_kernel_;
-
-  // Not stacked:
-  // Share of the total RAM occupied by Chrome browser process shared RSS.
-  Graph graph_chrome_rss_shared_;
-
+  // Source of graphs data.
   DataSource data_source_;
 
   SEQUENCE_CHECKER(ui_sequence_checker_);
diff --git a/ash/hud_display/hud_constants.h b/ash/hud_display/hud_constants.h
index 993d5b0..7351bcc 100644
--- a/ash/hud_display/hud_constants.h
+++ b/ash/hud_display/hud_constants.h
@@ -33,10 +33,15 @@
 // where needed.
 constexpr int kHUDInset = 5;
 
+// Defines both the pixel width of the graphs and the amount of data stored
+// in each graph ring buffer.
+static constexpr size_t kDefaultGraphWidth = 190;
+
 // HUD display modes.
-enum class DisplayModes {
+enum class DisplayMode {
+  CPU_DISPLAY =
+      1,  // First value should be different from default Views::ID = 0.
   MEMORY_DISPLAY,
-  DEFAULT = MEMORY_DISPLAY
 };
 
 }  // namespace hud_display
diff --git a/ash/hud_display/hud_display.cc b/ash/hud_display/hud_display.cc
index a738cdd..5d9f48e 100644
--- a/ash/hud_display/hud_display.cc
+++ b/ash/hud_display/hud_display.cc
@@ -96,7 +96,7 @@
                                       kShellWindowId_OverlayContainer);
   params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
   params.bounds =
-      gfx::Rect(Graph::kDefaultWidth + 2 * kHUDInset, kHUDViewDefaultHeight);
+      gfx::Rect(kDefaultGraphWidth + 2 * kHUDInset, kHUDViewDefaultHeight);
   auto* widget = CreateViewTreeHostWidget(std::move(params));
   widget->GetLayer()->SetName("HUDDisplayView");
   widget->Show();
@@ -132,8 +132,10 @@
   // Setup header.
 
   // TODO: Add tab buttons via:
-  // header_view_->tab_strip()->AddTabButton(this, DisplayModes::MEMORY_DISPLAY,
-  //                                        base::ASCIIToUTF16("RAM"));
+  header_view_->tab_strip()->AddTabButton(this, DisplayMode::CPU_DISPLAY,
+                                          base::ASCIIToUTF16("CPU"));
+  header_view_->tab_strip()->AddTabButton(this, DisplayMode::MEMORY_DISPLAY,
+                                          base::ASCIIToUTF16("RAM"));
 
   // Setup data.
   data->SetBackground(views::CreateSolidBackground(kHUDBackground));
@@ -148,6 +150,9 @@
       data->AddChildView(std::make_unique<GraphsContainerView>());
   settings_view_ = data->AddChildView(std::make_unique<HUDSettingsView>());
   settings_view_->SetVisible(false);
+
+  // CPU display is active by default.
+  SetDisplayMode(DisplayMode::CPU_DISPLAY);
 }
 
 HUDDisplayView::~HUDDisplayView() {
@@ -162,9 +167,9 @@
   return view->GetProperty(kHUDClickHandler);
 }
 
-void HUDDisplayView::TabButtonPressed(const HUDTabButton* tab_button) {
-  header_view_->tab_strip()->ActivateTab(tab_button);
-  // TODO: switch on tab_button->display_mode().
+void HUDDisplayView::SetDisplayMode(DisplayMode display_mode) {
+  graphs_container_->SetMode(display_mode);
+  header_view_->tab_strip()->ActivateTab(display_mode);
 }
 
 views::ClientView* HUDDisplayView::CreateClientView(views::Widget* widget) {
diff --git a/ash/hud_display/hud_display.h b/ash/hud_display/hud_display.h
index 99d9cfe..e97a0548 100644
--- a/ash/hud_display/hud_display.h
+++ b/ash/hud_display/hud_display.h
@@ -5,7 +5,6 @@
 #ifndef ASH_HUD_DISPLAY_HUD_DISPLAY_H_
 #define ASH_HUD_DISPLAY_HUD_DISPLAY_H_
 
-#include "ash/hud_display/hud_constants.h"
 #include "base/sequence_checker.h"
 #include "ui/views/controls/button/button.h"
 #include "ui/views/widget/widget_delegate.h"
@@ -13,10 +12,10 @@
 namespace ash {
 namespace hud_display {
 
+enum class DisplayMode;
 class GraphsContainerView;
 class HUDHeaderView;
 class HUDSettingsView;
-class HUDTabButton;
 
 // HUDDisplayView class can be used to display a system monitoring overview.
 class HUDDisplayView : public views::WidgetDelegateView,
@@ -48,7 +47,7 @@
   int NonClientHitTest(const gfx::Point& point);
 
   // Changes UI display mode.
-  void TabButtonPressed(const HUDTabButton* tab_button);
+  void SetDisplayMode(const DisplayMode display_mode);
 
  private:
   HUDHeaderView* header_view_ = nullptr;             // not owned
diff --git a/ash/hud_display/memory_graph_page_view.cc b/ash/hud_display/memory_graph_page_view.cc
new file mode 100644
index 0000000..2d40c2d
--- /dev/null
+++ b/ash/hud_display/memory_graph_page_view.cc
@@ -0,0 +1,128 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/hud_display/memory_graph_page_view.h"
+
+#include <numeric>
+
+#include "ash/hud_display/hud_constants.h"
+#include "ui/gfx/canvas.h"
+
+namespace ash {
+namespace hud_display {
+
+////////////////////////////////////////////////////////////////////////////////
+// MemoryGraphPageView, public:
+
+BEGIN_METADATA(MemoryGraphPageView)
+METADATA_PARENT_CLASS(GraphPageViewBase)
+END_METADATA()
+
+MemoryGraphPageView::MemoryGraphPageView()
+    : graph_chrome_rss_private_(Graph::Baseline::BASELINE_BOTTOM,
+                                Graph::Fill::SOLID,
+                                SkColorSetA(SK_ColorRED, kHUDAlpha)),
+      graph_mem_free_(Graph::Baseline::BASELINE_BOTTOM,
+                      Graph::Fill::NONE,
+                      SkColorSetA(SK_ColorDKGRAY, kHUDAlpha)),
+      graph_mem_used_unknown_(Graph::Baseline::BASELINE_BOTTOM,
+                              Graph::Fill::SOLID,
+                              SkColorSetA(SK_ColorLTGRAY, kHUDAlpha)),
+      graph_renderers_rss_private_(Graph::Baseline::BASELINE_BOTTOM,
+                                   Graph::Fill::SOLID,
+                                   SkColorSetA(SK_ColorCYAN, kHUDAlpha)),
+      graph_arc_rss_private_(Graph::Baseline::BASELINE_BOTTOM,
+                             Graph::Fill::SOLID,
+                             SkColorSetA(SK_ColorMAGENTA, kHUDAlpha)),
+      graph_gpu_rss_private_(Graph::Baseline::BASELINE_BOTTOM,
+                             Graph::Fill::SOLID,
+                             SkColorSetA(SK_ColorRED, kHUDAlpha)),
+      graph_gpu_kernel_(Graph::Baseline::BASELINE_BOTTOM,
+                        Graph::Fill::SOLID,
+                        SkColorSetA(SK_ColorYELLOW, kHUDAlpha)),
+      graph_chrome_rss_shared_(Graph::Baseline::BASELINE_BOTTOM,
+                               Graph::Fill::NONE,
+                               SkColorSetA(SK_ColorBLUE, kHUDAlpha)) {}
+
+MemoryGraphPageView::~MemoryGraphPageView() = default;
+
+////////////////////////////////////////////////////////////////////////////////
+
+void MemoryGraphPageView::OnPaint(gfx::Canvas* canvas) {
+  // TODO: Should probably update last graph point more often than shift graph.
+
+  // Layout graphs.
+  const gfx::Rect rect = GetContentsBounds();
+  graph_chrome_rss_private_.Layout(rect, /*base=*/nullptr);
+  graph_mem_free_.Layout(rect, &graph_chrome_rss_private_);
+  graph_mem_used_unknown_.Layout(rect, &graph_mem_free_);
+  graph_renderers_rss_private_.Layout(rect, &graph_mem_used_unknown_);
+  graph_arc_rss_private_.Layout(rect, &graph_renderers_rss_private_);
+  graph_gpu_rss_private_.Layout(rect, &graph_arc_rss_private_);
+  graph_gpu_kernel_.Layout(rect, &graph_gpu_rss_private_);
+  // Not stacked.
+  graph_chrome_rss_shared_.Layout(rect, /*base=*/nullptr);
+
+  // Paint damaged area now that all parameters have been determined.
+  graph_chrome_rss_private_.Draw(canvas);
+  graph_mem_free_.Draw(canvas);
+  graph_mem_used_unknown_.Draw(canvas);
+  graph_renderers_rss_private_.Draw(canvas);
+  graph_arc_rss_private_.Draw(canvas);
+  graph_gpu_rss_private_.Draw(canvas);
+  graph_gpu_kernel_.Draw(canvas);
+
+  graph_chrome_rss_shared_.Draw(canvas);
+}
+
+void MemoryGraphPageView::UpdateData(const DataSource::Snapshot& snapshot) {
+  // TODO: Should probably update last graph point more often than shift graph.
+  const double total = snapshot.total_ram;
+  // Nothing to do if data is not available yet.
+  if (total < 1)
+    return;
+
+  const float chrome_rss_private =
+      (snapshot.browser_rss - snapshot.browser_rss_shared) / total;
+  const float mem_free = snapshot.free_ram / total;
+  // mem_used_unknown is calculated below.
+  const float renderers_rss_private =
+      (snapshot.renderers_rss - snapshot.renderers_rss_shared) / total;
+  const float arc_rss_private =
+      (snapshot.arc_rss - snapshot.arc_rss_shared) / total;
+  const float gpu_rss_private =
+      (snapshot.gpu_rss - snapshot.gpu_rss_shared) / total;
+  const float gpu_kernel = snapshot.gpu_kernel / total;
+
+  // not stacked.
+  const float chrome_rss_shared = snapshot.browser_rss_shared / total;
+
+  std::vector<float> used_buckets;
+  used_buckets.push_back(chrome_rss_private);
+  used_buckets.push_back(mem_free);
+  used_buckets.push_back(renderers_rss_private);
+  used_buckets.push_back(arc_rss_private);
+  used_buckets.push_back(gpu_rss_private);
+  used_buckets.push_back(gpu_kernel);
+
+  float mem_used_unknown =
+      1 - std::accumulate(used_buckets.begin(), used_buckets.end(), 0.0f);
+
+  if (mem_used_unknown < 0)
+    LOG(WARNING) << "mem_used_unknown=" << mem_used_unknown << " < 0 !";
+
+  // Update graph data.
+  graph_chrome_rss_private_.AddValue(chrome_rss_private);
+  graph_mem_free_.AddValue(mem_free);
+  graph_mem_used_unknown_.AddValue(std::max(mem_used_unknown, 0.0f));
+  graph_renderers_rss_private_.AddValue(renderers_rss_private);
+  graph_arc_rss_private_.AddValue(arc_rss_private);
+  graph_gpu_rss_private_.AddValue(gpu_rss_private);
+  graph_gpu_kernel_.AddValue(gpu_kernel);
+  // Not stacked.
+  graph_chrome_rss_shared_.AddValue(chrome_rss_shared);
+}
+
+}  // namespace hud_display
+}  // namespace ash
diff --git a/ash/hud_display/memory_graph_page_view.h b/ash/hud_display/memory_graph_page_view.h
new file mode 100644
index 0000000..4f8fbdd
--- /dev/null
+++ b/ash/hud_display/memory_graph_page_view.h
@@ -0,0 +1,56 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_HUD_DISPLAY_MEMORY_GRAPH_PAGE_VIEW_H_
+#define ASH_HUD_DISPLAY_MEMORY_GRAPH_PAGE_VIEW_H_
+
+#include "ash/hud_display/graph.h"
+#include "ash/hud_display/graph_page_view_base.h"
+
+namespace ash {
+namespace hud_display {
+
+// MemoryGraphPageView class draws memory graphs.
+class MemoryGraphPageView : public GraphPageViewBase {
+ public:
+  METADATA_HEADER(MemoryGraphPageView);
+
+  MemoryGraphPageView();
+  MemoryGraphPageView(const MemoryGraphPageView&) = delete;
+  MemoryGraphPageView& operator=(const MemoryGraphPageView&) = delete;
+  ~MemoryGraphPageView() override;
+
+  // view::
+  void OnPaint(gfx::Canvas* canvas) override;
+
+  // Update page data from the new snapshot.
+  void UpdateData(const DataSource::Snapshot& snapshot) override;
+
+ private:
+  // --- Stacked:
+  // Share of the total RAM occupied by Chrome browser private RSS.
+  Graph graph_chrome_rss_private_;
+  // Share of the total RAM reported as Free memory be kernel.
+  Graph graph_mem_free_;
+  // Total RAM - other graphs in this stack.
+  Graph graph_mem_used_unknown_;
+  // Share of the total RAM occupied by Chrome type=renderer processes private
+  // RSS.
+  Graph graph_renderers_rss_private_;
+  // Share of the total RAM occupied by ARC++ processes private RSS.
+  Graph graph_arc_rss_private_;
+  // Share of the total RAM occupied by Chrome type=gpu process private RSS.
+  Graph graph_gpu_rss_private_;
+  // Share of the total RAM used by kernel GPU driver.
+  Graph graph_gpu_kernel_;
+
+  // Not stacked:
+  // Share of the total RAM occupied by Chrome browser process shared RSS.
+  Graph graph_chrome_rss_shared_;
+};
+
+}  // namespace hud_display
+}  // namespace ash
+
+#endif  // ASH_HUD_DISPLAY_MEMORY_GRAPH_PAGE_VIEW_H_
diff --git a/ash/hud_display/tab_strip.cc b/ash/hud_display/tab_strip.cc
index 8c2365d..7d0da57 100644
--- a/ash/hud_display/tab_strip.cc
+++ b/ash/hud_display/tab_strip.cc
@@ -77,7 +77,7 @@
 
 HUDTabButton::HUDTabButton(Style style,
                            HUDTabStrip* tab_strip,
-                           const DisplayModes display_mode,
+                           const DisplayMode display_mode,
                            const base::string16& text)
     : views::LabelButton(tab_strip, text),
       style_(style),
@@ -171,8 +171,9 @@
 HUDTabStrip::~HUDTabStrip() = default;
 
 HUDTabButton* HUDTabStrip::AddTabButton(HUDDisplayView* hud,
-                                        const DisplayModes display_mode,
+                                        const DisplayMode display_mode,
                                         const base::string16& label) {
+  CHECK_NE(static_cast<int>(display_mode), 0);
   // Make first tab active by default.
   HUDTabButton* tab_button = AddChildView(std::make_unique<HUDTabButton>(
       tabs_.size() ? HUDTabButton::Style::RIGHT : HUDTabButton::Style::ACTIVE,
@@ -185,14 +186,14 @@
                                 const ui::Event& /*event*/) {
   for (const auto* tab : tabs_) {
     if (tab == sender) {
-      hud_->TabButtonPressed(tab);
+      hud_->SetDisplayMode(tab->display_mode());
       return;
     }
   }
   NOTREACHED();
 }
 
-void HUDTabStrip::ActivateTab(const views::View* active_tab_button) {
+void HUDTabStrip::ActivateTab(const DisplayMode mode) {
   // True if we find given active tab.
   bool found = false;
 
@@ -201,7 +202,7 @@
       tab->SetStyle(HUDTabButton::Style::RIGHT);
       continue;
     }
-    if (tab == active_tab_button) {
+    if (tab->display_mode() == mode) {
       found = true;
       tab->SetStyle(HUDTabButton::Style::ACTIVE);
       continue;
diff --git a/ash/hud_display/tab_strip.h b/ash/hud_display/tab_strip.h
index b3ac3b0..262395cd8 100644
--- a/ash/hud_display/tab_strip.h
+++ b/ash/hud_display/tab_strip.h
@@ -37,7 +37,7 @@
 
   HUDTabButton(Style style,
                HUDTabStrip* tab_strip,
-               const DisplayModes display_mode,
+               const DisplayMode display_mode,
                const base::string16& text);
   HUDTabButton(const HUDTabButton&) = delete;
   HUDTabButton& operator=(const HUDTabButton&) = delete;
@@ -46,7 +46,7 @@
 
   void SetStyle(Style style);
 
-  DisplayModes display_mode() const { return display_mode_; }
+  DisplayMode display_mode() const { return display_mode_; }
 
  protected:
   // views::LabelButton:
@@ -56,7 +56,7 @@
   Style style_ = Style::LEFT;
 
   // Tab activation sends this display mode to the HUD.
-  DisplayModes display_mode_ = DisplayModes::DEFAULT;
+  DisplayMode display_mode_;
 };
 
 class HUDTabStrip : public views::View, public views::ButtonListener {
@@ -71,14 +71,14 @@
   ~HUDTabStrip() override;
 
   HUDTabButton* AddTabButton(HUDDisplayView* hud,
-                             const DisplayModes display_mode,
+                             const DisplayMode display_mode,
                              const base::string16& label);
 
   // views::ButtonListener
   void ButtonPressed(views::Button* sender, const ui::Event& event) override;
 
   // Mark tabs around the active one need repaint to modify borders.
-  void ActivateTab(const views::View* active_tab_button);
+  void ActivateTab(DisplayMode mode);
 
  private:
   HUDDisplayView* hud_;
diff --git a/ash/public/cpp/ambient/ambient_client.h b/ash/public/cpp/ambient/ambient_client.h
index 930d9272..784a490 100644
--- a/ash/public/cpp/ambient/ambient_client.h
+++ b/ash/public/cpp/ambient/ambient_client.h
@@ -49,9 +49,6 @@
   virtual void RequestWakeLockProvider(
       mojo::PendingReceiver<device::mojom::WakeLockProvider> receiver) = 0;
 
-  // Whether to use backend production server.
-  virtual bool ShouldUseProdServer() = 0;
-
  protected:
   AmbientClient();
   AmbientClient(const AmbientClient&) = delete;
diff --git a/ash/public/cpp/test/test_ambient_client.cc b/ash/public/cpp/test/test_ambient_client.cc
index ac0052b..65c3f3d 100644
--- a/ash/public/cpp/test/test_ambient_client.cc
+++ b/ash/public/cpp/test/test_ambient_client.cc
@@ -62,10 +62,6 @@
   }
 }
 
-bool TestAmbientClient::ShouldUseProdServer() {
-  return false;
-}
-
 bool TestAmbientClient::IsAccessTokenRequestPending() const {
   return !!pending_callback_;
 }
diff --git a/ash/public/cpp/test/test_ambient_client.h b/ash/public/cpp/test/test_ambient_client.h
index 75173d9f..0ac6add 100644
--- a/ash/public/cpp/test/test_ambient_client.h
+++ b/ash/public/cpp/test/test_ambient_client.h
@@ -27,7 +27,6 @@
   scoped_refptr<network::SharedURLLoaderFactory> GetURLLoaderFactory() override;
   void RequestWakeLockProvider(
       mojo::PendingReceiver<device::mojom::WakeLockProvider> receiver) override;
-  bool ShouldUseProdServer() override;
 
   // Simulate to issue an |access_token|.
   // If |with_error| is true, will return an empty access token.
diff --git a/build/android/gyp/dex_jdk_libs.py b/build/android/gyp/dex_jdk_libs.py
index 0d26add..671b153 100755
--- a/build/android/gyp/dex_jdk_libs.py
+++ b/build/android/gyp/dex_jdk_libs.py
@@ -53,7 +53,9 @@
         desugar_jdk_libs_json,
     ]
 
-    if keep_rule_file:
+    # If no desugaring is required, no keep rules are generated, and the keep
+    # file will not be created.
+    if keep_rule_file is not None and os.path.exists(keep_rule_file):
       cmd += ['--pg-conf', keep_rule_file]
 
     cmd += [
diff --git a/build/android/pylib/local/device/local_device_instrumentation_test_run.py b/build/android/pylib/local/device/local_device_instrumentation_test_run.py
index 00aed6b..a37e256 100644
--- a/build/android/pylib/local/device/local_device_instrumentation_test_run.py
+++ b/build/android/pylib/local/device/local_device_instrumentation_test_run.py
@@ -783,24 +783,30 @@
       logging.debug('raw output from %s:', test_display_name)
       for l in output:
         logging.debug('  %s', l)
+
     if self._test_instance.store_tombstones:
-      tombstones_url = None
-      for result in results:
-        if result.GetType() == base_test_result.ResultType.CRASH:
-          if not tombstones_url:
-            resolved_tombstones = tombstones.ResolveTombstones(
-                device,
-                resolve_all_tombstones=True,
-                include_stack_symbols=False,
-                wipe_tombstones=True,
-                tombstone_symbolizer=self._test_instance.symbolizer)
-            tombstone_filename = 'tombstones_%s_%s' % (
-                time.strftime('%Y%m%dT%H%M%S-UTC', time.gmtime()),
-                device.serial)
-            with self._env.output_manager.ArchivedTempfile(
-                tombstone_filename, 'tombstones') as tombstone_file:
-              tombstone_file.write('\n'.join(resolved_tombstones))
+      resolved_tombstones = tombstones.ResolveTombstones(
+          device,
+          resolve_all_tombstones=True,
+          include_stack_symbols=False,
+          wipe_tombstones=True,
+          tombstone_symbolizer=self._test_instance.symbolizer)
+      if resolved_tombstones:
+        tombstone_filename = 'tombstones_%s_%s' % (time.strftime(
+            '%Y%m%dT%H%M%S-UTC', time.gmtime()), device.serial)
+        with self._env.output_manager.ArchivedTempfile(
+            tombstone_filename, 'tombstones') as tombstone_file:
+          tombstone_file.write('\n'.join(resolved_tombstones))
+
+        # Associate tombstones with first crashing test.
+        for result in results:
+          if result.GetType() == base_test_result.ResultType.CRASH:
             result.SetLink('tombstones', tombstone_file.Link())
+            break
+        else:
+          # We don't always detect crashes correctly. In this case,
+          # associate with the first test.
+          results[0].SetLink('tombstones', tombstone_file.Link())
     return results, None
 
   def _GetTestsFromRunner(self):
diff --git a/build/config/mac/BUILD.gn b/build/config/mac/BUILD.gn
index 5369f76..1d7a248 100644
--- a/build/config/mac/BUILD.gn
+++ b/build/config/mac/BUILD.gn
@@ -28,6 +28,17 @@
     common_mac_flags += [
       "-arch",
       current_cpu,
+
+      # The baseline CPU for macOS on arm64 is the Apple A12 (Vortex/Tempest),
+      # and clang should default to this when building for this OS/CPU
+      # combination. Apple clang embedded in Xcode 12 does, but trunk clang does
+      # not, instead targeting the Apple A7 (Cyclone), the proper baseline CPU
+      # for iOS on arm64. https://reviews.llvm.org/D82699 would fix it, but it
+      # hasn't gained any traction. Specify the baseline CPU explicitly here.
+      #
+      # TODO(https://crbug.com/1119249): Remove this when trunk clang has a
+      # better default.
+      "-mcpu=apple-a12",
     ]
   } else {
     assert(false, "unknown current_cpu $current_cpu")
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 2898d54..008d1891 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-0.20200818.4.1
+0.20200819.0.1
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index cae9194..2898d54 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-0.20200818.3.1
+0.20200818.4.1
diff --git a/build/mac_toolchain.py b/build/mac_toolchain.py
index f0fcc06..a472b14 100755
--- a/build/mac_toolchain.py
+++ b/build/mac_toolchain.py
@@ -34,9 +34,9 @@
     # This contains binaries from Xcode 11.2.1, along with the 10.15 SDKs (aka
     # 11B53).
     'default': 'wXywrnOhzFxwLYlwO62UzRxVCjnu6DoSI2D2jrCd00gC',
-    # This contains binaries from Xcode 12 beta 4, along with the
-    # 11 SDK (aka 12A8179i).
-    'xcode_12_beta': 'cDHYuQ9HUo7nJd1w8GfZfifcjGt2ZWVNykJn399Xz6YC',
+    # This contains binaries from Xcode 12 beta 5, along with the
+    # 11 SDK (aka 12A8189h).
+    'xcode_12_beta': 'MzSaRpqZju2_boJy04DQnw5xpGgiQdPU53iHdst_dHQC',
 }
 
 # The toolchain will not be downloaded if the minimum OS version is not met.
diff --git a/cc/BUILD.gn b/cc/BUILD.gn
index c8db6cc..62949c3 100644
--- a/cc/BUILD.gn
+++ b/cc/BUILD.gn
@@ -32,6 +32,7 @@
     "input/browser_controls_offset_manager.cc",
     "input/browser_controls_offset_manager.h",
     "input/browser_controls_offset_manager_client.h",
+    "input/compositor_input_interfaces.h",
     "input/input_handler.h",
     "input/layer_selection_bound.cc",
     "input/layer_selection_bound.h",
diff --git a/cc/input/compositor_input_interfaces.h b/cc/input/compositor_input_interfaces.h
new file mode 100644
index 0000000..2d4e409
--- /dev/null
+++ b/cc/input/compositor_input_interfaces.h
@@ -0,0 +1,76 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CC_INPUT_COMPOSITOR_INPUT_INTERFACES_H_
+#define CC_INPUT_COMPOSITOR_INPUT_INTERFACES_H_
+
+#include "base/time/time.h"
+#include "cc/paint/element_id.h"
+
+namespace viz {
+struct BeginFrameArgs;
+}
+
+namespace cc {
+
+struct CompositorCommitData;
+
+// This is the interface that LayerTreeHostImpl and the "graphics" side of the
+// compositor uses to talk to the compositor ThreadedInputHandler. This
+// interface is two-way; it's used used both to communicate state changes from
+// the LayerTree to the input handler and also to query and update state in the
+// input handler.
+class InputDelegateForCompositor {
+ public:
+  // Called during a commit to fill in the changes that have occurred since the
+  // last commit.
+  virtual void ProcessCommitDeltas(CompositorCommitData* commit_data) = 0;
+
+  // Called to let the input handler perform animations.
+  virtual void TickAnimations(base::TimeTicks monotonic_time) = 0;
+
+  // Compositor lifecycle state observation.
+  virtual void WillShutdown() = 0;
+  virtual void WillDraw() = 0;
+  virtual void WillBeginImplFrame(const viz::BeginFrameArgs& args) = 0;
+  virtual void DidCommit() = 0;
+  virtual void DidActivatePendingTree() = 0;
+
+  // Called when the state of the "root layer" may have changed from outside
+  // the input system. The state includes: scroll offset, scrollable size,
+  // scroll limits, page scale, page scale limits.
+  virtual void RootLayerStateMayHaveChanged() = 0;
+
+  // Called to let the input handler know that a scrollbar for the given
+  // elementId has been removed.
+  virtual void DidUnregisterScrollbar(ElementId scroll_element_id) = 0;
+
+  // Called to let the input handler know that a scroll offset animation has
+  // completed.
+  virtual void ScrollOffsetAnimationFinished() = 0;
+
+  // Returns true if we're currently in a "gesture" (user-initiated) scroll.
+  // That is, between a GestureScrollBegin and a GestureScrollEnd. Note, a
+  // GestureScrollEnd is deferred if the gesture ended but we're still
+  // animating the scroll to its final position (e.g. the user released their
+  // finger from the touchscreen but we're scroll snapping).
+  virtual bool IsCurrentlyScrolling() const = 0;
+
+  // Returns true if there is an active scroll in progress.  "Active" here
+  // means that it's been latched (i.e. we have a CurrentlyScrollingNode()) but
+  // also that some ScrollUpdates have been received and their delta consumed
+  // for scrolling. These can differ significantly e.g. the page allows the
+  // touchstart but preventDefaults all the touchmoves. In that case, we latch
+  // and have a CurrentlyScrollingNode() but will never receive a ScrollUpdate.
+  //
+  // "Precision" means it's a non-animated scroll like a touchscreen or
+  // high-precision touchpad. The latter distinction is important for things
+  // like scheduling decisions which might schedule a wheel and a touch
+  // scrolling differently due to user perception.
+  virtual bool IsActivelyPrecisionScrolling() const = 0;
+};
+
+}  // namespace cc
+
+#endif  // CC_INPUT_COMPOSITOR_INPUT_INTERFACES_H_
diff --git a/cc/input/threaded_input_handler.cc b/cc/input/threaded_input_handler.cc
index 7904bd3c..3b4d03b 100644
--- a/cc/input/threaded_input_handler.cc
+++ b/cc/input/threaded_input_handler.cc
@@ -54,6 +54,9 @@
 
 ThreadedInputHandler::~ThreadedInputHandler() = default;
 
+//
+// =========== InputHandler Interface
+//
 void ThreadedInputHandler::BindToClient(InputHandlerClient* client) {
   DCHECK(input_handler_client_ == nullptr);
   input_handler_client_ = client;
@@ -880,6 +883,164 @@
   host_impl_.NotifyInputEvent();
 }
 
+//
+// =========== InputDelegateForCompositor Interface
+//
+
+void ThreadedInputHandler::ProcessCommitDeltas(
+    CompositorCommitData* commit_data) {
+  DCHECK(commit_data);
+  if (ActiveTree().LayerListIsEmpty())
+    return;
+
+  ElementId inner_viewport_scroll_element_id =
+      InnerViewportScrollNode() ? InnerViewportScrollNode()->element_id
+                                : ElementId();
+
+  base::flat_set<ElementId> snapped_elements;
+  updated_snapped_elements_.swap(snapped_elements);
+
+  // Scroll commit data is stored in the scroll tree so it has its own method
+  // for getting it.
+  GetScrollTree().CollectScrollDeltas(
+      commit_data, inner_viewport_scroll_element_id,
+      Settings().commit_fractional_scroll_deltas, snapped_elements);
+
+  // Record and reset scroll source flags.
+  DCHECK(!commit_data->manipulation_info);
+  if (has_scrolled_by_wheel_)
+    commit_data->manipulation_info |= kManipulationInfoWheel;
+  if (has_scrolled_by_touch_)
+    commit_data->manipulation_info |= kManipulationInfoTouch;
+  if (has_scrolled_by_precisiontouchpad_)
+    commit_data->manipulation_info |= kManipulationInfoPrecisionTouchPad;
+  if (has_pinch_zoomed_)
+    commit_data->manipulation_info |= kManipulationInfoPinchZoom;
+
+  has_scrolled_by_wheel_ = false;
+  has_scrolled_by_touch_ = false;
+  has_scrolled_by_precisiontouchpad_ = false;
+  has_pinch_zoomed_ = false;
+
+  commit_data->scroll_gesture_did_end = scroll_gesture_did_end_;
+  scroll_gesture_did_end_ = false;
+
+  commit_data->overscroll_delta = overscroll_delta_for_main_thread_;
+  overscroll_delta_for_main_thread_ = gfx::Vector2dF();
+
+  // Use the |last_latched_scroller_| rather than the
+  // |CurrentlyScrollingNode| since the latter may be cleared by a GSE before
+  // we've committed these values to the main thread.
+  // TODO(bokan): This is wrong - if we also started a scroll this frame then
+  // this will clear this value for that scroll. https://crbug.com/1116780.
+  commit_data->scroll_latched_element_id = last_latched_scroller_;
+  if (commit_data->scroll_gesture_did_end)
+    last_latched_scroller_ = ElementId();
+}
+
+void ThreadedInputHandler::TickAnimations(base::TimeTicks monotonic_time) {
+  if (input_handler_client_) {
+    // This does not set did_animate, because if the InputHandlerClient
+    // changes anything it will be through the InputHandler interface which
+    // does SetNeedsRedraw.
+    input_handler_client_->Animate(monotonic_time);
+  }
+}
+
+void ThreadedInputHandler::WillShutdown() {
+  if (input_handler_client_) {
+    input_handler_client_->WillShutdown();
+    input_handler_client_ = nullptr;
+  }
+
+  if (scroll_elasticity_helper_)
+    scroll_elasticity_helper_.reset();
+}
+
+void ThreadedInputHandler::WillDraw() {
+  if (input_handler_client_)
+    input_handler_client_->ReconcileElasticOverscrollAndRootScroll();
+}
+
+void ThreadedInputHandler::WillBeginImplFrame(const viz::BeginFrameArgs& args) {
+  if (input_handler_client_) {
+    scrollbar_controller_->WillBeginImplFrame();
+    input_handler_client_->DeliverInputForBeginFrame(args);
+  }
+}
+
+void ThreadedInputHandler::DidCommit() {
+  // In high latency mode commit cannot finish within the same frame. We need to
+  // flush input here to make sure they got picked up by |PrepareTiles()|.
+  if (input_handler_client_ &&
+      host_impl_.GetCompositorThreadPhase() == ImplThreadPhase::IDLE)
+    input_handler_client_->DeliverInputForHighLatencyMode();
+}
+
+void ThreadedInputHandler::DidActivatePendingTree() {
+  // The previous scrolling node might no longer exist in the new tree.
+  if (!CurrentlyScrollingNode())
+    ClearCurrentlyScrollingNode();
+
+  // Activation can change the root scroll offset, so inform the synchronous
+  // input handler.
+  UpdateRootLayerStateForSynchronousInputHandler();
+}
+
+void ThreadedInputHandler::RootLayerStateMayHaveChanged() {
+  UpdateRootLayerStateForSynchronousInputHandler();
+}
+
+void ThreadedInputHandler::DidUnregisterScrollbar(ElementId scroll_element_id) {
+  scrollbar_controller_->DidUnregisterScrollbar(scroll_element_id);
+}
+
+void ThreadedInputHandler::ScrollOffsetAnimationFinished() {
+  TRACE_EVENT0("cc", "ThreadedInputHandler::ScrollOffsetAnimationFinished");
+  // ScrollOffsetAnimationFinished is called in two cases:
+  //  1- smooth scrolling animation is over (IsAnimatingForSnap == false).
+  //  2- snap scroll animation is over (IsAnimatingForSnap == true).
+  //
+  //  Only for case (1) we should check and run snap scroll animation if needed.
+  if (!IsAnimatingForSnap() && SnapAtScrollEnd())
+    return;
+
+  // The end of a scroll offset animation means that the scrolling node is at
+  // the target offset.
+  ScrollNode* scroll_node = CurrentlyScrollingNode();
+  if (scroll_node && scroll_node->snap_container_data.has_value()) {
+    scroll_node->snap_container_data.value().SetTargetSnapAreaElementIds(
+        scroll_animating_snap_target_ids_);
+    updated_snapped_elements_.insert(scroll_node->element_id);
+    SetNeedsCommit();
+  }
+  scroll_animating_snap_target_ids_ = TargetSnapAreaElementIds();
+
+  // Call scrollEnd with the deferred scroll end state when the scroll animation
+  // completes after GSE arrival.
+  if (deferred_scroll_end_) {
+    ScrollEnd(/*should_snap=*/false);
+    return;
+  }
+}
+
+bool ThreadedInputHandler::IsCurrentlyScrolling() const {
+  return CurrentlyScrollingNode();
+}
+
+bool ThreadedInputHandler::IsActivelyPrecisionScrolling() const {
+  if (!CurrentlyScrollingNode())
+    return false;
+
+  if (!last_scroll_update_state_)
+    return false;
+
+  bool did_scroll_content =
+      did_scroll_x_for_scroll_gesture_ || did_scroll_y_for_scroll_gesture_;
+  return !ShouldAnimateScroll(last_scroll_update_state_.value()) &&
+         did_scroll_content;
+}
+
 ScrollNode* ThreadedInputHandler::CurrentlyScrollingNode() {
   return GetScrollTree().CurrentlyScrollingNode();
 }
@@ -956,149 +1117,6 @@
   return false;
 }
 
-gfx::Vector2dF ThreadedInputHandler::UserScrollableDelta(
-    const ScrollNode& node,
-    const gfx::Vector2dF& delta) const {
-  gfx::Vector2dF adjusted_delta = delta;
-  if (!node.user_scrollable_horizontal)
-    adjusted_delta.set_x(0);
-  if (!node.user_scrollable_vertical)
-    adjusted_delta.set_y(0);
-
-  return adjusted_delta;
-}
-
-void ThreadedInputHandler::ProcessCommitDeltas(
-    CompositorCommitData* commit_data) {
-  DCHECK(commit_data);
-  if (ActiveTree().LayerListIsEmpty())
-    return;
-
-  ElementId inner_viewport_scroll_element_id =
-      InnerViewportScrollNode() ? InnerViewportScrollNode()->element_id
-                                : ElementId();
-
-  base::flat_set<ElementId> snapped_elements;
-  updated_snapped_elements_.swap(snapped_elements);
-
-  // Scroll commit data is stored in the scroll tree so it has its own method
-  // for getting it.
-  GetScrollTree().CollectScrollDeltas(
-      commit_data, inner_viewport_scroll_element_id,
-      Settings().commit_fractional_scroll_deltas, snapped_elements);
-
-  // Record and reset scroll source flags.
-  DCHECK(!commit_data->manipulation_info);
-  if (has_scrolled_by_wheel_)
-    commit_data->manipulation_info |= kManipulationInfoWheel;
-  if (has_scrolled_by_touch_)
-    commit_data->manipulation_info |= kManipulationInfoTouch;
-  if (has_scrolled_by_precisiontouchpad_)
-    commit_data->manipulation_info |= kManipulationInfoPrecisionTouchPad;
-  if (has_pinch_zoomed_)
-    commit_data->manipulation_info |= kManipulationInfoPinchZoom;
-
-  has_scrolled_by_wheel_ = false;
-  has_scrolled_by_touch_ = false;
-  has_scrolled_by_precisiontouchpad_ = false;
-  has_pinch_zoomed_ = false;
-
-  commit_data->scroll_gesture_did_end = scroll_gesture_did_end_;
-  scroll_gesture_did_end_ = false;
-
-  commit_data->overscroll_delta = overscroll_delta_for_main_thread_;
-  overscroll_delta_for_main_thread_ = gfx::Vector2dF();
-
-  // Use the |last_latched_scroller_| rather than the
-  // |CurrentlyScrollingNode| since the latter may be cleared by a GSE before
-  // we've committed these values to the main thread.
-  // TODO(bokan): This is wrong - if we also started a scroll this frame then
-  // this will clear this value for that scroll. https://crbug.com/1116780.
-  commit_data->scroll_latched_element_id = last_latched_scroller_;
-  if (commit_data->scroll_gesture_did_end)
-    last_latched_scroller_ = ElementId();
-}
-
-void ThreadedInputHandler::WillShutdown() {
-  if (input_handler_client_) {
-    input_handler_client_->WillShutdown();
-    input_handler_client_ = nullptr;
-  }
-
-  if (scroll_elasticity_helper_)
-    scroll_elasticity_helper_.reset();
-}
-
-void ThreadedInputHandler::WillDraw() {
-  if (input_handler_client_)
-    input_handler_client_->ReconcileElasticOverscrollAndRootScroll();
-}
-
-void ThreadedInputHandler::WillBeginImplFrame(const viz::BeginFrameArgs& args) {
-  if (input_handler_client_) {
-    scrollbar_controller_->WillBeginImplFrame();
-    input_handler_client_->DeliverInputForBeginFrame(args);
-  }
-}
-
-void ThreadedInputHandler::DidCommitFromBlink() {
-  // In high latency mode commit cannot finish within the same frame. We need to
-  // flush input here to make sure they got picked up by |PrepareTiles()|.
-  if (input_handler_client_ &&
-      host_impl_.GetCompositorThreadPhase() == ImplThreadPhase::IDLE)
-    input_handler_client_->DeliverInputForHighLatencyMode();
-}
-
-void ThreadedInputHandler::DidActivatePendingTree() {
-  // The previous scrolling node no longer exists in the new tree.
-  if (!ActiveTree().CurrentlyScrollingNode())
-    ClearCurrentlyScrollingNode();
-}
-
-void ThreadedInputHandler::TickAnimations(base::TimeTicks monotonic_time) {
-  if (input_handler_client_) {
-    // This does not set did_animate, because if the InputHandlerClient
-    // changes anything it will be through the InputHandler interface which
-    // does SetNeedsRedraw.
-    input_handler_client_->Animate(monotonic_time);
-  }
-}
-
-bool ThreadedInputHandler::CurrentScrollAffectsScrollHandler() const {
-  DCHECK(!scroll_affects_scroll_handler_ || CurrentlyScrollingNode());
-  return scroll_affects_scroll_handler_;
-}
-
-// Return true if scrollable node for 'ancestor' is the same as 'child' or an
-// ancestor along the scroll tree.
-bool ThreadedInputHandler::IsScrolledBy(LayerImpl* child,
-                                        ScrollNode* ancestor) {
-  DCHECK(ancestor && ancestor->scrollable);
-  if (!child)
-    return false;
-  DCHECK_EQ(child->layer_tree_impl(), &ActiveTree());
-  ScrollTree& scroll_tree = GetScrollTree();
-  for (ScrollNode* scroll_node = scroll_tree.Node(child->scroll_tree_index());
-       scroll_node; scroll_node = scroll_tree.parent(scroll_node)) {
-    if (scroll_node->id == ancestor->id)
-      return true;
-  }
-  return false;
-}
-
-bool ThreadedInputHandler::IsActivelyPrecisionScrolling() const {
-  if (!CurrentlyScrollingNode())
-    return false;
-
-  if (!last_scroll_update_state_)
-    return false;
-
-  bool did_scroll_content =
-      did_scroll_x_for_scroll_gesture_ || did_scroll_y_for_scroll_gesture_;
-  return !ShouldAnimateScroll(last_scroll_update_state_.value()) &&
-         did_scroll_content;
-}
-
 gfx::Vector2dF ThreadedInputHandler::ResolveScrollGranularityToPixels(
     const ScrollNode& scroll_node,
     const gfx::Vector2dF& scroll_delta,
@@ -1817,6 +1835,16 @@
   return scroll_node;
 }
 
+void ThreadedInputHandler::UpdateRootLayerStateForSynchronousInputHandler() {
+  if (!input_handler_client_)
+    return;
+  input_handler_client_->UpdateRootLayerStateForSynchronousInputHandler(
+      ActiveTree().TotalScrollOffset(), ActiveTree().TotalMaxScrollOffset(),
+      ActiveTree().ScrollableSize(), ActiveTree().current_page_scale_factor(),
+      ActiveTree().min_page_scale_factor(),
+      ActiveTree().max_page_scale_factor());
+}
+
 void ThreadedInputHandler::DidLatchToScroller(const ScrollState& scroll_state,
                                               ui::ScrollInputType type) {
   DCHECK(CurrentlyScrollingNode());
@@ -1824,7 +1852,6 @@
   host_impl_.browser_controls_manager()->ScrollBegin();
   host_impl_.mutator_host()->ScrollAnimationAbort();
 
-  scroll_affects_scroll_handler_ = ActiveTree().have_scroll_event_handlers();
   scroll_animating_snap_target_ids_ = TargetSnapAreaElementIds();
   last_latched_scroller_ = CurrentlyScrollingNode()->element_id;
   latched_scroll_type_ = type;
@@ -1983,7 +2010,6 @@
 void ThreadedInputHandler::ClearCurrentlyScrollingNode() {
   TRACE_EVENT0("cc", "ThreadedInputHandler::ClearCurrentlyScrollingNode");
   ActiveTree().ClearCurrentlyScrollingNode();
-  scroll_affects_scroll_handler_ = false;
   accumulated_root_overscroll_ = gfx::Vector2dF();
   did_scroll_x_for_scroll_gesture_ = false;
   did_scroll_y_for_scroll_gesture_ = false;
@@ -1993,16 +2019,6 @@
   last_scroll_begin_state_.reset();
 }
 
-void ThreadedInputHandler::UpdateRootLayerStateForSynchronousInputHandler() {
-  if (!input_handler_client_)
-    return;
-  input_handler_client_->UpdateRootLayerStateForSynchronousInputHandler(
-      ActiveTree().TotalScrollOffset(), ActiveTree().TotalMaxScrollOffset(),
-      ActiveTree().ScrollableSize(), ActiveTree().current_page_scale_factor(),
-      ActiveTree().min_page_scale_factor(),
-      ActiveTree().max_page_scale_factor());
-}
-
 bool ThreadedInputHandler::ScrollAnimationUpdateTarget(
     const ScrollNode& scroll_node,
     const gfx::Vector2dF& scroll_delta,
@@ -2033,35 +2049,6 @@
   return animation_updated;
 }
 
-void ThreadedInputHandler::ScrollOffsetAnimationFinished() {
-  TRACE_EVENT0("cc", "ThreadedInputHandler::ScrollOffsetAnimationFinished");
-  // ScrollOffsetAnimationFinished is called in two cases:
-  //  1- smooth scrolling animation is over (IsAnimatingForSnap == false).
-  //  2- snap scroll animation is over (IsAnimatingForSnap == true).
-  //
-  //  Only for case (1) we should check and run snap scroll animation if needed.
-  if (!IsAnimatingForSnap() && SnapAtScrollEnd())
-    return;
-
-  // The end of a scroll offset animation means that the scrolling node is at
-  // the target offset.
-  ScrollNode* scroll_node = CurrentlyScrollingNode();
-  if (scroll_node && scroll_node->snap_container_data.has_value()) {
-    scroll_node->snap_container_data.value().SetTargetSnapAreaElementIds(
-        scroll_animating_snap_target_ids_);
-    updated_snapped_elements_.insert(scroll_node->element_id);
-    SetNeedsCommit();
-  }
-  scroll_animating_snap_target_ids_ = TargetSnapAreaElementIds();
-
-  // Call scrollEnd with the deferred scroll end state when the scroll animation
-  // completes after GSE arrival.
-  if (deferred_scroll_end_) {
-    ScrollEnd(/*should_snap=*/false);
-    return;
-  }
-}
-
 void ThreadedInputHandler::UpdateScrollSourceInfo(
     const ScrollState& scroll_state,
     ui::ScrollInputType type) {
@@ -2076,4 +2063,33 @@
   }
 }
 
+// Return true if scrollable node for 'ancestor' is the same as 'child' or an
+// ancestor along the scroll tree.
+bool ThreadedInputHandler::IsScrolledBy(LayerImpl* child,
+                                        ScrollNode* ancestor) {
+  DCHECK(ancestor && ancestor->scrollable);
+  if (!child)
+    return false;
+  DCHECK_EQ(child->layer_tree_impl(), &ActiveTree());
+  ScrollTree& scroll_tree = GetScrollTree();
+  for (ScrollNode* scroll_node = scroll_tree.Node(child->scroll_tree_index());
+       scroll_node; scroll_node = scroll_tree.parent(scroll_node)) {
+    if (scroll_node->id == ancestor->id)
+      return true;
+  }
+  return false;
+}
+
+gfx::Vector2dF ThreadedInputHandler::UserScrollableDelta(
+    const ScrollNode& node,
+    const gfx::Vector2dF& delta) const {
+  gfx::Vector2dF adjusted_delta = delta;
+  if (!node.user_scrollable_horizontal)
+    adjusted_delta.set_x(0);
+  if (!node.user_scrollable_vertical)
+    adjusted_delta.set_y(0);
+
+  return adjusted_delta;
+}
+
 }  // namespace cc
diff --git a/cc/input/threaded_input_handler.h b/cc/input/threaded_input_handler.h
index 45ad3ea..ec7ff8a 100644
--- a/cc/input/threaded_input_handler.h
+++ b/cc/input/threaded_input_handler.h
@@ -10,6 +10,7 @@
 #include "base/containers/flat_set.h"
 #include "base/optional.h"
 #include "base/time/time.h"
+#include "cc/input/compositor_input_interfaces.h"
 #include "cc/input/event_listener_properties.h"
 #include "cc/input/input_handler.h"
 #include "cc/input/scroll_snap_data.h"
@@ -39,12 +40,12 @@
 class SwapPromiseMonitor;
 class Viewport;
 
-class CC_EXPORT ThreadedInputHandler {
+class CC_EXPORT ThreadedInputHandler : public InputDelegateForCompositor {
  public:
   explicit ThreadedInputHandler(LayerTreeHostImpl* host_impl);
   ~ThreadedInputHandler();
 
-  // ============ InputHandler "Interface" - will override in a future CL
+  // =========== InputHandler "Interface" - will override in a future CL
   void BindToClient(InputHandlerClient* client);
   InputHandler::ScrollStatus ScrollBegin(ScrollState* scroll_state,
                                          ui::ScrollInputType type);
@@ -92,57 +93,30 @@
   void ScrollEndForSnapFling(bool did_finish);
   void NotifyInputEvent();
 
-  // ========== Rough Interface exposed for LTHI
-
-  // Called during a commit to communicate changes to Blink that it needs to
-  // know about. This method sets input-related values in the output parameter
-  // with the delta since the last commit. It then resets the tracking
-  // variables so that the next call will be a delta from this one.
-  void ProcessCommitDeltas(CompositorCommitData* commit_data);
-
-  void WillShutdown();
-  void WillDraw();
-  void WillBeginImplFrame(const viz::BeginFrameArgs& args);
-  void UpdateRootLayerStateForSynchronousInputHandler();
-  void DidCommitFromBlink();
-  void DidActivatePendingTree();
-  void TickAnimations(base::TimeTicks monotonic_time);
-  void ScrollOffsetAnimationFinished();
-
-  // Returns true if there is an ongoing scroll and the scroller has a scroll
-  // event handler.
-  bool CurrentScrollAffectsScrollHandler() const;
-
-  ScrollbarController* get_scrollbar_controller() const {
-    return scrollbar_controller_.get();
-  }
+  // =========== InputDelegateForCompositor Interface - This section implements
+  // the interface that LayerTreeHostImpl uses to communicate with the input
+  // system.
+  void ProcessCommitDeltas(CompositorCommitData* commit_data) override;
+  void TickAnimations(base::TimeTicks monotonic_time) override;
+  void WillShutdown() override;
+  void WillDraw() override;
+  void WillBeginImplFrame(const viz::BeginFrameArgs& args) override;
+  void DidCommit() override;
+  void DidActivatePendingTree() override;
+  void RootLayerStateMayHaveChanged() override;
+  void DidUnregisterScrollbar(ElementId scroll_element_id) override;
+  void ScrollOffsetAnimationFinished() override;
+  bool IsCurrentlyScrolling() const override;
+  bool IsActivelyPrecisionScrolling() const override;
 
   // =========== Public Interface
 
-  // This method gets the scroll offset for a regular scroller, or the combined
-  // visual and layout offsets of the viewport.
-  gfx::ScrollOffset GetVisualScrollOffset(const ScrollNode& scroll_node) const;
-
-  // Returns true if there is an active scroll in progress.  "Active" here
-  // means that it's been latched (i.e. we have a CurrentlyScrollingNode()) but
-  // also that some ScrollUpdates have been received and their delta consumed
-  // for scrolling. These can differ significantly e.g. the page allows the
-  // touchstart but preventDefaults all the touchmoves. In that case, we latch
-  // and have a CurrentlyScrollingNode() but will never receive a ScrollUpdate.
-  //
-  // "Precision" means it's a non-animated scroll like a touchscreen or
-  // high-precision touchpad. The latter distinction is important for things
-  // like scheduling decisions which might schedule a wheel and a touch
-  // scrolling differently due to user perception.
-  bool IsActivelyPrecisionScrolling() const;
-
   bool CanConsumeDelta(const ScrollState& scroll_state,
                        const ScrollNode& scroll_node);
   // Returns the amount of delta that can be applied to scroll_node, taking
   // page scale into account.
   gfx::Vector2dF ComputeScrollDelta(const ScrollNode& scroll_node,
                                     const gfx::Vector2dF& delta);
-  bool IsScrolledBy(LayerImpl* child, ScrollNode* ancestor);
 
   gfx::Vector2dF ScrollSingleNode(const ScrollNode& scroll_node,
                                   const gfx::Vector2dF& delta,
@@ -156,8 +130,6 @@
       const gfx::Vector2dF& scroll_delta,
       ui::ScrollGranularity granularity);
 
-  bool IsAnimatingForSnap() const;
-
   // Used to set the pinch gesture active state when the pinch gesture is
   // handled on another layer tree. In a page with OOPIFs, only the main
   // frame's layer tree directly handles pinch events. But layer trees for
@@ -182,7 +154,19 @@
     return accumulated_root_overscroll_;
   }
 
+  bool animating_for_snap_for_testing() const { return IsAnimatingForSnap(); }
+
  private:
+  FRIEND_TEST_ALL_PREFIXES(ScrollUnifiedLayerTreeHostImplTest,
+                           AnimatedScrollYielding);
+  FRIEND_TEST_ALL_PREFIXES(LayerTreeHostImplTest, AutoscrollTaskAbort);
+
+  // This method gets the scroll offset for a regular scroller, or the combined
+  // visual and layout offsets of the viewport.
+  gfx::ScrollOffset GetVisualScrollOffset(const ScrollNode& scroll_node) const;
+  bool IsScrolledBy(LayerImpl* child, ScrollNode* ancestor);
+  bool IsAnimatingForSnap() const;
+
   ScrollNode* CurrentlyScrollingNode();
   const ScrollNode* CurrentlyScrollingNode() const;
   void ClearCurrentlyScrollingNode();
@@ -208,6 +192,8 @@
 
   void ShowScrollbarsForImplScroll(ElementId element_id);
 
+  void UpdateRootLayerStateForSynchronousInputHandler();
+
   // Called during ScrollBegin once a scroller was successfully latched to
   // (i.e.  it can and will consume scroll delta on the compositor thread). The
   // latched scroller is now available in CurrentlyScrollingNode().
@@ -344,6 +330,10 @@
   FrameSequenceTrackerType GetTrackerTypeForScroll(
       ui::ScrollInputType input_type) const;
 
+  ScrollbarController* scrollbar_controller_for_testing() const {
+    return scrollbar_controller_.get();
+  }
+
   LayerTreeHostImpl& host_impl_;
 
   InputHandlerClient* input_handler_client_ = nullptr;
@@ -408,13 +398,6 @@
   bool did_scroll_x_for_scroll_gesture_ = false;
   bool did_scroll_y_for_scroll_gesture_ = false;
 
-  // This is used to tell the scheduler there are active scroll handlers on the
-  // page so we should prioritize latency during a scroll to try to keep
-  // scroll-linked effects up to data.
-  // TODO(bokan): This is quite old and scheduling has become much more
-  // sophisticated since so it's not clear how much value it's still providing.
-  bool scroll_affects_scroll_handler_ = false;
-
   // TODO(bokan): Mac doesn't yet have smooth scrolling for wheel; however, to
   // allow consistency in tests we use this bit to override that decision.
   // https://crbug.com/574283.
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index 8aedf95..262b564 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -405,6 +405,9 @@
                       compositor_frame_reporting_controller_.get()),
       lcd_text_metrics_reporter_(LCDTextMetricsReporter::CreateIfNeeded(this)),
       frame_rate_estimator_(GetTaskRunner()) {
+  // TODO(bokan): Temporary while we decouple input from the layer tree.
+  input_delegate_ = static_cast<InputDelegateForCompositor*>(&input_handler_);
+
   DCHECK(mutator_host_);
   mutator_host_->SetMutatorHostClient(this);
   mutator_events_ = mutator_host_->CreateEvents();
@@ -452,7 +455,7 @@
   DCHECK(!image_decode_cache_);
   DCHECK(!single_thread_synchronous_task_graph_runner_);
 
-  input_handler_.WillShutdown();
+  input_delegate_->WillShutdown();
 
   // The layer trees must be destroyed before the LayerTreeHost. Also, if they
   // are holding onto any resources, destroying them will release them, before
@@ -529,7 +532,7 @@
 void LayerTreeHostImpl::CommitComplete() {
   TRACE_EVENT0("cc", "LayerTreeHostImpl::CommitComplete");
 
-  input_handler_.DidCommitFromBlink();
+  input_delegate_->DidCommit();
 
   if (CommitToActiveTree()) {
     active_tree_->HandleScrollbarShowRequestsFromMain();
@@ -837,18 +840,20 @@
 
   bool did_animate = false;
 
-  // TODO(bokan): See TODO in ElasticOverscrollController::Animate
-  input_handler_.TickAnimations(monotonic_time);
+  // TODO(bokan): This should return did_animate, see TODO in
+  // ElasticOverscrollController::Animate. crbug.com/551138.
+  input_delegate_->TickAnimations(monotonic_time);
 
   did_animate |= AnimatePageScale(monotonic_time);
   did_animate |= AnimateLayers(monotonic_time, /* is_active_tree */ true);
   did_animate |= AnimateScrollbars(monotonic_time);
   did_animate |= AnimateBrowserControls(monotonic_time);
 
-  // Animating stuff can change the root scroll offset, so inform the
-  // synchronous input handler.
-  input_handler_.UpdateRootLayerStateForSynchronousInputHandler();
   if (did_animate) {
+    // Animating stuff can change the root scroll offset, so inform the
+    // synchronous input handler.
+    input_delegate_->RootLayerStateMayHaveChanged();
+
     // If the tree changed, then we want to draw at the end of the current
     // frame.
     SetNeedsRedraw();
@@ -1454,7 +1459,7 @@
                          TRACE_ID_GLOBAL(CurrentBeginFrameArgs().trace_id),
                          TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
                          "step", "GenerateRenderPass");
-  input_handler_.WillDraw();
+  input_delegate_->WillDraw();
 
   // |client_name| is used for various UMA histograms below.
   // GetClientNameForMetrics only returns one non-null value over the lifetime
@@ -2725,7 +2730,7 @@
     SetNeedsRedraw();
   }
 
-  input_handler_.WillBeginImplFrame(args);
+  input_delegate_->WillBeginImplFrame(args);
 
   Animate();
 
@@ -2977,7 +2982,13 @@
 }
 
 bool LayerTreeHostImpl::IsActivelyPrecisionScrolling() const {
-  return input_handler_.IsActivelyPrecisionScrolling();
+  return input_delegate_->IsActivelyPrecisionScrolling();
+}
+
+bool LayerTreeHostImpl::ScrollAffectsScrollHandler() const {
+  return settings_.enable_synchronized_scrolling &&
+         input_delegate_->IsCurrentlyScrolling() &&
+         active_tree()->have_scroll_event_handlers();
 }
 
 void LayerTreeHostImpl::CreatePendingTree() {
@@ -3069,8 +3080,6 @@
 
     active_tree_->lifecycle().AdvanceTo(LayerTreeLifecycle::kNotSyncing);
 
-    input_handler_.DidActivatePendingTree();
-
     // Now that we've synced everything from the pending tree to the active
     // tree, rename the pending tree the recycle tree so we can reuse it on the
     // next sync.
@@ -3129,9 +3138,8 @@
                             pending_page_scale_animation->scale,
                             pending_page_scale_animation->duration);
   }
-  // Activation can change the root scroll offset, so inform the synchronous
-  // input handler.
-  input_handler_.UpdateRootLayerStateForSynchronousInputHandler();
+
+  input_delegate_->DidActivatePendingTree();
 
   // Update the child's LocalSurfaceId.
   if (active_tree()->local_surface_id_allocation_from_parent().IsValid()) {
@@ -3787,7 +3795,7 @@
 }
 
 void LayerTreeHostImpl::RequestUpdateForSynchronousInputHandler() {
-  input_handler_.UpdateRootLayerStateForSynchronousInputHandler();
+  input_handler_.RequestUpdateForSynchronousInputHandler();
 }
 
 void LayerTreeHostImpl::SetSynchronousInputHandlerRootScrollOffset(
@@ -3879,7 +3887,7 @@
 LayerTreeHostImpl::ProcessCompositorDeltas() {
   auto commit_data = std::make_unique<CompositorCommitData>();
 
-  input_handler_.ProcessCommitDeltas(commit_data.get());
+  input_delegate_->ProcessCommitDeltas(commit_data.get());
   CollectScrollbarUpdatesForCommit(commit_data.get());
 
   commit_data->page_scale_delta =
@@ -4051,8 +4059,7 @@
 void LayerTreeHostImpl::DidUnregisterScrollbarLayer(
     ElementId scroll_element_id) {
   scrollbar_animation_controllers_.erase(scroll_element_id);
-  input_handler_.get_scrollbar_controller()->DidUnregisterScrollbar(
-      scroll_element_id);
+  input_delegate_->DidUnregisterScrollbar(scroll_element_id);
 }
 
 ScrollbarAnimationController*
@@ -4705,7 +4712,7 @@
 }
 
 void LayerTreeHostImpl::ScrollOffsetAnimationFinished() {
-  input_handler_.ScrollOffsetAnimationFinished();
+  input_delegate_->ScrollOffsetAnimationFinished();
 }
 
 void LayerTreeHostImpl::NotifyAnimationWorkletStateChange(
diff --git a/cc/trees/layer_tree_host_impl.h b/cc/trees/layer_tree_host_impl.h
index e495dbe..68ac5ae 100644
--- a/cc/trees/layer_tree_host_impl.h
+++ b/cc/trees/layer_tree_host_impl.h
@@ -654,15 +654,12 @@
   ScrollNode* CurrentlyScrollingNode();
   const ScrollNode* CurrentlyScrollingNode() const;
 
-  bool scroll_affects_scroll_handler() const {
-    return settings_.enable_synchronized_scrolling &&
-           input_handler_.CurrentScrollAffectsScrollHandler();
-  }
   void QueueSwapPromiseForMainThreadScrollUpdate(
       std::unique_ptr<SwapPromise> swap_promise);
 
   // See comment in equivalent ThreadedInputHandler method for what this means.
   bool IsActivelyPrecisionScrolling() const;
+  bool ScrollAffectsScrollHandler() const;
 
   virtual void SetVisible(bool visible);
   bool visible() const { return visible_; }
@@ -698,9 +695,6 @@
   }
 
   MutatorHost* mutator_host() const { return mutator_host_.get(); }
-  ScrollbarController* scrollbar_controller_for_testing() const {
-    return input_handler_.get_scrollbar_controller();
-  }
 
   void SetDebugState(const LayerTreeDebugState& new_debug_state);
   const LayerTreeDebugState& debug_state() const { return debug_state_; }
@@ -978,6 +972,10 @@
   void LogAverageLagEvents(uint32_t frame_token,
                            const viz::FrameTimingDetails& details);
 
+  // TODO(bokan): Temporary, keep multiple pointers to the input_handler_ while
+  // we decouple this into clean interfaces. Once we're done LTHI should only
+  // talk to InputDelegateForCompositor.
+  InputDelegateForCompositor* input_delegate_;
   ThreadedInputHandler input_handler_;
 
   const LayerTreeSettings settings_;
diff --git a/cc/trees/layer_tree_host_impl_unittest.cc b/cc/trees/layer_tree_host_impl_unittest.cc
index 7a00d266..0627b7f9 100644
--- a/cc/trees/layer_tree_host_impl_unittest.cc
+++ b/cc/trees/layer_tree_host_impl_unittest.cc
@@ -137,6 +137,8 @@
   }
 };
 
+}  // namespace
+
 class LayerTreeHostImplTest : public testing::Test,
                               public LayerTreeHostImplClient {
  public:
@@ -2289,14 +2291,14 @@
   EXPECT_FALSE(host_impl_->active_tree()->have_scroll_event_handlers());
   DrawFrame();
 
-  EXPECT_FALSE(host_impl_->scroll_affects_scroll_handler());
+  EXPECT_FALSE(host_impl_->ScrollAffectsScrollHandler());
   host_impl_->ScrollBegin(BeginState(gfx::Point(), gfx::Vector2d(0, 10),
                                      ui::ScrollInputType::kTouchscreen)
                               .get(),
                           ui::ScrollInputType::kTouchscreen);
-  EXPECT_FALSE(host_impl_->scroll_affects_scroll_handler());
+  EXPECT_FALSE(host_impl_->ScrollAffectsScrollHandler());
   host_impl_->ScrollEnd();
-  EXPECT_FALSE(host_impl_->scroll_affects_scroll_handler());
+  EXPECT_FALSE(host_impl_->ScrollAffectsScrollHandler());
 }
 
 TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollHandlerPresent) {
@@ -2304,14 +2306,14 @@
   host_impl_->active_tree()->set_have_scroll_event_handlers(true);
   DrawFrame();
 
-  EXPECT_FALSE(host_impl_->scroll_affects_scroll_handler());
+  EXPECT_FALSE(host_impl_->ScrollAffectsScrollHandler());
   host_impl_->ScrollBegin(BeginState(gfx::Point(), gfx::Vector2d(0, 10),
                                      ui::ScrollInputType::kTouchscreen)
                               .get(),
                           ui::ScrollInputType::kTouchscreen);
-  EXPECT_TRUE(host_impl_->scroll_affects_scroll_handler());
+  EXPECT_TRUE(host_impl_->ScrollAffectsScrollHandler());
   host_impl_->ScrollEnd();
-  EXPECT_FALSE(host_impl_->scroll_affects_scroll_handler());
+  EXPECT_FALSE(host_impl_->ScrollAffectsScrollHandler());
 }
 
 TEST_P(ScrollUnifiedLayerTreeHostImplTest, ScrollUpdateReturnsCorrectValue) {
@@ -2443,7 +2445,7 @@
   host_impl_->ScrollEnd(true);
 
   // Snap target should not be set until the end of the animation.
-  EXPECT_TRUE(host_impl_->GetInputHandler().IsAnimatingForSnap());
+  EXPECT_TRUE(host_impl_->GetInputHandler().animating_for_snap_for_testing());
   EXPECT_EQ(TargetSnapAreaElementIds(),
             GetSnapContainerData(overflow)->GetTargetSnapAreaElementIds());
 
@@ -2455,7 +2457,7 @@
   BeginImplFrameAndAnimate(
       begin_frame_args, start_time + base::TimeDelta::FromMilliseconds(1000));
 
-  EXPECT_FALSE(host_impl_->GetInputHandler().IsAnimatingForSnap());
+  EXPECT_FALSE(host_impl_->GetInputHandler().animating_for_snap_for_testing());
   EXPECT_VECTOR_EQ(gfx::Vector2dF(50, 0), CurrentScrollOffset(overflow));
   EXPECT_EQ(TargetSnapAreaElementIds(ElementId(10), ElementId()),
             GetSnapContainerData(overflow)->GetTargetSnapAreaElementIds());
@@ -2488,7 +2490,7 @@
   host_impl_->ScrollEnd(true);
 
   // Snap target should not be set until the end of the animation.
-  EXPECT_TRUE(host_impl_->GetInputHandler().IsAnimatingForSnap());
+  EXPECT_TRUE(host_impl_->GetInputHandler().animating_for_snap_for_testing());
   EXPECT_EQ(TargetSnapAreaElementIds(),
             GetSnapContainerData(overflow)->GetTargetSnapAreaElementIds());
 
@@ -2500,7 +2502,7 @@
   BeginImplFrameAndAnimate(
       begin_frame_args, start_time + base::TimeDelta::FromMilliseconds(1000));
 
-  EXPECT_FALSE(host_impl_->GetInputHandler().IsAnimatingForSnap());
+  EXPECT_FALSE(host_impl_->GetInputHandler().animating_for_snap_for_testing());
   EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 50), CurrentScrollOffset(overflow));
   EXPECT_EQ(TargetSnapAreaElementIds(ElementId(), ElementId(10)),
             GetSnapContainerData(overflow)->GetTargetSnapAreaElementIds());
@@ -2532,7 +2534,7 @@
   host_impl_->ScrollEnd(true);
 
   // Snap target should not be set until the end of the animation.
-  EXPECT_TRUE(host_impl_->GetInputHandler().IsAnimatingForSnap());
+  EXPECT_TRUE(host_impl_->GetInputHandler().animating_for_snap_for_testing());
   EXPECT_EQ(TargetSnapAreaElementIds(),
             GetSnapContainerData(overflow)->GetTargetSnapAreaElementIds());
 
@@ -2544,7 +2546,7 @@
   BeginImplFrameAndAnimate(
       begin_frame_args, start_time + base::TimeDelta::FromMilliseconds(1000));
 
-  EXPECT_FALSE(host_impl_->GetInputHandler().IsAnimatingForSnap());
+  EXPECT_FALSE(host_impl_->GetInputHandler().animating_for_snap_for_testing());
   EXPECT_VECTOR_EQ(gfx::Vector2dF(50, 50), CurrentScrollOffset(overflow));
   EXPECT_EQ(TargetSnapAreaElementIds(ElementId(10), ElementId(10)),
             GetSnapContainerData(overflow)->GetTargetSnapAreaElementIds());
@@ -2598,7 +2600,7 @@
   // Animating for the wheel scroll.
   BeginImplFrameAndAnimate(begin_frame_args,
                            start_time + base::TimeDelta::FromMilliseconds(50));
-  EXPECT_FALSE(host_impl_->GetInputHandler().IsAnimatingForSnap());
+  EXPECT_FALSE(host_impl_->GetInputHandler().animating_for_snap_for_testing());
   gfx::ScrollOffset current_offset = CurrentScrollOffset(overflow);
   EXPECT_LT(0, current_offset.x());
   EXPECT_GT(20, current_offset.x());
@@ -2612,7 +2614,7 @@
       begin_frame_args, start_time + base::TimeDelta::FromMilliseconds(1000));
 
   // The snap target should not be set until the end of the animation.
-  EXPECT_TRUE(host_impl_->GetInputHandler().IsAnimatingForSnap());
+  EXPECT_TRUE(host_impl_->GetInputHandler().animating_for_snap_for_testing());
   EXPECT_EQ(TargetSnapAreaElementIds(),
             GetSnapContainerData(overflow)->GetTargetSnapAreaElementIds());
 
@@ -2620,7 +2622,7 @@
   BeginImplFrameAndAnimate(
       begin_frame_args, start_time + base::TimeDelta::FromMilliseconds(1500));
   EXPECT_VECTOR_EQ(gfx::Vector2dF(50, 50), CurrentScrollOffset(overflow));
-  EXPECT_FALSE(host_impl_->GetInputHandler().IsAnimatingForSnap());
+  EXPECT_FALSE(host_impl_->GetInputHandler().animating_for_snap_for_testing());
   EXPECT_EQ(TargetSnapAreaElementIds(ElementId(10), ElementId(10)),
             GetSnapContainerData(overflow)->GetTargetSnapAreaElementIds());
 }
@@ -2642,7 +2644,7 @@
   host_impl_->ScrollUpdate(
       UpdateState(pointer_position, y_delta, ui::ScrollInputType::kWheel)
           .get());
-  EXPECT_FALSE(host_impl_->GetInputHandler().IsAnimatingForSnap());
+  EXPECT_FALSE(host_impl_->GetInputHandler().animating_for_snap_for_testing());
 
   viz::BeginFrameArgs begin_frame_args =
       viz::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1);
@@ -2654,7 +2656,7 @@
   // Finish smooth wheel scroll animation which starts a snap animation.
   BeginImplFrameAndAnimate(begin_frame_args,
                            start_time + base::TimeDelta::FromMilliseconds(100));
-  EXPECT_TRUE(host_impl_->GetInputHandler().IsAnimatingForSnap());
+  EXPECT_TRUE(host_impl_->GetInputHandler().animating_for_snap_for_testing());
   EXPECT_EQ(TargetSnapAreaElementIds(),
             GetSnapContainerData(overflow)->GetTargetSnapAreaElementIds());
 
@@ -2667,20 +2669,20 @@
   // animation.
   host_impl_->ScrollUpdate(
       AnimatedUpdateState(gfx::Point(10, 10), gfx::Vector2dF(0, -10)).get());
-  EXPECT_FALSE(host_impl_->GetInputHandler().IsAnimatingForSnap());
+  EXPECT_FALSE(host_impl_->GetInputHandler().animating_for_snap_for_testing());
   // Finish the smooth scroll animation for wheel.
   BeginImplFrameAndAnimate(begin_frame_args,
                            start_time + base::TimeDelta::FromMilliseconds(150));
 
   // At the end of the previous scroll animation, a new animation for the
   // snapping should have started.
-  EXPECT_TRUE(host_impl_->GetInputHandler().IsAnimatingForSnap());
+  EXPECT_TRUE(host_impl_->GetInputHandler().animating_for_snap_for_testing());
 
   // Finish the snap animation.
   BeginImplFrameAndAnimate(
       begin_frame_args, start_time + base::TimeDelta::FromMilliseconds(1000));
 
-  EXPECT_FALSE(host_impl_->GetInputHandler().IsAnimatingForSnap());
+  EXPECT_FALSE(host_impl_->GetInputHandler().animating_for_snap_for_testing());
   // At the end of snap animation we should have updated the
   // TargetSnapAreaElementIds.
   EXPECT_EQ(TargetSnapAreaElementIds(ElementId(), ElementId(10)),
@@ -2705,7 +2707,7 @@
   host_impl_->ScrollUpdate(
       UpdateState(pointer_position, x_delta, ui::ScrollInputType::kWheel)
           .get());
-  EXPECT_FALSE(host_impl_->GetInputHandler().IsAnimatingForSnap());
+  EXPECT_FALSE(host_impl_->GetInputHandler().animating_for_snap_for_testing());
 
   viz::BeginFrameArgs begin_frame_args =
       viz::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1);
@@ -2717,7 +2719,7 @@
   // Animating for the snap.
   BeginImplFrameAndAnimate(begin_frame_args,
                            start_time + base::TimeDelta::FromMilliseconds(100));
-  EXPECT_TRUE(host_impl_->GetInputHandler().IsAnimatingForSnap());
+  EXPECT_TRUE(host_impl_->GetInputHandler().animating_for_snap_for_testing());
   EXPECT_EQ(TargetSnapAreaElementIds(),
             GetSnapContainerData(overflow)->GetTargetSnapAreaElementIds());
 
@@ -2735,7 +2737,7 @@
       ScrollThread::SCROLL_ON_IMPL_THREAD,
       host_impl_->ScrollBegin(begin_state.get(), ui::ScrollInputType::kWheel)
           .thread);
-  EXPECT_FALSE(host_impl_->GetInputHandler().IsAnimatingForSnap());
+  EXPECT_FALSE(host_impl_->GetInputHandler().animating_for_snap_for_testing());
   EXPECT_EQ(TargetSnapAreaElementIds(),
             GetSnapContainerData(overflow)->GetTargetSnapAreaElementIds());
 
@@ -2770,14 +2772,14 @@
   host_impl_->ScrollUpdate(
       UpdateState(pointer_position, x_delta, ui::ScrollInputType::kWheel)
           .get());
-  EXPECT_FALSE(host_impl_->GetInputHandler().IsAnimatingForSnap());
+  EXPECT_FALSE(host_impl_->GetInputHandler().animating_for_snap_for_testing());
 
   viz::BeginFrameArgs begin_frame_args =
       viz::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1);
   host_impl_->ScrollEnd(true);
 
   // No animation is created, but the snap target should be updated.
-  EXPECT_FALSE(host_impl_->GetInputHandler().IsAnimatingForSnap());
+  EXPECT_FALSE(host_impl_->GetInputHandler().animating_for_snap_for_testing());
   EXPECT_EQ(TargetSnapAreaElementIds(ElementId(10), ElementId()),
             GetSnapContainerData(overflow)->GetTargetSnapAreaElementIds());
 
@@ -2786,13 +2788,13 @@
   BeginImplFrameAndAnimate(begin_frame_args, start_time);
 
   // We are already at a snap target so we should not animate for snap.
-  EXPECT_FALSE(host_impl_->GetInputHandler().IsAnimatingForSnap());
+  EXPECT_FALSE(host_impl_->GetInputHandler().animating_for_snap_for_testing());
 
   // Verify that we are not actually animating by running one frame and ensuring
   // scroll offset has not changed.
   BeginImplFrameAndAnimate(begin_frame_args,
                            start_time + base::TimeDelta::FromMilliseconds(100));
-  EXPECT_FALSE(host_impl_->GetInputHandler().IsAnimatingForSnap());
+  EXPECT_FALSE(host_impl_->GetInputHandler().animating_for_snap_for_testing());
   EXPECT_VECTOR_EQ(gfx::Vector2dF(50, 0), CurrentScrollOffset(overflow));
   EXPECT_EQ(TargetSnapAreaElementIds(ElementId(10), ElementId()),
             GetSnapContainerData(overflow)->GetTargetSnapAreaElementIds());
@@ -2825,7 +2827,7 @@
   gfx::Vector2dF initial_offset, target_offset;
   EXPECT_TRUE(host_impl_->GetSnapFlingInfoAndSetAnimatingSnapTarget(
       gfx::Vector2dF(10, 10), &initial_offset, &target_offset));
-  EXPECT_TRUE(host_impl_->GetInputHandler().IsAnimatingForSnap());
+  EXPECT_TRUE(host_impl_->GetInputHandler().animating_for_snap_for_testing());
   EXPECT_VECTOR_EQ(gfx::Vector2dF(4, 4), initial_offset);
   EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 10), target_offset);
   // Snap targets shouldn't be set until the fling animation is complete.
@@ -2833,7 +2835,7 @@
             GetSnapContainerData(overflow)->GetTargetSnapAreaElementIds());
 
   host_impl_->ScrollEndForSnapFling(true /* did_finish */);
-  EXPECT_FALSE(host_impl_->GetInputHandler().IsAnimatingForSnap());
+  EXPECT_FALSE(host_impl_->GetInputHandler().animating_for_snap_for_testing());
   EXPECT_EQ(TargetSnapAreaElementIds(ElementId(10), ElementId(10)),
             GetSnapContainerData(overflow)->GetTargetSnapAreaElementIds());
 }
@@ -2865,7 +2867,7 @@
   gfx::Vector2dF initial_offset, target_offset;
   EXPECT_TRUE(host_impl_->GetSnapFlingInfoAndSetAnimatingSnapTarget(
       gfx::Vector2dF(10, 10), &initial_offset, &target_offset));
-  EXPECT_TRUE(host_impl_->GetInputHandler().IsAnimatingForSnap());
+  EXPECT_TRUE(host_impl_->GetInputHandler().animating_for_snap_for_testing());
   EXPECT_VECTOR_EQ(gfx::Vector2dF(4, 4), initial_offset);
   EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 10), target_offset);
   // Snap targets shouldn't be set until the fling animation is complete.
@@ -2874,7 +2876,7 @@
 
   // The snap targets should not be set if the snap fling did not finish.
   host_impl_->ScrollEndForSnapFling(false /* did_finish */);
-  EXPECT_FALSE(host_impl_->GetInputHandler().IsAnimatingForSnap());
+  EXPECT_FALSE(host_impl_->GetInputHandler().animating_for_snap_for_testing());
   EXPECT_EQ(TargetSnapAreaElementIds(),
             GetSnapContainerData(overflow)->GetTargetSnapAreaElementIds());
 }
@@ -13902,7 +13904,8 @@
         host_impl_
             ->ScrollBegin(begin_state.get(), ui::ScrollInputType::kScrollbar)
             .thread);
-    EXPECT_TRUE(host_impl_->scrollbar_controller_for_testing()
+    EXPECT_TRUE(host_impl_->GetInputHandler()
+                    .scrollbar_controller_for_testing()
                     ->AutoscrollTaskIsScheduled());
   }
 
@@ -13913,7 +13916,8 @@
         gfx::PointF(350, 575), /*jump_key_modifier*/ false);
     EXPECT_EQ(result.type, PointerResultType::kScrollbarScroll);
     host_impl_->ScrollEnd();
-    EXPECT_FALSE(host_impl_->scrollbar_controller_for_testing()
+    EXPECT_FALSE(host_impl_->GetInputHandler()
+                     .scrollbar_controller_for_testing()
                      ->AutoscrollTaskIsScheduled());
   }
 
@@ -14061,8 +14065,9 @@
 
   {
     // Set up an animated scrollbar autoscroll.
-    host_impl_->scrollbar_controller_for_testing()->HandlePointerDown(
-        gfx::PointF(350, 560), /*jump_key_modifier*/ false);
+    host_impl_->GetInputHandler()
+        .scrollbar_controller_for_testing()
+        ->HandlePointerDown(gfx::PointF(350, 560), /*jump_key_modifier*/ false);
     auto begin_state = BeginState(gfx::Point(350, 560), gfx::Vector2d(0, 40),
                                   ui::ScrollInputType::kScrollbar);
     EXPECT_EQ(
@@ -14077,7 +14082,8 @@
     host_impl_->ScrollUpdate(update_state.get());
 
     // Autoscroll animations should be active.
-    EXPECT_TRUE(host_impl_->scrollbar_controller_for_testing()
+    EXPECT_TRUE(host_impl_->GetInputHandler()
+                    .scrollbar_controller_for_testing()
                     ->ScrollbarScrollIsActive());
     EXPECT_TRUE(GetImplAnimationHost()->ImplOnlyScrollAnimatingElement());
   }
@@ -14097,8 +14103,9 @@
     // clears ScrollbarController::autoscroll_state_,
     // captured_scrollbar_metadata_ etc. That means
     // ScrollbarController::ScrollbarLayer should return null.
-    EXPECT_FALSE(
-        host_impl_->scrollbar_controller_for_testing()->ScrollbarLayer());
+    EXPECT_FALSE(host_impl_->GetInputHandler()
+                     .scrollbar_controller_for_testing()
+                     ->ScrollbarLayer());
 
     EXPECT_EQ(
         ScrollThread::SCROLL_ON_IMPL_THREAD,
@@ -14114,7 +14121,9 @@
     EXPECT_TRUE(GetImplAnimationHost()->ImplOnlyScrollAnimatingElement());
 
     // This should not trigger any DCHECKs and will be a no-op.
-    host_impl_->scrollbar_controller_for_testing()->WillBeginImplFrame();
+    host_impl_->GetInputHandler()
+        .scrollbar_controller_for_testing()
+        ->WillBeginImplFrame();
   }
 
   // Tear down the LayerTreeHostImpl before the InputHandlerClient.
@@ -17746,5 +17755,4 @@
   EXPECT_FALSE(host_impl_->FindFrameElementIdAtPoint(gfx::PointF(30, 30)));
 }
 
-}  // namespace
 }  // namespace cc
diff --git a/cc/trees/layer_tree_host_unittest_scroll.cc b/cc/trees/layer_tree_host_unittest_scroll.cc
index 8002aad..cb7c24cd 100644
--- a/cc/trees/layer_tree_host_unittest_scroll.cc
+++ b/cc/trees/layer_tree_host_unittest_scroll.cc
@@ -1270,11 +1270,12 @@
 
       DoGestureScroll(host_impl, scroller_, impl_thread_scroll_);
 
-      EXPECT_TRUE(host_impl->GetInputHandler().IsAnimatingForSnap());
+      EXPECT_TRUE(
+          host_impl->GetInputHandler().animating_for_snap_for_testing());
       EXPECT_VECTOR_EQ(impl_thread_scroll_, ScrollDelta(scroller_impl));
     } else {
       snap_animation_finished_ =
-          !host_impl->GetInputHandler().IsAnimatingForSnap();
+          !host_impl->GetInputHandler().animating_for_snap_for_testing();
     }
   }
 
diff --git a/cc/trees/proxy_impl.cc b/cc/trees/proxy_impl.cc
index 125910bd..2a36795 100644
--- a/cc/trees/proxy_impl.cc
+++ b/cc/trees/proxy_impl.cc
@@ -428,7 +428,7 @@
   // handling scroll updates within the same frame. The tree itself is still
   // kept in prefer smoothness mode to allow checkerboarding.
   ScrollHandlerState scroll_handler_state =
-      host_impl_->scroll_affects_scroll_handler()
+      host_impl_->ScrollAffectsScrollHandler()
           ? ScrollHandlerState::SCROLL_AFFECTS_SCROLL_HANDLER
           : ScrollHandlerState::SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER;
   scheduler_->SetTreePrioritiesAndScrollState(tree_priority,
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 0c73dd5..9f44d3a 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -324,6 +324,7 @@
     "//chrome/browser/password_check:public_java",
     "//chrome/browser/password_manager/android:java",
     "//chrome/browser/performance_hints/android:java",
+    "//chrome/browser/policy/android:java",
     "//chrome/browser/preferences:java",
     "//chrome/browser/privacy:java",
     "//chrome/browser/profiles/android:java",
@@ -988,6 +989,7 @@
     "//chrome/browser/password_manager/android:java",
     "//chrome/browser/password_manager/android_test_helpers:test_support_java",
     "//chrome/browser/performance_hints/android:java",
+    "//chrome/browser/policy/android:java",
     "//chrome/browser/preferences:java",
     "//chrome/browser/profiles/android:java",
     "//chrome/browser/safe_browsing/android:java",
@@ -3107,7 +3109,6 @@
     "java/src/org/chromium/chrome/browser/password_manager/PasswordGenerationDialogBridge.java",
     "java/src/org/chromium/chrome/browser/password_manager/PasswordGenerationPopupBridge.java",
     "java/src/org/chromium/chrome/browser/password_manager/PasswordManagerLauncher.java",
-    "java/src/org/chromium/chrome/browser/password_manager/PasswordScriptsFetcherBridge.java",
     "java/src/org/chromium/chrome/browser/password_manager/settings/PasswordEditingBridge.java",
     "java/src/org/chromium/chrome/browser/password_manager/settings/PasswordUIView.java",
     "java/src/org/chromium/chrome/browser/permissions/PermissionSettingsBridge.java",
diff --git a/chrome/android/DEPS b/chrome/android/DEPS
index 0934c0ba..4a8b069 100644
--- a/chrome/android/DEPS
+++ b/chrome/android/DEPS
@@ -6,6 +6,7 @@
   "+chrome/browser/fullscreen/android",
   "+chrome/browser/notifications",
   "+chrome/browser/password_manager/android",
+  "+chrome/browser/policy/android",
   "+chrome/browser/preferences/android/java",
   "+chrome/browser/safe_browsing/android",
   "+chrome/browser/safety_check/android",
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index 1e172c3..bc93b306 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -1226,7 +1226,6 @@
   "java/src/org/chromium/chrome/browser/password_manager/PasswordManagerDialogView.java",
   "java/src/org/chromium/chrome/browser/password_manager/PasswordManagerDialogViewBinder.java",
   "java/src/org/chromium/chrome/browser/password_manager/PasswordManagerLauncher.java",
-  "java/src/org/chromium/chrome/browser/password_manager/PasswordScriptsFetcherBridge.java",
   "java/src/org/chromium/chrome/browser/password_manager/settings/CallbackDelayer.java",
   "java/src/org/chromium/chrome/browser/password_manager/settings/DialogManager.java",
   "java/src/org/chromium/chrome/browser/password_manager/settings/ExportErrorDialogFragment.java",
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
index 71d38b55f..f441365 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
@@ -835,6 +835,7 @@
     @MediumTest
     @Feature({"StartSurface"})
     // clang-format off
+    @DisableIf.Build(hardware_is = "bullhead", message = "https://crbug.com/1119322")
     @CommandLineFlags.Add({BASE_PARAMS + "/single/open_ntp_instead_of_start/true"})
     public void testHomeButton_OpenNTPInsteadOfStart() {
         // clang-format on
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/SingleTabSwitcherCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/SingleTabSwitcherCoordinator.java
index 25d3d31..7522613 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/SingleTabSwitcherCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/SingleTabSwitcherCoordinator.java
@@ -15,7 +15,10 @@
 import org.chromium.base.Callback;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
+import org.chromium.chrome.browser.flags.CachedFeatureFlags;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.tasks.pseudotab.TabAttributeCache;
 import org.chromium.chrome.browser.tasks.tab_management.TabListFaviconProvider;
 import org.chromium.chrome.browser.tasks.tab_management.TabSwitcher;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
@@ -42,6 +45,9 @@
         mTabListFaviconProvider = new TabListFaviconProvider(activity, false);
         mMediator = new SingleTabSwitcherMediator(
                 propertyModel, activity.getTabModelSelector(), mTabListFaviconProvider);
+        if (CachedFeatureFlags.isEnabled(ChromeFeatureList.INSTANT_START)) {
+            new TabAttributeCache(activity.getTabModelSelector());
+        }
 
         // Most of these interfaces should be unused. They are invalid implementations.
         mTabListDelegate = new TabSwitcher.TabListDelegate() {
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/pseudotab/TabAttributeCache.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/pseudotab/TabAttributeCache.java
index 705ff507..7d1dd83 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/pseudotab/TabAttributeCache.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/pseudotab/TabAttributeCache.java
@@ -58,6 +58,8 @@
      * @param tabModelSelector The {@link TabModelSelector} to observe.
      */
     public TabAttributeCache(TabModelSelector tabModelSelector) {
+        // TODO(hanxi): makes TabAttributeCache a singleton. The TabAttributeCache should be
+        //  instantiated and exactly once before it is used.
         mTabModelSelector = tabModelSelector;
         mTabModelSelectorTabObserver = new TabModelSelectorTabObserver(mTabModelSelector) {
             @Override
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities.java
index cf1648a..e0bb396 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities.java
@@ -9,6 +9,7 @@
 
 import androidx.annotation.Nullable;
 
+import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
 import org.chromium.base.SysUtils;
 import org.chromium.chrome.browser.device.DeviceClassManager;
@@ -20,6 +21,7 @@
 import org.chromium.chrome.browser.flags.StringCachedFieldTrialParameter;
 import org.chromium.chrome.browser.tasks.ConditionalTabStripUtils;
 import org.chromium.chrome.features.start_surface.StartSurfaceConfiguration;
+import org.chromium.ui.base.DeviceFormFactor;
 
 /**
  * A class to handle the state of flags for tab_management.
@@ -93,6 +95,12 @@
         // Disable grid tab switcher if stack tab switcher is enabled for the start surface.
         if (StartSurfaceConfiguration.isStartSurfaceStackTabSwitcherEnabled()) return false;
 
+        // Disable grid tab switcher for tablet.
+        if (DeviceFormFactor.isNonMultiDisplayContextOnTablet(
+                    ContextUtils.getApplicationContext())) {
+            return false;
+        }
+
         // Having Tab Groups or Start implies Grid Tab Switcher.
         return (!DeviceClassManager.enableAccessibilityLayout()
                        && CachedFeatureFlags.isEnabled(ChromeFeatureList.TAB_GRID_LAYOUT_ANDROID)
@@ -107,6 +115,12 @@
         // Disable tab groups if stack tab switcher is enabled for the start surface.
         if (StartSurfaceConfiguration.isStartSurfaceStackTabSwitcherEnabled()) return false;
 
+        // Disable tab group for tablet.
+        if (DeviceFormFactor.isNonMultiDisplayContextOnTablet(
+                    ContextUtils.getApplicationContext())) {
+            return false;
+        }
+
         return !DeviceClassManager.enableAccessibilityLayout()
                 && CachedFeatureFlags.isEnabled(ChromeFeatureList.TAB_GROUPS_ANDROID)
                 && isTabManagementModuleSupported();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/TosAndUmaFirstRunFragmentWithEnterpriseSupport.java b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/TosAndUmaFirstRunFragmentWithEnterpriseSupport.java
index e190294c..49e8fcf1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/TosAndUmaFirstRunFragmentWithEnterpriseSupport.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/TosAndUmaFirstRunFragmentWithEnterpriseSupport.java
@@ -13,7 +13,9 @@
 import org.chromium.base.CallbackController;
 import org.chromium.base.Log;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.policy.PolicyServiceFactory;
 import org.chromium.components.browser_ui.widget.LoadingView;
+import org.chromium.policy.PolicyService;
 
 /**
  * Another FirstRunFragment that is only used when running with CCT.
@@ -42,6 +44,7 @@
     private boolean mViewCreated;
     private LoadingView mLoadingSpinner;
     private CallbackController mCallbackController;
+    private PolicyService.Observer mPolicyServiceObserver;
 
     /**
      * Whether app restriction is found on the device. This can be null when this information is not
@@ -71,6 +74,10 @@
             mLoadingSpinner.destroy();
             mLoadingSpinner = null;
         }
+        if (mPolicyServiceObserver != null) {
+            PolicyServiceFactory.getGlobalPolicyService().removeObserver(mPolicyServiceObserver);
+            mPolicyServiceObserver = null;
+        }
         super.onDestroy();
     }
 
@@ -153,8 +160,7 @@
                 mCallbackController.makeCancelable(this::onAppRestrictionDetected));
     }
 
-    @VisibleForTesting
-    void onAppRestrictionDetected(boolean hasAppRestriction) {
+    private void onAppRestrictionDetected(boolean hasAppRestriction) {
         mHasRestriction = hasAppRestriction;
 
         if (!shouldWaitForPolicyLoading() && mViewCreated) {
@@ -164,20 +170,21 @@
     }
 
     private void checkEnterprisePolicies() {
-        // TODO(crbug.com/1106812): Monitor policy changes when it is ready.
-        if (!sBlockPolicyLoadingForTest) {
-            onCctTosPolicyDetected(getPolicyCctTosDialogEnabled());
+        PolicyService policyService = PolicyServiceFactory.getGlobalPolicyService();
+        if (policyService.isInitializationComplete()) {
+            updateCctTosPolicy();
+        } else {
+            mPolicyServiceObserver = () -> {
+                policyService.removeObserver(mPolicyServiceObserver);
+                mPolicyServiceObserver = null;
+                updateCctTosPolicy();
+            };
+            policyService.addObserver(mPolicyServiceObserver);
         }
     }
 
-    private boolean getPolicyCctTosDialogEnabled() {
-        // TODO(crbug.com/1108118): Do the actual fetching for CCT Policy to replace the fake one.
-        return true;
-    }
-
-    @VisibleForTesting
-    void onCctTosPolicyDetected(boolean cctTosDialogEnabled) {
-        mPolicyCctTosDialogEnabled = cctTosDialogEnabled;
+    private void updateCctTosPolicy() {
+        mPolicyCctTosDialogEnabled = FirstRunUtils.isCctTosDialogEnabled();
         if (mViewCreated) {
             mLoadingSpinner.hideLoadingUI();
         }
@@ -192,9 +199,4 @@
         Log.d(TAG, "TosAndUmaFirstRunFragmentWithEnterpriseSupport finished.");
         getPageDelegate().exitFirstRun();
     }
-
-    @VisibleForTesting
-    static void setBlockPolicyLoadingForTest(boolean blockPolicyLoadingForTest) {
-        sBlockPolicyLoadingForTest = blockPolicyLoadingForTest;
-    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/password_manager/PasswordManagerLauncher.java b/chrome/android/java/src/org/chromium/chrome/browser/password_manager/PasswordManagerLauncher.java
index ab36574..12211b3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/password_manager/PasswordManagerLauncher.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/password_manager/PasswordManagerLauncher.java
@@ -60,7 +60,7 @@
                 if (tryShowingTheGooglePasswordManager(activity)) return;
             }
             if (ChromeFeatureList.isEnabled(ChromeFeatureList.PASSWORD_CHANGE_IN_SETTINGS)) {
-                PasswordScriptsFetcherBridge.prewarmCache(Profile.getLastUsedRegularProfile());
+                PasswordScriptsFetcherBridge.prewarmCache();
             }
         }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunIntegrationTest.java
index 87219b2..3023936 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunIntegrationTest.java
@@ -48,7 +48,7 @@
 import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.test.util.Criteria;
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
-import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.policy.AbstractAppRestrictionsProvider;
 
 import java.util.HashMap;
 import java.util.List;
@@ -95,7 +95,6 @@
     public void tearDown() {
         FirstRunActivity.setEnableEnterpriseCCTForTest(false);
         FirstRunAppRestrictionInfo.setInstanceForTest(null);
-        TosAndUmaFirstRunFragmentWithEnterpriseSupport.setBlockPolicyLoadingForTest(false);
         if (mLastActivity != null) mLastActivity.finish();
     }
 
@@ -257,7 +256,9 @@
     public void testExitFirstRunWithPolicy() {
         setHasAppRestrictionForMock();
         FirstRunActivity.setEnableEnterpriseCCTForTest(true);
-        TosAndUmaFirstRunFragmentWithEnterpriseSupport.setBlockPolicyLoadingForTest(true);
+        Bundle restrictions = new Bundle();
+        restrictions.putBoolean("CCTToSDialogEnabled", false);
+        AbstractAppRestrictionsProvider.setTestRestrictions(restrictions);
 
         Intent intent =
                 CustomTabsTestUtils.createMinimalCustomTabIntent(mContext, "https://test.com");
@@ -266,20 +267,10 @@
         FirstRunActivity freActivity = waitForActivity(FirstRunActivity.class);
         CriteriaHelper.pollUiThread(
                 () -> freActivity.getSupportFragmentManager().getFragments().size() > 0);
-        TosAndUmaFirstRunFragmentWithEnterpriseSupport fragment =
-                (TosAndUmaFirstRunFragmentWithEnterpriseSupport) freActivity
-                        .getSupportFragmentManager()
-                        .getFragments()
-                        .get(0);
-
         // Make sure native is initialized so that the subseuqent transition doesn't get blocked.
         CriteriaHelper.pollUiThread((() -> freActivity.isNativeSideIsInitializedForTest()),
                 "native never initialized.");
 
-        // This policy cause the FRE to be skipped. It responds by relaunching the original intent,
-        // which should cause a {@link CustomTabActivity} because this was originally a CCT intent.
-        TestThreadUtils.runOnUiThreadBlocking(() -> fragment.onCctTosPolicyDetected(false));
-
         waitForActivity(CustomTabActivity.class);
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/TosAndUmaFirstRunFragmentWithEnterpriseSupportTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/TosAndUmaFirstRunFragmentWithEnterpriseSupportTest.java
index 559e0b82..8ae8235a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/TosAndUmaFirstRunFragmentWithEnterpriseSupportTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/TosAndUmaFirstRunFragmentWithEnterpriseSupportTest.java
@@ -1,4 +1,4 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
+// Copyright 2020 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -33,14 +33,18 @@
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.customtabs.CustomTabsTestUtils;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
+import org.chromium.chrome.browser.policy.PolicyServiceFactory;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.content_public.browser.test.util.Criteria;
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.policy.PolicyService;
 import org.chromium.ui.test.util.DisableAnimationsTestRule;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Test for first run activity and {@link TosAndUmaFirstRunFragmentWithEnterpriseSupport}.
@@ -62,10 +66,16 @@
 
     @Mock
     public FirstRunAppRestrictionInfo mMockAppRestrictionInfo;
+    @Mock
+    public PolicyService mPolicyService;
+    @Mock
+    public FirstRunUtils.Natives mFirstRunUtils;
 
     private FirstRunActivityTestObserver mTestObserver = new FirstRunActivityTestObserver();
     private FirstRunActivity mActivity;
     private TosAndUmaFirstRunFragmentWithEnterpriseSupport mFragment;
+    private final List<PolicyService.Observer> mPolicyServiceObservers = new ArrayList<>();
+    private final List<Callback<Boolean>> mAppRestrictonsCallbacks = new ArrayList<>();
 
     private View mTosText;
     private View mAcceptButton;
@@ -78,56 +88,60 @@
         Assert.assertFalse(
                 CommandLine.getInstance().hasSwitch(ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE));
 
-        // Static switches.
         FirstRunActivity.setEnableEnterpriseCCTForTest(true);
         FirstRunAppRestrictionInfo.setInstanceForTest(mMockAppRestrictionInfo);
-        TosAndUmaFirstRunFragmentWithEnterpriseSupport.setBlockPolicyLoadingForTest(true);
+        PolicyServiceFactory.setPolicyServiceForTest(mPolicyService);
+        FirstRunUtilsJni.TEST_HOOKS.setInstanceForTesting(mFirstRunUtils);
     }
 
     @After
     public void tearDown() {
         FirstRunActivity.setEnableEnterpriseCCTForTest(false);
         FirstRunAppRestrictionInfo.setInstanceForTest(null);
-        TosAndUmaFirstRunFragmentWithEnterpriseSupport.setBlockPolicyLoadingForTest(false);
+        PolicyServiceFactory.setPolicyServiceForTest(null);
+        FirstRunUtilsJni.TEST_HOOKS.setInstanceForTesting(mFirstRunUtils);
         if (mActivity != null) mActivity.finish();
     }
 
     @Test
     @SmallTest
     public void testNoRestriction() {
+        setAppRestrictiosnMockNotInitialized();
         launchFirstRunThroughCustomTab();
         assertUIState(FragmentState.LOADING);
 
-        TestThreadUtils.runOnUiThreadBlocking(() -> mFragment.onAppRestrictionDetected(false));
+        TestThreadUtils.runOnUiThreadBlocking(() -> setAppRestrictiosnMockInitialized(false));
         assertUIState(FragmentState.NO_POLICY);
     }
 
     @Test
     @SmallTest
-    public void testWithRestriction_NoPolicy() {
-        setHasAppRestrictionForMock();
+    public void testWithRestriction_DialogEnabled() {
+        setAppRestrictiosnMockInitialized(true);
+        setPolicyServiceMockNotInitialized();
         launchFirstRunThroughCustomTab();
         assertUIState(FragmentState.LOADING);
 
-        waitUntilNativeLoaded();
-
-        TestThreadUtils.runOnUiThreadBlocking(() -> mFragment.onCctTosPolicyDetected(true));
+        setMockCctTosDialogEnabled(true);
+        TestThreadUtils.runOnUiThreadBlocking(() -> setPolicyServiceMockInitialized());
         assertUIState(FragmentState.NO_POLICY);
     }
 
     @Test
     @SmallTest
-    public void testWithRestriction_WithPolicy() {
-        setHasAppRestrictionForMock();
+    public void testWithRestriction_DialogDisabled() {
+        setAppRestrictiosnMockInitialized(true);
+        setPolicyServiceMockNotInitialized();
         launchFirstRunThroughCustomTab();
         assertUIState(FragmentState.LOADING);
 
-        waitUntilNativeLoaded();
+        setMockCctTosDialogEnabled(false);
+        TestThreadUtils.runOnUiThreadBlocking(() -> setPolicyServiceMockInitialized());
 
-        TestThreadUtils.runOnUiThreadBlocking(() -> mFragment.onCctTosPolicyDetected(false));
         assertUIState(FragmentState.HAS_POLICY);
-
-        CriteriaHelper.pollUiThread(() -> mTestObserver.exitFirstRunCallback.getCallCount() > 0);
+        // TODO(https://crbug.com/1113229): Rework this to not depend on {@link FirstRunActivity}
+        // implementation details.
+        Assert.assertTrue(FirstRunStatus.isEphemeralSkipFirstRun());
     }
 
     /**
@@ -165,6 +179,11 @@
                             .getSupportFragmentManager()
                             .getFragments()
                             .get(0);
+
+        // Force this to happen now to try to make the tests more deterministic. Ideally the tests
+        // could control when this happens and test for difference sequences.
+        waitUntilNativeLoaded();
+
         mTosText = mActivity.findViewById(R.id.tos_and_privacy);
         mAcceptButton = mActivity.findViewById(R.id.tos_and_privacy);
         mLargeSpinner = mActivity.findViewById(R.id.progress_spinner_large);
@@ -187,19 +206,55 @@
                 tosVisibility, mAcceptButton.getVisibility());
     }
 
-    /** Set up mock FirstRunAppRestrictionInfo that there is app restriction on the device */
-    private void setHasAppRestrictionForMock() {
+    private void waitUntilNativeLoaded() {
+        CriteriaHelper.pollUiThread(
+                (() -> mActivity.isNativeSideIsInitializedForTest()), "native never initialized.");
+    }
+
+    private void setAppRestrictiosnMockNotInitialized() {
         Mockito.doAnswer(invocation -> {
                    Callback<Boolean> callback = invocation.getArgument(0);
-                   callback.onResult(true);
+                   mAppRestrictonsCallbacks.add(callback);
                    return null;
                })
                 .when(mMockAppRestrictionInfo)
                 .getHasAppRestriction(any());
     }
 
-    private void waitUntilNativeLoaded() {
-        CriteriaHelper.pollUiThread(
-                (() -> mActivity.isNativeSideIsInitializedForTest()), "native never initialized.");
+    private void setAppRestrictiosnMockInitialized(boolean hasAppRestrictons) {
+        Mockito.doAnswer(invocation -> {
+                   Callback<Boolean> callback = invocation.getArgument(0);
+                   callback.onResult(hasAppRestrictons);
+                   return null;
+               })
+                .when(mMockAppRestrictionInfo)
+                .getHasAppRestriction(any());
+
+        for (Callback<Boolean> callback : mAppRestrictonsCallbacks) {
+            callback.onResult(hasAppRestrictons);
+        }
+    }
+
+    private void setPolicyServiceMockNotInitialized() {
+        Mockito.when(mPolicyService.isInitializationComplete()).thenReturn(false);
+        Mockito.doAnswer(invocation -> {
+                   PolicyService.Observer observer = invocation.getArgument(0);
+                   mPolicyServiceObservers.add(observer);
+                   return null;
+               })
+                .when(mPolicyService)
+                .addObserver(any());
+    }
+
+    private void setPolicyServiceMockInitialized() {
+        Mockito.when(mPolicyService.isInitializationComplete()).thenReturn(true);
+        for (PolicyService.Observer observer : mPolicyServiceObservers) {
+            observer.onPolicyServiceInitialized();
+        }
+        mPolicyServiceObservers.clear();
+    }
+
+    private void setMockCctTosDialogEnabled(boolean cctTosDialogEnabled) {
+        Mockito.when(mFirstRunUtils.getCctTosDialogEnabled()).thenReturn(cctTosDialogEnabled);
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/portals/PortalsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/portals/PortalsTest.java
index dd5a5d18a..751e7a6 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/portals/PortalsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/portals/PortalsTest.java
@@ -34,7 +34,6 @@
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
-import org.chromium.base.test.util.FlakyTest;
 import org.chromium.base.test.util.MinAndroidSdkLevel;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeActivity;
@@ -515,6 +514,11 @@
 
     @TargetApi(Build.VERSION_CODES.M)
     private void waitForNotification(NotificationPredicate pred) {
+        waitForNotification(pred, CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL);
+    }
+
+    @TargetApi(Build.VERSION_CODES.M)
+    private void waitForNotification(NotificationPredicate pred, long maxTimeoutMs) {
         CriteriaHelper.pollInstrumentationThread(() -> {
             StatusBarNotification notifications[] =
                     ((NotificationManager) ContextUtils.getApplicationContext().getSystemService(
@@ -526,7 +530,7 @@
                 }
             }
             return false;
-        });
+        }, maxTimeoutMs, CriteriaHelper.DEFAULT_POLLING_INTERVAL);
     }
 
     @TargetApi(Build.VERSION_CODES.M)
@@ -549,7 +553,6 @@
     @LargeTest
     @Feature({"Portals"})
     @MinAndroidSdkLevel(Build.VERSION_CODES.M)
-    @FlakyTest(message = "https://crbug.com/1115888")
     public void testMediaCaptureNotificationVisibleAfterAdoption() throws Exception {
         String mainUrl = mTestServer.getURL("/chrome/test/data/android/portals/media-capture.html");
         mActivityTestRule.startMainActivityWithURL(mainUrl);
@@ -562,7 +565,7 @@
             PermissionDialogController permissionDialogController =
                     PermissionDialogController.getInstance();
             return permissionDialogController.isDialogShownForTest();
-        });
+        }, 12000, CriteriaHelper.DEFAULT_POLLING_INTERVAL);
         // Accept permissions request by clicking button on permissions dialog.
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             PermissionDialogController permissionDialogController =
@@ -571,7 +574,7 @@
                     ModalDialogProperties.ButtonType.POSITIVE);
         });
         // Wait for video capture notification.
-        waitForNotification(mMediaCaptureNotificationPred);
+        waitForNotification(mMediaCaptureNotificationPred, 12000);
         // Activate portal.
         executeScriptAndAwaitSwap(tab, "activate()");
         // Wait for adoption to complete.
diff --git a/chrome/android/profiles/newest.txt b/chrome/android/profiles/newest.txt
index 345729e..6906f99c 100644
--- a/chrome/android/profiles/newest.txt
+++ b/chrome/android/profiles/newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-86.0.4232.0_rc-r1-merged.afdo.bz2
+chromeos-chrome-amd64-86.0.4237.0_rc-r1-merged.afdo.bz2
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index cb0a127..7e1f5b5 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -5702,4 +5702,27 @@
   <message name="IDS_CUSTOM_TABS_ACTION_MENU_ACCESSIBLE_NAME" desc="Accessible name for the overflow menu of the Chrome Custom Tab toolbar.">
     Action menu button
   </message>
+
+  <!-- Strings for the System-proxy authentication dialog that requests proxy credentials for network traffic at OS level (outside the Browser) -->
+  <message name="IDS_SYSTEM_PROXY_AUTH_DIALOG_TITLE" desc="Title for the dialog.">
+    Sign in
+  </message>
+  <message name="IDS_SYSTEM_PROXY_AUTH_DIALOG_OK_BUTTON" desc="Text for the OK button.">
+    Sign in
+  </message>
+  <message name="IDS_SYSTEM_PROXY_AUTH_DIALOG_TEXT" desc="Text for the dialog informing users that the remote proxy requires authentication.">
+    The proxy <ph name="PROXY_SERVER">$1<ex>http://localhost:3128</ex></ph> requires a username and password.
+  </message>
+  <message name="IDS_SYSTEM_PROXY_AUTH_DIALOG_PRIVACY_WARNING" desc="Text for the privacy disclosure section on the dialog.">
+    Your connection is not private for all network traffic
+  </message>
+  <message name="IDS_SYSTEM_PROXY_AUTH_DIALOG_USERNAME_LABEL" desc="Text for the label associated with the username input.">
+    Username
+  </message>
+  <message name="IDS_SYSTEM_PROXY_AUTH_DIALOG_PASSWORD_LABEL" desc="Text for the label associated with the password input.">
+    Password
+  </message>
+  <message name="IDS_SYSTEM_PROXY_AUTH_DIALOG_ERROR_LABEL" desc="Text for the error label that informs users that the authentication attempt failed.">
+    Unable to connect to the proxy
+  </message>
 </grit-part>
diff --git a/chrome/app/chromeos_strings_grdp/IDS_SYSTEM_PROXY_AUTH_DIALOG_ERROR_LABEL.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_SYSTEM_PROXY_AUTH_DIALOG_ERROR_LABEL.png.sha1
new file mode 100644
index 0000000..686a9988
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_SYSTEM_PROXY_AUTH_DIALOG_ERROR_LABEL.png.sha1
@@ -0,0 +1 @@
+6102aa43999807804f4af6bf78d850fba98cdd84
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_SYSTEM_PROXY_AUTH_DIALOG_OK_BUTTON.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_SYSTEM_PROXY_AUTH_DIALOG_OK_BUTTON.png.sha1
new file mode 100644
index 0000000..686a9988
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_SYSTEM_PROXY_AUTH_DIALOG_OK_BUTTON.png.sha1
@@ -0,0 +1 @@
+6102aa43999807804f4af6bf78d850fba98cdd84
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_SYSTEM_PROXY_AUTH_DIALOG_PASSWORD_LABEL.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_SYSTEM_PROXY_AUTH_DIALOG_PASSWORD_LABEL.png.sha1
new file mode 100644
index 0000000..686a9988
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_SYSTEM_PROXY_AUTH_DIALOG_PASSWORD_LABEL.png.sha1
@@ -0,0 +1 @@
+6102aa43999807804f4af6bf78d850fba98cdd84
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_SYSTEM_PROXY_AUTH_DIALOG_PRIVACY_WARNING.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_SYSTEM_PROXY_AUTH_DIALOG_PRIVACY_WARNING.png.sha1
new file mode 100644
index 0000000..686a9988
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_SYSTEM_PROXY_AUTH_DIALOG_PRIVACY_WARNING.png.sha1
@@ -0,0 +1 @@
+6102aa43999807804f4af6bf78d850fba98cdd84
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_SYSTEM_PROXY_AUTH_DIALOG_TEXT.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_SYSTEM_PROXY_AUTH_DIALOG_TEXT.png.sha1
new file mode 100644
index 0000000..686a9988
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_SYSTEM_PROXY_AUTH_DIALOG_TEXT.png.sha1
@@ -0,0 +1 @@
+6102aa43999807804f4af6bf78d850fba98cdd84
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_SYSTEM_PROXY_AUTH_DIALOG_TITLE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_SYSTEM_PROXY_AUTH_DIALOG_TITLE.png.sha1
new file mode 100644
index 0000000..686a9988
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_SYSTEM_PROXY_AUTH_DIALOG_TITLE.png.sha1
@@ -0,0 +1 @@
+6102aa43999807804f4af6bf78d850fba98cdd84
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_SYSTEM_PROXY_AUTH_DIALOG_USERNAME_LABEL.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_SYSTEM_PROXY_AUTH_DIALOG_USERNAME_LABEL.png.sha1
new file mode 100644
index 0000000..686a9988
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_SYSTEM_PROXY_AUTH_DIALOG_USERNAME_LABEL.png.sha1
@@ -0,0 +1 @@
+6102aa43999807804f4af6bf78d850fba98cdd84
\ No newline at end of file
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 204b280..65589d6 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -5995,9 +5995,12 @@
         <message name="IDS_PASSWORD_MANAGER_SAVE_BUTTON" desc="Save button text for password manager">
           Save
         </message>
-        <message name="IDS_PASSWORD_MANAGER_MOVE_BUTTON" desc="Move button text for password manager">
+        <message name="IDS_PASSWORD_MANAGER_MOVE_BUBBLE_OK_BUTTON" desc="Text for the ok button in the password manager move bubble.">
           Move
         </message>
+        <message name="IDS_PASSWORD_MANAGER_MOVE_BUBBLE_CANCEL_BUTTON" desc="Text for the cancel button in the password manager move bubble.">
+          No thanks
+        </message>
         <if expr="use_titlecase">
           <message name="IDS_PASSWORD_MANAGER_UPDATE_BUTTON" desc="In Title Case: Update button text for password manager">
             Update Password
@@ -7853,26 +7856,17 @@
       </message>
 
       <!-- Account password storage reauth tab modal dialog -->
-      <message name="IDS_ACCOUNT_PASSWORDS_REAUTH_SAVE_TITLE" desc="Title of the account password storage reauth tab modal dialog. The dialog was shown because the user requested to save a password to their Google account.">
-        Save this and other passwords in your Google Account?
-      </message>
-      <message name="IDS_ACCOUNT_PASSWORDS_REAUTH_SHOW_TITLE" desc="Title of the account password storage reauth tab modal dialog. The dialog was shown because the user requested to get passwords from their Google account.">
-        Show passwords from your Google Account
+      <message name="IDS_ACCOUNT_PASSWORDS_REAUTH_TITLE" desc="Title of the account password storage reauth tab modal dialog.">
+        Use your Google Account to save and fill passwords?
       </message>
       <message name="IDS_ACCOUNT_PASSWORDS_REAUTH_DESC" desc="Body of the account password storage reauth tab modal dialog.">
-        Your passwords from your Google Account will also be available on this device while you're signed in
+        Passwords from your Google Account will also be available on this device while you're signed in
       </message>
-      <message name="IDS_ACCOUNT_PASSWORDS_REAUTH_SAVE_BUTTON_LABEL" desc="Label of the confirmation button in the account password storage reauth tab modal dialog. The dialog was shown because the user requested to save a password to their Google account.">
-        Save
-      </message>
-      <message name="IDS_ACCOUNT_PASSWORDS_REAUTH_SHOW_BUTTON_LABEL" desc="Label of the confirmation button in the account password storage reauth tab modal dialog. The dialog was shown because the user requested to get passwords from their Google account.">
-        Show
+      <message name="IDS_ACCOUNT_PASSWORDS_REAUTH_CONFIRM_BUTTON_LABEL" desc="Label of the confirmation button in the account password storage reauth tab modal dialog.">
+        Yes
       </message>
       <message name="IDS_ACCOUNT_PASSWORDS_REAUTH_CLOSE_BUTTON_LABEL" desc="Label of the close button in the account password storage reauth tab modal dialog.">
-        Cancel
-      </message>
-      <message name="IDS_ACCOUNT_PASSWORDS_REAUTH_NEXT_BUTTON_LABEL" desc="Label of the next button in the account password storage reauth tab modal dialog. This button invites the user to authenticate to unlock the account password storage.">
-        Next
+        No thanks
       </message>
 
       <message name="IDS_PLUGIN_OUTDATED_PROMPT" desc="Infobar message when an outdated plugin was disabled">
@@ -9602,7 +9596,7 @@
 
       <!--Cloud printing deprecation messages for chrome://devices & Print Preview-->
       <message name="IDS_CLOUD_PRINTING_NOT_SUPPORTED_WARNING" desc="Warning shown to the user to inform them that cloud printing will no longer be supported.">
-        Cloud printing will no longer be supported after December 31. <ph name="BEGIN_LINK_LEARN_MORE">&lt;a is="action-link" href="$1" target="_blank"&gt;</ph>Learn more<ph name="END_LINK_LEARN_MORE">&lt;/a&gt;</ph>
+        Cloud printing will no longer be supported after December 31. <ph name="BEGIN_LINK_LEARN_MORE">&lt;a href="$1<ex>https://google.com/</ex>" target="_blank"&gt;</ph>Learn more<ph name="END_LINK_LEARN_MORE">&lt;/a&gt;</ph>
       </message>
       <message name="IDS_CLOUD_PRINTING_NOT_SUPPORTED_WARNING_ENTERPRISE" desc="Warning shown to users managed by enterprise policy to inform them that cloud printing will no longer be supported.">
         Cloud printing will no longer be supported after December 31. Contact your administrator.
diff --git a/chrome/app/generated_resources_grd/IDS_ACCOUNT_PASSWORDS_REAUTH_CLOSE_BUTTON_LABEL.png.sha1 b/chrome/app/generated_resources_grd/IDS_ACCOUNT_PASSWORDS_REAUTH_CLOSE_BUTTON_LABEL.png.sha1
index 846cbd5d..f016236 100644
--- a/chrome/app/generated_resources_grd/IDS_ACCOUNT_PASSWORDS_REAUTH_CLOSE_BUTTON_LABEL.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_ACCOUNT_PASSWORDS_REAUTH_CLOSE_BUTTON_LABEL.png.sha1
@@ -1 +1 @@
-5c3bac7f7a6409b8916770f6604e1c3fd13bd8a5
\ No newline at end of file
+5e146f2042d26ea36f0691767e3f7d28d10e96de
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_ACCOUNT_PASSWORDS_REAUTH_CONFIRM_BUTTON_LABEL.png.sha1 b/chrome/app/generated_resources_grd/IDS_ACCOUNT_PASSWORDS_REAUTH_CONFIRM_BUTTON_LABEL.png.sha1
index e3b42d4..f016236 100644
--- a/chrome/app/generated_resources_grd/IDS_ACCOUNT_PASSWORDS_REAUTH_CONFIRM_BUTTON_LABEL.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_ACCOUNT_PASSWORDS_REAUTH_CONFIRM_BUTTON_LABEL.png.sha1
@@ -1 +1 @@
-8600966344b959251abb06a3f2f4a2df9f07806c
\ No newline at end of file
+5e146f2042d26ea36f0691767e3f7d28d10e96de
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_ACCOUNT_PASSWORDS_REAUTH_DESC.png.sha1 b/chrome/app/generated_resources_grd/IDS_ACCOUNT_PASSWORDS_REAUTH_DESC.png.sha1
index 846cbd5d..f016236 100644
--- a/chrome/app/generated_resources_grd/IDS_ACCOUNT_PASSWORDS_REAUTH_DESC.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_ACCOUNT_PASSWORDS_REAUTH_DESC.png.sha1
@@ -1 +1 @@
-5c3bac7f7a6409b8916770f6604e1c3fd13bd8a5
\ No newline at end of file
+5e146f2042d26ea36f0691767e3f7d28d10e96de
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_ACCOUNT_PASSWORDS_REAUTH_NEXT_BUTTON_LABEL.png.sha1 b/chrome/app/generated_resources_grd/IDS_ACCOUNT_PASSWORDS_REAUTH_NEXT_BUTTON_LABEL.png.sha1
deleted file mode 100644
index 7e6747d..0000000
--- a/chrome/app/generated_resources_grd/IDS_ACCOUNT_PASSWORDS_REAUTH_NEXT_BUTTON_LABEL.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-5d949e5b40e238692e6a12ed95ed43f59a27ecad
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_ACCOUNT_PASSWORDS_REAUTH_SAVE_BUTTON_LABEL.png.sha1 b/chrome/app/generated_resources_grd/IDS_ACCOUNT_PASSWORDS_REAUTH_SAVE_BUTTON_LABEL.png.sha1
deleted file mode 100644
index 846cbd5d..0000000
--- a/chrome/app/generated_resources_grd/IDS_ACCOUNT_PASSWORDS_REAUTH_SAVE_BUTTON_LABEL.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-5c3bac7f7a6409b8916770f6604e1c3fd13bd8a5
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_ACCOUNT_PASSWORDS_REAUTH_SAVE_TITLE.png.sha1 b/chrome/app/generated_resources_grd/IDS_ACCOUNT_PASSWORDS_REAUTH_SAVE_TITLE.png.sha1
deleted file mode 100644
index 846cbd5d..0000000
--- a/chrome/app/generated_resources_grd/IDS_ACCOUNT_PASSWORDS_REAUTH_SAVE_TITLE.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-5c3bac7f7a6409b8916770f6604e1c3fd13bd8a5
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_ACCOUNT_PASSWORDS_REAUTH_SHOW_BUTTON_LABEL.png.sha1 b/chrome/app/generated_resources_grd/IDS_ACCOUNT_PASSWORDS_REAUTH_SHOW_BUTTON_LABEL.png.sha1
deleted file mode 100644
index 1524ab8d..0000000
--- a/chrome/app/generated_resources_grd/IDS_ACCOUNT_PASSWORDS_REAUTH_SHOW_BUTTON_LABEL.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-a9e352937c1dced585b1244ad469f3ff1dedf106
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_ACCOUNT_PASSWORDS_REAUTH_SHOW_TITLE.png.sha1 b/chrome/app/generated_resources_grd/IDS_ACCOUNT_PASSWORDS_REAUTH_SHOW_TITLE.png.sha1
deleted file mode 100644
index 1524ab8d..0000000
--- a/chrome/app/generated_resources_grd/IDS_ACCOUNT_PASSWORDS_REAUTH_SHOW_TITLE.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-a9e352937c1dced585b1244ad469f3ff1dedf106
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_ACCOUNT_PASSWORDS_REAUTH_TITLE.png.sha1 b/chrome/app/generated_resources_grd/IDS_ACCOUNT_PASSWORDS_REAUTH_TITLE.png.sha1
index e3b42d4..f016236 100644
--- a/chrome/app/generated_resources_grd/IDS_ACCOUNT_PASSWORDS_REAUTH_TITLE.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_ACCOUNT_PASSWORDS_REAUTH_TITLE.png.sha1
@@ -1 +1 @@
-8600966344b959251abb06a3f2f4a2df9f07806c
\ No newline at end of file
+5e146f2042d26ea36f0691767e3f7d28d10e96de
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_CLOUD_PRINTING_NOT_SUPPORTED_WARNING.png.sha1 b/chrome/app/generated_resources_grd/IDS_CLOUD_PRINTING_NOT_SUPPORTED_WARNING.png.sha1
index 5cf6a7e..042f3dde 100644
--- a/chrome/app/generated_resources_grd/IDS_CLOUD_PRINTING_NOT_SUPPORTED_WARNING.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_CLOUD_PRINTING_NOT_SUPPORTED_WARNING.png.sha1
@@ -1 +1 @@
-4718d4b2fdf1d1d568e31fc99b052ed9b9766243
\ No newline at end of file
+87675a3014a60ca7b660e94df7edb4987ae9a8a2
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_PASSWORD_MANAGER_MOVE_BUBBLE_CANCEL_BUTTON.png.sha1 b/chrome/app/generated_resources_grd/IDS_PASSWORD_MANAGER_MOVE_BUBBLE_CANCEL_BUTTON.png.sha1
new file mode 100644
index 0000000..d2fb6523
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_PASSWORD_MANAGER_MOVE_BUBBLE_CANCEL_BUTTON.png.sha1
@@ -0,0 +1 @@
+ebcf220382bb6b59a7d185e0bdb17a9f9898a6c4
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_PASSWORD_MANAGER_MOVE_BUBBLE_OK_BUTTON.png.sha1 b/chrome/app/generated_resources_grd/IDS_PASSWORD_MANAGER_MOVE_BUBBLE_OK_BUTTON.png.sha1
new file mode 100644
index 0000000..d2fb6523
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_PASSWORD_MANAGER_MOVE_BUBBLE_OK_BUTTON.png.sha1
@@ -0,0 +1 @@
+ebcf220382bb6b59a7d185e0bdb17a9f9898a6c4
\ No newline at end of file
diff --git a/chrome/app/printing_strings.grdp b/chrome/app/printing_strings.grdp
index 1ec81a23..2af5363 100644
--- a/chrome/app/printing_strings.grdp
+++ b/chrome/app/printing_strings.grdp
@@ -418,6 +418,9 @@
     <message name="IDS_DESTINATION_NOT_SUPPORTED_WARNING" desc="Warning shown next to a destination to the user to inform them that the destination will no longer be supported.">
       Not supported after December
     </message>
+    <message name="IDS_WARNING_ICON_ARIA_LABEL" desc="Accessibility text for a warning icon that is next to a string indicating cloud printing will no longer be supported.">
+      Warning
+    </message>
     <if expr="not chromeos">
       <message name="IDS_GOOGLE_DRIVE_OPTION_NOT_SUPPORTED_WARNING" desc="Warning shown to the user to inform them that saving to Google Drive will no longer be supported.">
         This option won't be supported after December. <ph name="BEGIN_LINK_LEARN_MORE">&lt;a href="$1<ex>https://google.com/</ex>" target="_blank"&gt;</ph>Learn more<ph name="END_LINK_LEARN_MORE">&lt;/a&gt;</ph>
diff --git a/chrome/app/printing_strings_grdp/IDS_WARNING_ICON_ARIA_LABEL.png.sha1 b/chrome/app/printing_strings_grdp/IDS_WARNING_ICON_ARIA_LABEL.png.sha1
new file mode 100644
index 0000000..042f3dde
--- /dev/null
+++ b/chrome/app/printing_strings_grdp/IDS_WARNING_ICON_ARIA_LABEL.png.sha1
@@ -0,0 +1 @@
+87675a3014a60ca7b660e94df7edb4987ae9a8a2
\ No newline at end of file
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index 6ff0a3d..996f3e0f 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -530,7 +530,7 @@
     Move
   </message>
   <message name="IDS_SETTINGS_PASSWORD_MOVE_TO_ACCOUNT_DIALOG_CANCEL_BUTTON_TEXT" desc="Text for the button that cancels the action of moving a password to the user Google Account.">
-    No, thanks
+    No thanks
   </message>
   <message name="IDS_SETTINGS_PASSWORD_REMOVE_DIALOG_TITLE" desc="Title for the dialog that asks the user which versions of a password to remove (device, Google Account or both).">
     Delete password?
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_PASSWORD_MOVE_TO_ACCOUNT_DIALOG_CANCEL_BUTTON_TEXT.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PASSWORD_MOVE_TO_ACCOUNT_DIALOG_CANCEL_BUTTON_TEXT.png.sha1
index 8e59143..d51f775 100644
--- a/chrome/app/settings_strings_grdp/IDS_SETTINGS_PASSWORD_MOVE_TO_ACCOUNT_DIALOG_CANCEL_BUTTON_TEXT.png.sha1
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PASSWORD_MOVE_TO_ACCOUNT_DIALOG_CANCEL_BUTTON_TEXT.png.sha1
@@ -1 +1 @@
-25f8f6709956367a56fef4b13a7877f7896cefed
\ No newline at end of file
+30f492c6f7b9086acf66d2b2b24fbd54781f2c34
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index b786eb5b..21cca26e 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -2855,6 +2855,7 @@
       "password_manager/android/password_manager_infobar_delegate_android.h",
       "password_manager/android/password_manager_launcher_android.cc",
       "password_manager/android/password_manager_launcher_android.h",
+      "password_manager/android/password_scripts_fetcher_android.cc",
       "password_manager/android/save_password_infobar_delegate_android.cc",
       "password_manager/android/save_password_infobar_delegate_android.h",
       "password_manager/android/touch_to_fill_view.h",
@@ -2968,6 +2969,7 @@
       "//chrome/browser/offline_pages/prefetch/notifications",
       "//chrome/browser/optimization_guide/android:jni_headers",
       "//chrome/browser/password_check/android:jni_headers",
+      "//chrome/browser/password_manager/android:jni_headers",
       "//chrome/browser/payments/android:jni_headers",
       "//chrome/browser/policy/android:jni_headers",
       "//chrome/browser/privacy:jni_headers",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 7b3cbf7..4695939 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -2868,13 +2868,6 @@
      flag_descriptions::kReducedReferrerGranularityDescription, kOsAll,
      FEATURE_VALUE_TYPE(blink::features::kReducedReferrerGranularity)},
 #if defined(OS_CHROMEOS)
-    {"crostini-port-forwarding", flag_descriptions::kCrostiniPortForwardingName,
-     flag_descriptions::kCrostiniPortForwardingDescription, kOsCrOS,
-     FEATURE_VALUE_TYPE(chromeos::features::kCrostiniPortForwarding)},
-    {"crostini-show-mic-setting",
-     flag_descriptions::kCrostiniShowMicSettingName,
-     flag_descriptions::kCrostiniShowMicSettingDescription, kOsCrOS,
-     FEATURE_VALUE_TYPE(chromeos::features::kCrostiniShowMicSetting)},
     {"crostini-use-dlc", flag_descriptions::kCrostiniUseDlcName,
      flag_descriptions::kCrostiniUseDlcDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(chromeos::features::kCrostiniUseDlc)},
diff --git a/chrome/browser/accessibility/accessibility_ui.cc b/chrome/browser/accessibility/accessibility_ui.cc
index acec475..34e1fe5 100644
--- a/chrome/browser/accessibility/accessibility_ui.cc
+++ b/chrome/browser/accessibility/accessibility_ui.cc
@@ -262,7 +262,7 @@
 bool MatchesPropertyFilters(
     const std::vector<content::AccessibilityTreeFormatter::PropertyFilter>&
         property_filters,
-    const base::string16& text) {
+    const std::string& text) {
   bool allow = false;
   for (const auto& filter : property_filters) {
     if (base::MatchPattern(text, filter.match_str)) {
@@ -271,7 +271,7 @@
           allow = true;
           break;
         case content::AccessibilityTreeFormatter::PropertyFilter::ALLOW:
-          allow = (!base::MatchPattern(text, base::UTF8ToUTF16("*=''")));
+          allow = (!base::MatchPattern(text, "*=''"));
           break;
         case content::AccessibilityTreeFormatter::PropertyFilter::DENY:
           allow = false;
@@ -294,8 +294,7 @@
   std::vector<std::string> attributes = base::SplitString(
       line, " ", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
   for (std::string attribute : attributes) {
-    if (MatchesPropertyFilters(property_filters,
-                               base::UTF8ToUTF16(attribute))) {
+    if (MatchesPropertyFilters(property_filters, attribute)) {
       str += attribute + " ";
     }
   }
@@ -320,9 +319,7 @@
     content::AccessibilityTreeFormatter::PropertyFilter::Type type) {
   for (const std::string& attribute : base::SplitString(
            attributes, " ", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) {
-    property_filters.push_back(
-        content::AccessibilityTreeFormatter::PropertyFilter(
-            base::ASCIIToUTF16(attribute), type));
+    property_filters.emplace_back(attribute, type);
   }
 }
 
diff --git a/chrome/browser/apps/app_service/app_service_metrics.cc b/chrome/browser/apps/app_service/app_service_metrics.cc
index cbac677f..4bc9c8c6 100644
--- a/chrome/browser/apps/app_service/app_service_metrics.cc
+++ b/chrome/browser/apps/app_service/app_service_metrics.cc
@@ -132,6 +132,11 @@
       base::UmaHistogramEnumeration("Apps.DefaultAppLaunch.FromSharesheet",
                                     default_app_name);
       break;
+    case apps::mojom::LaunchSource::kFromReleaseNotesNotification:
+      base::UmaHistogramEnumeration(
+          "Apps.DefaultAppLaunch.FromReleaseNotesNotification",
+          default_app_name);
+      break;
   }
 }
 
@@ -164,6 +169,7 @@
     case apps::mojom::LaunchSource::kFromTest:
     case apps::mojom::LaunchSource::kFromArc:
     case apps::mojom::LaunchSource::kFromSharesheet:
+    case apps::mojom::LaunchSource::kFromReleaseNotesNotification:
       break;
   }
 }
diff --git a/chrome/browser/apps/app_service/app_service_proxy.cc b/chrome/browser/apps/app_service/app_service_proxy.cc
index 1ca9634..96fcbe1 100644
--- a/chrome/browser/apps/app_service/app_service_proxy.cc
+++ b/chrome/browser/apps/app_service/app_service_proxy.cc
@@ -508,20 +508,20 @@
 #endif
 
 std::vector<std::string> AppServiceProxy::GetAppIdsForUrl(const GURL& url) {
-  auto app_id_and_activities =
+  auto intent_launch_info =
       GetAppsForIntent(apps_util::CreateIntentFromUrl(url));
   std::vector<std::string> app_ids;
-  for (auto& app_id_and_activity : app_id_and_activities) {
-    app_ids.push_back(std::move(app_id_and_activity.app_id));
+  for (auto& entry : intent_launch_info) {
+    app_ids.push_back(std::move(entry.app_id));
   }
   return app_ids;
 }
 
-std::vector<AppIdAndActivityName> AppServiceProxy::GetAppsForIntent(
+std::vector<IntentLaunchInfo> AppServiceProxy::GetAppsForIntent(
     const apps::mojom::IntentPtr& intent) {
-  std::vector<AppIdAndActivityName> app_id_and_activities;
+  std::vector<IntentLaunchInfo> intent_launch_info;
   if (app_service_.is_bound()) {
-    cache_.ForEachApp([&app_id_and_activities,
+    cache_.ForEachApp([&intent_launch_info,
                        &intent](const apps::AppUpdate& update) {
       if (update.Readiness() == apps::mojom::Readiness::kUninstalledByUser) {
         return;
@@ -529,28 +529,30 @@
       std::set<std::string> existing_activities;
       for (const auto& filter : update.IntentFilters()) {
         if (apps_util::IntentMatchesFilter(intent, filter)) {
-          AppIdAndActivityName app_id_and_activity;
-          app_id_and_activity.app_id = update.AppId();
-          std::string activity_name;
-          if (filter->activity_name && !filter->activity_name.value().empty()) {
-            activity_name = filter->activity_name.value();
+          IntentLaunchInfo entry;
+          entry.app_id = update.AppId();
+          std::string activity_label;
+          if (filter->activity_label &&
+              !filter->activity_label.value().empty()) {
+            activity_label = filter->activity_label.value();
           } else {
-            activity_name = update.Name();
+            activity_label = update.Name();
           }
-          if (base::Contains(existing_activities, activity_name)) {
+          if (base::Contains(existing_activities, activity_label)) {
             continue;
           }
-          existing_activities.insert(activity_name);
-          app_id_and_activity.activity_name = activity_name;
-          app_id_and_activities.push_back(app_id_and_activity);
+          existing_activities.insert(activity_label);
+          entry.activity_label = activity_label;
+          entry.activity_name = filter->activity_name.value_or("");
+          intent_launch_info.push_back(entry);
         }
       }
     });
   }
-  return app_id_and_activities;
+  return intent_launch_info;
 }
 
-std::vector<AppIdAndActivityName> AppServiceProxy::GetAppsForFiles(
+std::vector<IntentLaunchInfo> AppServiceProxy::GetAppsForFiles(
     const std::vector<GURL>& filesystem_urls,
     const std::vector<std::string>& mime_types) {
   return GetAppsForIntent(
diff --git a/chrome/browser/apps/app_service/app_service_proxy.h b/chrome/browser/apps/app_service/app_service_proxy.h
index 22b858d..321d3c2 100644
--- a/chrome/browser/apps/app_service/app_service_proxy.h
+++ b/chrome/browser/apps/app_service/app_service_proxy.h
@@ -56,9 +56,10 @@
 };
 #endif
 
-struct AppIdAndActivityName {
+struct IntentLaunchInfo {
   std::string app_id;
   std::string activity_name;
+  std::string activity_label;
 };
 
 // Singleton (per Profile) proxy and cache of an App Service's apps.
@@ -223,12 +224,12 @@
 
   // Returns a list of apps (represented by their ids) and activities (if
   // applied) which can handle |intent|.
-  std::vector<AppIdAndActivityName> GetAppsForIntent(
+  std::vector<IntentLaunchInfo> GetAppsForIntent(
       const apps::mojom::IntentPtr& intent);
 
   // Returns a list of apps (represented by their ids) and activities (if
   // applied) which can handle |filesystem_urls| and |mime_types|.
-  std::vector<AppIdAndActivityName> GetAppsForFiles(
+  std::vector<IntentLaunchInfo> GetAppsForFiles(
       const std::vector<GURL>& filesystem_urls,
       const std::vector<std::string>& mime_types);
 
diff --git a/chrome/browser/apps/app_service/arc_apps.cc b/chrome/browser/apps/app_service/arc_apps.cc
index a01de13..c1d59c2 100644
--- a/chrome/browser/apps/app_service/arc_apps.cc
+++ b/chrome/browser/apps/app_service/arc_apps.cc
@@ -323,6 +323,9 @@
     if (!arc_intent_filter.activity_name().empty()) {
       intent_filter->activity_name = arc_intent_filter.activity_name();
     }
+    if (!arc_intent_filter.activity_label().empty()) {
+      intent_filter->activity_label = arc_intent_filter.activity_label();
+    }
   }
 
   return intent_filter;
@@ -512,8 +515,6 @@
   arc::mojom::OpenUrlsRequestPtr request = arc::mojom::OpenUrlsRequest::New();
   request->action_type = GetArcActionType(intent->action.value());
   request->activity_name = activity.Clone();
-  // Set activity name to empty to avoid launching the main activity.
-  request->activity_name->activity_name = "";
   for (const auto& content_url : content_urls) {
     arc::mojom::ContentUrlWithMimeTypePtr url_with_type =
         arc::mojom::ContentUrlWithMimeType::New();
@@ -756,7 +757,10 @@
     arc::mojom::ActivityNamePtr activity = arc::mojom::ActivityName::New();
     activity->package_name = app_info->package_name;
     activity->activity_name = app_info->activity;
-
+    if (intent->activity_name.has_value() &&
+        !intent->activity_name.value().empty()) {
+      activity->activity_name = intent->activity_name.value();
+    }
     // At the moment, the only case we have mime_type field set is to share
     // files, in this case, convert the file urls to content urls and use
     // arc file system instance to launch the app with files.
diff --git a/chrome/browser/apps/app_service/built_in_chromeos_apps.cc b/chrome/browser/apps/app_service/built_in_chromeos_apps.cc
index 84da674a..6f0831f 100644
--- a/chrome/browser/apps/app_service/built_in_chromeos_apps.cc
+++ b/chrome/browser/apps/app_service/built_in_chromeos_apps.cc
@@ -148,7 +148,7 @@
   } else if (app_id == ash::kReleaseNotesAppId) {
     base::RecordAction(
         base::UserMetricsAction("ReleaseNotes.SuggestionChipLaunched"));
-    chrome::LaunchReleaseNotes(profile_);
+    chrome::LaunchReleaseNotes(profile_, launch_source);
   }
 }
 
diff --git a/chrome/browser/apps/app_service/extension_apps_base.cc b/chrome/browser/apps/app_service/extension_apps_base.cc
index 198bf263..b16ea11 100644
--- a/chrome/browser/apps/app_service/extension_apps_base.cc
+++ b/chrome/browser/apps/app_service/extension_apps_base.cc
@@ -133,6 +133,7 @@
     case apps::mojom::LaunchSource::kFromTest:
     case apps::mojom::LaunchSource::kFromArc:
     case apps::mojom::LaunchSource::kFromSharesheet:
+    case apps::mojom::LaunchSource::kFromReleaseNotesNotification:
       return ash::LAUNCH_FROM_UNKNOWN;
   }
 }
@@ -530,6 +531,7 @@
     case apps::mojom::LaunchSource::kFromTest:
     case apps::mojom::LaunchSource::kFromArc:
     case apps::mojom::LaunchSource::kFromSharesheet:
+    case apps::mojom::LaunchSource::kFromReleaseNotesNotification:
       break;
   }
 
diff --git a/chrome/browser/apps/app_service/launch_utils.cc b/chrome/browser/apps/app_service/launch_utils.cc
index 9b87d65d..9de5f62 100644
--- a/chrome/browser/apps/app_service/launch_utils.cc
+++ b/chrome/browser/apps/app_service/launch_utils.cc
@@ -201,6 +201,7 @@
     case apps::mojom::LaunchSource::kFromFileManager:
       return apps::mojom::AppLaunchSource::kSourceFileHandler;
     case apps::mojom::LaunchSource::kFromChromeInternal:
+    case apps::mojom::LaunchSource::kFromReleaseNotesNotification:
       return apps::mojom::AppLaunchSource::kSourceChromeInternal;
     case apps::mojom::LaunchSource::kFromInstalledNotification:
       return apps::mojom::AppLaunchSource::kSourceInstalledNotification;
diff --git a/chrome/browser/apps/app_service/web_apps_base.cc b/chrome/browser/apps/app_service/web_apps_base.cc
index 3d083f8..ba4abc6 100644
--- a/chrome/browser/apps/app_service/web_apps_base.cc
+++ b/chrome/browser/apps/app_service/web_apps_base.cc
@@ -257,6 +257,7 @@
     case apps::mojom::LaunchSource::kFromTest:
     case apps::mojom::LaunchSource::kFromArc:
     case apps::mojom::LaunchSource::kFromSharesheet:
+    case apps::mojom::LaunchSource::kFromReleaseNotesNotification:
       break;
   }
 
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 379fd3f..94fe22a 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -119,6 +119,7 @@
     "//chromeos/components/print_management/mojom",
     "//chromeos/components/proximity_auth",
     "//chromeos/components/quick_answers/public/cpp:prefs",
+    "//chromeos/components/remote_apps/mojom",
     "//chromeos/components/scanning",
     "//chromeos/components/smbfs",
     "//chromeos/components/smbfs/mojom",
@@ -2421,12 +2422,15 @@
     "release_notes/release_notes_storage.h",
     "remote_apps/id_generator.cc",
     "remote_apps/id_generator.h",
+    "remote_apps/remote_apps_impl.cc",
+    "remote_apps/remote_apps_impl.h",
     "remote_apps/remote_apps_manager.cc",
     "remote_apps/remote_apps_manager.h",
     "remote_apps/remote_apps_manager_factory.cc",
     "remote_apps/remote_apps_manager_factory.h",
     "remote_apps/remote_apps_model.cc",
     "remote_apps/remote_apps_model.h",
+    "remote_apps/remote_apps_types.h",
     "reset/metrics.h",
     "scanning/lorgnette_scanner_manager.cc",
     "scanning/lorgnette_scanner_manager.h",
@@ -2625,6 +2629,8 @@
     "ui/passphrase_textfield.h",
     "ui/request_pin_view.cc",
     "ui/request_pin_view.h",
+    "ui/request_system_proxy_credentials_view.cc",
+    "ui/request_system_proxy_credentials_view.h",
     "ui/screen_capture_notification_ui_chromeos.cc",
     "ui/screen_capture_notification_ui_chromeos.h",
     "ui/tpm_auto_update_notification.cc",
@@ -3539,6 +3545,7 @@
     "ui/low_disk_notification_unittest.cc",
     "ui/mock_adb_sideloading_policy_change_notification.cc",
     "ui/mock_adb_sideloading_policy_change_notification.h",
+    "ui/request_system_proxy_credentials_view_unittest.cc",
     "usb/cros_usb_detector_unittest.cc",
     "wilco_dtc_supportd/testing_wilco_dtc_supportd_bridge_wrapper.cc",
     "wilco_dtc_supportd/testing_wilco_dtc_supportd_bridge_wrapper.h",
diff --git a/chrome/browser/chromeos/app_mode/web_app/web_kiosk_app_data.cc b/chrome/browser/chromeos/app_mode/web_app/web_kiosk_app_data.cc
index 399b4bc..3b03b73 100644
--- a/chrome/browser/chromeos/app_mode/web_app/web_kiosk_app_data.cc
+++ b/chrome/browser/chromeos/app_mode/web_app/web_kiosk_app_data.cc
@@ -28,6 +28,7 @@
 constexpr int kMaxIconFileSize = (2 * kIconSize) * (2 * kIconSize) * 4 + 1000;
 
 const char kKeyLaunchUrl[] = "launch_url";
+const char kKeyLastIconUrl[] = "last_icon_url";
 
 // Resizes image into other size on blocking I/O thread.
 SkBitmap ResizeImageBlocking(const SkBitmap& image, int target_size) {
@@ -169,6 +170,11 @@
                           /* lazy_icon_load= */ true))
     return false;
 
+  // If the icon was previously downloaded using a different url, do not use
+  // that icon.
+  if (GetLastIconUrl(*dict) != icon_url_)
+    return false;
+
   if (LoadLaunchUrlFromDictionary(*dict)) {
     SetStatus(STATUS_INSTALLED);
     return true;
@@ -184,7 +190,7 @@
   if (!icon_.isNull())
     return;
 
-  // We already had some icon cached, it is time to decode it.
+  // Decode the icon if one is already cached.
   if (status_ != STATUS_INIT) {
     DecodeIcon();
     return;
@@ -252,6 +258,17 @@
   return true;
 }
 
+GURL WebKioskAppData::GetLastIconUrl(const base::Value& dict) const {
+  // All the previous keys should be present since this function is executed
+  // after LoadFromDictionary().
+  const std::string* icon_url_string =
+      dict.FindDictKey(KioskAppDataBase::kKeyApps)
+          ->FindDictKey(app_id())
+          ->FindStringKey(kKeyLastIconUrl);
+
+  return icon_url_string ? GURL(*icon_url_string) : GURL();
+}
+
 void WebKioskAppData::OnDidDownloadIcon(const SkBitmap& icon) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
@@ -270,6 +287,10 @@
   DictionaryPrefUpdate dict_update(local_state, dictionary_name());
   SaveIconToDictionary(dict_update);
 
+  dict_update->FindDictKey(KioskAppDataBase::kKeyApps)
+      ->FindDictKey(app_id())
+      ->SetStringKey(kKeyLastIconUrl, launch_url_.spec());
+
   SetStatus(STATUS_LOADED);
 }
 
diff --git a/chrome/browser/chromeos/app_mode/web_app/web_kiosk_app_data.h b/chrome/browser/chromeos/app_mode/web_app/web_kiosk_app_data.h
index 58f781e..fc4da11e 100644
--- a/chrome/browser/chromeos/app_mode/web_app/web_kiosk_app_data.h
+++ b/chrome/browser/chromeos/app_mode/web_app/web_kiosk_app_data.h
@@ -62,12 +62,16 @@
 
   bool LoadLaunchUrlFromDictionary(const base::Value& dict);
 
+  // Returns the icon url of the icon that was being provided during previous
+  // session.
+  GURL GetLastIconUrl(const base::Value& dict) const;
+
   KioskAppDataDelegate* delegate_;  // not owned.
   Status status_;
   const GURL install_url_;  // installation url.
   GURL launch_url_;         // app launch url.
 
-  const GURL icon_url_;  // Url of the icon in case nothing is cached.
+  GURL icon_url_;  // Url of the icon in case nothing is cached.
   // Used to download icon from |icon_url_|.
   std::unique_ptr<IconFetcher> icon_fetcher_;
 
diff --git a/chrome/browser/chromeos/app_mode/web_app/web_kiosk_app_data_browsertest.cc b/chrome/browser/chromeos/app_mode/web_app/web_kiosk_app_data_browsertest.cc
index ecb50b8..08565b3 100644
--- a/chrome/browser/chromeos/app_mode/web_app/web_kiosk_app_data_browsertest.cc
+++ b/chrome/browser/chromeos/app_mode/web_app/web_kiosk_app_data_browsertest.cc
@@ -32,6 +32,8 @@
 const char kLaunchUrlKey[] = "launch_url";
 const char kIconPath[] = "chrome/test/data/load_image/image.png";
 const char kIconUrl[] = "/load_image/image.png";
+const char kIconUrl2[] = "/load_image/fail_image.png";
+const char kLastIconUrlKey[] = "last_icon_url";
 const char kLaunchUrl[] = "https://example.com/launch";
 
 base::FilePath GetFullPathToImage() {
@@ -123,7 +125,6 @@
   test_server.AddDefaultHandlers(GetChromeTestDataDir());
   ASSERT_TRUE(test_server.Start());
 
-  // SetCachedNameAndIcon();
   WebKioskAppData app_data(this, kAppId, EmptyAccountId(), GURL(kAppUrl),
                            kAppTitle,
                            /*icon_url*/ test_server.GetURL(kIconUrl));
@@ -138,9 +139,23 @@
 IN_PROC_BROWSER_TEST_F(WebKioskAppDataTest, DownloadedIconPersists) {
   // No test server is launched intentionaly to verify that we are using the
   // cached icon.
+  // We should still find the correct icon url in order to not initiate a
+  // redownload.
+  const std::string* icon_url_string =
+      g_browser_process->local_state()
+          ->GetDictionary(WebKioskAppManager::kWebKioskDictionaryName)
+          ->FindDictKey(KioskAppDataBase::kKeyApps)
+          ->FindDictKey(kAppId)
+          ->FindStringKey(kLastIconUrlKey);
+  ASSERT_TRUE(icon_url_string);
+  const GURL icon_url = GURL(*icon_url_string);
+
   WebKioskAppData app_data(this, kAppId, EmptyAccountId(), GURL(kAppUrl),
-                           kAppTitle2, /*icon_url=*/GURL());
+                           kAppTitle2, /*icon_url=*/icon_url);
   app_data.LoadFromCache();
+  // Icon is stored in cache.
+  EXPECT_EQ(app_data.status(), WebKioskAppData::STATUS_LOADING);
+
   app_data.LoadIcon();
   WaitForAppDataChange(2);
 
@@ -149,6 +164,45 @@
   EXPECT_EQ(app_data.name(), kAppTitle2);
 }
 
+IN_PROC_BROWSER_TEST_F(WebKioskAppDataTest,
+                       PRE_RedownloadIconWhenDifferentUrl) {
+  // Start test server.
+  net::EmbeddedTestServer test_server;
+  test_server.AddDefaultHandlers(GetChromeTestDataDir());
+  ASSERT_TRUE(test_server.Start());
+
+  WebKioskAppData app_data(this, kAppId, EmptyAccountId(), GURL(kAppUrl),
+                           kAppTitle,
+                           /*icon_url*/ test_server.GetURL(kIconUrl));
+  app_data.LoadFromCache();
+  app_data.LoadIcon();
+  WaitForAppDataChange(1);
+
+  EXPECT_EQ(app_data.status(), WebKioskAppData::STATUS_LOADED);
+  EXPECT_EQ(app_data.name(), kAppTitle);
+}
+
+IN_PROC_BROWSER_TEST_F(WebKioskAppDataTest, RedownloadIconWhenDifferentUrl) {
+  // Start test server.
+  net::EmbeddedTestServer test_server;
+  test_server.AddDefaultHandlers(GetChromeTestDataDir());
+  ASSERT_TRUE(test_server.Start());
+
+  WebKioskAppData app_data(this, kAppId, EmptyAccountId(), GURL(kAppUrl),
+                           kAppTitle2,
+                           /*icon_url*/ test_server.GetURL(kIconUrl2));
+
+  app_data.LoadFromCache();
+  // No icon was loaded from cache because urls are different.
+  EXPECT_EQ(app_data.status(), WebKioskAppData::STATUS_INIT);
+
+  app_data.LoadIcon();
+  WaitForAppDataChange(1);
+
+  EXPECT_EQ(app_data.status(), WebKioskAppData::STATUS_LOADED);
+  EXPECT_EQ(app_data.name(), kAppTitle2);
+}
+
 IN_PROC_BROWSER_TEST_F(WebKioskAppDataTest, AlreadyInstalled) {
   SetCached(/*installed = */ true);
   WebKioskAppData app_data(this, kAppId, EmptyAccountId(), GURL(kAppUrl),
diff --git a/chrome/browser/chromeos/crostini/crostini_features.cc b/chrome/browser/chromeos/crostini/crostini_features.cc
index 6dd61d08..329e6f8 100644
--- a/chrome/browser/chromeos/crostini/crostini_features.cc
+++ b/chrome/browser/chromeos/crostini/crostini_features.cc
@@ -315,11 +315,6 @@
 }
 
 bool CrostiniFeatures::IsPortForwardingAllowed(Profile* profile) {
-  if (!base::FeatureList::IsEnabled(
-          chromeos::features::kCrostiniPortForwarding)) {
-    VLOG(1) << "kCrostiniPortForwarding chromeos feature is disabled.";
-    return false;
-  }
   if (!profile->GetPrefs()->GetBoolean(
           crostini::prefs::kCrostiniPortForwardingAllowedByPolicy)) {
     VLOG(1) << "kCrostiniPortForwardingAllowedByPolicy preference is false.";
diff --git a/chrome/browser/chromeos/crostini/crostini_features_unittest.cc b/chrome/browser/chromeos/crostini/crostini_features_unittest.cc
index 3cf1871..e059db6 100644
--- a/chrome/browser/chromeos/crostini/crostini_features_unittest.cc
+++ b/chrome/browser/chromeos/crostini/crostini_features_unittest.cc
@@ -297,11 +297,6 @@
   content::BrowserTaskEnvironment task_environment;
   TestingProfile profile;
   FakeCrostiniFeatures crostini_features;
-  base::test::ScopedFeatureList scoped_feature_list;
-
-  // Enable feature.
-  scoped_feature_list.InitWithFeatures(
-      {chromeos::features::kCrostiniPortForwarding}, {});
 
   // Default case.
   EXPECT_TRUE(crostini_features.IsPortForwardingAllowed(&profile));
@@ -319,11 +314,6 @@
   content::BrowserTaskEnvironment task_environment;
   TestingProfile profile;
   FakeCrostiniFeatures crostini_features;
-  base::test::ScopedFeatureList scoped_feature_list;
-
-  // Enable feature.
-  scoped_feature_list.InitWithFeatures(
-      {chromeos::features::kCrostiniPortForwarding}, {});
 
   // Set pref to false.
   profile.GetTestingPrefService()->SetManagedPref(
@@ -332,20 +322,6 @@
 
   // Disallowed.
   EXPECT_FALSE(crostini_features.IsPortForwardingAllowed(&profile));
-
-  // Disable feature.
-  scoped_feature_list.Reset();
-  scoped_feature_list.InitWithFeatures(
-      {}, {chromeos::features::kCrostiniPortForwarding});
-  // Disallowed.
-  EXPECT_FALSE(crostini_features.IsPortForwardingAllowed(&profile));
-
-  // Set pref to true.
-  profile.GetTestingPrefService()->SetManagedPref(
-      crostini::prefs::kCrostiniPortForwardingAllowedByPolicy,
-      std::make_unique<base::Value>(true));
-  // Disallowed.
-  EXPECT_FALSE(crostini_features.IsPortForwardingAllowed(&profile));
 }
 
 }  // namespace crostini
diff --git a/chrome/browser/chromeos/crostini/crostini_manager.cc b/chrome/browser/chromeos/crostini/crostini_manager.cc
index 9d6973b..279f3e34 100644
--- a/chrome/browser/chromeos/crostini/crostini_manager.cc
+++ b/chrome/browser/chromeos/crostini/crostini_manager.cc
@@ -139,16 +139,6 @@
   container_callbacks->erase(range.first, range.second);
 }
 
-bool IsMicSharingEnabled(Profile* profile, bool crostini_mic_sharing_enabled) {
-  if (base::FeatureList::IsEnabled(
-          chromeos::features::kCrostiniShowMicSetting) &&
-      profile->GetPrefs()->GetBoolean(::prefs::kAudioCaptureAllowed) &&
-      crostini_mic_sharing_enabled) {
-    return true;
-  }
-  return false;
-}
-
 void EmitCorruptionStateMetric(CorruptionStates state) {
   base::UmaHistogramEnumeration("Crostini.FilesystemCorruption", state);
 }
@@ -1210,9 +1200,8 @@
   request.set_owner_id(owner_id_);
   if (base::FeatureList::IsEnabled(chromeos::features::kCrostiniGpuSupport))
     request.set_enable_gpu(true);
-  bool is_mic_sharing_enabled =
-      IsMicSharingEnabled(profile_, crostini_mic_sharing_enabled_);
-  if (is_mic_sharing_enabled) {
+  if (crostini_mic_sharing_enabled_ &&
+      profile_->GetPrefs()->GetBoolean(::prefs::kAudioCaptureAllowed)) {
     request.set_enable_audio_capture(true);
   }
   const int32_t cpus = base::SysInfo::NumberOfProcessors() - num_cores_disabled;
diff --git a/chrome/browser/chromeos/file_manager/app_service_file_tasks.cc b/chrome/browser/chromeos/file_manager/app_service_file_tasks.cc
index b22ac55..ac1f166 100644
--- a/chrome/browser/chromeos/file_manager/app_service_file_tasks.cc
+++ b/chrome/browser/chromeos/file_manager/app_service_file_tasks.cc
@@ -77,16 +77,16 @@
   std::vector<std::string> mime_types;
   for (auto& entry : entries)
     mime_types.push_back(entry.mime_type);
-  std::vector<apps::AppIdAndActivityName> app_id_and_activities =
+  std::vector<apps::IntentLaunchInfo> intent_launch_info =
       proxy->GetAppsForFiles(file_urls, mime_types);
 
   std::string task_action_id =
       entries.size() == 1 ? kActionIdSend : kActionIdSendMultiple;
   using extensions::api::file_manager_private::Verb;
   // TODO(crbug/1092784): Support file open with in the future.
-  for (auto& app_id_and_activity : app_id_and_activities) {
+  for (auto& launch_entry : intent_launch_info) {
     apps::mojom::AppType app_type =
-        proxy->AppRegistryCache().GetAppType(app_id_and_activity.app_id);
+        proxy->AppRegistryCache().GetAppType(launch_entry.app_id);
     // TODO(crbug/1092784): Only going to support ARC app and web app.
     if (!(app_type == apps::mojom::AppType::kArc ||
           app_type == apps::mojom::AppType::kWeb)) {
@@ -95,11 +95,11 @@
 
     constexpr int kIconSize = 32;
     GURL icon_url =
-        apps::AppIconSource::GetIconURL(app_id_and_activity.app_id, kIconSize);
+        apps::AppIconSource::GetIconURL(launch_entry.app_id, kIconSize);
     result_list->push_back(FullTaskDescriptor(
-        TaskDescriptor(app_id_and_activity.app_id, GetTaskType(app_type),
+        TaskDescriptor(launch_entry.app_id, GetTaskType(app_type),
                        task_action_id),
-        app_id_and_activity.activity_name, Verb::VERB_SHARE_WITH, icon_url,
+        launch_entry.activity_label, Verb::VERB_SHARE_WITH, icon_url,
         /* is_default=*/false,
         /* is_generic=*/true,
         /* is_file_extension_match=*/false));
diff --git a/chrome/browser/chromeos/file_manager/app_service_file_tasks_unittest.cc b/chrome/browser/chromeos/file_manager/app_service_file_tasks_unittest.cc
index 139fa74b..eeb164f 100644
--- a/chrome/browser/chromeos/file_manager/app_service_file_tasks_unittest.cc
+++ b/chrome/browser/chromeos/file_manager/app_service_file_tasks_unittest.cc
@@ -32,9 +32,9 @@
 const char kMimeTypeText[] = "text/plain";
 const char kMimeTypeImage[] = "image/jpeg";
 const char kMimeTypeAny[] = "*/*";
-const char kActivityNameText[] = "some_text_activity";
-const char kActivityNameImage[] = "some_image_activity";
-const char kActivityNameAny[] = "some_any_file";
+const char kActivityLabelText[] = "some_text_activity";
+const char kActivityLabelImage[] = "some_image_activity";
+const char kActivityLabelAny[] = "some_any_file";
 }  // namespace
 
 namespace file_manager {
@@ -58,7 +58,7 @@
 
   void AddFakeAppWithIntentFilter(const std::string& app_id,
                                   const std::string& mime_type,
-                                  const std::string& activity_name,
+                                  const std::string& activity_label,
                                   bool is_send_multiple) {
     std::vector<apps::mojom::AppPtr> apps;
     auto app = apps::mojom::App::New();
@@ -67,8 +67,8 @@
     auto intent_filter =
         is_send_multiple
             ? apps_util::CreateIntentFilterForSendMultiple(mime_type,
-                                                           activity_name)
-            : apps_util::CreateIntentFilterForSend(mime_type, activity_name);
+                                                           activity_label)
+            : apps_util::CreateIntentFilterForSend(mime_type, activity_label);
     app->intent_filters.push_back(std::move(intent_filter));
     apps.push_back(std::move(app));
     app_service_proxy_->AppRegistryCache().OnApps(std::move(apps));
@@ -76,11 +76,11 @@
   }
 
   void AddApps() {
-    AddFakeAppWithIntentFilter(kAppIdText, kMimeTypeText, kActivityNameText,
+    AddFakeAppWithIntentFilter(kAppIdText, kMimeTypeText, kActivityLabelText,
                                /*is_send_multiple=*/false);
-    AddFakeAppWithIntentFilter(kAppIdImage, kMimeTypeImage, kActivityNameImage,
+    AddFakeAppWithIntentFilter(kAppIdImage, kMimeTypeImage, kActivityLabelImage,
                                /*is_send_multiple=*/false);
-    AddFakeAppWithIntentFilter(kAppIdAny, kMimeTypeAny, kActivityNameAny,
+    AddFakeAppWithIntentFilter(kAppIdAny, kMimeTypeAny, kActivityLabelAny,
                                /*is_send_multiple=*/true);
   }
 
@@ -106,7 +106,7 @@
   FindAppServiceTasks(profile(), entries, file_urls, &tasks);
   ASSERT_EQ(1U, tasks.size());
   EXPECT_EQ(kAppIdText, tasks[0].task_descriptor().app_id);
-  EXPECT_EQ(kActivityNameText, tasks[0].task_title());
+  EXPECT_EQ(kActivityLabelText, tasks[0].task_title());
 }
 
 // Test that between an image app and text app, the image app can be
@@ -124,7 +124,7 @@
   FindAppServiceTasks(profile(), entries, file_urls, &tasks);
   ASSERT_EQ(1U, tasks.size());
   EXPECT_EQ(kAppIdImage, tasks[0].task_descriptor().app_id);
-  EXPECT_EQ(kActivityNameImage, tasks[0].task_title());
+  EXPECT_EQ(kActivityLabelImage, tasks[0].task_title());
 }
 
 // Test that between an image app, text app and an app that can handle every
@@ -145,7 +145,7 @@
   FindAppServiceTasks(profile(), entries, file_urls, &tasks);
   ASSERT_EQ(1U, tasks.size());
   EXPECT_EQ(kAppIdAny, tasks[0].task_descriptor().app_id);
-  EXPECT_EQ(kActivityNameAny, tasks[0].task_title());
+  EXPECT_EQ(kActivityLabelAny, tasks[0].task_title());
 }
 
 }  // namespace file_tasks
diff --git a/chrome/browser/chromeos/file_manager/file_manager_uitest.cc b/chrome/browser/chromeos/file_manager/file_manager_uitest.cc
index 38c5a68..f7a1d549 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_uitest.cc
+++ b/chrome/browser/chromeos/file_manager/file_manager_uitest.cc
@@ -23,13 +23,6 @@
     // --disable-web-security required to load resources from
     // files and from chrome://resources/... urls.
     command_line->AppendSwitch(switches::kDisableWebSecurity);
-
-    // TODO(yoichio): This is temporary switch to support chrome internal
-    // components migration from the old web APIs.
-    // After completion of the migration, we should remove this.
-    // See crbug.com/924873 for detail.
-    command_line->AppendSwitchASCII(switches::kDisableBlinkFeatures,
-                                    "ShadowDOMV0,CustomElementsV0,HTMLImports");
   }
 
   void RunTest(std::string test_scope) {
diff --git a/chrome/browser/chromeos/file_manager/web_file_tasks.cc b/chrome/browser/chromeos/file_manager/web_file_tasks.cc
index 3ed2085..e525d12 100644
--- a/chrome/browser/chromeos/file_manager/web_file_tasks.cc
+++ b/chrome/browser/chromeos/file_manager/web_file_tasks.cc
@@ -142,6 +142,14 @@
   for (const auto& file_system_url : file_system_urls)
     launch_files->file_paths.push_back(file_system_url.path());
 
+  // App Service doesn't exist in Incognito mode but apps can be
+  // launched (ie. default handler to open a download from its
+  // notification) from Incognito mode. Use the base profile in these
+  // cases (see crbug.com/1111695).
+  if (profile->IsOffTheRecord()) {
+    profile = profile->GetOriginalProfile();
+  }
+
   apps::AppServiceProxy* proxy =
       apps::AppServiceProxyFactory::GetForProfile(profile);
   proxy->LaunchAppWithFiles(
diff --git a/chrome/browser/chromeos/login/arc_terms_of_service_browsertest.cc b/chrome/browser/chromeos/login/arc_terms_of_service_browsertest.cc
index f0d1a17..53990c8a 100644
--- a/chrome/browser/chromeos/login/arc_terms_of_service_browsertest.cc
+++ b/chrome/browser/chromeos/login/arc_terms_of_service_browsertest.cc
@@ -215,7 +215,7 @@
   void LoginAsRegularUser() {
     SetUpExitCallback();
     login_manager_mixin_.LoginAsNewRegularUser();
-    OobeScreenExitWaiter(GaiaView::kScreenId).Wait();
+    OobeScreenExitWaiter(GetFirstSigninScreen()).Wait();
   }
 
   void ShowArcTosScreen() {
diff --git a/chrome/browser/chromeos/login/demo_mode/demo_setup_browsertest.cc b/chrome/browser/chromeos/login/demo_mode/demo_setup_browsertest.cc
index e74227f..3281753 100644
--- a/chrome/browser/chromeos/login/demo_mode/demo_setup_browsertest.cc
+++ b/chrome/browser/chromeos/login/demo_mode/demo_setup_browsertest.cc
@@ -583,7 +583,7 @@
   // TODO(agawronska): Progress dialog transition is async - extra work is
   // needed to be able to check it reliably.
 
-  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+  OobeScreenWaiter(GetFirstSigninScreen()).Wait();
   EXPECT_TRUE(StartupUtils::IsOobeCompleted());
   EXPECT_TRUE(StartupUtils::IsDeviceRegistered());
 }
@@ -671,7 +671,7 @@
   EXPECT_EQ("admin-fr@cros-demo-mode.com",
             DemoSetupController::GetSubOrganizationEmail());
 
-  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+  OobeScreenWaiter(GetFirstSigninScreen()).Wait();
   EXPECT_TRUE(StartupUtils::IsOobeCompleted());
   EXPECT_TRUE(StartupUtils::IsDeviceRegistered());
 }
@@ -931,7 +931,7 @@
   // TODO(agawronska): Progress dialog transition is async - extra work is
   // needed to be able to check it reliably.
 
-  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+  OobeScreenWaiter(GetFirstSigninScreen()).Wait();
   EXPECT_TRUE(StartupUtils::IsOobeCompleted());
   EXPECT_TRUE(StartupUtils::IsDeviceRegistered());
 }
@@ -1155,7 +1155,7 @@
                                       JSExecution::kAsync);
   // TODO(agawronska): Progress dialog transition is async - extra work is
   // needed to be able to check it reliably.
-  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+  OobeScreenWaiter(GetFirstSigninScreen()).Wait();
 }
 
 // Test is flaky: crbug.com/1099402
@@ -1337,7 +1337,7 @@
   // TODO(agawronska): Progress dialog transition is async - extra work is
   // needed to be able to check it reliably.
 
-  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+  OobeScreenWaiter(GetFirstSigninScreen()).Wait();
   EXPECT_TRUE(StartupUtils::IsOobeCompleted());
   EXPECT_TRUE(StartupUtils::IsDeviceRegistered());
 }
@@ -1372,7 +1372,7 @@
   // TODO(agawronska): Progress dialog transition is async - extra work is
   // needed to be able to check it reliably.
 
-  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+  OobeScreenWaiter(GetFirstSigninScreen()).Wait();
   EXPECT_TRUE(StartupUtils::IsOobeCompleted());
   EXPECT_TRUE(StartupUtils::IsDeviceRegistered());
 }
@@ -1408,7 +1408,7 @@
   // TODO(agawronska): Progress dialog transition is async - extra work is
   // needed to be able to check it reliably.
 
-  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+  OobeScreenWaiter(GetFirstSigninScreen()).Wait();
   EXPECT_TRUE(StartupUtils::IsOobeCompleted());
   EXPECT_TRUE(StartupUtils::IsDeviceRegistered());
 }
diff --git a/chrome/browser/chromeos/login/encryption_migration_browsertest.cc b/chrome/browser/chromeos/login/encryption_migration_browsertest.cc
index 4327385..0eac9fe 100644
--- a/chrome/browser/chromeos/login/encryption_migration_browsertest.cc
+++ b/chrome/browser/chromeos/login/encryption_migration_browsertest.cc
@@ -369,7 +369,7 @@
       cryptohome::DIRCRYPTO_MIGRATION_SUCCESS, 5 /*current*/, 5 /*total*/);
   EXPECT_EQ(0, FakePowerManagerClient::Get()->num_request_restart_calls());
 
-  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+  OobeScreenWaiter(GetFirstSigninScreen()).Wait();
 }
 
 IN_PROC_BROWSER_TEST_F(EncryptionMigrationTest,
@@ -449,7 +449,7 @@
   SetUpStubAuthenticatorAndAttemptLogin(false /* has_incomplete_migration */);
 
   // Wipe is expected to wipe the cryptohome, and force online login.
-  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+  OobeScreenWaiter(GetFirstSigninScreen()).Wait();
 
   EXPECT_FALSE(FakeCryptohomeClient::Get()
                    ->get_id_for_disk_migrated_to_dircrypto()
diff --git a/chrome/browser/chromeos/login/enrollment/enrollment_local_policy_server_browsertest.cc b/chrome/browser/chromeos/login/enrollment/enrollment_local_policy_server_browsertest.cc
index bbeb374..7dd09302 100644
--- a/chrome/browser/chromeos/login/enrollment/enrollment_local_policy_server_browsertest.cc
+++ b/chrome/browser/chromeos/login/enrollment/enrollment_local_policy_server_browsertest.cc
@@ -463,7 +463,7 @@
   auto login_waiter = CreateLoginVisibleWaiter();
   enrollment_ui_.LeaveDeviceAttributeErrorScreen();
   login_waiter->Wait();
-  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+  OobeScreenWaiter(GetFirstSigninScreen()).Wait();
 }
 
 // Error during enrollment : Error fetching policy : 500 server error.
@@ -515,7 +515,7 @@
 // No state keys on the server. Auto enrollment check should proceed to login.
 IN_PROC_BROWSER_TEST_F(AutoEnrollmentLocalPolicyServer, AutoEnrollmentCheck) {
   host()->StartWizard(AutoEnrollmentCheckScreenView::kScreenId);
-  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+  OobeScreenWaiter(GetFirstSigninScreen()).Wait();
 }
 
 // State keys are present but restore mode is not requested.
@@ -525,7 +525,7 @@
       enterprise_management::DeviceStateRetrievalResponse::RESTORE_MODE_NONE,
       test::kTestDomain));
   host()->StartWizard(AutoEnrollmentCheckScreenView::kScreenId);
-  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+  OobeScreenWaiter(GetFirstSigninScreen()).Wait();
 }
 
 // Reenrollment requested. User can skip.
@@ -538,7 +538,7 @@
   host()->StartWizard(AutoEnrollmentCheckScreenView::kScreenId);
   OobeScreenWaiter(EnrollmentScreenView::kScreenId).Wait();
   enrollment_screen()->OnCancel();
-  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+  OobeScreenWaiter(GetFirstSigninScreen()).Wait();
 }
 
 // Reenrollment forced. User can not skip.
@@ -595,7 +595,7 @@
 // normal signin.
 IN_PROC_BROWSER_TEST_F(AutoEnrollmentNoStateKeys, NotRequired) {
   host()->StartWizard(AutoEnrollmentCheckScreenView::kScreenId);
-  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+  OobeScreenWaiter(GetFirstSigninScreen()).Wait();
 }
 
 // FRE explicitly not required in VPD, so it should not even contact the policy
@@ -611,7 +611,7 @@
       test::kTestDomain));
 
   host()->StartWizard(AutoEnrollmentCheckScreenView::kScreenId);
-  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+  OobeScreenWaiter(GetFirstSigninScreen()).Wait();
 }
 
 // FRE is not required when VPD is valid and activate date is not there.
@@ -624,7 +624,7 @@
       test::kTestDomain));
 
   host()->StartWizard(AutoEnrollmentCheckScreenView::kScreenId);
-  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+  OobeScreenWaiter(GetFirstSigninScreen()).Wait();
 }
 
 // FRE is required when VPD is valid and activate date is there.
@@ -806,7 +806,7 @@
   TriggerEnrollmentAndSignInSuccessfully();
   enrollment_ui_.WaitForStep(test::ui::kEnrollmentStepSuccess);
   ConfirmAndWaitLoginScreen();
-  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+  OobeScreenWaiter(GetFirstSigninScreen()).Wait();
 
   ASSERT_EQ(GetParam(),
             user_manager::UserManager::Get()->IsGuestSessionAllowed());
diff --git a/chrome/browser/chromeos/login/enrollment/enrollment_screen_browsertest.cc b/chrome/browser/chromeos/login/enrollment/enrollment_screen_browsertest.cc
index 074dce30..b3a6fe1 100644
--- a/chrome/browser/chromeos/login/enrollment/enrollment_screen_browsertest.cc
+++ b/chrome/browser/chromeos/login/enrollment/enrollment_screen_browsertest.cc
@@ -19,7 +19,6 @@
 #include "chrome/browser/chromeos/login/ui/login_display_host.h"
 #include "chrome/browser/chromeos/login/ui/webui_login_view.h"
 #include "chrome/browser/chromeos/login/wizard_controller.h"
-#include "chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h"
 #include "chromeos/constants/chromeos_switches.h"
 #include "chromeos/test/chromeos_test_utils.h"
 #include "content/public/test/browser_test.h"
@@ -205,7 +204,7 @@
 // Tests that enrollment screen could be triggered after OOBE completed and
 // Chrome restarted (or device rebooted).
 IN_PROC_BROWSER_TEST_F(OobeCompletedUnownedTest, TriggerEnrollment) {
-  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+  OobeScreenWaiter(GetFirstSigninScreen()).Wait();
   LoginDisplayHost::default_host()->StartWizard(
       EnrollmentScreenView::kScreenId);
   OobeScreenWaiter(EnrollmentScreenView::kScreenId).Wait();
diff --git a/chrome/browser/chromeos/login/existing_user_controller_browsertest.cc b/chrome/browser/chromeos/login/existing_user_controller_browsertest.cc
index 46231cd9..7da43c5b4 100644
--- a/chrome/browser/chromeos/login/existing_user_controller_browsertest.cc
+++ b/chrome/browser/chromeos/login/existing_user_controller_browsertest.cc
@@ -29,6 +29,7 @@
 #include "chrome/browser/chromeos/login/session/user_session_manager_test_api.h"
 #include "chrome/browser/chromeos/login/test/js_checker.h"
 #include "chrome/browser/chromeos/login/test/login_manager_mixin.h"
+#include "chrome/browser/chromeos/login/test/oobe_base_test.h"
 #include "chrome/browser/chromeos/login/test/oobe_screen_waiter.h"
 #include "chrome/browser/chromeos/login/ui/mock_login_display.h"
 #include "chrome/browser/chromeos/login/ui/mock_login_display_host.h"
@@ -1080,7 +1081,7 @@
                        CryptohomeMissing) {
   SetUpStubAuthenticatorAndAttemptLogin(AuthFailure::MISSING_CRYPTOHOME);
 
-  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+  OobeScreenWaiter(OobeBaseTest::GetFirstSigninScreen()).Wait();
   const user_manager::User* user =
       user_manager::UserManager::Get()->FindUser(test_user_.account_id);
   ASSERT_TRUE(user);
diff --git a/chrome/browser/chromeos/login/kiosk_browsertest.cc b/chrome/browser/chromeos/login/kiosk_browsertest.cc
index ec5f706..a86ff90e 100644
--- a/chrome/browser/chromeos/login/kiosk_browsertest.cc
+++ b/chrome/browser/chromeos/login/kiosk_browsertest.cc
@@ -80,10 +80,12 @@
 #include "chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/kiosk_autolaunch_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/kiosk_enable_screen_handler.h"
+#include "chrome/browser/ui/webui/chromeos/login/user_creation_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/welcome_screen_handler.h"
 #include "chrome/common/chrome_constants.h"
 #include "chrome/common/chrome_paths.h"
 #include "chrome/common/pref_names.h"
+#include "chromeos/constants/chromeos_features.h"
 #include "chromeos/constants/chromeos_switches.h"
 #include "chromeos/dbus/cryptohome/cryptohome_client.h"
 #include "chromeos/disks/disk_mount_manager.h"
@@ -587,6 +589,13 @@
     PrepareAppLaunch();
 
     network_portal_detector_.SimulateDefaultNetworkState(network_status);
+
+    // TODO(crbug.com/1101318): LaunchAppUserCancel and
+    // LaunchAppWithNetworkConfigAccelerator are failing without skipping
+    // user creation screen. Need to investigate and fix this.
+    chromeos::WizardController::default_controller()
+        ->get_wizard_context_for_testing()
+        ->skip_to_login_for_tests = true;
     EXPECT_TRUE(LaunchApp(test_app_id()));
   }
 
@@ -1187,7 +1196,7 @@
   test::OobeJS().TapOnPath({"kiosk-enable", "close"});
 
   // Wait for the kiosk_enable screen to disappear.
-  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+  OobeScreenWaiter(GetFirstSigninScreen()).Wait();
 
   // Check that the status still says configurable.
   EXPECT_EQ(KioskAppManager::CONSUMER_KIOSK_AUTO_LAUNCH_CONFIGURABLE,
@@ -1244,6 +1253,12 @@
   OobeScreenWaiter(KioskEnableScreenView::kScreenId).Wait();
   test::OobeJS().TapOnPath({"kiosk-enable", "close"});
 
+  // Navigate to gaia sign in screen.
+  if (features::IsChildSpecificSigninEnabled()) {
+    OobeScreenWaiter(UserCreationView::kScreenId).Wait();
+    test::OobeJS().TapOnPath({"user-creation", "nextButton"});
+  }
+
   // Wait for signin screen to appear again.
   OobeScreenWaiter(GaiaView::kScreenId).Wait();
 
diff --git a/chrome/browser/chromeos/login/login_screen_policy_browsertest.cc b/chrome/browser/chromeos/login/login_screen_policy_browsertest.cc
index ec4ef75..74352adf 100644
--- a/chrome/browser/chromeos/login/login_screen_policy_browsertest.cc
+++ b/chrome/browser/chromeos/login/login_screen_policy_browsertest.cc
@@ -20,12 +20,12 @@
 #include "chrome/browser/chromeos/login/startup_utils.h"
 #include "chrome/browser/chromeos/login/test/js_checker.h"
 #include "chrome/browser/chromeos/login/test/login_manager_mixin.h"
+#include "chrome/browser/chromeos/login/test/oobe_base_test.h"
 #include "chrome/browser/chromeos/login/test/oobe_screen_waiter.h"
 #include "chrome/browser/chromeos/policy/device_policy_cros_browser_test.h"
 #include "chrome/browser/chromeos/settings/cros_settings.h"
 #include "chrome/browser/lifetime/application_lifetime.h"
 #include "chrome/browser/ui/ash/login_screen_client.h"
-#include "chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h"
 #include "chrome/grit/generated_resources.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "chromeos/constants/chromeos_switches.h"
@@ -98,7 +98,7 @@
 };
 
 IN_PROC_BROWSER_TEST_F(LoginScreenGuestButtonPolicyTest, NoUsers) {
-  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+  OobeScreenWaiter(OobeBaseTest::GetFirstSigninScreen()).Wait();
 
   // Default.
   EXPECT_TRUE(ash::LoginScreenTestApi::IsGuestButtonShown());
@@ -122,7 +122,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(LoginScreenGuestButtonPolicyTest, HasUsers) {
-  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+  OobeScreenWaiter(OobeBaseTest::GetFirstSigninScreen()).Wait();
 
   // Default.
   EXPECT_TRUE(ash::LoginScreenTestApi::IsGuestButtonShown());
diff --git a/chrome/browser/chromeos/login/oobe_interactive_ui_test.cc b/chrome/browser/chromeos/login/oobe_interactive_ui_test.cc
index 2c4bbac8..d20116c 100644
--- a/chrome/browser/chromeos/login/oobe_interactive_ui_test.cc
+++ b/chrome/browser/chromeos/login/oobe_interactive_ui_test.cc
@@ -512,12 +512,16 @@
     std::tie(params_.is_tablet, params_.is_quick_unlock_enabled,
              params_.hide_shelf_controls_in_tablet_mode, params_.arc_state) =
         parameters;
+    // TODO(crbug.com/1101318): disable ChildSpecificSign in feature for now due
+    // to test flakiness
     if (params_.hide_shelf_controls_in_tablet_mode) {
-      feature_list_.InitAndEnableFeature(
-          ash::features::kHideShelfControlsInTabletMode);
+      feature_list_.InitWithFeatures(
+          {ash::features::kHideShelfControlsInTabletMode},
+          {features::kChildSpecificSignin});
     } else {
-      feature_list_.InitAndDisableFeature(
-          ash::features::kHideShelfControlsInTabletMode);
+      feature_list_.InitWithFeatures(
+          {}, {ash::features::kHideShelfControlsInTabletMode,
+               features::kChildSpecificSignin});
     }
   }
   ~OobeEndToEndTestSetupMixin() override = default;
@@ -695,6 +699,10 @@
 void OobeInteractiveUITest::PerformSessionSignInSteps(
     const ScopedQuickUnlockPrivateGetAuthTokenFunctionObserver&
         get_auth_token_observer) {
+  if (features::IsChildSpecificSigninEnabled()) {
+    test::WaitForUserCreationScreen();
+    test::TapUserCreationNext();
+  }
   WaitForGaiaSignInScreen(test_setup()->arc_state() != ArcState::kNotAvailable);
   LogInAsRegularUser();
 
diff --git a/chrome/browser/chromeos/login/proxy_auth_dialog_browsertest.cc b/chrome/browser/chromeos/login/proxy_auth_dialog_browsertest.cc
index 06f920a9..d60c3b4e 100644
--- a/chrome/browser/chromeos/login/proxy_auth_dialog_browsertest.cc
+++ b/chrome/browser/chromeos/login/proxy_auth_dialog_browsertest.cc
@@ -10,9 +10,9 @@
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/chromeos/login/login_manager_test.h"
 #include "chrome/browser/chromeos/login/test/login_manager_mixin.h"
+#include "chrome/browser/chromeos/login/test/oobe_base_test.h"
 #include "chrome/browser/chromeos/login/test/oobe_screen_waiter.h"
 #include "chrome/browser/ui/login/login_handler.h"
-#include "chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h"
 #include "chrome/common/chrome_switches.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test.h"
@@ -86,7 +86,7 @@
   ASSERT_FALSE(ash::LoginScreenTestApi::IsOobeDialogVisible());
   ProxyAuthDialogWaiter auth_dialog_waiter;
   ASSERT_TRUE(ash::LoginScreenTestApi::ClickAddUserButton());
-  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+  OobeScreenWaiter(OobeBaseTest::GetFirstSigninScreen()).Wait();
   auth_dialog_waiter.Wait();
   ASSERT_TRUE(auth_dialog_waiter.login_handler());
 }
diff --git a/chrome/browser/chromeos/login/saml/saml_browsertest.cc b/chrome/browser/chromeos/login/saml/saml_browsertest.cc
index 75ff8423..c45a7b2 100644
--- a/chrome/browser/chromeos/login/saml/saml_browsertest.cc
+++ b/chrome/browser/chromeos/login/saml/saml_browsertest.cc
@@ -649,7 +649,7 @@
   }
 
   virtual void StartSamlAndWaitForIdpPageLoad(const std::string& gaia_email) {
-    OobeScreenWaiter(GaiaView::kScreenId).Wait();
+    OobeScreenWaiter(GetFirstSigninScreen()).Wait();
 
     content::DOMMessageQueue message_queue;  // Start observe before SAML.
     SetupAuthFlowChangeListener();
diff --git a/chrome/browser/chromeos/login/screens/app_downloading_screen_browsertest.cc b/chrome/browser/chromeos/login/screens/app_downloading_screen_browsertest.cc
index 7122455..07c9ff6 100644
--- a/chrome/browser/chromeos/login/screens/app_downloading_screen_browsertest.cc
+++ b/chrome/browser/chromeos/login/screens/app_downloading_screen_browsertest.cc
@@ -53,7 +53,7 @@
 
   void Login() {
     login_manager_.LoginAsNewRegularUser();
-    OobeScreenExitWaiter(GaiaView::kScreenId).Wait();
+    OobeScreenExitWaiter(GetFirstSigninScreen()).Wait();
   }
 
   void ShowAppDownloadingScreen() {
diff --git a/chrome/browser/chromeos/login/screens/assistant_optin_flow_screen_browsertest.cc b/chrome/browser/chromeos/login/screens/assistant_optin_flow_screen_browsertest.cc
index e158c94..1ec89f5 100644
--- a/chrome/browser/chromeos/login/screens/assistant_optin_flow_screen_browsertest.cc
+++ b/chrome/browser/chromeos/login/screens/assistant_optin_flow_screen_browsertest.cc
@@ -333,7 +333,7 @@
 
   void ShowAssistantOptInFlowScreen() {
     login_manager_.LoginAsNewRegularUser();
-    OobeScreenExitWaiter(GaiaView::kScreenId).Wait();
+    OobeScreenExitWaiter(GetFirstSigninScreen()).Wait();
     if (!screen_exited_) {
       LoginDisplayHost::default_host()->StartWizard(
           AssistantOptInFlowScreenView::kScreenId);
diff --git a/chrome/browser/chromeos/login/screens/discover_screen_browsertest.cc b/chrome/browser/chromeos/login/screens/discover_screen_browsertest.cc
index f674abb..27af721 100644
--- a/chrome/browser/chromeos/login/screens/discover_screen_browsertest.cc
+++ b/chrome/browser/chromeos/login/screens/discover_screen_browsertest.cc
@@ -19,7 +19,6 @@
 #include "chrome/browser/chromeos/login/ui/login_display_host.h"
 #include "chrome/browser/chromeos/login/wizard_controller.h"
 #include "chrome/browser/ui/webui/chromeos/login/discover_screen_handler.h"
-#include "chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h"
 #include "chromeos/constants/chromeos_features.h"
 #include "chromeos/login/auth/stub_authenticator_builder.h"
 #include "components/user_manager/user_type.h"
@@ -86,7 +85,7 @@
 
   void ShowDiscoverScreen() {
     LogIn();
-    OobeScreenExitWaiter(GaiaView::kScreenId).Wait();
+    OobeScreenExitWaiter(GetFirstSigninScreen()).Wait();
     if (!screen_exited_) {
       LoginDisplayHost::default_host()->StartWizard(
           DiscoverScreenView::kScreenId);
diff --git a/chrome/browser/chromeos/login/screens/family_link_notice_browsertest.cc b/chrome/browser/chromeos/login/screens/family_link_notice_browsertest.cc
index d8d7a55..997111f 100644
--- a/chrome/browser/chromeos/login/screens/family_link_notice_browsertest.cc
+++ b/chrome/browser/chromeos/login/screens/family_link_notice_browsertest.cc
@@ -16,7 +16,7 @@
 #include "chrome/browser/chromeos/login/wizard_controller.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/webui/chromeos/login/family_link_notice_screen_handler.h"
-#include "chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h"
+#include "chrome/browser/ui/webui/chromeos/login/user_creation_screen_handler.h"
 #include "chrome/common/pref_names.h"
 #include "chromeos/constants/chromeos_features.h"
 #include "chromeos/login/auth/stub_authenticator_builder.h"
@@ -53,7 +53,7 @@
 
   void LoginAsRegularUser() {
     login_manager_mixin_.LoginAsNewRegularUser();
-    OobeScreenExitWaiter(GaiaView::kScreenId).Wait();
+    OobeScreenExitWaiter(UserCreationView::kScreenId).Wait();
   }
 
   void ExpectHelpAppPrefValue(bool expected) {
@@ -138,7 +138,7 @@
 
   void LoginAsChildUser() {
     login_manager_mixin_.LoginAsNewChildUser();
-    OobeScreenExitWaiter(GaiaView::kScreenId).Wait();
+    OobeScreenExitWaiter(UserCreationView::kScreenId).Wait();
   }
 
  private:
@@ -179,7 +179,7 @@
   void LoginAsManagedUser() {
     user_policy_mixin_.RequestPolicyUpdate();
     login_manager_mixin_.LoginWithDefaultContext(test_user_);
-    OobeScreenExitWaiter(GaiaView::kScreenId).Wait();
+    OobeScreenExitWaiter(UserCreationView::kScreenId).Wait();
   }
 
  private:
diff --git a/chrome/browser/chromeos/login/screens/gaia_screen.cc b/chrome/browser/chromeos/login/screens/gaia_screen.cc
index a38979e7..32abbcb 100644
--- a/chrome/browser/chromeos/login/screens/gaia_screen.cc
+++ b/chrome/browser/chromeos/login/screens/gaia_screen.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/chromeos/login/screens/gaia_screen.h"
 
 #include "chrome/browser/chromeos/login/screen_manager.h"
+#include "chrome/browser/chromeos/login/wizard_context.h"
 #include "chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h"
 #include "components/account_id/account_id.h"
 
@@ -47,18 +48,32 @@
 }
 
 void GaiaScreen::LoadOnline(const AccountId& account) {
+  view_->SetGaiaPath(GaiaView::GaiaPath::kDefault);
   view_->LoadGaiaAsync(account);
 }
 
+void GaiaScreen::LoadOnlineForChildSignup() {
+  view_->SetGaiaPath(GaiaView::GaiaPath::kChildSignup);
+  view_->LoadGaiaAsync(EmptyAccountId());
+}
+
+void GaiaScreen::LoadOnlineForChildSignin() {
+  view_->SetGaiaPath(GaiaView::GaiaPath::kChildSignin);
+  view_->LoadGaiaAsync(EmptyAccountId());
+}
+
 void GaiaScreen::LoadOffline(const AccountId& account) {
   view_->LoadOfflineGaia(account);
 }
 
 void GaiaScreen::ShowImpl() {
+  // Landed on the login screen. No longer skipping enrollment for tests.
+  context()->skip_to_login_for_tests = false;
   view_->Show();
 }
 
 void GaiaScreen::HideImpl() {
+  view_->SetGaiaPath(GaiaView::GaiaPath::kDefault);
   view_->LoadGaiaAsync(EmptyAccountId());
   view_->Hide();
 }
diff --git a/chrome/browser/chromeos/login/screens/gaia_screen.h b/chrome/browser/chromeos/login/screens/gaia_screen.h
index 862d3ea..5a747ec 100644
--- a/chrome/browser/chromeos/login/screens/gaia_screen.h
+++ b/chrome/browser/chromeos/login/screens/gaia_screen.h
@@ -36,6 +36,10 @@
   void MaybePreloadAuthExtension();
   // Loads online Gaia into the webview.
   void LoadOnline(const AccountId& account);
+  // Loads online Gaia (for child signup) into the webview.
+  void LoadOnlineForChildSignup();
+  // Loads online Gaia (for child signin) into the webview.
+  void LoadOnlineForChildSignin();
   // Loads offline version of Gaia.
   void LoadOffline(const AccountId& account);
 
diff --git a/chrome/browser/chromeos/login/screens/marketing_opt_in_screen_browsertest.cc b/chrome/browser/chromeos/login/screens/marketing_opt_in_screen_browsertest.cc
index fb345bf..cf7241d 100644
--- a/chrome/browser/chromeos/login/screens/marketing_opt_in_screen_browsertest.cc
+++ b/chrome/browser/chromeos/login/screens/marketing_opt_in_screen_browsertest.cc
@@ -180,7 +180,7 @@
 
   OobeBaseTest::SetUpOnMainThread();
   login_manager_mixin_.LoginAsNewRegularUser();
-  OobeScreenExitWaiter(GaiaView::kScreenId).Wait();
+  OobeScreenExitWaiter(GetFirstSigninScreen()).Wait();
   ProfileManager::GetActiveUserProfile()->GetPrefs()->SetBoolean(
       ash::prefs::kGestureEducationNotificationShown, true);
 }
diff --git a/chrome/browser/chromeos/login/screens/multidevice_setup_screen_browsertest.cc b/chrome/browser/chromeos/login/screens/multidevice_setup_screen_browsertest.cc
index fff1201f..1bb7d0f 100644
--- a/chrome/browser/chromeos/login/screens/multidevice_setup_screen_browsertest.cc
+++ b/chrome/browser/chromeos/login/screens/multidevice_setup_screen_browsertest.cc
@@ -58,7 +58,7 @@
 
   void ShowMultiDeviceSetupScreen() {
     login_manager_mixin_.LoginAsNewRegularUser();
-    OobeScreenExitWaiter(GaiaView::kScreenId).Wait();
+    OobeScreenExitWaiter(GetFirstSigninScreen()).Wait();
     if (!screen_exited_) {
       LoginDisplayHost::default_host()->StartWizard(
           MultiDeviceSetupScreenView::kScreenId);
diff --git a/chrome/browser/chromeos/login/screens/recommend_apps_screen_browsertest.cc b/chrome/browser/chromeos/login/screens/recommend_apps_screen_browsertest.cc
index 25d9ebea..6a210b9 100644
--- a/chrome/browser/chromeos/login/screens/recommend_apps_screen_browsertest.cc
+++ b/chrome/browser/chromeos/login/screens/recommend_apps_screen_browsertest.cc
@@ -142,7 +142,7 @@
 
   void ShowRecommendAppsScreen() {
     login_manager_.LoginAsNewRegularUser();
-    OobeScreenExitWaiter(GaiaView::kScreenId).Wait();
+    OobeScreenExitWaiter(GetFirstSigninScreen()).Wait();
     LoginDisplayHost::default_host()->StartWizard(
         RecommendAppsScreenView::kScreenId);
   }
@@ -562,7 +562,7 @@
   user_policy_mixin_.RequestPolicyUpdate();
 
   login_manager_.LoginWithDefaultContext(test_user_);
-  OobeScreenExitWaiter(GaiaView::kScreenId).Wait();
+  OobeScreenExitWaiter(GetFirstSigninScreen()).Wait();
   if (!screen_result_.has_value()) {
     // Skip screens to the tested one.
     LoginDisplayHost::default_host()->StartWizard(
diff --git a/chrome/browser/chromeos/login/screens/sync_consent_browsertest.cc b/chrome/browser/chromeos/login/screens/sync_consent_browsertest.cc
index 3310746..f5fa16f 100644
--- a/chrome/browser/chromeos/login/screens/sync_consent_browsertest.cc
+++ b/chrome/browser/chromeos/login/screens/sync_consent_browsertest.cc
@@ -191,7 +191,7 @@
 
   void LoginToSyncConsentScreen() {
     login_manager_mixin_.LoginAsNewRegularUser();
-    OobeScreenExitWaiter(GaiaView::kScreenId).Wait();
+    OobeScreenExitWaiter(GetFirstSigninScreen()).Wait();
     // No need to explicitly show the screen as it is the first one after login.
   }
 
diff --git a/chrome/browser/chromeos/login/screens/update_required_screen_browsertest.cc b/chrome/browser/chromeos/login/screens/update_required_screen_browsertest.cc
index 95ca0ac..5a9c45e 100644
--- a/chrome/browser/chromeos/login/screens/update_required_screen_browsertest.cc
+++ b/chrome/browser/chromeos/login/screens/update_required_screen_browsertest.cc
@@ -127,7 +127,7 @@
     network_state_test_helper_->manager_test()->SetupDefaultEnvironment();
     // Fake networks have been set up. Connect to WiFi network.
     SetConnected(kWifiServicePath);
-    chromeos::OobeScreenWaiter(chromeos::GaiaView::kScreenId).Wait();
+    chromeos::OobeScreenWaiter(GetFirstSigninScreen()).Wait();
   }
   void TearDownOnMainThread() override {
     network_state_test_helper_.reset();
diff --git a/chrome/browser/chromeos/login/screens/user_creation_screen.cc b/chrome/browser/chromeos/login/screens/user_creation_screen.cc
index 7826a26..b4f0246 100644
--- a/chrome/browser/chromeos/login/screens/user_creation_screen.cc
+++ b/chrome/browser/chromeos/login/screens/user_creation_screen.cc
@@ -5,7 +5,10 @@
 #include "chrome/browser/chromeos/login/screens/user_creation_screen.h"
 
 #include "ash/public/cpp/login_screen.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/browser_process_platform_part.h"
 #include "chrome/browser/chromeos/login/wizard_context.h"
+#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
 #include "chrome/browser/ui/webui/chromeos/login/user_creation_screen_handler.h"
 #include "chromeos/constants/chromeos_features.h"
 
@@ -56,12 +59,15 @@
 }
 
 bool UserCreationScreen::MaybeSkip(WizardContext* context) {
-  if (features::IsChildSpecificSigninEnabled() &&
-      !context->skip_to_login_for_tests) {
-    return false;
+  policy::BrowserPolicyConnectorChromeOS* connector =
+      g_browser_process->platform_part()->browser_policy_connector_chromeos();
+  if (!features::IsChildSpecificSigninEnabled() ||
+      context->skip_to_login_for_tests ||
+      connector->GetDeviceMode() == policy::DEVICE_MODE_ENTERPRISE_AD) {
+    exit_callback_.Run(Result::SKIPPED);
+    return true;
   }
-  exit_callback_.Run(Result::SKIPPED);
-  return true;
+  return false;
 }
 
 void UserCreationScreen::ShowImpl() {
diff --git a/chrome/browser/chromeos/login/screens/user_creation_screen_browsertest.cc b/chrome/browser/chromeos/login/screens/user_creation_screen_browsertest.cc
index 1d8e42627..4d55c7a 100644
--- a/chrome/browser/chromeos/login/screens/user_creation_screen_browsertest.cc
+++ b/chrome/browser/chromeos/login/screens/user_creation_screen_browsertest.cc
@@ -4,8 +4,10 @@
 #include "chrome/browser/chromeos/login/screens/user_creation_screen.h"
 
 #include "ash/public/cpp/login_screen_test_api.h"
+#include "chrome/browser/chromeos/login/enrollment/enrollment_screen_view.h"
 #include "chrome/browser/chromeos/login/oobe_screen.h"
 #include "chrome/browser/chromeos/login/test/device_state_mixin.h"
+#include "chrome/browser/chromeos/login/test/fake_gaia_mixin.h"
 #include "chrome/browser/chromeos/login/test/js_checker.h"
 #include "chrome/browser/chromeos/login/test/login_manager_mixin.h"
 #include "chrome/browser/chromeos/login/test/oobe_base_test.h"
@@ -13,6 +15,7 @@
 #include "chrome/browser/chromeos/login/test/oobe_screen_waiter.h"
 #include "chrome/browser/chromeos/login/ui/login_display_host.h"
 #include "chrome/browser/chromeos/login/wizard_controller.h"
+#include "chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/user_creation_screen_handler.h"
 #include "chromeos/constants/chromeos_features.h"
 #include "content/public/test/browser_test.h"
@@ -100,6 +103,7 @@
   UserCreationScreen::ScreenExitCallback original_callback_;
 
   base::test::ScopedFeatureList feature_list_;
+  FakeGaiaMixin fake_gaia_{&mixin_host_, embedded_test_server()};
 };
 
 // Verify flow for setting up the device for self.
@@ -110,6 +114,7 @@
                    ->get_wizard_context_for_testing()
                    ->sign_in_as_child);
   EXPECT_EQ(screen_result_.value(), UserCreationScreen::Result::SIGNIN);
+  OobeScreenWaiter(GaiaView::kScreenId).Wait();
 }
 
 // Verify flow for setting up the device for a child with a newly created gaia
@@ -126,6 +131,7 @@
                   ->is_child_gaia_account_new);
   EXPECT_EQ(screen_result_.value(),
             UserCreationScreen::Result::CHILD_ACCOUNT_CREATE);
+  OobeScreenWaiter(GaiaView::kScreenId).Wait();
 }
 
 // Verify flow for setting up the device for a child with an existing gaia
@@ -141,6 +147,7 @@
                    ->get_wizard_context_for_testing()
                    ->is_child_gaia_account_new);
   EXPECT_EQ(screen_result_.value(), UserCreationScreen::Result::CHILD_SIGNIN);
+  OobeScreenWaiter(GaiaView::kScreenId).Wait();
 }
 
 // Verify back button is hidden during the oobe flow (when no existing users).
@@ -163,6 +170,7 @@
   WaitForScreenExit();
   EXPECT_EQ(screen_result_.value(),
             UserCreationScreen::Result::ENTERPRISE_ENROLL);
+  OobeScreenWaiter(EnrollmentScreenView::kScreenId).Wait();
 }
 
 class UserCreationScreenLoginTest : public UserCreationScreenTest {
diff --git a/chrome/browser/chromeos/login/session/chrome_session_manager_browsertest.cc b/chrome/browser/chromeos/login/session/chrome_session_manager_browsertest.cc
index 538c133..157a6b5e 100644
--- a/chrome/browser/chromeos/login/session/chrome_session_manager_browsertest.cc
+++ b/chrome/browser/chromeos/login/session/chrome_session_manager_browsertest.cc
@@ -99,7 +99,7 @@
   fake_gaia_.SetupFakeGaiaForLoginManager();
   fake_gaia_.fake_gaia()->SetFakeMergeSessionParams(
       FakeGaiaMixin::kFakeUserEmail, "fake_sid", "fake_lsid");
-  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+  OobeScreenWaiter(OobeBaseTest::GetFirstSigninScreen()).Wait();
 
   LoginDisplayHost::default_host()
       ->GetOobeUI()
@@ -183,7 +183,7 @@
     fake_gaia_.SetupFakeGaiaForLoginManager();
     fake_gaia_.fake_gaia()->SetFakeMergeSessionParams(
         FakeGaiaMixin::kFakeUserEmail, "fake_sid", "fake_lsid");
-    OobeScreenWaiter(GaiaView::kScreenId).Wait();
+    OobeScreenWaiter(OobeBaseTest::GetFirstSigninScreen()).Wait();
 
     LoginDisplayHost::default_host()
         ->GetOobeUI()
diff --git a/chrome/browser/chromeos/login/test/oobe_base_test.cc b/chrome/browser/chromeos/login/test/oobe_base_test.cc
index db7e783..783ef120 100644
--- a/chrome/browser/chromeos/login/test/oobe_base_test.cc
+++ b/chrome/browser/chromeos/login/test/oobe_base_test.cc
@@ -20,10 +20,13 @@
 #include "chrome/browser/chromeos/login/ui/login_display_host_webui.h"
 #include "chrome/browser/chromeos/login/ui/webui_login_view.h"
 #include "chrome/browser/lifetime/application_lifetime.h"
+#include "chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/oobe_ui.h"
 #include "chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h"
+#include "chrome/browser/ui/webui/chromeos/login/user_creation_screen_handler.h"
 #include "chrome/browser/ui/webui/signin/signin_utils.h"
 #include "chrome/common/chrome_switches.h"
+#include "chromeos/constants/chromeos_features.h"
 #include "chromeos/constants/chromeos_switches.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/fake_update_engine_client.h"
@@ -217,6 +220,12 @@
   return result;
 }
 
+// static
+OobeScreenId OobeBaseTest::GetFirstSigninScreen() {
+  return features::IsChildSpecificSigninEnabled() ? UserCreationView::kScreenId
+                                                  : GaiaView::kScreenId;
+}
+
 void OobeBaseTest::MaybeWaitForLoginScreenLoad() {
   if (!login_screen_load_observer_)
     return;
diff --git a/chrome/browser/chromeos/login/test/oobe_base_test.h b/chrome/browser/chromeos/login/test/oobe_base_test.h
index 0b31385..439e0da 100644
--- a/chrome/browser/chromeos/login/test/oobe_base_test.h
+++ b/chrome/browser/chromeos/login/test/oobe_base_test.h
@@ -10,6 +10,7 @@
 
 #include "base/compiler_specific.h"
 #include "base/macros.h"
+#include "chrome/browser/chromeos/login/oobe_screen.h"
 #include "chrome/browser/chromeos/login/test/embedded_test_server_mixin.h"
 #include "chrome/browser/chromeos/login/test/js_checker.h"
 #include "chrome/test/base/mixin_based_in_process_browser_test.h"
@@ -33,6 +34,8 @@
   // process requests prior it gets handled by FakeGaia instance.
   virtual void RegisterAdditionalRequestHandlers();
 
+  static OobeScreenId GetFirstSigninScreen();
+
  protected:
   // MixinBasedInProcessBrowserTest::
   void SetUp() override;
diff --git a/chrome/browser/chromeos/login/test/oobe_screens_utils.cc b/chrome/browser/chromeos/login/test/oobe_screens_utils.cc
index 893f355..aefef7d3 100644
--- a/chrome/browser/chromeos/login/test/oobe_screens_utils.cc
+++ b/chrome/browser/chromeos/login/test/oobe_screens_utils.cc
@@ -21,6 +21,7 @@
 #include "chrome/browser/ui/webui/chromeos/login/marketing_opt_in_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/network_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/update_screen_handler.h"
+#include "chrome/browser/ui/webui/chromeos/login/user_creation_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/welcome_screen_handler.h"
 #include "content/public/browser/notification_service.h"
 #include "content/public/test/browser_test_utils.h"
@@ -135,6 +136,14 @@
   WaitFor(EnrollmentScreenView::kScreenId);
 }
 
+void WaitForUserCreationScreen() {
+  WaitFor(UserCreationView::kScreenId);
+}
+
+void TapUserCreationNext() {
+  test::OobeJS().TapOnPath({"user-creation", "nextButton"});
+}
+
 void WaitForLastScreenAndTapGetStarted() {
   WaitFor(MarketingOptInScreenView::kScreenId);
   test::OobeJS().TapOnPath(
diff --git a/chrome/browser/chromeos/login/test/oobe_screens_utils.h b/chrome/browser/chromeos/login/test/oobe_screens_utils.h
index e727595..208172b 100644
--- a/chrome/browser/chromeos/login/test/oobe_screens_utils.h
+++ b/chrome/browser/chromeos/login/test/oobe_screens_utils.h
@@ -24,6 +24,8 @@
 void ExitDiscoverPinSetupScreen();
 void SkipToEnrollmentOnRecovery();
 void WaitForEnrollmentScreen();
+void WaitForUserCreationScreen();
+void TapUserCreationNext();
 
 void WaitForEulaScreen();
 void TapEulaAccept();
diff --git a/chrome/browser/chromeos/login/ui/captive_portal_window_browsertest.cc b/chrome/browser/chromeos/login/ui/captive_portal_window_browsertest.cc
index f05d0f33..e1d4798 100644
--- a/chrome/browser/chromeos/login/ui/captive_portal_window_browsertest.cc
+++ b/chrome/browser/chromeos/login/ui/captive_portal_window_browsertest.cc
@@ -20,8 +20,10 @@
 #include "chrome/browser/chromeos/login/ui/captive_portal_window_proxy.h"
 #include "chrome/browser/chromeos/login/ui/login_display_host.h"
 #include "chrome/browser/chromeos/login/ui/webui_login_view.h"
+#include "chrome/browser/chromeos/login/wizard_controller.h"
 #include "chrome/browser/chromeos/net/network_portal_detector_test_impl.h"
 #include "chrome/browser/ui/webui/chromeos/login/error_screen_handler.h"
+#include "chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chromeos/constants/chromeos_switches.h"
 #include "chromeos/dbus/shill/fake_shill_manager_client.h"
@@ -220,6 +222,10 @@
   OobeUI* oobe = host->GetOobeUI();
   ASSERT_TRUE(oobe);
 
+  // Skip to gaia screen.
+  host->GetWizardController()->SkipToLoginForTesting();
+  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+
   // Error screen asks portal detector to change detection strategy.
   ErrorScreen* error_screen = oobe->GetErrorScreen();
   ASSERT_TRUE(error_screen);
diff --git a/chrome/browser/chromeos/login/webview_login_browsertest.cc b/chrome/browser/chromeos/login/webview_login_browsertest.cc
index acd5e27..8b738c522 100644
--- a/chrome/browser/chromeos/login/webview_login_browsertest.cc
+++ b/chrome/browser/chromeos/login/webview_login_browsertest.cc
@@ -33,6 +33,7 @@
 #include "chrome/browser/chromeos/login/test/js_checker.h"
 #include "chrome/browser/chromeos/login/test/local_policy_test_server_mixin.h"
 #include "chrome/browser/chromeos/login/test/oobe_base_test.h"
+#include "chrome/browser/chromeos/login/test/oobe_screen_exit_waiter.h"
 #include "chrome/browser/chromeos/login/test/oobe_screen_waiter.h"
 #include "chrome/browser/chromeos/login/test/session_manager_state_waiter.h"
 #include "chrome/browser/chromeos/login/ui/login_display_host.h"
@@ -48,6 +49,7 @@
 #include "chrome/browser/ui/webui/chromeos/login/error_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/eula_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h"
+#include "chrome/browser/ui/webui/chromeos/login/user_creation_screen_handler.h"
 #include "chrome/browser/ui/webui/signin/signin_utils.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "chromeos/constants/chromeos_features.h"
@@ -240,7 +242,8 @@
 class WebviewLoginTest : public OobeBaseTest {
  public:
   WebviewLoginTest() {
-    scoped_feature_list_.InitWithFeatures({features::kGaiaActionButtons}, {});
+    scoped_feature_list_.InitWithFeatures(
+        {features::kGaiaActionButtons, features::kChildSpecificSignin}, {});
   }
   ~WebviewLoginTest() override = default;
 
@@ -303,9 +306,9 @@
  protected:
   chromeos::ScopedTestingCrosSettings scoped_testing_cros_settings_;
   FakeGaiaMixin fake_gaia_{&mixin_host_, embedded_test_server()};
+  base::test::ScopedFeatureList scoped_feature_list_;
 
  private:
-  base::test::ScopedFeatureList scoped_feature_list_;
   DISALLOW_COPY_AND_ASSIGN(WebviewLoginTest);
 };
 
@@ -423,18 +426,16 @@
   test::WaitForPrimaryUserSessionStart();
 }
 
-IN_PROC_BROWSER_TEST_F(WebviewLoginTest, ErrorScreenOnGaiaError) {
+IN_PROC_BROWSER_TEST_F(WebviewLoginTest, BackToUserCreationScreen) {
   WaitForGaiaPageLoadAndPropertyUpdate();
+
+  // Start with identifier page.
   ExpectIdentifierPage();
 
-  // Make gaia landing page unreachable
-  fake_gaia_.fake_gaia()->SetErrorResponse(
-      GaiaUrls::GetInstance()->embedded_setup_chromeos_url(2),
-      net::HTTP_NOT_FOUND);
-
-  // Click back to reload (unreachable) identifier page.
+  // Click back to exit gaia screen.
   test::OobeJS().ClickOnPath({"gaia-signin", "signin-back-button"});
-  OobeScreenWaiter(ErrorScreenView::kScreenId).Wait();
+  OobeScreenExitWaiter(GaiaView::kScreenId).Wait();
+  OobeScreenWaiter(UserCreationView::kScreenId).Wait();
 }
 
 // Create new account option should be available only if the settings allow it.
@@ -658,7 +659,12 @@
 // Base class for tests of the client certificates in the sign-in frame.
 class WebviewClientCertsLoginTestBase : public WebviewLoginTest {
  public:
-  WebviewClientCertsLoginTestBase() = default;
+  WebviewClientCertsLoginTestBase() {
+    // TODO(crbug.com/1101318): Fix tests when kChildSpecificSignin is enabled.
+    scoped_feature_list_.Reset();
+    scoped_feature_list_.InitWithFeatures({features::kGaiaActionButtons},
+                                          {features::kChildSpecificSignin});
+  }
   WebviewClientCertsLoginTestBase(const WebviewClientCertsLoginTestBase&) =
       delete;
   WebviewClientCertsLoginTestBase& operator=(
@@ -1327,4 +1333,28 @@
   ExpectIdentifierPage();
 }
 
+class WebviewLoginTestWithChildSigninDisabled : public WebviewLoginTest {
+ public:
+  WebviewLoginTestWithChildSigninDisabled() {
+    scoped_feature_list_.Reset();
+    scoped_feature_list_.InitWithFeatures({features::kGaiaActionButtons},
+                                          {features::kChildSpecificSignin});
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(WebviewLoginTestWithChildSigninDisabled,
+                       ErrorScreenOnGaiaError) {
+  WaitForGaiaPageLoadAndPropertyUpdate();
+  ExpectIdentifierPage();
+
+  // Make gaia landing page unreachable
+  fake_gaia_.fake_gaia()->SetErrorResponse(
+      GaiaUrls::GetInstance()->embedded_setup_chromeos_url(2),
+      net::HTTP_NOT_FOUND);
+
+  // Click back to reload (unreachable) identifier page.
+  test::OobeJS().ClickOnPath({"gaia-signin", "signin-back-button"});
+  OobeScreenWaiter(ErrorScreenView::kScreenId).Wait();
+}
+
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/wizard_controller.cc b/chrome/browser/chromeos/login/wizard_controller.cc
index f8ef4cf..ae075d64 100644
--- a/chrome/browser/chromeos/login/wizard_controller.cc
+++ b/chrome/browser/chromeos/login/wizard_controller.cc
@@ -654,9 +654,6 @@
   if (login_screen_started_)
     return;
 
-  // Landed on the login screen. No longer skipping enrollment for tests.
-  wizard_context_->skip_to_login_for_tests = false;
-
   if (!time_eula_accepted_.is_null()) {
     base::TimeDelta delta = base::TimeTicks::Now() - time_eula_accepted_;
     UMA_HISTOGRAM_MEDIUM_TIMES("OOBE.EULAToSignInTime", delta);
@@ -834,13 +831,11 @@
       AdvanceToScreen(GaiaView::kScreenId);
       break;
     case UserCreationScreen::Result::CHILD_SIGNIN:
-      // TODO(crbug.com/1101318): entry point for child sign in screen
-      GaiaScreen::Get(screen_manager())->LoadOnline(EmptyAccountId());
+      GaiaScreen::Get(screen_manager())->LoadOnlineForChildSignin();
       AdvanceToScreen(GaiaView::kScreenId);
       break;
     case UserCreationScreen::Result::CHILD_ACCOUNT_CREATE:
-      // TODO(crbug.com/1101318): entry point for account creation screen
-      GaiaScreen::Get(screen_manager())->LoadOnline(EmptyAccountId());
+      GaiaScreen::Get(screen_manager())->LoadOnlineForChildSignup();
       AdvanceToScreen(GaiaView::kScreenId);
       break;
     case UserCreationScreen::Result::ENTERPRISE_ENROLL:
@@ -1538,10 +1533,10 @@
 void WizardController::SetCurrentScreen(BaseScreen* new_current) {
   VLOG(1) << "SetCurrentScreen: "
           << (new_current ? new_current->screen_id().name : "null");
-  if (current_screen_ == new_current || GetOobeUI() == nullptr)
+  if (new_current && new_current->MaybeSkip(wizard_context_.get()))
     return;
 
-  if (new_current && new_current->MaybeSkip(wizard_context_.get()))
+  if (current_screen_ == new_current || GetOobeUI() == nullptr)
     return;
 
   if (current_screen_) {
diff --git a/chrome/browser/chromeos/login/wizard_controller_browsertest.cc b/chrome/browser/chromeos/login/wizard_controller_browsertest.cc
index b541e95..cd57183 100644
--- a/chrome/browser/chromeos/login/wizard_controller_browsertest.cc
+++ b/chrome/browser/chromeos/login/wizard_controller_browsertest.cc
@@ -1460,7 +1460,7 @@
     // Don't expect that the auto enrollment screen will be hidden, because
     // OOBE is exited from the auto enrollment screen. Instead only expect
     // that the sign-in screen is reached.
-    OobeScreenWaiter(GaiaView::kScreenId).Wait();
+    OobeScreenWaiter(GetFirstSigninScreen()).Wait();
     EXPECT_EQ(
         0, FakeCryptohomeClient::Get()
                ->remove_firmware_management_parameters_from_tpm_call_count());
diff --git a/chrome/browser/chromeos/policy/device_local_account_browsertest.cc b/chrome/browser/chromeos/policy/device_local_account_browsertest.cc
index b6a2560..e5e6696 100644
--- a/chrome/browser/chromeos/policy/device_local_account_browsertest.cc
+++ b/chrome/browser/chromeos/policy/device_local_account_browsertest.cc
@@ -56,6 +56,7 @@
 #include "chrome/browser/chromeos/login/signin_specifics.h"
 #include "chrome/browser/chromeos/login/test/js_checker.h"
 #include "chrome/browser/chromeos/login/test/local_policy_test_server_mixin.h"
+#include "chrome/browser/chromeos/login/test/oobe_base_test.h"
 #include "chrome/browser/chromeos/login/test/oobe_screen_waiter.h"
 #include "chrome/browser/chromeos/login/test/session_manager_state_waiter.h"
 #include "chrome/browser/chromeos/login/test/test_predicate_waiter.h"
@@ -465,7 +466,8 @@
       run_loop.Run();
 
     // Skip to the login screen.
-    chromeos::OobeScreenWaiter(chromeos::GaiaView::kScreenId).Wait();
+    chromeos::OobeScreenWaiter(chromeos::OobeBaseTest::GetFirstSigninScreen())
+        .Wait();
 
     chromeos::test::UserSessionManagerTestApi session_manager_test_api(
         chromeos::UserSessionManager::GetInstance());
diff --git a/chrome/browser/chromeos/policy/device_login_screen_policy_browsertest.cc b/chrome/browser/chromeos/policy/device_login_screen_policy_browsertest.cc
index 8519462..84ee7e5 100644
--- a/chrome/browser/chromeos/policy/device_login_screen_policy_browsertest.cc
+++ b/chrome/browser/chromeos/policy/device_login_screen_policy_browsertest.cc
@@ -18,6 +18,7 @@
 #include "base/values.h"
 #include "chrome/browser/chromeos/accessibility/accessibility_manager.h"
 #include "chrome/browser/chromeos/accessibility/magnification_manager.h"
+#include "chrome/browser/chromeos/login/test/oobe_base_test.h"
 #include "chrome/browser/chromeos/login/test/oobe_screen_waiter.h"
 #include "chrome/browser/chromeos/login/test/test_predicate_waiter.h"
 #include "chrome/browser/chromeos/login/ui/login_display_host.h"
@@ -230,7 +231,8 @@
 // Tests that adding public accounts does not close the Oobe dialog when it
 // shows a screen different from the Gaia login screen.
 IN_PROC_BROWSER_TEST_F(DeviceLoginScreenPolicyBrowsertest, ResetScreen) {
-  chromeos::OobeScreenWaiter(chromeos::GaiaView::kScreenId).Wait();
+  chromeos::OobeScreenWaiter(chromeos::OobeBaseTest::GetFirstSigninScreen())
+      .Wait();
   EXPECT_TRUE(ash::LoginScreenTestApi::IsOobeDialogVisible());
   EXPECT_EQ(ash::LoginScreenTestApi::GetUsersCount(), 0);
 
diff --git a/chrome/browser/chromeos/policy/minimum_version_policy_handler_browsertest.cc b/chrome/browser/chromeos/policy/minimum_version_policy_handler_browsertest.cc
index 7117af81..ee887f364 100644
--- a/chrome/browser/chromeos/policy/minimum_version_policy_handler_browsertest.cc
+++ b/chrome/browser/chromeos/policy/minimum_version_policy_handler_browsertest.cc
@@ -22,6 +22,7 @@
 #include "chrome/browser/chromeos/login/login_wizard.h"
 #include "chrome/browser/chromeos/login/test/device_state_mixin.h"
 #include "chrome/browser/chromeos/login/test/login_manager_mixin.h"
+#include "chrome/browser/chromeos/login/test/oobe_base_test.h"
 #include "chrome/browser/chromeos/login/test/oobe_screen_exit_waiter.h"
 #include "chrome/browser/chromeos/login/test/oobe_screen_waiter.h"
 #include "chrome/browser/chromeos/login/test/user_policy_mixin.h"
@@ -804,7 +805,8 @@
 
 IN_PROC_BROWSER_TEST_F(MinimumVersionNoUsersLoginTest,
                        CriticalUpdateOnLoginScreen) {
-  chromeos::OobeScreenWaiter(chromeos::GaiaView::kScreenId).Wait();
+  chromeos::OobeScreenWaiter(chromeos::OobeBaseTest::GetFirstSigninScreen())
+      .Wait();
   EXPECT_EQ(ash::LoginScreenTestApi::GetUsersCount(), 0);
 
   // Create and set policy value.
@@ -825,7 +827,8 @@
   SetDevicePolicyAndWaitForSettingChange(empty_policy);
   chromeos::OobeScreenExitWaiter(chromeos::UpdateRequiredView::kScreenId)
       .Wait();
-  chromeos::OobeScreenWaiter(chromeos::GaiaView::kScreenId).Wait();
+  chromeos::OobeScreenWaiter(chromeos::OobeBaseTest::GetFirstSigninScreen())
+      .Wait();
 }
 
 class MinimumVersionPolicyPresentTest : public MinimumVersionPolicyTestBase {
diff --git a/chrome/browser/chromeos/policy/policy_certs_browsertest.cc b/chrome/browser/chromeos/policy/policy_certs_browsertest.cc
index 3c6715c..7bf339f1 100644
--- a/chrome/browser/chromeos/policy/policy_certs_browsertest.cc
+++ b/chrome/browser/chromeos/policy/policy_certs_browsertest.cc
@@ -19,6 +19,7 @@
 #include "chrome/browser/chromeos/login/helper.h"
 #include "chrome/browser/chromeos/login/startup_utils.h"
 #include "chrome/browser/chromeos/login/test/local_policy_test_server_mixin.h"
+#include "chrome/browser/chromeos/login/test/oobe_base_test.h"
 #include "chrome/browser/chromeos/login/test/oobe_screen_waiter.h"
 #include "chrome/browser/chromeos/login/test/session_manager_state_waiter.h"
 #include "chrome/browser/chromeos/login/ui/login_display_host.h"
@@ -881,7 +882,8 @@
 // caches), the test is able to catch that.
 IN_PROC_BROWSER_TEST_P(PolicyProvidedCertsForSigninExtensionTest,
                        ActiveOnlyInSelectedExtension) {
-  chromeos::OobeScreenWaiter(chromeos::GaiaView::kScreenId).Wait();
+  chromeos::OobeScreenWaiter(chromeos::OobeBaseTest::GetFirstSigninScreen())
+      .Wait();
   content::StoragePartition* signin_profile_default_partition =
       content::BrowserContext::GetDefaultStoragePartition(signin_profile_);
 
diff --git a/chrome/browser/chromeos/policy/system_proxy_manager.cc b/chrome/browser/chromeos/policy/system_proxy_manager.cc
index 237a982..a0dfdf8 100644
--- a/chrome/browser/chromeos/policy/system_proxy_manager.cc
+++ b/chrome/browser/chromeos/policy/system_proxy_manager.cc
@@ -5,11 +5,17 @@
 #include "chrome/browser/chromeos/policy/system_proxy_manager.h"
 
 #include "base/bind.h"
+#include "base/strings/string16.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
+#include "chrome/browser/chromeos/login/ui/login_display_host.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
+#include "chrome/browser/chromeos/ui/request_system_proxy_credentials_view.h"
 #include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/browser_window.h"
 #include "chrome/common/pref_names.h"
 #include "chromeos/dbus/system_proxy/system_proxy_client.h"
 #include "chromeos/network/network_event_log.h"
@@ -27,6 +33,10 @@
 #include "net/http/http_auth_scheme.h"
 #include "net/http/http_util.h"
 #include "services/network/public/mojom/network_context.mojom.h"
+#include "ui/aura/window.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/window/dialog_delegate.h"
 
 namespace {
 const char kSystemProxyService[] = "system-proxy-service";
@@ -138,6 +148,7 @@
                                 weak_factory_.GetWeakPtr()));
     system_services_address_.clear();
     SetUserTrafficProxyPref(std::string());
+    CloseAuthenticationDialog();
     return;
   }
 
@@ -207,6 +218,31 @@
          primary_profile_->GetPrefs()->GetBoolean(arc::prefs::kArcEnabled);
 }
 
+void SystemProxyManager::SendUserAuthenticationCredentials(
+    const system_proxy::ProtectionSpace& protection_space,
+    const std::string& username,
+    const std::string& password) {
+  // System-proxy is started via d-bus activation, meaning the first d-bus call
+  // will start the daemon. Check that System-proxy was not disabled by policy
+  // while looking for credentials so we don't accidentally restart it.
+  if (!system_proxy_enabled_) {
+    return;
+  }
+
+  system_proxy::Credentials user_credentials;
+  user_credentials.set_username(username);
+  user_credentials.set_password(password);
+
+  system_proxy::SetAuthenticationDetailsRequest request;
+  request.set_traffic_type(system_proxy::TrafficOrigin::ALL);
+  *request.mutable_credentials() = user_credentials;
+  *request.mutable_protection_space() = protection_space;
+
+  chromeos::SystemProxyClient::Get()->SetAuthenticationDetails(
+      request, base::BindOnce(&SystemProxyManager::OnSetAuthenticationDetails,
+                              weak_factory_.GetWeakPtr()));
+}
+
 void SystemProxyManager::SendKerberosAuthenticationDetails() {
   if (!system_proxy_enabled_) {
     return;
@@ -227,6 +263,13 @@
                               weak_factory_.GetWeakPtr()));
 }
 
+void SystemProxyManager::SendEmptyCredentials(
+    const system_proxy::ProtectionSpace& protection_space) {
+  SendUserAuthenticationCredentials(protection_space,
+                                    /*username=*/std::string(),
+                                    /*password=*/std::string());
+}
+
 void SystemProxyManager::SetSystemProxyEnabledForTest(bool enabled) {
   system_proxy_enabled_ = enabled;
 }
@@ -293,8 +336,7 @@
   system_proxy::ProtectionSpace protection_space =
       details.proxy_protection_space();
   if (!primary_profile_) {
-    LookupProxyAuthCredentialsCallback(protection_space,
-                                       /* credentials = */ base::nullopt);
+    SendEmptyCredentials(protection_space);
     return;
   }
 
@@ -307,8 +349,7 @@
       protection_space.origin(), net::ProxyServer::Scheme::SCHEME_HTTP);
 
   if (!proxy_server.is_valid()) {
-    LookupProxyAuthCredentialsCallback(protection_space,
-                                       /* credentials = */ base::nullopt);
+    SendEmptyCredentials(protection_space);
     return;
   }
   content::BrowserContext::GetDefaultStoragePartition(primary_profile_)
@@ -335,20 +376,61 @@
   if (credentials) {
     username = base::UTF16ToUTF8(credentials->username());
     password = base::UTF16ToUTF8(credentials->password());
+    // If there's a dialog requesting credentials for this proxy, close it.
+    if (active_auth_dialog_ &&
+        active_auth_dialog_->GetProxyServer() == protection_space.origin()) {
+      CloseAuthenticationDialog();
+    }
   }
+  SendUserAuthenticationCredentials(protection_space, username, password);
+}
 
-  system_proxy::Credentials user_credentials;
-  user_credentials.set_username(username);
-  user_credentials.set_password(password);
+void SystemProxyManager::ShowAuthenticationDialog(
+    const system_proxy::ProtectionSpace& protection_space,
+    bool show_error_label) {
+  if (active_auth_dialog_)
+    return;
 
-  system_proxy::SetAuthenticationDetailsRequest request;
-  request.set_traffic_type(system_proxy::TrafficOrigin::ALL);
-  *request.mutable_credentials() = user_credentials;
-  *request.mutable_protection_space() = protection_space;
+  active_auth_dialog_ = new chromeos::RequestSystemProxyCredentialsView(
+      protection_space.origin(), show_error_label,
+      base::BindOnce(&SystemProxyManager::OnDialogClosed,
+                     weak_factory_.GetWeakPtr(), protection_space));
 
-  chromeos::SystemProxyClient::Get()->SetAuthenticationDetails(
-      request, base::BindOnce(&SystemProxyManager::OnSetAuthenticationDetails,
-                              weak_factory_.GetWeakPtr()));
+  active_auth_dialog_->SetAcceptCallback(
+      base::BindRepeating(&SystemProxyManager::OnDialogAccepted,
+                          weak_factory_.GetWeakPtr(), protection_space));
+  active_auth_dialog_->SetCancelCallback(
+      base::BindRepeating(&SystemProxyManager::OnDialogCanceled,
+                          weak_factory_.GetWeakPtr(), protection_space));
+
+  auth_widget_ = views::DialogDelegate::CreateDialogWidget(
+      active_auth_dialog_, /*context=*/nullptr, /*parent=*/nullptr);
+  auth_widget_->Show();
+}
+
+void SystemProxyManager::OnDialogAccepted(
+    const system_proxy::ProtectionSpace& protection_space) {
+  SendUserAuthenticationCredentials(
+      protection_space, base::UTF16ToUTF8(active_auth_dialog_->GetUsername()),
+      base::UTF16ToUTF8(active_auth_dialog_->GetPassword()));
+}
+
+void SystemProxyManager::OnDialogCanceled(
+    const system_proxy::ProtectionSpace& protection_space) {
+  SendEmptyCredentials(protection_space);
+}
+
+void SystemProxyManager::OnDialogClosed(
+    const system_proxy::ProtectionSpace& protection_space) {
+  active_auth_dialog_ = nullptr;
+  auth_widget_ = nullptr;
+}
+
+void SystemProxyManager::CloseAuthenticationDialog() {
+  if (!auth_widget_)
+    return;
+  // Also deletes the |auth_widget_| instance.
+  auth_widget_->CloseWithReason(views::Widget::ClosedReason::kUnspecified);
 }
 
 }  // namespace policy
diff --git a/chrome/browser/chromeos/policy/system_proxy_manager.h b/chrome/browser/chromeos/policy/system_proxy_manager.h
index 4642c9b..7e712f6 100644
--- a/chrome/browser/chromeos/policy/system_proxy_manager.h
+++ b/chrome/browser/chromeos/policy/system_proxy_manager.h
@@ -16,11 +16,19 @@
 #include "chromeos/dbus/system_proxy/system_proxy_service.pb.h"
 #include "net/base/auth.h"
 
+namespace chromeos {
+class RequestSystemProxyCredentialsView;
+}
+
 namespace system_proxy {
 class SetAuthenticationDetailsResponse;
 class ShutDownResponse;
 }  // namespace system_proxy
 
+namespace views {
+class Widget;
+}
+
 class PrefRegistrySimple;
 class PrefService;
 class PrefChangeRegistrar;
@@ -77,7 +85,22 @@
   void SetUserTrafficProxyPref(const std::string& user_traffic_address);
   bool IsArcEnabled() const;
 
+  // Sends the authentication details for |protection_space| to System-proxy via
+  // D-Bus.
+  void SendUserAuthenticationCredentials(
+      const system_proxy::ProtectionSpace& protection_space,
+      const std::string& username,
+      const std::string& password);
+  // Send the Kerberos enabled state and active principal name to System-proxy
+  // via D-Bus.
   void SendKerberosAuthenticationDetails();
+  // Sends empty credentials for |protection_space| to System-proxy via D-Bus.
+  // This can mean that a user is not signed into Chrome OS or they didn't
+  // provide proxy authentication credentials. In this case, System-proxy will
+  // forward the authentication failure (HTTP 407 status code) to the Chrome OS
+  // client.
+  void SendEmptyCredentials(
+      const system_proxy::ProtectionSpace& protection_space);
 
   // Once a trusted set of policies is established, this function calls
   // the System-proxy dbus client to start/shutdown the daemon and, if
@@ -100,6 +123,20 @@
       const system_proxy::ProtectionSpace& protection_space,
       const base::Optional<net::AuthCredentials>& credentials);
 
+  // Shows a dialog which prompts the user to introduce proxy authentication
+  // credentials for OS level traffic. If |show_error_label| is true, the
+  // dialog will show a label that indicates the previous attempt to
+  // authenticate has failed due to invalid credentials.
+  void ShowAuthenticationDialog(
+      const system_proxy::ProtectionSpace& protection_space,
+      bool show_error_label);
+  void OnDialogAccepted(const system_proxy::ProtectionSpace& protection_space);
+  void OnDialogCanceled(const system_proxy::ProtectionSpace& protection_space);
+  void OnDialogClosed(const system_proxy::ProtectionSpace& protection_space);
+
+  // Closes the authentication dialog if shown.
+  void CloseAuthenticationDialog();
+
   chromeos::CrosSettings* cros_settings_;
   std::unique_ptr<chromeos::CrosSettings::ObserverSubscription>
       system_proxy_subscription_;
@@ -112,6 +149,11 @@
   // Local state prefs, not owned.
   PrefService* local_state_ = nullptr;
 
+  // Owned by |auth_widget_|.
+  chromeos::RequestSystemProxyCredentialsView* active_auth_dialog_ = nullptr;
+  // Owned by the UI code (NativeWidget).
+  views::Widget* auth_widget_ = nullptr;
+
   // Primary profile, not owned.
   Profile* primary_profile_ = nullptr;
 
diff --git a/chrome/browser/chromeos/printing/calculators_policies_binder.cc b/chrome/browser/chromeos/printing/calculators_policies_binder.cc
index 9557d2f..e598164 100644
--- a/chrome/browser/chromeos/printing/calculators_policies_binder.cc
+++ b/chrome/browser/chromeos/printing/calculators_policies_binder.cc
@@ -55,7 +55,7 @@
   PrefBinder(PrefService* pref_service,
              base::WeakPtr<BulkPrintersCalculator> calculator)
       : CalculatorsPoliciesBinder(prefs::kRecommendedPrintersAccessMode,
-                                  prefs::kRecommendedNativePrintersBlacklist,
+                                  prefs::kRecommendedPrintersBlocklist,
                                   prefs::kRecommendedNativePrintersWhitelist,
                                   calculator),
         prefs_(pref_service) {
@@ -128,7 +128,7 @@
   // Default value for access mode is AllAccess.
   registry->RegisterIntegerPref(prefs::kRecommendedPrintersAccessMode,
                                 BulkPrintersCalculator::ALL_ACCESS);
-  registry->RegisterListPref(prefs::kRecommendedNativePrintersBlacklist);
+  registry->RegisterListPref(prefs::kRecommendedPrintersBlocklist);
   registry->RegisterListPref(prefs::kRecommendedNativePrintersWhitelist);
 }
 
diff --git a/chrome/browser/chromeos/printing/calculators_policies_binder_unittest.cc b/chrome/browser/chromeos/printing/calculators_policies_binder_unittest.cc
index 2f114f72c..b42815900 100644
--- a/chrome/browser/chromeos/printing/calculators_policies_binder_unittest.cc
+++ b/chrome/browser/chromeos/printing/calculators_policies_binder_unittest.cc
@@ -179,7 +179,7 @@
       prefs::kRecommendedPrintersAccessMode,
       std::make_unique<base::Value>(
           BulkPrintersCalculator::AccessMode::BLOCKLIST_ONLY));
-  prefs_.SetManagedPref(prefs::kRecommendedNativePrintersBlacklist,
+  prefs_.SetManagedPref(prefs::kRecommendedPrintersBlocklist,
                         StringsToList(kBlocklistIds));
 
   env_.RunUntilIdle();
diff --git a/chrome/browser/chromeos/release_notes/release_notes_notification.cc b/chrome/browser/chromeos/release_notes/release_notes_notification.cc
index e63682c2..6ae32c9 100644
--- a/chrome/browser/chromeos/release_notes/release_notes_notification.cc
+++ b/chrome/browser/chromeos/release_notes/release_notes_notification.cc
@@ -48,7 +48,8 @@
   SystemNotificationHelper::GetInstance()->Close(kShowNotificationID);
   base::RecordAction(
       base::UserMetricsAction("ReleaseNotes.LaunchedNotification"));
-  chrome::LaunchReleaseNotes(profile_);
+  chrome::LaunchReleaseNotes(
+      profile_, apps::mojom::LaunchSource::kFromReleaseNotesNotification);
 }
 
 void ReleaseNotesNotification::ShowReleaseNotesNotification() {
diff --git a/chrome/browser/chromeos/remote_apps/DEPS b/chrome/browser/chromeos/remote_apps/DEPS
index 12a0b7b..5d92f21c 100644
--- a/chrome/browser/chromeos/remote_apps/DEPS
+++ b/chrome/browser/chromeos/remote_apps/DEPS
@@ -1,5 +1,5 @@
 specific_include_rules = {
-  "remote_apps_manager_browsertest\.cc": [
+  ".*_browsertest\.cc": [
     "+ash/app_list",
     "+ash/shell.h",
   ],
diff --git a/chrome/browser/chromeos/remote_apps/remote_apps_impl.cc b/chrome/browser/chromeos/remote_apps/remote_apps_impl.cc
new file mode 100644
index 0000000..e7b9b50
--- /dev/null
+++ b/chrome/browser/chromeos/remote_apps/remote_apps_impl.cc
@@ -0,0 +1,121 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/remote_apps/remote_apps_impl.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/optional.h"
+#include "base/stl_util.h"
+#include "chrome/browser/chromeos/remote_apps/remote_apps_manager.h"
+#include "chrome/browser/chromeos/remote_apps/remote_apps_manager_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "content/public/browser/render_frame_host.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/features/behavior_feature.h"
+#include "extensions/common/features/feature.h"
+#include "extensions/common/features/feature_provider.h"
+
+namespace chromeos {
+
+namespace {
+
+constexpr char kErrNotReady[] = "Manager for remote apps is not ready";
+constexpr char kErrFolderIdDoesNotExist[] = "Folder ID provided does not exist";
+
+static bool g_bypass_checks_for_testing_ = false;
+
+}  // namespace
+
+// static
+bool RemoteAppsImpl::IsAllowed(content::RenderFrameHost* render_frame_host,
+                               const extensions::Extension* extension) {
+  if (!render_frame_host || !extension)
+    return false;
+
+  Profile* profile =
+      Profile::FromBrowserContext(render_frame_host->GetBrowserContext());
+  DCHECK(profile);
+  // RemoteApps are not available for non-managed guest sessions.
+  if (!RemoteAppsManagerFactory::GetForProfile(profile))
+    return false;
+
+  if (g_bypass_checks_for_testing_)
+    return true;
+
+  const extensions::Feature* feature =
+      extensions::FeatureProvider::GetBehaviorFeature(
+          extensions::behavior_feature::kKeyImprivataInSessionExtension);
+  DCHECK(feature);
+  return feature->IsAvailableToExtension(extension).is_available();
+}
+
+// static
+void RemoteAppsImpl::SetBypassChecksForTesting(bool bypass_checks_for_testing) {
+  g_bypass_checks_for_testing_ = bypass_checks_for_testing;
+}
+
+RemoteAppsImpl::RemoteAppsImpl(RemoteAppsManager* manager) : manager_(manager) {
+  DCHECK(manager);
+}
+
+RemoteAppsImpl::~RemoteAppsImpl() = default;
+
+void RemoteAppsImpl::Bind(
+    mojo::PendingReceiver<remote_apps::mojom::RemoteApps> pending_remote_apps,
+    mojo::PendingRemote<remote_apps::mojom::RemoteAppLaunchObserver>
+        pending_observer) {
+  receivers_.Add(this, std::move(pending_remote_apps));
+  app_launch_observers_.Add(
+      mojo::Remote<remote_apps::mojom::RemoteAppLaunchObserver>(
+          std::move(pending_observer)));
+}
+
+void RemoteAppsImpl::AddFolder(const std::string& name,
+                               AddFolderCallback callback) {
+  const std::string& folder_id = manager_->AddFolder(name);
+  std::move(callback).Run(folder_id, base::nullopt);
+}
+
+void RemoteAppsImpl::AddApp(const std::string& name,
+                            const std::string& folder_id,
+                            const GURL& icon_url,
+                            AddAppCallback callback) {
+  manager_->AddApp(
+      name, folder_id, icon_url,
+      base::BindOnce(&RemoteAppsImpl::OnAppAdded, weak_factory_.GetWeakPtr(),
+                     std::move(callback)));
+}
+
+void RemoteAppsImpl::OnAppLaunched(const std::string& id) {
+  if (!base::Contains(app_ids_, id))
+    return;
+  for (auto& observer : app_launch_observers_)
+    observer->OnRemoteAppLaunched(id);
+}
+
+void RemoteAppsImpl::OnAppAdded(AddAppCallback callback,
+                                const std::string& id,
+                                RemoteAppsError error) {
+  switch (error) {
+    case RemoteAppsError::kNotReady:
+      std::move(callback).Run(base::nullopt, kErrNotReady);
+      return;
+    case RemoteAppsError::kFolderIdDoesNotExist:
+      std::move(callback).Run(base::nullopt, kErrFolderIdDoesNotExist);
+      return;
+    case RemoteAppsError::kNone:
+      app_ids_.insert(id);
+      std::move(callback).Run(id, base::nullopt);
+      return;
+    case RemoteAppsError::kAppIdDoesNotExist:
+      // Only occurs when deleting an app, which is not yet implemented in the
+      // API.
+      DCHECK(false);
+  }
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/remote_apps/remote_apps_impl.h b/chrome/browser/chromeos/remote_apps/remote_apps_impl.h
new file mode 100644
index 0000000..3102800
--- /dev/null
+++ b/chrome/browser/chromeos/remote_apps/remote_apps_impl.h
@@ -0,0 +1,76 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_REMOTE_APPS_REMOTE_APPS_IMPL_H_
+#define CHROME_BROWSER_CHROMEOS_REMOTE_APPS_REMOTE_APPS_IMPL_H_
+
+#include <set>
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "base/scoped_observer.h"
+#include "chrome/browser/chromeos/remote_apps/remote_apps_types.h"
+#include "chromeos/components/remote_apps/mojom/remote_apps.mojom.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "mojo/public/cpp/bindings/remote_set.h"
+#include "url/gurl.h"
+
+namespace content {
+class RenderFrameHost;
+}
+
+namespace extensions {
+class Extension;
+}
+
+namespace chromeos {
+
+class RemoteAppsManager;
+
+// Forwards calls to RemoteAppsManager via mojo. Also keeps track of apps added
+// by the extension for |OnAppLaunched()|.
+class RemoteAppsImpl : public remote_apps::mojom::RemoteApps {
+ public:
+  static bool IsAllowed(content::RenderFrameHost* render_frame_host,
+                        const extensions::Extension* extension);
+
+  static void SetBypassChecksForTesting(bool bypass_checks_for_testing);
+
+  explicit RemoteAppsImpl(RemoteAppsManager* manager);
+  RemoteAppsImpl(const RemoteAppsImpl&) = delete;
+  RemoteAppsImpl& operator=(const RemoteAppsImpl&) = delete;
+  ~RemoteAppsImpl() override;
+
+  void Bind(
+      mojo::PendingReceiver<remote_apps::mojom::RemoteApps> pending_remote_apps,
+      mojo::PendingRemote<remote_apps::mojom::RemoteAppLaunchObserver>
+          pending_observer);
+
+  // remote_apps::mojom::RemoteApps:
+  void AddFolder(const std::string& name, AddFolderCallback callback) override;
+  void AddApp(const std::string& name,
+              const std::string& folder_id,
+              const GURL& icon_url,
+              AddAppCallback callback) override;
+
+  void OnAppLaunched(const std::string& id);
+
+ private:
+  void OnAppAdded(AddAppCallback callback,
+                  const std::string& id,
+                  RemoteAppsError error);
+
+  RemoteAppsManager* manager_ = nullptr;
+  std::set<std::string> app_ids_;
+  mojo::ReceiverSet<remote_apps::mojom::RemoteApps> receivers_;
+  mojo::RemoteSet<remote_apps::mojom::RemoteAppLaunchObserver>
+      app_launch_observers_;
+  base::WeakPtrFactory<RemoteAppsImpl> weak_factory_{this};
+};
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_REMOTE_APPS_REMOTE_APPS_IMPL_H_
diff --git a/chrome/browser/chromeos/remote_apps/remote_apps_impl_browsertest.cc b/chrome/browser/chromeos/remote_apps/remote_apps_impl_browsertest.cc
new file mode 100644
index 0000000..58a7e522
--- /dev/null
+++ b/chrome/browser/chromeos/remote_apps/remote_apps_impl_browsertest.cc
@@ -0,0 +1,188 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/remote_apps/remote_apps_impl.h"
+
+#include <string>
+#include <vector>
+
+#include "ash/app_list/app_list_controller_impl.h"
+#include "ash/app_list/model/app_list_item.h"
+#include "ash/app_list/model/app_list_model.h"
+#include "ash/shell.h"
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "base/values.h"
+#include "chrome/browser/chromeos/login/test/local_policy_test_server_mixin.h"
+#include "chrome/browser/chromeos/login/test/session_manager_state_waiter.h"
+#include "chrome/browser/chromeos/login/wizard_controller.h"
+#include "chrome/browser/chromeos/policy/device_policy_cros_browser_test.h"
+#include "chrome/browser/chromeos/profiles/profile_helper.h"
+#include "chrome/browser/chromeos/remote_apps/id_generator.h"
+#include "chrome/browser/chromeos/remote_apps/remote_apps_manager.h"
+#include "chrome/browser/chromeos/remote_apps/remote_apps_manager_factory.h"
+#include "chrome/browser/chromeos/remote_apps/remote_apps_model.h"
+#include "chrome/browser/extensions/chrome_test_extension_loader.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/app_list/app_list_syncable_service_factory.h"
+#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
+#include "chrome/common/chrome_paths.h"
+#include "chromeos/constants/chromeos_switches.h"
+#include "components/policy/proto/chrome_device_policy.pb.h"
+#include "components/user_manager/user.h"
+#include "components/user_manager/user_manager.h"
+#include "content/public/test/browser_test.h"
+#include "extensions/browser/api/test/test_api.h"
+#include "extensions/common/manifest.h"
+#include "extensions/common/switches.h"
+#include "extensions/test/extension_test_message_listener.h"
+#include "extensions/test/result_catcher.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromeos {
+
+namespace {
+
+// ID of extension found at chrome/test/data/remote_apps.
+constexpr char kExtensionId[] = "ceddkihciiemhnpnhbndbinppokgoidh";
+
+constexpr char kId1[] = "Id 1";
+constexpr char kId2[] = "Id 2";
+constexpr char kId3[] = "Id 3";
+
+}  // namespace
+
+class RemoteAppsImplBrowsertest : public policy::DevicePolicyCrosBrowserTest {
+ public:
+  RemoteAppsImplBrowsertest() : policy::DevicePolicyCrosBrowserTest() {}
+
+  // DevicePolicyCrosBrowserTest:
+  void SetUp() override {
+    app_list::AppListSyncableServiceFactory::SetUseInTesting(true);
+    RemoteAppsImpl::SetBypassChecksForTesting(true);
+    DevicePolicyCrosBrowserTest::SetUp();
+  }
+
+  // DevicePolicyCrosBrowserTest:
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    DevicePolicyCrosBrowserTest::SetUpCommandLine(command_line);
+    command_line->AppendSwitch(chromeos::switches::kLoginManager);
+    command_line->AppendSwitch(chromeos::switches::kForceLoginManagerInTests);
+    command_line->AppendSwitchASCII(
+        extensions::switches::kAllowlistedExtensionID, kExtensionId);
+  }
+
+  // DevicePolicyCrosBrowserTest:
+  void SetUpOnMainThread() override {
+    policy::DevicePolicyCrosBrowserTest::SetUpOnMainThread();
+
+    SetUpDeviceLocalAccountPolicy();
+    WizardController::SkipPostLoginScreensForTesting();
+    SessionStateWaiter(session_manager::SessionState::ACTIVE).Wait();
+  }
+
+  void SetUpDeviceLocalAccountPolicy() {
+    enterprise_management::DeviceLocalAccountsProto* const
+        device_local_accounts =
+            device_policy()->payload().mutable_device_local_accounts();
+    enterprise_management::DeviceLocalAccountInfoProto* const account =
+        device_local_accounts->add_account();
+    account->set_account_id("user@test");
+    account->set_type(enterprise_management::DeviceLocalAccountInfoProto::
+                          ACCOUNT_TYPE_PUBLIC_SESSION);
+    device_local_accounts->set_auto_login_id("user@test");
+    device_local_accounts->set_auto_login_delay(0);
+    RefreshDevicePolicy();
+  }
+
+  void LoadExtensionAndRunTest(const std::string& test_name) {
+    config_.SetKey("customArg", base::Value(test_name));
+    extensions::TestGetConfigFunction::set_test_config_state(&config_);
+
+    base::FilePath test_dir_path;
+    base::PathService::Get(chrome::DIR_TEST_DATA, &test_dir_path);
+    base::FilePath extension_path =
+        test_dir_path.AppendASCII("extensions/remote_apps/extension");
+    base::FilePath pem_path =
+        test_dir_path.AppendASCII("extensions/remote_apps/remote_apps.pem");
+
+    user_manager::User* user =
+        user_manager::UserManager::Get()->GetActiveUser();
+    Profile* profile = ProfileHelper::Get()->GetProfileByUser(user);
+
+    std::unique_ptr<FakeIdGenerator> id_generator =
+        std::make_unique<FakeIdGenerator>(
+            std::vector<std::string>{kId1, kId2, kId3});
+    RemoteAppsManagerFactory::GetForProfile(profile)
+        ->GetModelForTesting()
+        ->SetIdGeneratorForTesting(std::move(id_generator));
+
+    extensions::ChromeTestExtensionLoader loader(profile);
+    loader.set_location(extensions::Manifest::EXTERNAL_POLICY);
+    loader.set_pack_extension(true);
+    loader.set_pem_path(pem_path);
+    // When |set_pack_extension_| is true, the |loader| first packs and then
+    // loads the extension. The packing step creates a _metadata folder which
+    // causes an install warning when loading.
+    loader.set_ignore_manifest_warnings(true);
+    ASSERT_TRUE(loader.LoadExtension(extension_path));
+  }
+
+  ash::AppListItem* GetAppListItem(const std::string& id) {
+    ash::AppListControllerImpl* controller =
+        ash::Shell::Get()->app_list_controller();
+    ash::AppListModel* model = controller->GetModel();
+    return model->FindItem(id);
+  }
+
+ private:
+  base::DictionaryValue config_;
+  chromeos::LocalPolicyTestServerMixin local_policy_mixin_{&mixin_host_};
+};
+
+IN_PROC_BROWSER_TEST_F(RemoteAppsImplBrowsertest, AddApp) {
+  extensions::ResultCatcher catcher;
+  LoadExtensionAndRunTest("AddApp");
+  ASSERT_TRUE(catcher.GetNextResult());
+
+  ash::AppListItem* app = GetAppListItem(kId1);
+  EXPECT_FALSE(app->is_folder());
+  EXPECT_EQ("App 1", app->name());
+}
+
+IN_PROC_BROWSER_TEST_F(RemoteAppsImplBrowsertest, AddFolderAndApps) {
+  extensions::ResultCatcher catcher;
+  LoadExtensionAndRunTest("AddFolderAndApps");
+  ASSERT_TRUE(catcher.GetNextResult());
+
+  ash::AppListItem* folder = GetAppListItem(kId1);
+  EXPECT_TRUE(folder->is_folder());
+  EXPECT_EQ("Folder 1", folder->name());
+  EXPECT_EQ(2u, folder->ChildItemCount());
+  EXPECT_TRUE(folder->FindChildItem(kId2));
+  EXPECT_TRUE(folder->FindChildItem(kId3));
+
+  ash::AppListItem* app1 = GetAppListItem(kId2);
+  EXPECT_EQ(kId1, app1->folder_id());
+
+  ash::AppListItem* app2 = GetAppListItem(kId3);
+  EXPECT_EQ(kId1, app2->folder_id());
+}
+
+IN_PROC_BROWSER_TEST_F(RemoteAppsImplBrowsertest, OnRemoteAppLaunched) {
+  extensions::ResultCatcher catcher;
+  ExtensionTestMessageListener listener("Remote app added",
+                                        /*will_reply=*/false);
+  listener.set_extension_id(kExtensionId);
+  LoadExtensionAndRunTest("OnRemoteAppLaunched");
+  ASSERT_TRUE(listener.WaitUntilSatisfied());
+
+  ChromeLauncherController::instance()->LaunchApp(
+      ash::ShelfID(kId1), ash::ShelfLaunchSource::LAUNCH_FROM_APP_LIST,
+      /*event_flags=*/0, /*display_id=*/0);
+  ASSERT_TRUE(catcher.GetNextResult());
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/remote_apps/remote_apps_manager.cc b/chrome/browser/chromeos/remote_apps/remote_apps_manager.cc
index ceb25bb..4509fd4 100644
--- a/chrome/browser/chromeos/remote_apps/remote_apps_manager.cc
+++ b/chrome/browser/chromeos/remote_apps/remote_apps_manager.cc
@@ -10,6 +10,7 @@
 #include "ash/public/cpp/image_downloader.h"
 #include "base/bind.h"
 #include "chrome/browser/apps/app_service/menu_util.h"
+#include "chrome/browser/chromeos/remote_apps/remote_apps_impl.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/app_list/app_list_model_updater.h"
 #include "chrome/browser/ui/app_list/app_list_syncable_service.h"
@@ -73,12 +74,13 @@
                                const GURL& icon_url,
                                AddAppCallback callback) {
   if (!is_initialized_) {
-    std::move(callback).Run(std::string(), Error::kNotReady);
+    std::move(callback).Run(std::string(), RemoteAppsError::kNotReady);
     return;
   }
 
   if (!folder_id.empty() && !model_->HasFolder(folder_id)) {
-    std::move(callback).Run(std::string(), Error::kFolderIdDoesNotExist);
+    std::move(callback).Run(std::string(),
+                            RemoteAppsError::kFolderIdDoesNotExist);
     return;
   }
 
@@ -88,15 +90,15 @@
   remote_apps_->AddApp(info);
 }
 
-RemoteAppsManager::Error RemoteAppsManager::DeleteApp(const std::string& id) {
+RemoteAppsError RemoteAppsManager::DeleteApp(const std::string& id) {
   // Check if app was added but |HandleOnAppAdded| has not been called.
   if (!model_->HasApp(id) ||
       add_app_callback_map_.find(id) != add_app_callback_map_.end())
-    return Error::kAppIdDoesNotExist;
+    return RemoteAppsError::kAppIdDoesNotExist;
 
   model_->DeleteApp(id);
   remote_apps_->DeleteApp(id);
-  return Error::kNone;
+  return RemoteAppsError::kNone;
 }
 
 std::string RemoteAppsManager::AddFolder(const std::string& folder_name) {
@@ -105,17 +107,16 @@
   return folder_info.id;
 }
 
-RemoteAppsManager::Error RemoteAppsManager::DeleteFolder(
-    const std::string& folder_id) {
+RemoteAppsError RemoteAppsManager::DeleteFolder(const std::string& folder_id) {
   if (!model_->HasFolder(folder_id))
-    return Error::kFolderIdDoesNotExist;
+    return RemoteAppsError::kFolderIdDoesNotExist;
 
   // Move all items out of the folder. Empty folders are automatically deleted.
   RemoteAppsModel::FolderInfo& folder_info = model_->GetFolderInfo(folder_id);
   for (const auto& app : folder_info.items)
     model_updater_->MoveItemToFolder(app, std::string());
   model_->DeleteFolder(folder_id);
-  return Error::kNone;
+  return RemoteAppsError::kNone;
 }
 
 void RemoteAppsManager::AddObserver(Observer* observer) {
@@ -126,8 +127,22 @@
   observer_list_.RemoveObserver(observer);
 }
 
+void RemoteAppsManager::BindInterface(
+    mojo::PendingReceiver<remote_apps::mojom::RemoteAppsFactory>
+        pending_remote_apps_factory) {
+  receivers_.Add(this, std::move(pending_remote_apps_factory));
+}
+
 void RemoteAppsManager::Shutdown() {}
 
+void RemoteAppsManager::Create(
+    mojo::PendingReceiver<remote_apps::mojom::RemoteApps> pending_remote_apps,
+    mojo::PendingRemote<remote_apps::mojom::RemoteAppLaunchObserver>
+        pending_observer) {
+  remote_apps_impl_.Bind(std::move(pending_remote_apps),
+                         std::move(pending_observer));
+}
+
 const std::map<std::string, RemoteAppsModel::AppInfo>&
 RemoteAppsManager::GetApps() {
   return model_->GetAllAppInfo();
@@ -136,6 +151,7 @@
 void RemoteAppsManager::LaunchApp(const std::string& id) {
   for (Observer& observer : observer_list_)
     observer.OnAppLaunched(id);
+  remote_apps_impl_.OnAppLaunched(id);
 }
 
 gfx::ImageSkia RemoteAppsManager::GetIcon(const std::string& id) {
@@ -214,7 +230,7 @@
   auto it = add_app_callback_map_.find(id);
   DCHECK(it != add_app_callback_map_.end())
       << "Missing callback for id: " << id;
-  std::move(it->second).Run(id, Error::kNone);
+  std::move(it->second).Run(id, RemoteAppsError::kNone);
   add_app_callback_map_.erase(it);
 }
 
diff --git a/chrome/browser/chromeos/remote_apps/remote_apps_manager.h b/chrome/browser/chromeos/remote_apps/remote_apps_manager.h
index 0ca73492..78c7efe 100644
--- a/chrome/browser/chromeos/remote_apps/remote_apps_manager.h
+++ b/chrome/browser/chromeos/remote_apps/remote_apps_manager.h
@@ -13,11 +13,16 @@
 #include "base/observer_list.h"
 #include "base/scoped_observer.h"
 #include "chrome/browser/apps/app_service/remote_apps.h"
+#include "chrome/browser/chromeos/remote_apps/remote_apps_impl.h"
 #include "chrome/browser/chromeos/remote_apps/remote_apps_model.h"
+#include "chrome/browser/chromeos/remote_apps/remote_apps_types.h"
 #include "chrome/browser/ui/app_list/app_list_model_updater_observer.h"
 #include "chrome/browser/ui/app_list/app_list_syncable_service.h"
 #include "chrome/browser/ui/app_list/chrome_app_list_model_updater.h"
+#include "chromeos/components/remote_apps/mojom/remote_apps.mojom.h"
 #include "components/keyed_service/core/keyed_service.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
 
 class AppListModelUpdater;
 class ChromeAppListItem;
@@ -33,6 +38,8 @@
 
 namespace chromeos {
 
+class RemoteAppsImpl;
+
 // KeyedService which manages the logic for |AppType::kRemote| in AppService.
 // This service is only created for Managed Guest Sessions.
 // The IDs of the added apps and folders are GUIDs generated using
@@ -40,16 +47,9 @@
 class RemoteAppsManager : public KeyedService,
                           public apps::RemoteApps::Delegate,
                           public app_list::AppListSyncableService::Observer,
-                          public AppListModelUpdaterObserver {
+                          public AppListModelUpdaterObserver,
+                          public remote_apps::mojom::RemoteAppsFactory {
  public:
-  enum class Error {
-    kNone = 0,
-    kAppIdDoesNotExist,
-    kFolderIdDoesNotExist,
-    // Manager has not been initialized.
-    kNotReady,
-  };
-
   class Observer : public base::CheckedObserver {
    public:
     ~Observer() override = default;
@@ -73,8 +73,12 @@
 
   bool is_initialized() const { return is_initialized_; }
 
+  void BindInterface(
+      mojo::PendingReceiver<remote_apps::mojom::RemoteAppsFactory>
+          pending_remote_apps_factory);
+
   using AddAppCallback =
-      base::OnceCallback<void(const std::string& id, Error error)>;
+      base::OnceCallback<void(const std::string& id, RemoteAppsError error)>;
 
   // Adds a app with the given |name|. If |folder_id| is non-empty, the app is
   // added to the folder with the given ID. The icon of the app is an image
@@ -92,7 +96,7 @@
 
   // Deletes the app with id |id|.
   // Deleting a non-existent app will result in an error.
-  Error DeleteApp(const std::string& id);
+  RemoteAppsError DeleteApp(const std::string& id);
 
   // Adds a folder with |folder_name|. Note that empty folders are not
   // shown in the launcher. Returns the ID for the added folder.
@@ -101,7 +105,7 @@
   // Deletes the folder with id |folder_id|. All items in the folder are moved
   // to the top-level in the launcher.
   // Deleting a non-existent folder will result in an error.
-  Error DeleteFolder(const std::string& folder_id);
+  RemoteAppsError DeleteFolder(const std::string& folder_id);
 
   void AddObserver(Observer* observer);
   void RemoveObserver(Observer* observer);
@@ -109,6 +113,12 @@
   // KeyedService:
   void Shutdown() override;
 
+  // remote_apps::mojom::RemoteAppsFactory:
+  void Create(
+      mojo::PendingReceiver<remote_apps::mojom::RemoteApps> pending_remote_apps,
+      mojo::PendingRemote<remote_apps::mojom::RemoteAppLaunchObserver>
+          pending_observer) override;
+
   // apps::RemoteApps::Delegate:
   const std::map<std::string, RemoteAppsModel::AppInfo>& GetApps() override;
   void LaunchApp(const std::string& id) override;
@@ -146,9 +156,11 @@
   app_list::AppListSyncableService* app_list_syncable_service_ = nullptr;
   AppListModelUpdater* model_updater_ = nullptr;
   std::unique_ptr<apps::RemoteApps> remote_apps_;
+  RemoteAppsImpl remote_apps_impl_{this};
   std::unique_ptr<RemoteAppsModel> model_;
   std::unique_ptr<ImageDownloader> image_downloader_;
   base::ObserverList<Observer> observer_list_;
+  mojo::ReceiverSet<remote_apps::mojom::RemoteAppsFactory> receivers_;
   // Map from id to callback. The callback is run after |OnAppUpdate| for the
   // app has been observed.
   std::map<std::string, AddAppCallback> add_app_callback_map_;
diff --git a/chrome/browser/chromeos/remote_apps/remote_apps_manager_browsertest.cc b/chrome/browser/chromeos/remote_apps/remote_apps_manager_browsertest.cc
index d374ae1..a7768cb 100644
--- a/chrome/browser/chromeos/remote_apps/remote_apps_manager_browsertest.cc
+++ b/chrome/browser/chromeos/remote_apps/remote_apps_manager_browsertest.cc
@@ -213,35 +213,34 @@
                      const GURL& icon_url) {
     base::RunLoop run_loop;
     std::string id;
-    manager_->AddApp(
-        name, folder_id, icon_url,
-        base::BindOnce(
-            [](base::RepeatingClosure closure, std::string* id_arg,
-               const std::string& id, RemoteAppsManager::Error error) {
-              ASSERT_EQ(RemoteAppsManager::Error::kNone, error);
+    manager_->AddApp(name, folder_id, icon_url,
+                     base::BindOnce(
+                         [](base::RepeatingClosure closure, std::string* id_arg,
+                            const std::string& id, RemoteAppsError error) {
+                           ASSERT_EQ(RemoteAppsError::kNone, error);
 
-              ash::AppListControllerImpl* controller =
-                  ash::Shell::Get()->app_list_controller();
-              ash::AppListModel* model = controller->GetModel();
-              ASSERT_TRUE(model->FindItem(id));
-              *id_arg = id;
+                           ash::AppListControllerImpl* controller =
+                               ash::Shell::Get()->app_list_controller();
+                           ash::AppListModel* model = controller->GetModel();
+                           ASSERT_TRUE(model->FindItem(id));
+                           *id_arg = id;
 
-              closure.Run();
-            },
-            run_loop.QuitClosure(), &id));
+                           closure.Run();
+                         },
+                         run_loop.QuitClosure(), &id));
     run_loop.Run();
     return id;
   }
 
-  RemoteAppsManager::Error DeleteApp(const std::string& id) {
-    RemoteAppsManager::Error error = manager_->DeleteApp(id);
+  RemoteAppsError DeleteApp(const std::string& id) {
+    RemoteAppsError error = manager_->DeleteApp(id);
     // Allow updates to propagate to AppList.
     base::RunLoop().RunUntilIdle();
     return error;
   }
 
-  RemoteAppsManager::Error DeleteFolder(const std::string& id) {
-    RemoteAppsManager::Error error = manager_->DeleteFolder(id);
+  RemoteAppsError DeleteFolder(const std::string& id) {
+    RemoteAppsError error = manager_->DeleteFolder(id);
     // Allow updates to propagate to AppList.
     base::RunLoop().RunUntilIdle();
     return error;
@@ -258,7 +257,7 @@
     waiter.Wait();
   }
 
-  void AddAppAssertError(RemoteAppsManager::Error error,
+  void AddAppAssertError(RemoteAppsError error,
                          const std::string& name,
                          const std::string& folder_id,
                          const GURL& icon_url) {
@@ -266,9 +265,8 @@
     manager_->AddApp(
         name, folder_id, icon_url,
         base::BindOnce(
-            [](base::RepeatingClosure closure,
-               RemoteAppsManager::Error expected_error, const std::string& id,
-               RemoteAppsManager::Error error) {
+            [](base::RepeatingClosure closure, RemoteAppsError expected_error,
+               const std::string& id, RemoteAppsError error) {
               ASSERT_EQ(expected_error, error);
               closure.Run();
             },
@@ -308,8 +306,8 @@
   GURL icon_url("icon_url");
   gfx::ImageSkia icon = CreateTestIcon(32, SK_ColorRED);
 
-  AddAppAssertError(RemoteAppsManager::Error::kFolderIdDoesNotExist, name,
-                    kMissingId, icon_url);
+  AddAppAssertError(RemoteAppsError::kFolderIdDoesNotExist, name, kMissingId,
+                    icon_url);
 }
 
 IN_PROC_BROWSER_TEST_F(RemoteAppsManagerBrowsertest, AddAppErrorNotReady) {
@@ -318,8 +316,7 @@
   gfx::ImageSkia icon = CreateTestIcon(32, SK_ColorRED);
 
   manager_->SetIsInitializedForTesting(false);
-  AddAppAssertError(RemoteAppsManager::Error::kNotReady, name, std::string(),
-                    icon_url);
+  AddAppAssertError(RemoteAppsError::kNotReady, name, std::string(), icon_url);
 }
 
 IN_PROC_BROWSER_TEST_F(RemoteAppsManagerBrowsertest, DeleteApp) {
@@ -327,15 +324,14 @@
   AddAppAndWaitForIconChange(kId1, "name", std::string(), GURL("icon_url"),
                              CreateTestIcon(32, SK_ColorRED));
 
-  RemoteAppsManager::Error error = DeleteApp(kId1);
+  RemoteAppsError error = DeleteApp(kId1);
   base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(RemoteAppsManager::Error::kNone, error);
+  EXPECT_EQ(RemoteAppsError::kNone, error);
   EXPECT_FALSE(GetAppListItem(kId1));
 }
 
 IN_PROC_BROWSER_TEST_F(RemoteAppsManagerBrowsertest, DeleteAppError) {
-  EXPECT_EQ(RemoteAppsManager::Error::kAppIdDoesNotExist,
-            DeleteApp(kMissingId));
+  EXPECT_EQ(RemoteAppsError::kAppIdDoesNotExist, DeleteApp(kMissingId));
 }
 
 IN_PROC_BROWSER_TEST_F(RemoteAppsManagerBrowsertest, AddAndDeleteFolder) {
@@ -343,12 +339,11 @@
   // Empty folder has no AppListItem.
   EXPECT_FALSE(GetAppListItem(kId1));
 
-  EXPECT_EQ(RemoteAppsManager::Error::kNone, DeleteFolder(kId1));
+  EXPECT_EQ(RemoteAppsError::kNone, DeleteFolder(kId1));
 }
 
 IN_PROC_BROWSER_TEST_F(RemoteAppsManagerBrowsertest, DeleteFolderError) {
-  EXPECT_EQ(RemoteAppsManager::Error::kFolderIdDoesNotExist,
-            DeleteFolder(kMissingId));
+  EXPECT_EQ(RemoteAppsError::kFolderIdDoesNotExist, DeleteFolder(kMissingId));
 }
 
 IN_PROC_BROWSER_TEST_F(RemoteAppsManagerBrowsertest, AddFolderAndApp) {
diff --git a/chrome/browser/chromeos/remote_apps/remote_apps_types.h b/chrome/browser/chromeos/remote_apps/remote_apps_types.h
new file mode 100644
index 0000000..e3d9f00
--- /dev/null
+++ b/chrome/browser/chromeos/remote_apps/remote_apps_types.h
@@ -0,0 +1,20 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_REMOTE_APPS_REMOTE_APPS_TYPES_H_
+#define CHROME_BROWSER_CHROMEOS_REMOTE_APPS_REMOTE_APPS_TYPES_H_
+
+namespace chromeos {
+
+enum class RemoteAppsError {
+  kNone = 0,
+  kAppIdDoesNotExist,
+  kFolderIdDoesNotExist,
+  // Manager has not been initialized.
+  kNotReady,
+};
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_REMOTE_APPS_REMOTE_APPS_TYPES_H_
diff --git a/chrome/browser/chromeos/system/device_disabling_browsertest.cc b/chrome/browser/chromeos/system/device_disabling_browsertest.cc
index d290cf9..b1e7ebe 100644
--- a/chrome/browser/chromeos/system/device_disabling_browsertest.cc
+++ b/chrome/browser/chromeos/system/device_disabling_browsertest.cc
@@ -159,7 +159,7 @@
   connect_run_loop.Run();
 
   // Skip to the login screen.
-  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+  OobeScreenWaiter(GetFirstSigninScreen()).Wait();
 
   // Mark the device as disabled and wait until cros settings update.
   MarkDisabledAndWaitForPolicyFetch();
@@ -227,7 +227,7 @@
 IN_PROC_BROWSER_TEST_F(DeviceDisablingWithUsersTest, DialogNotHidden) {
   EXPECT_TRUE(ash::LoginScreenTestApi::ClickAddUserButton());
   EXPECT_TRUE(ash::LoginScreenTestApi::IsOobeDialogVisible());
-  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+  OobeScreenWaiter(GetFirstSigninScreen()).Wait();
   MarkDisabledAndWaitForPolicyFetch();
   OobeScreenWaiter(DeviceDisabledScreenView::kScreenId).Wait();
   LoginDisplayHost::default_host()->StartSignInScreen();
diff --git a/chrome/browser/chromeos/ui/request_system_proxy_credentials_view.cc b/chrome/browser/chromeos/ui/request_system_proxy_credentials_view.cc
new file mode 100644
index 0000000..c8308b9
--- /dev/null
+++ b/chrome/browser/chromeos/ui/request_system_proxy_credentials_view.cc
@@ -0,0 +1,173 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/ui/request_system_proxy_credentials_view.h"
+
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/i18n/number_formatting.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/chromeos/ui/passphrase_textfield.h"
+#include "chrome/browser/ui/browser_dialogs.h"
+#include "chrome/browser/ui/views/chrome_layout_provider.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/vector_icons/vector_icons.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/events/event.h"
+#include "ui/gfx/color_palette.h"
+#include "ui/gfx/paint_vector_icon.h"
+#include "ui/native_theme/native_theme.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/controls/textfield/textfield.h"
+#include "ui/views/layout/grid_layout.h"
+#include "ui/views/widget/widget.h"
+
+namespace chromeos {
+
+RequestSystemProxyCredentialsView::RequestSystemProxyCredentialsView(
+    const std::string& proxy_server,
+    bool show_error_label,
+    base::OnceClosure view_destruction_callback)
+    : window_title_(
+          l10n_util::GetStringUTF16(IDS_SYSTEM_PROXY_AUTH_DIALOG_TITLE)),
+      proxy_server_(proxy_server),
+      show_error_label_(show_error_label),
+      view_destruction_callback_(std::move(view_destruction_callback)) {
+  Init();
+}
+
+RequestSystemProxyCredentialsView::~RequestSystemProxyCredentialsView() {
+  std::move(view_destruction_callback_).Run();
+}
+
+views::View* RequestSystemProxyCredentialsView::GetInitiallyFocusedView() {
+  return username_textfield_;
+}
+
+base::string16 RequestSystemProxyCredentialsView::GetWindowTitle() const {
+  return window_title_;
+}
+
+bool RequestSystemProxyCredentialsView::ShouldShowCloseButton() const {
+  return false;
+}
+
+const std::string& RequestSystemProxyCredentialsView::GetProxyServer() const {
+  return proxy_server_;
+}
+
+base::string16 RequestSystemProxyCredentialsView::GetUsername() const {
+  return username_textfield_->GetText();
+}
+
+base::string16 RequestSystemProxyCredentialsView::GetPassword() const {
+  return password_textfield_->GetText();
+}
+
+void RequestSystemProxyCredentialsView::Init() {
+  const views::LayoutProvider* provider = views::LayoutProvider::Get();
+  SetBorder(views::CreateEmptyBorder(
+      provider->GetDialogInsetsForContentType(views::TEXT, views::TEXT)));
+  SetButtonLabel(
+      ui::DIALOG_BUTTON_OK,
+      l10n_util::GetStringUTF16(IDS_SYSTEM_PROXY_AUTH_DIALOG_OK_BUTTON));
+
+  views::GridLayout* layout =
+      SetLayoutManager(std::make_unique<views::GridLayout>());
+
+  int column_view_set_id = 0;
+  views::ColumnSet* column_set = layout->AddColumnSet(column_view_set_id);
+
+  column_set->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL,
+                        100, views::GridLayout::ColumnSize::kUsePreferred, 0,
+                        0);
+
+  layout->StartRow(0, column_view_set_id);
+  auto info_label = std::make_unique<views::Label>(l10n_util::GetStringFUTF16(
+      IDS_SYSTEM_PROXY_AUTH_DIALOG_TEXT, base::ASCIIToUTF16(GetProxyServer())));
+  info_label->SetEnabled(true);
+  info_label->SetTextStyle(views::style::STYLE_PRIMARY);
+  layout->AddView(std::move(info_label));
+
+  layout->StartRow(0, column_view_set_id);
+  auto info_label_privacy = std::make_unique<views::Label>(
+      l10n_util::GetStringUTF16(IDS_SYSTEM_PROXY_AUTH_DIALOG_PRIVACY_WARNING));
+  info_label_privacy->SetEnabled(true);
+  info_label_privacy->SetTextStyle(views::style::STYLE_SECONDARY);
+  layout->AddView(std::move(info_label_privacy));
+
+  column_view_set_id++;
+  column_set = layout->AddColumnSet(column_view_set_id);
+  column_set->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL,
+                        views::GridLayout::kFixedSize,
+                        views::GridLayout::ColumnSize::kUsePreferred, 0, 0);
+  const int label_padding =
+      provider->GetDistanceMetric(views::DISTANCE_RELATED_LABEL_HORIZONTAL);
+  column_set->AddPaddingColumn(0, label_padding);
+  column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1.0,
+                        views::GridLayout::ColumnSize::kUsePreferred, 0, 0);
+
+  const int unrelated_vertical_spacing =
+      provider->GetDistanceMetric(views::DISTANCE_UNRELATED_CONTROL_VERTICAL);
+  layout->StartRowWithPadding(1.0, column_view_set_id, 0,
+                              unrelated_vertical_spacing);
+  auto username_label = std::make_unique<views::Label>(
+      l10n_util::GetStringUTF16(IDS_SYSTEM_PROXY_AUTH_DIALOG_USERNAME_LABEL));
+  username_label->SetEnabled(true);
+
+  auto username_textfield = std::make_unique<views::Textfield>();
+  username_textfield->SetEnabled(true);
+  username_textfield->SetAssociatedLabel(
+      layout->AddView(std::move(username_label)));
+  username_textfield_ = layout->AddView(std::move(username_textfield));
+
+  const int related_vertical_spacing =
+      provider->GetDistanceMetric(views::DISTANCE_RELATED_CONTROL_VERTICAL);
+  layout->StartRowWithPadding(1.0, column_view_set_id, 0,
+                              related_vertical_spacing);
+  auto password_label = std::make_unique<views::Label>(
+      l10n_util::GetStringUTF16(IDS_SYSTEM_PROXY_AUTH_DIALOG_PASSWORD_LABEL));
+  password_label->SetEnabled(true);
+  auto password_textfield = std::make_unique<PassphraseTextfield>();
+  password_textfield->SetEnabled(true);
+  password_textfield->SetAssociatedLabel(
+      layout->AddView(std::move(password_label)));
+  password_textfield_ = layout->AddView(std::move(password_textfield));
+
+  column_view_set_id++;
+  column_set = layout->AddColumnSet(column_view_set_id);
+  column_set->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL,
+                        views::GridLayout::kFixedSize,
+                        views::GridLayout::ColumnSize::kUsePreferred, 0, 0);
+  column_set->AddPaddingColumn(0, label_padding);
+  column_set->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL,
+                        1.0, views::GridLayout::ColumnSize::kUsePreferred, 0,
+                        0);
+  layout->StartRowWithPadding(1.0, column_view_set_id, 0,
+                              related_vertical_spacing);
+  auto error_icon = std::make_unique<views::ImageView>();
+  const int kIconSize = 18;
+  error_icon->SetImage(
+      gfx::CreateVectorIcon(vector_icons::kInfoOutlineIcon, kIconSize,
+                            GetNativeTheme()->GetSystemColor(
+                                ui::NativeTheme::kColorId_AlertSeverityHigh)));
+  error_icon->SetImageSize(gfx::Size(kIconSize, kIconSize));
+  error_icon->SetVisible(show_error_label_);
+  layout->AddView(std::move(error_icon));
+  auto error_label = std::make_unique<views::Label>(
+      l10n_util::GetStringUTF16(IDS_SYSTEM_PROXY_AUTH_DIALOG_ERROR_LABEL));
+  error_label->SetEnabled(true);
+  error_label->SetVisible(show_error_label_);
+  error_label->SetEnabledColor(error_label->GetNativeTheme()->GetSystemColor(
+      ui::NativeTheme::kColorId_AlertSeverityHigh));
+  error_label_ = layout->AddView(std::move(error_label));
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/ui/request_system_proxy_credentials_view.h b/chrome/browser/chromeos/ui/request_system_proxy_credentials_view.h
new file mode 100644
index 0000000..8962308
--- /dev/null
+++ b/chrome/browser/chromeos/ui/request_system_proxy_credentials_view.h
@@ -0,0 +1,75 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_UI_REQUEST_SYSTEM_PROXY_CREDENTIALS_VIEW_H_
+#define CHROME_BROWSER_CHROMEOS_UI_REQUEST_SYSTEM_PROXY_CREDENTIALS_VIEW_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/strings/string16.h"
+#include "ui/base/ui_base_types.h"
+#include "ui/views/view.h"
+#include "ui/views/window/dialog_delegate.h"
+
+namespace views {
+class Label;
+class Textfield;
+}  // namespace views
+
+namespace chromeos {
+
+// A dialog box for requesting proxy authentication credentials for network
+// traffic at OS level (outside the browser).
+class RequestSystemProxyCredentialsView final
+    : public views::DialogDelegateView {
+ public:
+  RequestSystemProxyCredentialsView(
+      const std::string& proxy_server,
+      bool show_error_label,
+      base::OnceClosure view_destruction_callback);
+  RequestSystemProxyCredentialsView(const RequestSystemProxyCredentialsView&) =
+      delete;
+  RequestSystemProxyCredentialsView& operator=(
+      const RequestSystemProxyCredentialsView&) = delete;
+  ~RequestSystemProxyCredentialsView() override;
+
+  // views::DialogDelegateView
+  views::View* GetInitiallyFocusedView() override;
+  base::string16 GetWindowTitle() const override;
+  bool ShouldShowCloseButton() const override;
+
+  // Returns the proxy server for which the dialog is asking for credentials,
+  // in the format scheme://host:port.
+  const std::string& GetProxyServer() const;
+
+  base::string16 GetUsername() const;
+  base::string16 GetPassword() const;
+
+  views::Textfield* username_textfield_for_testing() {
+    return username_textfield_;
+  }
+  views::Textfield* password_textfield_for_testing() {
+    return password_textfield_;
+  }
+  views::Label* error_label_for_testing() { return error_label_; }
+
+ private:
+  void Init();
+
+  const base::string16 window_title_;
+
+  views::Textfield* username_textfield_ = nullptr;
+  views::Textfield* password_textfield_ = nullptr;
+  views::Label* error_label_ = nullptr;
+
+  const std::string proxy_server_;
+  const bool show_error_label_;
+  base::OnceClosure view_destruction_callback_;
+};
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_UI_REQUEST_SYSTEM_PROXY_CREDENTIALS_VIEW_H_
diff --git a/chrome/browser/chromeos/ui/request_system_proxy_credentials_view_unittest.cc b/chrome/browser/chromeos/ui/request_system_proxy_credentials_view_unittest.cc
new file mode 100644
index 0000000..860e1122
--- /dev/null
+++ b/chrome/browser/chromeos/ui/request_system_proxy_credentials_view_unittest.cc
@@ -0,0 +1,121 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/ui/request_system_proxy_credentials_view.h"
+
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_dialogs.h"
+#include "chrome/test/base/browser_with_test_window_test.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/controls/textfield/textfield.h"
+
+namespace {
+constexpr char kProxy[] = "http://localserver";
+constexpr char kUsername[] = "testuser";
+constexpr char kPassword[] = "testpwd";
+}  // namespace
+
+namespace chromeos {
+
+class RequestSystemProxyCredentialsViewTest : public BrowserWithTestWindowTest {
+ public:
+  RequestSystemProxyCredentialsViewTest()
+      : BrowserWithTestWindowTest(Browser::TYPE_NORMAL) {}
+  RequestSystemProxyCredentialsViewTest(
+      const RequestSystemProxyCredentialsViewTest&) = delete;
+  RequestSystemProxyCredentialsViewTest& operator=(
+      const RequestSystemProxyCredentialsViewTest&) = delete;
+  ~RequestSystemProxyCredentialsViewTest() override = default;
+
+  void TearDown() override {
+    active_widget_->CloseNow();
+    BrowserWithTestWindowTest::TearDown();
+  }
+
+ protected:
+  void CreateDialog(bool show_error) {
+    system_proxy_dialog_ = new chromeos::RequestSystemProxyCredentialsView(
+        kProxy, show_error, base::DoNothing());
+
+    system_proxy_dialog_->SetAcceptCallback(
+        base::BindRepeating(&RequestSystemProxyCredentialsViewTest::OnAccept,
+                            base::Unretained(this)));
+    system_proxy_dialog_->SetCancelCallback(
+        base::BindRepeating(&RequestSystemProxyCredentialsViewTest::OnCancel,
+                            base::Unretained(this)));
+
+    active_widget_ = views::DialogDelegate::CreateDialogWidget(
+        system_proxy_dialog_, GetContext(), /*parent=*/nullptr);
+    active_widget_->Show();
+  }
+
+  void OnAccept() { accepted_ = true; }
+
+  void OnCancel() { canceled_ = true; }
+
+  bool accepted_ = false;
+  bool canceled_ = false;
+  // Owned by |active_widget_|.
+  chromeos::RequestSystemProxyCredentialsView* system_proxy_dialog_ = nullptr;
+
+ private:
+  // Owned by the UI code (NativeWidget).
+  views::Widget* active_widget_ = nullptr;
+};
+
+// Tests that clicking "OK" in the UI will result in calling the
+// |system_proxy_dialog_.accept_callback_| with the user entered
+// credentials as arguments.
+TEST_F(RequestSystemProxyCredentialsViewTest, AcceptCallback) {
+  CreateDialog(/*show_error=*/false);
+  system_proxy_dialog_->username_textfield_for_testing()->SetText(
+      base::ASCIIToUTF16(kUsername));
+  system_proxy_dialog_->password_textfield_for_testing()->SetText(
+      base::ASCIIToUTF16(kPassword));
+
+  // Simulate pressing the "OK" button.
+  system_proxy_dialog_->Accept();
+
+  EXPECT_TRUE(accepted_);
+  EXPECT_EQ(base::UTF16ToUTF8(system_proxy_dialog_->GetUsername()), kUsername);
+  EXPECT_EQ(base::UTF16ToUTF8(system_proxy_dialog_->GetPassword()), kPassword);
+}
+
+TEST_F(RequestSystemProxyCredentialsViewTest, CancelCallback) {
+  CreateDialog(/*show_error=*/false);
+  system_proxy_dialog_->Cancel();
+
+  EXPECT_TRUE(canceled_);
+}
+
+TEST_F(RequestSystemProxyCredentialsViewTest, GetProxyServer) {
+  CreateDialog(/*show_error=*/false);
+  EXPECT_EQ(system_proxy_dialog_->GetProxyServer(), kProxy);
+}
+
+TEST_F(RequestSystemProxyCredentialsViewTest, GetWindowTitle) {
+  CreateDialog(/*show_error=*/false);
+  EXPECT_EQ(system_proxy_dialog_->GetWindowTitle(),
+            base::ASCIIToUTF16("Sign in"));
+}
+
+TEST_F(RequestSystemProxyCredentialsViewTest, ErrorLabelHidden) {
+  CreateDialog(/*show_error=*/false);
+  EXPECT_FALSE(system_proxy_dialog_->error_label_for_testing()->GetVisible());
+}
+
+TEST_F(RequestSystemProxyCredentialsViewTest, ErrorLabelVisible) {
+  CreateDialog(/*show_error=*/true);
+  EXPECT_TRUE(system_proxy_dialog_->error_label_for_testing()->GetVisible());
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/web_applications/help_app_integration_browsertest.cc b/chrome/browser/chromeos/web_applications/help_app_integration_browsertest.cc
index 809cc7da..4f554eb 100644
--- a/chrome/browser/chromeos/web_applications/help_app_integration_browsertest.cc
+++ b/chrome/browser/chromeos/web_applications/help_app_integration_browsertest.cc
@@ -7,6 +7,8 @@
 
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/metrics/user_action_tester.h"
+#include "build/branding_buildflags.h"
+#include "build/build_config.h"
 #include "chrome/browser/apps/app_service/app_launch_params.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
@@ -19,6 +21,7 @@
 #include "chrome/browser/web_applications/system_web_app_manager_browsertest.h"
 #include "chromeos/components/help_app_ui/url_constants.h"
 #include "chromeos/components/web_applications/test/sandboxed_web_ui_test_base.h"
+#include "chromeos/constants/chromeos_features.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/test_navigation_observer.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -27,7 +30,16 @@
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/size.h"
 
-using HelpAppIntegrationTest = SystemWebAppIntegrationTest;
+class HelpAppIntegrationTest : public SystemWebAppIntegrationTest {
+ public:
+  HelpAppIntegrationTest() {
+    scoped_feature_list_.InitWithFeatures(
+        {chromeos::features::kHelpAppReleaseNotes}, {});
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
 
 // Test that the Help App installs and launches correctly. Runs some spot
 // checks on the manifest.
@@ -110,6 +122,68 @@
       chrome::ShowHelp(incognito_browser, chrome::HELP_SOURCE_KEYBOARD));
 }
 
+// Test that launching the Help App's release notes opens the app on the Release
+// Notes page.
+IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTest, HelpAppV2LaunchReleaseNotes) {
+  WaitForTestSystemAppInstall();
+
+  // There should be 1 browser window initially.
+  EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
+
+  const GURL expected_url("chrome://help-app/updates");
+  content::TestNavigationObserver navigation_observer(expected_url);
+  navigation_observer.StartWatchingNewWebContents();
+
+  chrome::LaunchReleaseNotes(profile(),
+                             apps::mojom::LaunchSource::kFromOtherApp);
+#if defined(OS_CHROMEOS) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
+  // If no navigation happens, then this test will time out due to the wait.
+  navigation_observer.Wait();
+
+  // There should be two browser windows, one regular and one for the newly
+  // opened app.
+  EXPECT_EQ(2u, chrome::GetTotalBrowserCount());
+
+  // The opened window should be showing the url with attached WebUI.
+  content::WebContents* web_contents =
+      chrome::FindLastActive()->tab_strip_model()->GetActiveWebContents();
+
+  // The inner frame should be the pathname for the release notes pathname.
+  EXPECT_EQ("chrome-untrusted://help-app/updates",
+            SandboxedWebUiAppTestBase::EvalJsInAppFrame(
+                web_contents, "window.location.href"));
+#else
+  // Nothing should happen on non-branded builds.
+  EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
+#endif
+}
+
+// Test that launching the Help App's release notes logs metrics.
+IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTest, HelpAppV2ReleaseNotesMetrics) {
+  WaitForTestSystemAppInstall();
+
+  base::UserActionTester user_action_tester;
+  chrome::LaunchReleaseNotes(profile(),
+                             apps::mojom::LaunchSource::kFromOtherApp);
+#if defined(OS_CHROMEOS) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
+  EXPECT_EQ(1,
+            user_action_tester.GetActionCount("ReleaseNotes.ShowReleaseNotes"));
+#else
+  EXPECT_EQ(0,
+            user_action_tester.GetActionCount("ReleaseNotes.ShowReleaseNotes"));
+#endif
+}
+
+// Test that launching the Help App's release notes doesn't crash an incognito
+// browser.
+IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTest, HelpAppV2ReleaseNotesIncognito) {
+  WaitForTestSystemAppInstall();
+
+  Browser* incognito_browser = CreateIncognitoBrowser();
+  EXPECT_NO_FATAL_FAILURE(chrome::LaunchReleaseNotes(
+      incognito_browser->profile(), apps::mojom::LaunchSource::kFromOtherApp));
+}
+
 // Test that the Help App does a navigation on launch even when it was already
 // open with the same URL.
 IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTest, HelpAppV2NavigateOnRelaunch) {
diff --git a/chrome/browser/custom_handlers/protocol_handler_registry_unittest.cc b/chrome/browser/custom_handlers/protocol_handler_registry_unittest.cc
index cd1e596..824904da 100644
--- a/chrome/browser/custom_handlers/protocol_handler_registry_unittest.cc
+++ b/chrome/browser/custom_handlers/protocol_handler_registry_unittest.cc
@@ -977,9 +977,8 @@
 
   // Space character.
   translated_url = ph.TranslateUrl(GURL("web+custom://custom handler"));
-  // TODO(mgiuca): Check whether this(' ') should be encoded as '%20'.
   ASSERT_EQ(translated_url,
-            GURL("https://test.com/url=web%2Bcustom%3A%2F%2Fcustom+handler"));
+            GURL("https://test.com/url=web%2Bcustom%3A%2F%2Fcustom%20handler"));
 
   // Query parameters.
   translated_url = ph.TranslateUrl(GURL("web+custom://custom?foo=bar&bar=baz"));
diff --git a/chrome/browser/extensions/BUILD.gn b/chrome/browser/extensions/BUILD.gn
index 814316e..240e561 100644
--- a/chrome/browser/extensions/BUILD.gn
+++ b/chrome/browser/extensions/BUILD.gn
@@ -1028,6 +1028,7 @@
       "//chromeos/components/camera_app_ui:mojo_bindings",
       "//chromeos/components/camera_app_ui/resources:chrome_camera_app",
       "//chromeos/components/proximity_auth",
+      "//chromeos/components/remote_apps/mojom",
       "//chromeos/constants",
       "//chromeos/cryptohome",
       "//chromeos/dbus",
diff --git a/chrome/browser/extensions/chrome_extensions_browser_interface_binders.cc b/chrome/browser/extensions/chrome_extensions_browser_interface_binders.cc
index b872063..a434c9d 100644
--- a/chrome/browser/extensions/chrome_extensions_browser_interface_binders.cc
+++ b/chrome/browser/extensions/chrome_extensions_browser_interface_binders.cc
@@ -18,10 +18,14 @@
 #include "extensions/common/permissions/permissions_data.h"
 
 #if defined(OS_CHROMEOS)
+#include "chrome/browser/chromeos/remote_apps/remote_apps_impl.h"
+#include "chrome/browser/chromeos/remote_apps/remote_apps_manager.h"
+#include "chrome/browser/chromeos/remote_apps/remote_apps_manager_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/speech/extension_api/tts_engine_extension_observer.h"
 #include "chrome/common/extensions/extension_constants.h"
 #include "chromeos/components/camera_app_ui/camera_app_ui.h"
+#include "chromeos/components/remote_apps/mojom/remote_apps.mojom.h"
 #include "chromeos/services/cfm/public/buildflags/buildflags.h"
 #include "chromeos/services/media_perception/public/mojom/media_perception.mojom.h"
 #include "chromeos/services/tts/public/mojom/tts_service.mojom.h"
@@ -80,7 +84,20 @@
       ->BindTtsStream(std::move(receiver));
 }
 
-#endif
+void BindRemoteAppsFactory(
+    content::RenderFrameHost* render_frame_host,
+    mojo::PendingReceiver<chromeos::remote_apps::mojom::RemoteAppsFactory>
+        pending_receiver) {
+  // |remote_apps_manager| will be null in non-managed guest sessions, but this
+  // is already checked in |RemoteAppsImpl::IsAllowed()|.
+  chromeos::RemoteAppsManager* remote_apps_manager =
+      chromeos::RemoteAppsManagerFactory::GetForProfile(
+          Profile::FromBrowserContext(render_frame_host->GetBrowserContext()));
+  DCHECK(remote_apps_manager);
+  remote_apps_manager->BindInterface(std::move(pending_receiver));
+}
+
+#endif  // defined(OS_CHROMEOS)
 }  // namespace
 
 void PopulateChromeFrameBindersForExtension(
@@ -155,7 +172,12 @@
     binder_map->Add<chromeos::tts::mojom::TtsStream>(
         base::BindRepeating(&BindTtsStream));
   }
-#endif
+
+  if (chromeos::RemoteAppsImpl::IsAllowed(render_frame_host, extension)) {
+    binder_map->Add<chromeos::remote_apps::mojom::RemoteAppsFactory>(
+        base::BindRepeating(&BindRemoteAppsFactory));
+  }
+#endif  // defined(OS_CHROMEOS)
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/federated_learning/floc_id_provider_browsertest.cc b/chrome/browser/federated_learning/floc_id_provider_browsertest.cc
index 915bfa6f9..e6cd18c 100644
--- a/chrome/browser/federated_learning/floc_id_provider_browsertest.cc
+++ b/chrome/browser/federated_learning/floc_id_provider_browsertest.cc
@@ -278,7 +278,7 @@
 
   EXPECT_EQ(GetFlocId().ToDebugHeaderValue(), FlocId().ToDebugHeaderValue());
 
-  // Turn on sync-history to trigger the start of the 1st floc session.
+  // Turn on sync-history to trigger the 1st floc computation.
   sync_service()->SetActiveDataTypes(syncer::ModelTypeSet::All());
   sync_service()->FireStateChanged();
 
@@ -313,7 +313,7 @@
 
   EXPECT_EQ(GetFlocId().ToDebugHeaderValue(), FlocId().ToDebugHeaderValue());
 
-  // Turn on sync-history to trigger the start of the 1st floc session.
+  // Turn on sync-history to trigger the 1st floc computation.
   sync_service()->SetActiveDataTypes(syncer::ModelTypeSet::All());
   sync_service()->FireStateChanged();
 
diff --git a/chrome/browser/federated_learning/floc_id_provider_impl.cc b/chrome/browser/federated_learning/floc_id_provider_impl.cc
index 7ad5243..12dd718 100644
--- a/chrome/browser/federated_learning/floc_id_provider_impl.cc
+++ b/chrome/browser/federated_learning/floc_id_provider_impl.cc
@@ -25,7 +25,7 @@
 namespace {
 
 constexpr size_t kMinHistoryDomainSizeToReportFlocId = 1;
-constexpr base::TimeDelta kFlocSessionRenewInterval =
+constexpr base::TimeDelta kFlocScheduledUpdateInterval =
     base::TimeDelta::FromDays(1);
 constexpr int kQueryHistoryWindowInDays = 7;
 
@@ -48,9 +48,7 @@
 
 FlocIdProviderImpl::~FlocIdProviderImpl() = default;
 
-void FlocIdProviderImpl::NotifyFlocIdUpdated() {
-  DCHECK(floc_session_count_ > 0);
-
+void FlocIdProviderImpl::NotifyFlocUpdated(ComputeFlocTrigger trigger) {
   if (!base::FeatureList::IsEnabled(features::kFlocIdComputedEventLogging))
     return;
 
@@ -60,9 +58,9 @@
       specifics->mutable_floc_id_computed_event();
 
   sync_pb::UserEventSpecifics_FlocIdComputed_EventTrigger event_trigger =
-      (floc_session_count_ == 1u)
-          ? sync_pb::UserEventSpecifics::FlocIdComputed::NEW
-          : sync_pb::UserEventSpecifics::FlocIdComputed::REFRESHED;
+      (trigger == ComputeFlocTrigger::kBrowserStart)
+          ? sync_pb::UserEventSpecifics_FlocIdComputed_EventTrigger_NEW
+          : sync_pb::UserEventSpecifics_FlocIdComputed_EventTrigger_REFRESHED;
 
   floc_id_computed_event->set_event_trigger(event_trigger);
 
@@ -86,7 +84,7 @@
 }
 
 void FlocIdProviderImpl::IsSwaaNacAccountEnabled(
-    CanComputeFlocIdCallback callback) {
+    CanComputeFlocCallback callback) {
   net::PartialNetworkTrafficAnnotationTag partial_traffic_annotation =
       net::DefinePartialNetworkTrafficAnnotation(
           "floc_id_provider_impl", "floc_remote_permission_service",
@@ -127,29 +125,59 @@
 }
 
 void FlocIdProviderImpl::OnStateChanged(syncer::SyncService* sync_service) {
-  if (floc_session_count_ > 0)
+  if (first_floc_computation_triggered_)
     return;
 
   if (!IsSyncHistoryEnabled())
     return;
 
-  CalculateFloc();
-
-  floc_session_start_timer_.Start(
-      FROM_HERE, kFlocSessionRenewInterval,
-      base::BindRepeating(&FlocIdProviderImpl::CalculateFloc,
-                          weak_ptr_factory_.GetWeakPtr()));
+  ComputeFloc(ComputeFlocTrigger::kBrowserStart);
 }
 
-void FlocIdProviderImpl::CalculateFloc() {
-  floc_session_count_ += 1;
-  CheckCanComputeFlocId(
-      base::BindOnce(&FlocIdProviderImpl::OnCheckCanComputeFlocIdCompleted,
-                     weak_ptr_factory_.GetWeakPtr()));
+void FlocIdProviderImpl::ComputeFloc(ComputeFlocTrigger trigger) {
+  DCHECK_NE(trigger == ComputeFlocTrigger::kBrowserStart,
+            first_floc_computation_triggered_);
+
+  DCHECK(trigger != ComputeFlocTrigger::kBrowserStart ||
+         !floc_computation_in_progress_);
+
+  // Skip computing as long as there's one still in progress.
+  if (floc_computation_in_progress_)
+    return;
+
+  floc_computation_in_progress_ = true;
+  first_floc_computation_triggered_ = true;
+
+  auto compute_floc_completed_callback =
+      base::BindOnce(&FlocIdProviderImpl::OnComputeFlocCompleted,
+                     weak_ptr_factory_.GetWeakPtr(), trigger);
+
+  CheckCanComputeFloc(
+      base::BindOnce(&FlocIdProviderImpl::OnCheckCanComputeFlocCompleted,
+                     weak_ptr_factory_.GetWeakPtr(),
+                     std::move(compute_floc_completed_callback)));
 }
 
-void FlocIdProviderImpl::CheckCanComputeFlocId(
-    CanComputeFlocIdCallback callback) {
+void FlocIdProviderImpl::OnComputeFlocCompleted(ComputeFlocTrigger trigger,
+                                                FlocId floc_id) {
+  DCHECK(floc_computation_in_progress_);
+  floc_computation_in_progress_ = false;
+
+  if (floc_id_ != floc_id) {
+    floc_id_ = floc_id;
+    NotifyFlocUpdated(trigger);
+  }
+
+  // Abandon the scheduled task if any, and schedule a new compute-floc task
+  // that is |kFlocScheduledUpdateInterval| from now.
+  compute_floc_timer_.Start(
+      FROM_HERE, kFlocScheduledUpdateInterval,
+      base::BindOnce(&FlocIdProviderImpl::ComputeFloc,
+                     weak_ptr_factory_.GetWeakPtr(),
+                     ComputeFlocTrigger::kScheduledUpdate));
+}
+
+void FlocIdProviderImpl::CheckCanComputeFloc(CanComputeFlocCallback callback) {
   if (!IsSyncHistoryEnabled() || !AreThirdPartyCookiesAllowed()) {
     std::move(callback).Run(false);
     return;
@@ -158,19 +186,17 @@
   IsSwaaNacAccountEnabled(std::move(callback));
 }
 
-void FlocIdProviderImpl::OnCheckCanComputeFlocIdCompleted(
+void FlocIdProviderImpl::OnCheckCanComputeFlocCompleted(
+    ComputeFlocCompletedCallback callback,
     bool can_compute_floc) {
   if (!can_compute_floc) {
-    if (floc_id_.IsValid()) {
-      floc_id_ = FlocId();
-      NotifyFlocIdUpdated();
-    }
+    std::move(callback).Run(FlocId());
     return;
   }
 
   GetRecentlyVisitedURLs(
       base::BindOnce(&FlocIdProviderImpl::OnGetRecentlyVisitedURLsCompleted,
-                     weak_ptr_factory_.GetWeakPtr(), floc_session_count_));
+                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
 }
 
 void FlocIdProviderImpl::GetRecentlyVisitedURLs(
@@ -184,12 +210,8 @@
 }
 
 void FlocIdProviderImpl::OnGetRecentlyVisitedURLsCompleted(
-    size_t floc_session_count,
+    ComputeFlocCompletedCallback callback,
     history::QueryResults results) {
-  DCHECK_LE(floc_session_count, floc_session_count_);
-  if (floc_session_count < floc_session_count_)
-    return;
-
   std::unordered_set<std::string> domains;
   for (const history::URLResult& url_result : results) {
     if (!url_result.publicly_routable())
@@ -204,10 +226,7 @@
                        ? FlocId::CreateFromHistory(domains)
                        : FlocId();
 
-  if (floc_id_ != floc_id) {
-    floc_id_ = floc_id;
-    NotifyFlocIdUpdated();
-  }
+  std::move(callback).Run(floc_id);
 }
 
 }  // namespace federated_learning
diff --git a/chrome/browser/federated_learning/floc_id_provider_impl.h b/chrome/browser/federated_learning/floc_id_provider_impl.h
index 43d4f3f..fef2d7d0 100644
--- a/chrome/browser/federated_learning/floc_id_provider_impl.h
+++ b/chrome/browser/federated_learning/floc_id_provider_impl.h
@@ -25,19 +25,31 @@
 
 class FlocRemotePermissionService;
 
-// A service that regularly computes the floc id and logs it in a user event.
+// A service that regularly computes the floc id and logs it in a user event. A
+// computed floc can be in either a valid or invalid state, based on whether all
+// the prerequisites are met:
+// 1) Sync & sync-history are enabled.
+// 2) 3rd party cookies are NOT blocked.
+// 3) Supplemental Web and App Activity is enabled.
+// 4) Supplemental Ad Personalization is enabled.
+// 5) The account type is NOT a child account.
 //
-// A floc session starts when sync & sync-history is first enabled. We validate
-// the following conditions in sequence before computing the floc id: user
-// doesn’t block 3rd party cookies; all 3 of swaa & nac & account_type are
-// enabled; the user has visited at least 1 domain with publicly routable ip in
-// the past 7 days. Every 24 hours, it'll go over the conditions above again
-// (including the sync & sync-history check), and reset or recompute the id
-// accordingly.
+// When all the prerequisites are met, the floc will be computed by sim-hashing
+// navigation URL domains in the last 7 days; otherwise, an invalid floc will be
+// given.
+//
+// The floc will be first computed after sync & sync-history are enabled. After
+// each computation, another computation will be scheduled 24 hours later.
 class FlocIdProviderImpl : public FlocIdProvider,
                            public syncer::SyncServiceObserver {
  public:
-  using CanComputeFlocIdCallback = base::OnceCallback<void(bool)>;
+  enum class ComputeFlocTrigger {
+    kBrowserStart,
+    kScheduledUpdate,
+  };
+
+  using CanComputeFlocCallback = base::OnceCallback<void(bool)>;
+  using ComputeFlocCompletedCallback = base::OnceCallback<void(FlocId)>;
   using GetRecentlyVisitedURLsCallback =
       history::HistoryService::QueryHistoryCallback;
 
@@ -53,10 +65,10 @@
 
  protected:
   // protected virtual for testing.
-  virtual void NotifyFlocIdUpdated();
+  virtual void NotifyFlocUpdated(ComputeFlocTrigger trigger);
   virtual bool IsSyncHistoryEnabled();
   virtual bool AreThirdPartyCookiesAllowed();
-  virtual void IsSwaaNacAccountEnabled(CanComputeFlocIdCallback callback);
+  virtual void IsSwaaNacAccountEnabled(CanComputeFlocCallback callback);
 
  private:
   friend class FlocIdProviderUnitTest;
@@ -68,17 +80,20 @@
   // syncer::SyncServiceObserver:
   void OnStateChanged(syncer::SyncService* sync_service) override;
 
-  void CalculateFloc();
+  void ComputeFloc(ComputeFlocTrigger trigger);
+  void OnComputeFlocCompleted(ComputeFlocTrigger trigger, FlocId floc_id);
 
-  void CheckCanComputeFlocId(CanComputeFlocIdCallback callback);
-  void OnCheckCanComputeFlocIdCompleted(bool can_compute_floc);
+  void CheckCanComputeFloc(CanComputeFlocCallback callback);
+  void OnCheckCanComputeFlocCompleted(ComputeFlocCompletedCallback callback,
+                                      bool can_compute_floc);
 
   void GetRecentlyVisitedURLs(GetRecentlyVisitedURLsCallback callback);
-  void OnGetRecentlyVisitedURLsCompleted(size_t floc_session_count,
+  void OnGetRecentlyVisitedURLsCompleted(ComputeFlocCompletedCallback callback,
                                          history::QueryResults results);
 
   FlocId floc_id_;
-  size_t floc_session_count_ = 0;
+  bool floc_computation_in_progress_ = false;
+  bool first_floc_computation_triggered_ = false;
 
   syncer::SyncService* sync_service_;
   scoped_refptr<content_settings::CookieSettings> cookie_settings_;
@@ -89,8 +104,8 @@
   // Used for the async tasks querying the HistoryService.
   base::CancelableTaskTracker history_task_tracker_;
 
-  // The timer used to start the floc session at regular intervals.
-  base::RepeatingTimer floc_session_start_timer_;
+  // The timer used to schedule a floc computation.
+  base::OneShotTimer compute_floc_timer_;
 
   base::WeakPtrFactory<FlocIdProviderImpl> weak_ptr_factory_{this};
 };
diff --git a/chrome/browser/federated_learning/floc_id_provider_unittest.cc b/chrome/browser/federated_learning/floc_id_provider_unittest.cc
index bb2e4ed9..df02095 100644
--- a/chrome/browser/federated_learning/floc_id_provider_unittest.cc
+++ b/chrome/browser/federated_learning/floc_id_provider_unittest.cc
@@ -29,16 +29,17 @@
  public:
   using FlocIdProviderImpl::FlocIdProviderImpl;
 
-  void NotifyFlocIdUpdated() override {
+  void NotifyFlocUpdated(
+      FlocIdProviderImpl::ComputeFlocTrigger trigger) override {
     ++floc_update_notification_count_;
-    FlocIdProviderImpl::NotifyFlocIdUpdated();
+    FlocIdProviderImpl::NotifyFlocUpdated(trigger);
   }
 
   bool AreThirdPartyCookiesAllowed() override {
     return third_party_cookies_allowed_;
   }
 
-  void IsSwaaNacAccountEnabled(CanComputeFlocIdCallback callback) override {
+  void IsSwaaNacAccountEnabled(CanComputeFlocCallback callback) override {
     std::move(callback).Run(swaa_nac_account_enabled_);
   }
 
@@ -90,15 +91,20 @@
     task_environment_.RunUntilIdle();
   }
 
-  void CheckCanComputeFlocId(
-      FlocIdProviderImpl::CanComputeFlocIdCallback callback) {
-    floc_id_provider_->CheckCanComputeFlocId(std::move(callback));
+  void CheckCanComputeFloc(
+      FlocIdProviderImpl::CanComputeFlocCallback callback) {
+    floc_id_provider_->CheckCanComputeFloc(std::move(callback));
   }
 
-  void OnGetRecentlyVisitedURLsCompleted(size_t floc_session_count,
-                                         history::QueryResults results) {
-    floc_id_provider_->OnGetRecentlyVisitedURLsCompleted(floc_session_count,
-                                                         std::move(results));
+  void OnGetRecentlyVisitedURLsCompleted(
+      FlocIdProviderImpl::ComputeFlocTrigger trigger,
+      history::QueryResults results) {
+    auto compute_floc_completed_callback =
+        base::BindOnce(&FlocIdProviderImpl::OnComputeFlocCompleted,
+                       base::Unretained(floc_id_provider_.get()), trigger);
+
+    floc_id_provider_->OnGetRecentlyVisitedURLsCompleted(
+        std::move(compute_floc_completed_callback), std::move(results));
   }
 
   FlocId floc_id() const { return floc_id_provider_->floc_id_; }
@@ -107,12 +113,21 @@
     floc_id_provider_->floc_id_ = floc_id;
   }
 
-  size_t floc_session_count() const {
-    return floc_id_provider_->floc_session_count_;
+  bool floc_computation_in_progress() const {
+    return floc_id_provider_->floc_computation_in_progress_;
   }
 
-  void set_floc_session_count(size_t count) {
-    floc_id_provider_->floc_session_count_ = count;
+  void set_floc_computation_in_progress(bool floc_computation_in_progress) {
+    floc_id_provider_->floc_computation_in_progress_ =
+        floc_computation_in_progress;
+  }
+
+  bool first_floc_computation_triggered() const {
+    return floc_id_provider_->first_floc_computation_triggered_;
+  }
+
+  void set_first_floc_computation_triggered(bool triggered) {
+    floc_id_provider_->first_floc_computation_triggered_ = triggered;
   }
 
  protected:
@@ -140,13 +155,13 @@
 
   task_environment_.RunUntilIdle();
 
-  // Expect that the floc session hasn't started, as the floc_id_provider hasn't
-  // been notified about state of the sync_service.
+  // Expect that the floc computation hasn't started, as the floc_id_provider
+  // hasn't been notified about state of the sync_service.
   ASSERT_EQ(0u, floc_id_provider_->floc_update_notification_count());
   ASSERT_FALSE(floc_id().IsValid());
-  ASSERT_EQ(0u, floc_session_count());
+  ASSERT_FALSE(first_floc_computation_triggered());
 
-  // Trigger the start of the 1st floc session.
+  // Trigger the 1st floc computation.
   test_sync_service_->SetTransportState(
       syncer::SyncService::TransportState::ACTIVE);
   test_sync_service_->FireStateChanged();
@@ -158,7 +173,7 @@
   ASSERT_TRUE(floc_id().IsValid());
   ASSERT_EQ(FlocId::CreateFromHistory({domain}).ToDebugHeaderValue(),
             floc_id().ToDebugHeaderValue());
-  ASSERT_EQ(1u, floc_session_count());
+  ASSERT_TRUE(first_floc_computation_triggered());
 
   // Advance the clock by 1 day. Expect a floc id update notification, as
   // there's no history in the last 7 days so the id has been reset to empty.
@@ -166,7 +181,6 @@
 
   ASSERT_EQ(2u, floc_id_provider_->floc_update_notification_count());
   ASSERT_FALSE(floc_id().IsValid());
-  ASSERT_EQ(2u, floc_session_count());
 }
 
 TEST_F(FlocIdProviderUnitTest, UnqualifiedInitialHistory) {
@@ -181,13 +195,13 @@
 
   task_environment_.RunUntilIdle();
 
-  // Expect that the floc session hasn't started, as the floc_id_provider hasn't
-  // been notified about state of the sync_service.
+  // Expect that the floc computation hasn't started, as the floc_id_provider
+  // hasn't been notified about state of the sync_service.
   ASSERT_EQ(0u, floc_id_provider_->floc_update_notification_count());
   ASSERT_FALSE(floc_id().IsValid());
-  ASSERT_EQ(0u, floc_session_count());
+  ASSERT_FALSE(first_floc_computation_triggered());
 
-  // Trigger the start of the 1st floc session.
+  // Trigger the 1st floc computation.
   test_sync_service_->SetTransportState(
       syncer::SyncService::TransportState::ACTIVE);
   test_sync_service_->FireStateChanged();
@@ -195,9 +209,9 @@
   task_environment_.RunUntilIdle();
 
   // Expect no floc id update notification, as there is no qualified history
-  // entry. However, the 1st session should already have started.
+  // entry. However, the 1st computation should already have started.
   ASSERT_EQ(0u, floc_id_provider_->floc_update_notification_count());
-  ASSERT_EQ(1u, floc_session_count());
+  ASSERT_TRUE(first_floc_computation_triggered());
 
   // Add a history entry with a timestamp 6 days back from now.
   add_page_args.time = base::Time::Now() - base::TimeDelta::FromDays(6);
@@ -208,7 +222,6 @@
   task_environment_.FastForwardBy(base::TimeDelta::FromHours(23));
 
   ASSERT_EQ(0u, floc_id_provider_->floc_update_notification_count());
-  ASSERT_EQ(1u, floc_session_count());
 
   // Advance the clock by 1 hour. Expect a floc id update notification, as the
   // refresh time is reached and there's a valid history entry in the last 7
@@ -219,7 +232,6 @@
   ASSERT_TRUE(floc_id().IsValid());
   ASSERT_EQ(FlocId::CreateFromHistory({domain}).ToDebugHeaderValue(),
             floc_id().ToDebugHeaderValue());
-  ASSERT_EQ(2u, floc_session_count());
 }
 
 TEST_F(FlocIdProviderUnitTest, ScheduledUpdateSameFloc_NoNotification) {
@@ -234,7 +246,7 @@
 
   task_environment_.RunUntilIdle();
 
-  // Trigger the start of the 1st floc session.
+  // Trigger the 1st floc computation.
   test_sync_service_->SetTransportState(
       syncer::SyncService::TransportState::ACTIVE);
   test_sync_service_->FireStateChanged();
@@ -251,27 +263,27 @@
   ASSERT_EQ(1u, floc_id_provider_->floc_update_notification_count());
 }
 
-TEST_F(FlocIdProviderUnitTest, CheckCanComputeFlocId_Success) {
+TEST_F(FlocIdProviderUnitTest, CheckCanComputeFloc_Success) {
   test_sync_service_->SetTransportState(
       syncer::SyncService::TransportState::ACTIVE);
 
   base::OnceCallback<void(bool)> cb = base::BindOnce(
       [](bool can_compute_floc) { ASSERT_TRUE(can_compute_floc); });
 
-  CheckCanComputeFlocId(std::move(cb));
+  CheckCanComputeFloc(std::move(cb));
   task_environment_.RunUntilIdle();
 }
 
-TEST_F(FlocIdProviderUnitTest, CheckCanComputeFlocId_Failure_SyncDisabled) {
+TEST_F(FlocIdProviderUnitTest, CheckCanComputeFloc_Failure_SyncDisabled) {
   base::OnceCallback<void(bool)> cb = base::BindOnce(
       [](bool can_compute_floc) { ASSERT_FALSE(can_compute_floc); });
 
-  CheckCanComputeFlocId(std::move(cb));
+  CheckCanComputeFloc(std::move(cb));
   task_environment_.RunUntilIdle();
 }
 
 TEST_F(FlocIdProviderUnitTest,
-       CheckCanComputeFlocId_Failure_BlockThirdPartyCookies) {
+       CheckCanComputeFloc_Failure_BlockThirdPartyCookies) {
   test_sync_service_->SetTransportState(
       syncer::SyncService::TransportState::ACTIVE);
 
@@ -280,12 +292,12 @@
   base::OnceCallback<void(bool)> cb = base::BindOnce(
       [](bool can_compute_floc) { ASSERT_FALSE(can_compute_floc); });
 
-  CheckCanComputeFlocId(std::move(cb));
+  CheckCanComputeFloc(std::move(cb));
   task_environment_.RunUntilIdle();
 }
 
 TEST_F(FlocIdProviderUnitTest,
-       CheckCanComputeFlocId_Failure_SwaaNacAccountDisabled) {
+       CheckCanComputeFloc_Failure_SwaaNacAccountDisabled) {
   test_sync_service_->SetTransportState(
       syncer::SyncService::TransportState::ACTIVE);
 
@@ -294,7 +306,7 @@
   base::OnceCallback<void(bool)> cb = base::BindOnce(
       [](bool can_compute_floc) { ASSERT_FALSE(can_compute_floc); });
 
-  CheckCanComputeFlocId(std::move(cb));
+  CheckCanComputeFloc(std::move(cb));
   task_environment_.RunUntilIdle();
 }
 
@@ -302,9 +314,9 @@
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeature(features::kFlocIdComputedEventLogging);
 
-  set_floc_session_count(1u);
   set_floc_id(FlocId(12345ULL));
-  floc_id_provider_->NotifyFlocIdUpdated();
+  floc_id_provider_->NotifyFlocUpdated(
+      FlocIdProviderImpl::ComputeFlocTrigger::kBrowserStart);
 
   ASSERT_EQ(1u, fake_user_event_service_->GetRecordedUserEvents().size());
   const sync_pb::UserEventSpecifics& specifics1 =
@@ -318,9 +330,9 @@
             event1.event_trigger());
   EXPECT_EQ(12345ULL, event1.floc_id());
 
-  set_floc_session_count(2u);
   set_floc_id(FlocId(999ULL));
-  floc_id_provider_->NotifyFlocIdUpdated();
+  floc_id_provider_->NotifyFlocUpdated(
+      FlocIdProviderImpl::ComputeFlocTrigger::kScheduledUpdate);
 
   ASSERT_EQ(2u, fake_user_event_service_->GetRecordedUserEvents().size());
   const sync_pb::UserEventSpecifics& specifics2 =
@@ -335,7 +347,8 @@
   EXPECT_EQ(999ULL, event2.floc_id());
 
   set_floc_id(FlocId());
-  floc_id_provider_->NotifyFlocIdUpdated();
+  floc_id_provider_->NotifyFlocUpdated(
+      FlocIdProviderImpl::ComputeFlocTrigger::kScheduledUpdate);
 
   ASSERT_EQ(3u, fake_user_event_service_->GetRecordedUserEvents().size());
   const sync_pb::UserEventSpecifics& specifics3 =
@@ -356,8 +369,12 @@
       {history::URLResult(GURL("https://a.test"),
                           base::Time::Now() - base::TimeDelta::FromDays(1))});
 
-  set_floc_session_count(1u);
-  OnGetRecentlyVisitedURLsCompleted(1u, std::move(query_results));
+  set_first_floc_computation_triggered(true);
+  set_floc_computation_in_progress(true);
+
+  OnGetRecentlyVisitedURLsCompleted(
+      FlocIdProviderImpl::ComputeFlocTrigger::kBrowserStart,
+      std::move(query_results));
 
   ASSERT_FALSE(floc_id().IsValid());
 }
@@ -379,8 +396,12 @@
   history::QueryResults query_results;
   query_results.SetURLResults(std::move(url_results));
 
-  set_floc_session_count(1u);
-  OnGetRecentlyVisitedURLsCompleted(1u, std::move(query_results));
+  set_first_floc_computation_triggered(true);
+  set_floc_computation_in_progress(true);
+
+  OnGetRecentlyVisitedURLsCompleted(
+      FlocIdProviderImpl::ComputeFlocTrigger::kBrowserStart,
+      std::move(query_results));
 
   ASSERT_EQ(
       FlocId::CreateFromHistory({"a.test", "b.test"}).ToDebugHeaderValue(),
@@ -398,7 +419,7 @@
 
   task_environment_.RunUntilIdle();
 
-  // Trigger the start of the 1st floc session.
+  // Trigger the 1st floc computation.
   test_sync_service_->SetTransportState(
       syncer::SyncService::TransportState::ACTIVE);
   test_sync_service_->FireStateChanged();
@@ -409,7 +430,6 @@
   ASSERT_EQ(1u, floc_id_provider_->floc_update_notification_count());
   ASSERT_EQ(FlocId::CreateFromHistory({domain}).ToDebugHeaderValue(),
             floc_id().ToDebugHeaderValue());
-  ASSERT_EQ(1u, floc_session_count());
 
   // Turn off sync.
   test_sync_service_->SetTransportState(
@@ -421,7 +441,6 @@
 
   ASSERT_EQ(2u, floc_id_provider_->floc_update_notification_count());
   ASSERT_FALSE(floc_id().IsValid());
-  ASSERT_EQ(2u, floc_session_count());
 
   // Turn on sync.
   test_sync_service_->SetTransportState(
@@ -434,7 +453,6 @@
   ASSERT_EQ(3u, floc_id_provider_->floc_update_notification_count());
   ASSERT_EQ(FlocId::CreateFromHistory({domain}).ToDebugHeaderValue(),
             floc_id().ToDebugHeaderValue());
-  ASSERT_EQ(3u, floc_session_count());
 }
 
 }  // namespace federated_learning
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 3c8f8d7..1156e0f4 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -697,16 +697,6 @@
     "expiry_milestone": 95
   },
   {
-    "name": "crostini-port-forwarding",
-    "owners": [ "danielng", "davidmunro@google.com", "hollingum@google.com" ],
-    "expiry_milestone": 86
-  },
-  {
-    "name": "crostini-show-mic-setting",
-    "owners": [ "danielng" ],
-    "expiry_milestone": 85
-  },
-  {
     "name": "crostini-usb-allow-unsupported",
     "owners": [ "nverne", "benwells" ],
     "expiry_milestone": 85
@@ -4440,7 +4430,7 @@
   {
     "name": "use-multilogin-endpoint",
     "owners": [ "droger", "msarda" ],
-    "expiry_milestone": 85
+    "expiry_milestone": 88
   },
   {
     "name": "use-preferred-interval-for-video",
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index d6727f6..aad6e1b 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -3617,19 +3617,11 @@
 const char kCrosRegionsModeOverride[] = "Override VPD values.";
 const char kCrosRegionsModeHide[] = "Hide VPD values.";
 
-const char kCrostiniPortForwardingName[] = "Crostini Port Forwarding";
-const char kCrostiniPortForwardingDescription[] =
-    "Enable Crostini port forwarding.";
-
 const char kCrostiniDiskResizingName[] = "Allow resizing Crostini disks";
 const char kCrostiniDiskResizingDescription[] =
     "Use preallocated user-resizeable disks for Crostini instead of sparse "
     "automatically sized disks.";
 
-const char kCrostiniShowMicSettingName[] = "Allow Crostini mic setting";
-const char kCrostiniShowMicSettingDescription[] =
-    "Adds the option to allow Crostini to access the mic";
-
 const char kCrostiniUseBusterImageName[] = "New Crostini containers use Buster";
 const char kCrostiniUseBusterImageDescription[] =
     "New Crostini containers use Debian Buster images instead of Debian "
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 6b66617..1979229 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -2092,15 +2092,9 @@
 extern const char kCrosRegionsModeOverride[];
 extern const char kCrosRegionsModeHide[];
 
-extern const char kCrostiniPortForwardingName[];
-extern const char kCrostiniPortForwardingDescription[];
-
 extern const char kCrostiniDiskResizingName[];
 extern const char kCrostiniDiskResizingDescription[];
 
-extern const char kCrostiniShowMicSettingName[];
-extern const char kCrostiniShowMicSettingDescription[];
-
 extern const char kCrostiniUseBusterImageName[];
 extern const char kCrostiniUseBusterImageDescription[];
 
diff --git a/chrome/browser/google/google_brand_code_map_chromeos.cc b/chrome/browser/google/google_brand_code_map_chromeos.cc
index 2a3eb04..1dc0c9b0 100644
--- a/chrome/browser/google/google_brand_code_map_chromeos.cc
+++ b/chrome/browser/google/google_brand_code_map_chromeos.cc
@@ -77,6 +77,7 @@
                      {"BDIW", {"UDUG", "TRYQ", "PWFV"}},
                      {"BMNE", {"HLSA", "WXJQ", "TULR"}},
                      {"CBUY", {"POUW", "GHJY", "USXU"}},
+                     {"CFUL", {"GIFL", "EDYW", "GOJE"}},
                      {"CLSF", {"OWOB", "RLJX", "OZWK"}},
                      {"CPPT", {"CQFF", "PCCZ", "HZEW"}},
                      {"CQFV", {"OJUW", "FCMY", "VCYR"}},
@@ -97,10 +98,14 @@
                      {"DISZ", {"PPAR", "VCPW", "NJKK"}},
                      {"DKJM", {"VRGL", "PZYF", "VBTW"}},
                      {"DRYI", {"LWTQ", "OLEY", "NWUA"}},
+                     {"DUKI", {"FRGD", "SACE", "AAMW"}},
                      {"DVUG", {"HJHV", "KPAH", "DCQS"}},
+                     {"DWCY", {"ZJQH", "JLCB", "QOAI"}},
+                     {"DXVL", {"EBBY", "NMQL", "GTHA"}},
                      {"DXZT", {"WNSK", "WNDA", "DZWQ"}},
                      {"EDHM", {"NLAE", "JYDL", "BTWJ"}},
                      {"EGSC", {"DWAW", "FZRC", "PKWJ"}},
+                     {"EKWL", {"PGWE", "JEHJ", "WQYW"}},
                      {"ELQA", {"GTJZ", "DTIH", "IXVN"}},
                      {"EOJH", {"GTAZ", "APYI", "UHAZ"}},
                      {"EXCQ", {"LAOZ", "QTVX", "ZCLW"}},
@@ -112,6 +117,7 @@
                      {"FQPJ", {"ZTQG", "ZNEO", "LYMZ"}},
                      {"FQZI", {"WPBA", "YZDA", "FXCI"}},
                      {"FRGW", {"ZPJY", "MYPP", "KQFE"}},
+                     {"FRGW", {"ZPJY", "MYPP", "KQFE"}},
                      {"FSFR", {"ZDAR", "BERM", "COKX"}},
                      {"FSGY", {"PJQC", "RHZW", "POVI"}},
                      {"FWVK", {"MUTD", "GWKK", "SQSC"}},
@@ -145,6 +151,7 @@
                      {"HTPV", {"LAEC", "NGRO", "BGEX"}},
                      {"HUIJ", {"EVJI", "RNMR", "JQZR"}},
                      {"HVPU", {"HUTT", "JXOO", "HHMM"}},
+                     {"HXZN", {"XTOL", "YHGP", "HMAG"}},
                      {"HYMD", {"LPEG", "UDVW", "KUBO"}},
                      {"HYZI", {"YBVF", "EUST", "WJVV"}},
                      {"IHZG", {"MLLN", "EZTK", "GJEJ"}},
@@ -154,6 +161,7 @@
                      {"JBPA", {"VUZL", "XYPI", "XOWE"}},
                      {"JFZB", {"PFDC", "XJDX", "CPXX"}},
                      {"JICX", {"GUZK", "TIZA", "HTUW"}},
+                     {"JLOF", {"IWFR", "CJHY", "DOPK"}},
                      {"JLRH", {"SAMJ", "GLJZ", "SKTN"}},
                      {"JPZQ", {"CCBQ", "ABTW", "KFNE"}},
                      {"JQUD", {"CUTW", "DLJE", "DOON"}},
@@ -250,6 +258,8 @@
                      {"MXEQ", {"EKJV", "UWUR", "CPES"}},
                      {"MXUY", {"IRZH", "ADQR", "PCST"}},
                      {"MYQR", {"VMHK", "QHCZ", "HMFN"}},
+                     {"MZVS", {"VUZM", "RIDT", "URTS"}},
+                     {"NAMM", {"BFSS", "BKVK", "EBDV"}},
                      {"NBQS", {"KMJF", "MFWA", "UWRX"}},
                      {"NISD", {"MISA", "YDPG", "NCLQ"}},
                      {"NMOG", {"UYQU", "ZWTV", "TQFQ"}},
@@ -268,11 +278,14 @@
                      {"PWFL", {"WGJQ", "KMBF", "UKJV"}},
                      {"PXDO", {"ZXCF", "TQWC", "HOAL"}},
                      {"QAPN", {"EMNZ", "SJTH", "HJKU"}},
+                     {"QBJC", {"WAQG", "MSEN", "FQYE"}},
                      {"QBTA", {"UDQV", "UIZV", "SGMN"}},
+                     {"QJHH", {"TIHM", "SOII", "SXVL"}},
                      {"QKTA", {"USGV", "UPMS", "ZVTZ"}},
                      {"QTMI", {"YMOW", "FZIR", "YKGT"}},
                      {"QYGU", {"FYBR", "QLFJ", "OLRV"}},
                      {"QZUX", {"HNBM", "BUJY", "FFDE"}},
+                     {"RHDN", {"MGVK", "EQPB", "UAHY"}},
                      {"RIKG", {"VRBT", "LEPX", "VWIV"}},
                      {"RKRB", {"OPOY", "QMZZ", "FAGR"}},
                      {"RVRM", {"MZJU", "IGXP", "DSJP"}},
@@ -295,6 +308,8 @@
                      {"SWLP", {"GLDC", "WZKJ", "GTXT"}},
                      {"TAAB", {"ZBMY", "NYDT", "CXYZ"}},
                      {"TAAC", {"YBVP", "RXXN", "HMDY"}},
+                     {"TFIY", {"RVUF", "DHKE", "GFPK"}},
+                     {"TJKH", {"ZHMG", "RBXM", "VIVU"}},
                      {"TKER", {"KOSM", "IUCL", "LIIM"}},
                      {"TKZT", {"KWCM", "APLN", "STGO"}},
                      {"TMSE", {"PSOE", "RFGT", "DVAS"}},
@@ -311,15 +326,19 @@
                      {"VHUH", {"JYDF", "SFJY", "JMBU"}},
                      {"VICR", {"VNCX", "OLSV", "YCZO"}},
                      {"VVUC", {"WQCU", "YUMW", "YHYC"}},
+                     {"VYRC", {"VKSO", "NKTO", "ZPZX"}},
                      {"VZMB", {"YCKT", "WSPC", "SHYP"}},
                      {"WBZQ", {"LAYK", "LQDM", "QBFV"}},
                      {"WFIQ", {"KKHX", "UTHS", "HDSP"}},
                      {"WJOZ", {"BASQ", "BRTL", "CQAV"}},
                      {"WMVU", {"GMMR", "AVVS", "IMDF"}},
+                     {"WNNA", {"ERXU", "TWMI", "ZOER"}},
                      {"WVRW", {"GJGN", "QQFA", "AGVP"}},
                      {"WXZG", {"IUGR", "JOEE", "PTHY"}},
                      {"XBWL", {"IQEI", "JEGU", "QSKW"}},
                      {"XFUX", {"UHAM", "NEHU", "SHMG"}},
+                     {"XHVI", {"KVWL", "GQOJ", "JLLW"}},
+                     {"XOGA", {"BIWO", "JPWZ", "YYDG"}},
                      {"XOKS", {"DEVR", "YKLR", "QYBF"}},
                      {"XVTK", {"TMUU", "BTWW", "THQH"}},
                      {"XVYQ", {"UAVB", "OEMI", "VQVK"}},
diff --git a/chrome/browser/media/webrtc/media_stream_infobar_browsertest.cc b/chrome/browser/media/webrtc/media_stream_permission_browsertest.cc
similarity index 78%
rename from chrome/browser/media/webrtc/media_stream_infobar_browsertest.cc
rename to chrome/browser/media/webrtc/media_stream_permission_browsertest.cc
index 49d5e62..d2f3282 100644
--- a/chrome/browser/media/webrtc/media_stream_infobar_browsertest.cc
+++ b/chrome/browser/media/webrtc/media_stream_permission_browsertest.cc
@@ -5,6 +5,7 @@
 #include "base/command_line.h"
 #include "base/files/file_util.h"
 #include "base/macros.h"
+#include "base/run_loop.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
@@ -65,7 +66,9 @@
 
  private:
   content::WebContents* LoadTestPageInBrowser(Browser* browser) {
-    EXPECT_TRUE(embedded_test_server()->Start());
+    if (!embedded_test_server()->Started()) {
+      EXPECT_TRUE(embedded_test_server()->Start());
+    }
 
     // Uses the default server.
     GURL url = test_page_url();
@@ -76,12 +79,6 @@
     return browser->tab_strip_model()->GetActiveWebContents();
   }
 
-  // Dummy callback for when we deny the current request directly.
-  static void OnMediaStreamResponse(
-      const blink::MediaStreamDevices& devices,
-      blink::mojom::MediaStreamRequestResult result,
-      std::unique_ptr<content::MediaStreamUI> ui) {}
-
   DISALLOW_COPY_AND_ASSIGN(MediaStreamPermissionTest);
 };
 
@@ -169,3 +166,50 @@
   EXPECT_TRUE(GetUserMediaWithSpecificConstraintsAndAccept(
       tab_contents, kAudioOnlyCallConstraints));
 }
+
+IN_PROC_BROWSER_TEST_F(MediaStreamPermissionTest,
+                       DenyingPermissionStopsStreamWhenRelevant) {
+  struct {
+    std::string constraints;
+    ContentSettingsType setting_to_clear;
+    bool should_video_stop;
+  } kTests[] = {
+      {kAudioVideoCallConstraints, ContentSettingsType::MEDIASTREAM_CAMERA,
+       true},
+      {kAudioVideoCallConstraints, ContentSettingsType::MEDIASTREAM_MIC, true},
+      {kVideoOnlyCallConstraints, ContentSettingsType::MEDIASTREAM_CAMERA,
+       true},
+      {kVideoOnlyCallConstraints, ContentSettingsType::MEDIASTREAM_MIC, false},
+  };
+
+  HostContentSettingsMap* settings_map =
+      HostContentSettingsMapFactory::GetForProfile(browser()->profile());
+
+  for (const auto& kTest : kTests) {
+    content::WebContents* tab_contents = LoadTestPageInTab();
+
+    EXPECT_TRUE(GetUserMediaWithSpecificConstraintsAndAcceptIfPrompted(
+        tab_contents, kTest.constraints));
+
+    StartDetectingVideo(tab_contents, "local-view");
+    EXPECT_TRUE(WaitForVideoToPlay(tab_contents));
+
+    settings_map->ClearSettingsForOneType(kTest.setting_to_clear);
+
+    // Let all the cross-thread tasks do their work.
+    base::RunLoop().RunUntilIdle();
+
+    StartDetectingVideo(tab_contents, "local-view");
+
+    if (kTest.should_video_stop) {
+      EXPECT_TRUE(WaitForVideoToStop(tab_contents));
+    } else {
+      EXPECT_TRUE(WaitForVideoToPlay(tab_contents));
+    }
+
+    // Clean up settings for the following tests.
+    settings_map->ClearSettingsForOneType(ContentSettingsType::MEDIASTREAM_MIC);
+    settings_map->ClearSettingsForOneType(
+        ContentSettingsType::MEDIASTREAM_CAMERA);
+  }
+}
diff --git a/chrome/browser/media/webrtc/webrtc_browsertest_base.cc b/chrome/browser/media/webrtc/webrtc_browsertest_base.cc
index 806b591..27819d1 100644
--- a/chrome/browser/media/webrtc/webrtc_browsertest_base.cc
+++ b/chrome/browser/media/webrtc/webrtc_browsertest_base.cc
@@ -473,6 +473,14 @@
   return is_video_playing;
 }
 
+bool WebRtcTestBase::WaitForVideoToStop(
+    content::WebContents* tab_contents) const {
+  bool is_video_stopped =
+      test::PollingWaitUntil("isVideoStopped()", "video-stopped", tab_contents);
+  EXPECT_TRUE(is_video_stopped);
+  return is_video_stopped;
+}
+
 std::string WebRtcTestBase::GetStreamSize(
     content::WebContents* tab_contents,
     const std::string& video_element) const {
diff --git a/chrome/browser/media/webrtc/webrtc_browsertest_base.h b/chrome/browser/media/webrtc/webrtc_browsertest_base.h
index e85f002..3c98df5 100644
--- a/chrome/browser/media/webrtc/webrtc_browsertest_base.h
+++ b/chrome/browser/media/webrtc/webrtc_browsertest_base.h
@@ -182,7 +182,11 @@
   // make that work). Looks at a 320x240 area of the target video tag.
   void StartDetectingVideo(content::WebContents* tab_contents,
                            const std::string& video_element) const;
+
+  // Wait for a video to start/stop playing. StartDetectingVideo must have
+  // been called already.
   bool WaitForVideoToPlay(content::WebContents* tab_contents) const;
+  bool WaitForVideoToStop(content::WebContents* tab_contents) const;
 
   // Returns the stream size as a string on the format <width>x<height>.
   std::string GetStreamSize(content::WebContents* tab_contents,
diff --git a/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.cc
index bf28a4e..f1c83f9 100644
--- a/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.cc
@@ -867,15 +867,6 @@
       "PageLoad.LayoutInstability.CumulativeShiftScore.MainFrame",
       page_load_metrics::LayoutShiftUmaValue(
           GetDelegate().GetMainFrameRenderData().layout_shift_score));
-
-  // Note: This depends on PageLoadMetrics internally processing loading
-  // behavior before timing metrics if they come in the same IPC update.
-  if (font_preload_started_before_rendering_observed_) {
-    UMA_HISTOGRAM_COUNTS_100(
-        "PageLoad.Clients.FontPreload.LayoutInstability.CumulativeShiftScore",
-        page_load_metrics::LayoutShiftUmaValue(
-            GetDelegate().GetPageRenderData().layout_shift_score));
-  }
 }
 
 void UkmPageLoadMetricsObserver::ReportPerfectHeuristicsMetrics() {
@@ -1150,11 +1141,6 @@
 void UkmPageLoadMetricsObserver::OnLoadingBehaviorObserved(
     content::RenderFrameHost* rfh,
     int behavior_flag) {
-  if (behavior_flag & blink::LoadingBehaviorFlag::
-                          kLoadingBehaviorFontPreloadStartedBeforeRendering) {
-    font_preload_started_before_rendering_observed_ = true;
-  }
-
   if (behavior_flag &
       blink::LoadingBehaviorFlag::
           kLoadingBehaviorAsyncScriptReadyBeforeDocumentFinishedParsing) {
diff --git a/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.h b/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.h
index 3ceb54a8..d4fbe574 100644
--- a/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.h
+++ b/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.h
@@ -250,8 +250,7 @@
   // Unique across the lifetime of the browser process.
   int main_document_sequence_number_ = -1;
 
-  // These are to capture observed LoadingBehaviorFlags.
-  bool font_preload_started_before_rendering_observed_ = false;
+  // This is to capture observed LoadingBehaviorFlags.
   bool delay_async_script_execution_before_finished_parsing_seen_ = false;
 
   bool currently_in_foreground_ = false;
diff --git a/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer_unittest.cc b/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer_unittest.cc
index f9ba91fe..0c96265 100644
--- a/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer_unittest.cc
+++ b/chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer_unittest.cc
@@ -1425,40 +1425,6 @@
   EXPECT_THAT(tester()->histogram_tester().GetAllSamples(
                   "PageLoad.LayoutInstability.CumulativeShiftScore"),
               testing::ElementsAre(base::Bucket(25, 1)));
-
-  tester()->histogram_tester().ExpectTotalCount(
-      "PageLoad.Clients.FontPreload.LayoutInstability."
-      "CumulativeShiftScore",
-      0);
-}
-
-TEST_F(UkmPageLoadMetricsObserverTest,
-       UpdateAfterHide_RecordsLayoutInstabilityWithFontPreload) {
-  NavigateAndCommit(GURL(kTestUrl1));
-
-  page_load_metrics::mojom::FrameMetadata metadata;
-  metadata.behavior_flags |= blink::LoadingBehaviorFlag::
-      kLoadingBehaviorFontPreloadStartedBeforeRendering;
-  tester()->SimulateMetadataUpdate(metadata, web_contents()->GetMainFrame());
-
-  page_load_metrics::mojom::FrameRenderDataUpdate render_data(1.0, 1.0, 0, 0, 0,
-                                                              0);
-  tester()->SimulateRenderDataUpdate(render_data);
-
-  // Simulate hiding the tab (the report should include shifts after hide).
-  web_contents()->WasHidden();
-
-  render_data.layout_shift_delta = 1.5;
-  render_data.layout_shift_delta_before_input_or_scroll = 0.0;
-  tester()->SimulateRenderDataUpdate(render_data);
-
-  // Simulate closing the tab.
-  DeleteContents();
-
-  EXPECT_THAT(tester()->histogram_tester().GetAllSamples(
-                  "PageLoad.Clients.FontPreload.LayoutInstability."
-                  "CumulativeShiftScore"),
-              testing::ElementsAre(base::Bucket(25, 1)));
 }
 
 TEST_F(UkmPageLoadMetricsObserverTest,
diff --git a/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckBridge.java b/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckBridge.java
index ee57286..0b2cfb0a 100644
--- a/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckBridge.java
+++ b/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckBridge.java
@@ -104,6 +104,21 @@
     }
 
     /**
+     *
+     * @return Whether the scripts refreshment is finished.
+     */
+    boolean areScriptsRefreshed() {
+        return PasswordCheckBridgeJni.get().areScriptsRefreshed(mNativePasswordCheckBridge);
+    }
+
+    /**
+     * Invokes scripts refreshment.
+     */
+    void refreshScripts() {
+        PasswordCheckBridgeJni.get().refreshScripts(mNativePasswordCheckBridge);
+    }
+
+    /**
      * @return The timestamp of the last completed check.
      */
     long getLastCheckTimestamp() {
@@ -163,6 +178,8 @@
         long create(PasswordCheckBridge passwordCheckBridge);
         void startCheck(long nativePasswordCheckBridge);
         void stopCheck(long nativePasswordCheckBridge);
+        boolean areScriptsRefreshed(long nativePasswordCheckBridge);
+        void refreshScripts(long nativePasswordCheckBridge);
         long getLastCheckTimestamp(long nativePasswordCheckBridge);
         int getCompromisedCredentialsCount(long nativePasswordCheckBridge);
         int getSavedPasswordsCount(long nativePasswordCheckBridge);
diff --git a/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckCoordinator.java b/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckCoordinator.java
index 097b25e..f166a6b 100644
--- a/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckCoordinator.java
+++ b/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckCoordinator.java
@@ -43,6 +43,12 @@
         void onRemove(CompromisedCredential credential);
 
         /**
+         * View the given Credential.
+         * @param credential A {@link CompromisedCredential} to be viewed.
+         */
+        void onView(CompromisedCredential credential);
+
+        /**
          * Opens a password change form or home page of |credential|'s origin or an app.
          * @param credential A {@link CompromisedCredential} to be changed.
          */
diff --git a/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckImpl.java b/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckImpl.java
index f48461d..1a258e2 100644
--- a/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckImpl.java
+++ b/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckImpl.java
@@ -37,6 +37,7 @@
         fragmentArgs.putInt(
                 PasswordCheckFragmentView.PASSWORD_CHECK_REFERRER, passwordCheckReferrer);
         launcher.launchSettingsActivity(context, PasswordCheckFragmentView.class, fragmentArgs);
+        mPasswordCheckBridge.refreshScripts();
     }
 
     @Override
@@ -136,4 +137,9 @@
     public void stopCheck() {
         mPasswordCheckBridge.stopCheck();
     }
+
+    @Override
+    public boolean areScriptsRefreshed() {
+        return mPasswordCheckBridge.areScriptsRefreshed();
+    }
 }
diff --git a/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckMediator.java b/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckMediator.java
index 9f2acda..cb02d365 100644
--- a/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckMediator.java
+++ b/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckMediator.java
@@ -66,6 +66,9 @@
 
     @Override
     public void onCompromisedCredentialsFetchCompleted() {
+        if (!getPasswordCheck().areScriptsRefreshed()) {
+            return;
+        }
         CompromisedCredential[] credentials = getPasswordCheck().getCompromisedCredentials();
         assert credentials != null;
         ListModel<ListItem> items = mModel.get(ITEMS);
@@ -172,6 +175,21 @@
     }
 
     @Override
+    public void onView(CompromisedCredential credential) {
+        if (!mReauthenticationHelper.canReauthenticate()) {
+            mReauthenticationHelper.showScreenLockToast();
+            return;
+        }
+
+        mReauthenticationHelper.reauthenticate(ReauthReason.VIEW_PASSWORD,
+                reauthSucceeded
+                -> {
+                        // TODO(crbug.com/1117502) Add implementation to view the compromised
+                        // credential
+                });
+    }
+
+    @Override
     public void onChangePasswordButtonClick(CompromisedCredential credential) {
         mChangePasswordDelegate.launchAppOrCctWithChangePasswordUrl(credential);
     }
diff --git a/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckViewBinder.java b/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckViewBinder.java
index e868167..35eb37d 100644
--- a/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckViewBinder.java
+++ b/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckViewBinder.java
@@ -451,12 +451,16 @@
             PasswordCheckCoordinator.CredentialEventHandler credentialHandler) {
         MVCListAdapter.ModelList menuItems = new MVCListAdapter.ModelList();
         menuItems.add(BasicListMenu.buildMenuListItem(
+                R.string.password_check_credential_menu_item_view_button_caption, 0, 0, true));
+        menuItems.add(BasicListMenu.buildMenuListItem(
                 R.string.password_check_credential_menu_item_edit_button_caption, 0, 0, true));
         menuItems.add(
                 BasicListMenu.buildMenuListItem(org.chromium.chrome.R.string.remove, 0, 0, true));
         ListMenu.Delegate delegate = (listModel) -> {
             int textId = listModel.get(ListMenuItemProperties.TITLE_ID);
-            if (textId == R.string.password_check_credential_menu_item_edit_button_caption) {
+            if (textId == R.string.password_check_credential_menu_item_view_button_caption) {
+                credentialHandler.onView(credential);
+            } else if (textId == R.string.password_check_credential_menu_item_edit_button_caption) {
                 credentialHandler.onEdit(credential);
             } else if (textId == org.chromium.chrome.R.string.remove) {
                 credentialHandler.onRemove(credential);
diff --git a/chrome/browser/password_check/android/internal/java/strings/android_password_check_strings.grd b/chrome/browser/password_check/android/internal/java/strings/android_password_check_strings.grd
index 4ebc7ee8..e99f5f07 100644
--- a/chrome/browser/password_check/android/internal/java/strings/android_password_check_strings.grd
+++ b/chrome/browser/password_check/android/internal/java/strings/android_password_check_strings.grd
@@ -253,6 +253,9 @@
       <message name="IDS_PASSWORD_CHECK_CREDENTIAL_MENU_ITEM_EDIT_BUTTON_CAPTION" desc="Caption for the menu button allowing to edit a compromised credential.">
         Edit password
       </message>
+      <message name="IDS_PASSWORD_CHECK_CREDENTIAL_MENU_ITEM_VIEW_BUTTON_CAPTION" desc="Caption for the menu button allowing to view a compromised credential.">
+        View password
+      </message>
 
       <!-- Password Check Toast strings -->
       <message name="IDS_PASSWORD_CHECK_SET_SCREEN_LOCK_TEXT" desc="Text prompting user to set a screen lock in order to view or edit their compromised passwords.">
diff --git a/chrome/browser/password_check/android/internal/java/strings/android_password_check_strings_grd/IDS_PASSWORD_CHECK_CREDENTIAL_MENU_ITEM_VIEW_BUTTON_CAPTION.png.sha1 b/chrome/browser/password_check/android/internal/java/strings/android_password_check_strings_grd/IDS_PASSWORD_CHECK_CREDENTIAL_MENU_ITEM_VIEW_BUTTON_CAPTION.png.sha1
new file mode 100644
index 0000000..1e22f91
--- /dev/null
+++ b/chrome/browser/password_check/android/internal/java/strings/android_password_check_strings_grd/IDS_PASSWORD_CHECK_CREDENTIAL_MENU_ITEM_VIEW_BUTTON_CAPTION.png.sha1
@@ -0,0 +1 @@
+2531deee57820ab74470f0ebbb23616862e03840
\ No newline at end of file
diff --git a/chrome/browser/password_check/android/java/src/org/chromium/chrome/browser/password_check/PasswordCheck.java b/chrome/browser/password_check/android/java/src/org/chromium/chrome/browser/password_check/PasswordCheck.java
index c8067aa..f356a8e 100644
--- a/chrome/browser/password_check/android/java/src/org/chromium/chrome/browser/password_check/PasswordCheck.java
+++ b/chrome/browser/password_check/android/java/src/org/chromium/chrome/browser/password_check/PasswordCheck.java
@@ -113,4 +113,9 @@
      * Stops the password check, if one is running. Otherwise, does nothing.
      */
     void stopCheck();
+
+    /**
+     * Checks if scripts refreshment is finished.
+     */
+    boolean areScriptsRefreshed();
 }
diff --git a/chrome/browser/password_check/android/javatests/src/org/chromium/chrome/browser/password_check/PasswordCheckViewTest.java b/chrome/browser/password_check/android/javatests/src/org/chromium/chrome/browser/password_check/PasswordCheckViewTest.java
index f65d110..45bd62e 100644
--- a/chrome/browser/password_check/android/javatests/src/org/chromium/chrome/browser/password_check/PasswordCheckViewTest.java
+++ b/chrome/browser/password_check/android/javatests/src/org/chromium/chrome/browser/password_check/PasswordCheckViewTest.java
@@ -528,6 +528,22 @@
     }
 
     @Test
+    @MediumTest
+    public void testClickingViewInMoreMenuTriggersHandler() {
+        runOnUiThreadBlocking(() -> mModel.get(ITEMS).add(buildCredentialItem(ANA)));
+        waitForListViewToHaveLength(1);
+
+        TouchCommon.singleClickView(getCredentialMoreButtonAt(0));
+
+        onView(withText(R.string.password_check_credential_menu_item_view_button_caption))
+                .inRoot(withDecorView(
+                        not(is(mPasswordCheckView.getActivity().getWindow().getDecorView()))))
+                .perform(click());
+
+        verify(mMockHandler).onView(eq(ANA));
+    }
+
+    @Test
     @SmallTest
     public void testGetTimestampStrings() {
         Resources res = mPasswordCheckView.getContext().getResources();
diff --git a/chrome/browser/password_check/android/junit/src/org/chromium/chrome/browser/password_check/PasswordCheckControllerTest.java b/chrome/browser/password_check/android/junit/src/org/chromium/chrome/browser/password_check/PasswordCheckControllerTest.java
index be863c3..30402b5 100644
--- a/chrome/browser/password_check/android/junit/src/org/chromium/chrome/browser/password_check/PasswordCheckControllerTest.java
+++ b/chrome/browser/password_check/android/junit/src/org/chromium/chrome/browser/password_check/PasswordCheckControllerTest.java
@@ -193,6 +193,7 @@
     public void testCreatesEntryForExistingCredentials() {
         when(mPasswordCheck.getCompromisedCredentials())
                 .thenReturn(new CompromisedCredential[] {ANA});
+        when(mPasswordCheck.areScriptsRefreshed()).thenReturn(true);
         when(mChangePasswordDelegate.canManuallyChangeCredential(eq(ANA))).thenReturn(true);
 
         mMediator.onPasswordCheckStatusChanged(IDLE);
@@ -208,6 +209,7 @@
     public void testHidesChangeButtonIfManualChangeIsNotPossible() {
         when(mPasswordCheck.getCompromisedCredentials())
                 .thenReturn(new CompromisedCredential[] {BOB});
+        when(mPasswordCheck.areScriptsRefreshed()).thenReturn(true);
         when(mChangePasswordDelegate.canManuallyChangeCredential(eq(BOB))).thenReturn(false);
 
         mMediator.onPasswordCheckStatusChanged(IDLE);
@@ -223,6 +225,7 @@
     public void testAppendsEntryForNewlyFoundCredentials() {
         when(mPasswordCheck.getCompromisedCredentials())
                 .thenReturn(new CompromisedCredential[] {ANA});
+        when(mPasswordCheck.areScriptsRefreshed()).thenReturn(true);
         when(mChangePasswordDelegate.canManuallyChangeCredential(eq(BOB))).thenReturn(true);
         mMediator.onPasswordCheckStatusChanged(IDLE);
         mMediator.onCompromisedCredentialsFetchCompleted();
@@ -243,6 +246,7 @@
         // First call adds only ANA.
         when(mPasswordCheck.getCompromisedCredentials())
                 .thenReturn(new CompromisedCredential[] {ANA});
+        when(mPasswordCheck.areScriptsRefreshed()).thenReturn(true);
         mMediator.onCompromisedCredentialsFetchCompleted();
         assertThat(mModel.get(ITEMS).size(), is(2)); // Header + existing credentials.
 
diff --git a/chrome/browser/password_check/android/password_check_bridge.cc b/chrome/browser/password_check/android/password_check_bridge.cc
index 4a327c3..7be613c 100644
--- a/chrome/browser/password_check/android/password_check_bridge.cc
+++ b/chrome/browser/password_check/android/password_check_bridge.cc
@@ -110,6 +110,14 @@
   delete this;
 }
 
+bool PasswordCheckBridge::AreScriptsRefreshed(JNIEnv* env) const {
+  return check_manager_.AreScriptsRefreshed();
+}
+
+void PasswordCheckBridge::RefreshScripts(JNIEnv* env) {
+  check_manager_.RefreshScripts();
+}
+
 void PasswordCheckBridge::OnSavedPasswordsFetched(int count) {
   Java_PasswordCheckBridge_onSavedPasswordsFetched(
       base::android::AttachCurrentThread(), java_bridge_, count);
diff --git a/chrome/browser/password_check/android/password_check_bridge.h b/chrome/browser/password_check/android/password_check_bridge.h
index 74c74856f..08dac51 100644
--- a/chrome/browser/password_check/android/password_check_bridge.h
+++ b/chrome/browser/password_check/android/password_check_bridge.h
@@ -58,6 +58,12 @@
   // Called by Java when the bridge is no longer needed. Destructs itself.
   void Destroy(JNIEnv* env);
 
+  // Checks if script refreshment is finished.
+  bool AreScriptsRefreshed(JNIEnv* env) const;
+
+  // Invokes scripts refreshment.
+  void RefreshScripts(JNIEnv* env);
+
   // Called by the check manager when the saved passwords have been first loaded
   // in memory. `count` is the number of saved passwords.
   void OnSavedPasswordsFetched(int count) override;
diff --git a/chrome/browser/password_check/android/password_check_manager.cc b/chrome/browser/password_check/android/password_check_manager.cc
index 1b47e46..eb9d43c7 100644
--- a/chrome/browser/password_check/android/password_check_manager.cc
+++ b/chrome/browser/password_check/android/password_check_manager.cc
@@ -41,6 +41,29 @@
   return url.GetOrigin().spec();
 }
 
+// Orders |compromised_credentials| in such a way that phished credentials
+// precede leaked credentials, and that credentials of the same compromise type
+// are ordered by recency.
+// TODO(crbug.com/1117579): Share the common logic with desktop.
+void OrderCompromisedCredentials(
+    std::vector<password_manager::CredentialWithPassword>& credentials) {
+  // Reordering phished credential to the beginning.
+  auto last_phished = std::partition(
+      credentials.begin(), credentials.end(), [](const auto& credential) {
+        return credential.compromise_type !=
+               password_manager::CompromiseTypeFlags::kCredentialLeaked;
+      });
+
+  // By construction the phished credentials precede the leaked credentials in
+  // |credentials|. Now sort both groups by their creation date so that most recent
+  // compromises appear first in both lists.
+  auto create_time_cmp = [](const auto& lhs, const auto& rhs) {
+    return lhs.create_time > rhs.create_time;
+  };
+  std::sort(credentials.begin(), last_phished, create_time_cmp);
+  std::sort(last_phished, credentials.end(), create_time_cmp);
+}
+
 }  // namespace
 
 using autofill::PasswordForm;
@@ -87,7 +110,7 @@
 PasswordCheckManager::~PasswordCheckManager() = default;
 
 void PasswordCheckManager::StartCheck() {
-  if (!is_initialized_) {
+  if (!is_initialized_ || !AreScriptsRefreshed()) {
     was_start_requested_ = true;
     return;
   }
@@ -119,6 +142,7 @@
 PasswordCheckManager::GetCompromisedCredentials() const {
   std::vector<CredentialWithPassword> credentials =
       compromised_credentials_manager_.GetCompromisedCredentials();
+  OrderCompromisedCredentials(credentials);
   std::vector<CompromisedCredentialForUI> ui_credentials;
   ui_credentials.reserve(credentials.size());
   for (const auto& credential : credentials) {
@@ -161,6 +185,9 @@
 void PasswordCheckManager::OnCompromisedCredentialsChanged(
     password_manager::CompromisedCredentialsManager::CredentialsView
         credentials) {
+  if (!AreScriptsRefreshed()) {
+    credentials_count_to_notify_ = credentials.size();
+  }
   observer_->OnCompromisedCredentialsChanged(credentials.size());
 }
 
@@ -192,10 +219,19 @@
 CompromisedCredentialForUI PasswordCheckManager::MakeUICredential(
     const CredentialWithPassword& credential) const {
   CompromisedCredentialForUI ui_credential(credential);
+  // UI is only be created after the list of available password check
+  // scripts has been refreshed.
+  DCHECK(AreScriptsRefreshed());
   auto facet = password_manager::FacetURI::FromPotentiallyInvalidSpec(
       credential.signon_realm);
 
   ui_credential.display_username = GetDisplayUsername(credential.username);
+  ui_credential.has_script =
+      base::FeatureList::IsEnabled(
+          password_manager::features::kPasswordChangeInSettings) &&
+      password_script_fetcher_->IsScriptAvailable(
+          url::Origin::Create(credential.url.GetOrigin()));
+
   if (facet.IsValidAndroidFacetURI()) {
     const PasswordForm& android_form =
         compromised_credentials_manager_.GetSavedPasswordsFor(credential)[0];
@@ -262,3 +298,38 @@
   return sync_state == SyncState::SYNCING_NORMAL_ENCRYPTION ||
          sync_state == SyncState::ACCOUNT_PASSWORDS_ACTIVE_NORMAL_ENCRYPTION;
 }
+
+bool PasswordCheckManager::AreScriptsRefreshed() const {
+  // Don't fetch scripts if password change is not enabled.
+  return are_scripts_refreshed_ ||
+         !base::FeatureList::IsEnabled(
+             password_manager::features::kPasswordChangeInSettings);
+}
+
+void PasswordCheckManager::RefreshScripts() {
+  // Don't fetch scripts if password change is not enabled.
+  if (!base::FeatureList::IsEnabled(
+          password_manager::features::kPasswordChangeInSettings)) {
+    return;
+  }
+
+  are_scripts_refreshed_ = false;
+  password_script_fetcher_->RefreshScriptsIfNecessary(base::BindOnce(
+      &PasswordCheckManager::OnScriptsFetched, base::Unretained(this)));
+}
+
+void PasswordCheckManager::OnScriptsFetched() {
+  are_scripts_refreshed_ = true;
+
+  if (credentials_count_to_notify_.has_value()) {
+    // Inform the UI about compromised credentials another time because it was
+    // not allowed to generate UI before the availability of password scripts is
+    // known.
+    observer_->OnCompromisedCredentialsChanged(
+        credentials_count_to_notify_.value());
+    credentials_count_to_notify_.reset();
+  }
+
+  if (was_start_requested_)
+    StartCheck();
+}
diff --git a/chrome/browser/password_check/android/password_check_manager.h b/chrome/browser/password_check/android/password_check_manager.h
index 730ff77..4f4f359 100644
--- a/chrome/browser/password_check/android/password_check_manager.h
+++ b/chrome/browser/password_check/android/password_check_manager.h
@@ -6,13 +6,16 @@
 #define CHROME_BROWSER_PASSWORD_CHECK_ANDROID_PASSWORD_CHECK_MANAGER_H_
 
 #include "base/memory/scoped_refptr.h"
+#include "base/optional.h"
 #include "base/strings/string_piece_forward.h"
 #include "chrome/browser/password_check/android/password_check_ui_status.h"
 #include "chrome/browser/password_manager/bulk_leak_check_service_factory.h"
+#include "chrome/browser/password_manager/password_scripts_fetcher_factory.h"
 #include "chrome/browser/password_manager/password_store_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "components/password_manager/core/browser/bulk_leak_check_service.h"
 #include "components/password_manager/core/browser/bulk_leak_check_service_interface.h"
+#include "components/password_manager/core/browser/password_scripts_fetcher.h"
 #include "components/password_manager/core/browser/ui/bulk_leak_check_service_adapter.h"
 #include "components/password_manager/core/browser/ui/compromised_credentials_manager.h"
 #include "components/password_manager/core/browser/ui/saved_passwords_presenter.h"
@@ -82,6 +85,12 @@
   // UI update on completion.
   void RemoveCredential(const password_manager::CredentialView& credential);
 
+  // Invokes `PasswordScriptsFetcher`'s scripts refreshment.
+  void RefreshScripts();
+
+  // Returns if scripts refreshment is finished.
+  bool AreScriptsRefreshed() const;
+
   // Not copyable or movable
   PasswordCheckManager(const PasswordCheckManager&) = delete;
   PasswordCheckManager& operator=(const PasswordCheckManager&) = delete;
@@ -122,6 +131,9 @@
   // in the account if the quota limit was reached.
   bool CanUseAccountCheck() const;
 
+  // Callback when PasswordScriptsFetcher's cache has been warmed up.
+  void OnScriptsFetched();
+
   // Obsever being notified of UI-relevant events.
   // It must outlive `this`.
   Observer* observer_ = nullptr;
@@ -135,6 +147,12 @@
       PasswordStoreFactory::GetForProfile(profile_,
                                           ServiceAccessType::EXPLICIT_ACCESS);
 
+  // Used to check whether autofill assistant scripts are available for
+  // the specified domain.
+  password_manager::PasswordScriptsFetcher* password_script_fetcher_ =
+      PasswordScriptsFetcherFactory::GetInstance()->GetForBrowserContext(
+          profile_);
+
   // Used by `compromised_credentials_manager_` to obtain the list of saved
   // passwords.
   password_manager::SavedPasswordsPresenter saved_passwords_presenter_{
@@ -161,6 +179,14 @@
   // Whether a check is currently running.
   bool is_check_running_ = false;
 
+  // Whether scripts refreshement is finished.
+  bool are_scripts_refreshed_ = false;
+
+  // Latest number of changed compromised credentials while script fetching
+  // was running. If `credentials_count_to_notify_` has value, after scripts are
+  // fetched `onCompromisedCredentials` should be called.
+  base::Optional<size_t> credentials_count_to_notify_;
+
   // A scoped observer for `saved_passwords_presenter_`.
   ScopedObserver<password_manager::SavedPasswordsPresenter,
                  password_manager::SavedPasswordsPresenter::Observer>
diff --git a/chrome/browser/password_check/android/password_check_manager_unittest.cc b/chrome/browser/password_check/android/password_check_manager_unittest.cc
index d1f510c..14406f23 100644
--- a/chrome/browser/password_check/android/password_check_manager_unittest.cc
+++ b/chrome/browser/password_check/android/password_check_manager_unittest.cc
@@ -12,14 +12,17 @@
 #include "base/strings/strcat.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/bind_test_util.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/time/time.h"
 #include "chrome/browser/password_check/android/password_check_ui_status.h"
 #include "chrome/browser/password_manager/bulk_leak_check_service_factory.h"
+#include "chrome/browser/password_manager/password_scripts_fetcher_factory.h"
 #include "chrome/browser/password_manager/password_store_factory.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/password_manager/core/browser/bulk_leak_check_service.h"
 #include "components/password_manager/core/browser/password_manager_test_utils.h"
 #include "components/password_manager/core/browser/test_password_store.h"
+#include "components/password_manager/core/common/password_manager_features.h"
 #include "components/password_manager/core/common/password_manager_pref_names.h"
 #include "components/prefs/testing_pref_service.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
@@ -41,16 +44,20 @@
 using testing::_;
 using testing::ElementsAre;
 using testing::Field;
+using testing::Invoke;
 using testing::IsEmpty;
 using testing::NiceMock;
+using testing::Return;
 
 using CompromisedCredentialForUI =
     PasswordCheckManager::CompromisedCredentialForUI;
+using CompromiseTypeFlags = password_manager::CompromiseTypeFlags;
 using State = password_manager::BulkLeakCheckService::State;
 
 namespace {
 
 constexpr char kExampleCom[] = "https://example.com";
+constexpr char kExampleOrg[] = "http://www.example.org";
 constexpr char kExampleApp[] = "com.example.app";
 
 constexpr char kUsername1[] = "alice";
@@ -70,6 +77,21 @@
               (override));
 };
 
+class MockPasswordScriptsFetcher
+    : public password_manager::PasswordScriptsFetcher {
+ public:
+  MOCK_METHOD(void, PrewarmCache, (), (override));
+
+  MOCK_METHOD(void, RefreshScriptsIfNecessary, (base::OnceClosure), (override));
+
+  MOCK_METHOD(void,
+              FetchScriptAvailability,
+              (const url::Origin&, base::OnceCallback<void(bool)>),
+              (override));
+
+  MOCK_METHOD(bool, IsScriptAvailable, (const url::Origin&), (const override));
+};
+
 // TODO(crbug.com/1112804): Extract this into a password manager test utils
 // file, since it's used across multiple tests.
 scoped_refptr<TestPasswordStore> CreateAndUseTestPasswordStore(
@@ -95,6 +117,15 @@
           }));
 }
 
+MockPasswordScriptsFetcher* CreateAndUseMockPasswordScriptsFetcher(
+    Profile* profile) {
+  return PasswordScriptsFetcherFactory::GetInstance()
+      ->SetTestingSubclassFactoryAndUse(
+          profile, base::BindRepeating([](content::BrowserContext*) {
+            return std::make_unique<MockPasswordScriptsFetcher>();
+          }));
+}
+
 PasswordForm MakeSavedPassword(base::StringPiece signon_realm,
                                base::StringPiece username,
                                base::StringPiece password = kPassword1,
@@ -143,6 +174,7 @@
     const base::string16& display_origin,
     const base::Optional<std::string>& package_name,
     const base::Optional<std::string>& change_password_url,
+    CompromiseTypeFlags compromise_type,
     bool has_script) {
   auto package_name_field_matcher =
       package_name.has_value()
@@ -158,6 +190,7 @@
       Field(&CompromisedCredentialForUI::display_username, display_username),
       Field(&CompromisedCredentialForUI::display_origin, display_origin),
       package_name_field_matcher, change_password_url_field_matcher,
+      Field(&CompromisedCredentialForUI::compromise_type, compromise_type),
       Field(&CompromisedCredentialForUI::has_script, has_script));
 }
 
@@ -174,10 +207,10 @@
 
   BulkLeakCheckService* service() { return service_; }
   TestPasswordStore& store() { return *store_; }
-
- protected:
-  NiceMock<MockPasswordCheckManagerObserver> mock_observer_;
-  std::unique_ptr<PasswordCheckManager> manager_;
+  MockPasswordCheckManagerObserver& mock_observer() { return mock_observer_; }
+  MockPasswordScriptsFetcher& fetcher() { return *fetcher_; }
+  PasswordCheckManager& manager() { return *manager_; }
+  base::test::ScopedFeatureList& feature_list() { return feature_list_; }
 
  private:
   content::BrowserTaskEnvironment task_env_;
@@ -188,25 +221,29 @@
                                        &profile_);
   scoped_refptr<TestPasswordStore> store_ =
       CreateAndUseTestPasswordStore(&profile_);
+  NiceMock<MockPasswordCheckManagerObserver> mock_observer_;
+  MockPasswordScriptsFetcher* fetcher_ =
+      CreateAndUseMockPasswordScriptsFetcher(&profile_);
+  base::test::ScopedFeatureList feature_list_;
+  std::unique_ptr<PasswordCheckManager> manager_;
 };
 
 TEST_F(PasswordCheckManagerTest, SendsNoPasswordsMessageIfNoPasswordsAreSaved) {
-  EXPECT_CALL(mock_observer_, OnPasswordCheckStatusChanged(
-                                  PasswordCheckUIStatus::kErrorNoPasswords));
+  EXPECT_CALL(mock_observer(), OnPasswordCheckStatusChanged(
+                                   PasswordCheckUIStatus::kErrorNoPasswords));
   InitializeManager();
   RunUntilIdle();
 }
 
 TEST_F(PasswordCheckManagerTest, OnSavedPasswordsFetched) {
   store().AddLogin(MakeSavedPassword(kExampleCom, kUsername1));
-
-  EXPECT_CALL(mock_observer_, OnSavedPasswordsFetched(1));
+  EXPECT_CALL(mock_observer(), OnSavedPasswordsFetched(1));
   InitializeManager();
   RunUntilIdle();
 
   // Verify that OnSavedPasswordsFetched is not called after the initial fetch
   // even if the saved passwords change.
-  EXPECT_CALL(mock_observer_, OnSavedPasswordsFetched(_)).Times(0);
+  EXPECT_CALL(mock_observer(), OnSavedPasswordsFetched(_)).Times(0);
   store().AddLogin(MakeSavedPassword(kExampleCom, kUsername2));
   RunUntilIdle();
 }
@@ -215,15 +252,13 @@
   // This is called on multiple events: once for saved passwords retrieval,
   // once for compromised credentials retrieval and once when the saved password
   // is added.
-  EXPECT_CALL(mock_observer_, OnCompromisedCredentialsChanged(0)).Times(3);
+  EXPECT_CALL(mock_observer(), OnCompromisedCredentialsChanged(0)).Times(3);
   InitializeManager();
   store().AddLogin(MakeSavedPassword(kExampleCom, kUsername1));
   RunUntilIdle();
 
-  EXPECT_CALL(mock_observer_, OnCompromisedCredentialsChanged(1));
-  store().AddCompromisedCredentials(
-      MakeCompromised(kExampleCom, kUsername1, base::TimeDelta::FromMinutes(1),
-                      CompromiseType::kLeaked));
+  EXPECT_CALL(mock_observer(), OnCompromisedCredentialsChanged(1));
+  store().AddCompromisedCredentials(MakeCompromised(kExampleCom, kUsername1));
   RunUntilIdle();
 }
 
@@ -233,10 +268,12 @@
   store().AddCompromisedCredentials(MakeCompromised(kExampleCom, kUsername1));
   RunUntilIdle();
   EXPECT_THAT(
-      manager_->GetCompromisedCredentials(),
+      manager().GetCompromisedCredentials(),
       ElementsAre(ExpectCompromisedCredentialForUI(
           base::ASCIIToUTF16(kUsername1), base::ASCIIToUTF16("example.com"),
-          base::nullopt, "https://example.com/", /*has_script=*/false)));
+          base::nullopt, "https://example.com/",
+          CompromiseTypeFlags::kCredentialLeaked,
+          /*has_script=*/false)));
 }
 
 TEST_F(PasswordCheckManagerTest, CorrectlyCreatesUIStructForAppCredentials) {
@@ -253,17 +290,19 @@
 
   RunUntilIdle();
 
-  EXPECT_THAT(manager_->GetCompromisedCredentials(),
-              ElementsAre(ExpectCompromisedCredentialForUI(
-                              base::ASCIIToUTF16(kUsername1),
-                              base::ASCIIToUTF16("App (com.example.app)"),
-                              "com.example.app", base::nullopt,
-                              /*has_script=*/false),
-                          ExpectCompromisedCredentialForUI(
-                              base::ASCIIToUTF16(kUsername2),
-                              base::ASCIIToUTF16("Example App"),
-                              "com.example.app", base::nullopt,
-                              /*has_script=*/false)));
+  EXPECT_THAT(
+      manager().GetCompromisedCredentials(),
+      UnorderedElementsAre(
+          ExpectCompromisedCredentialForUI(
+              base::ASCIIToUTF16(kUsername1),
+              base::ASCIIToUTF16("App (com.example.app)"), "com.example.app",
+              base::nullopt, CompromiseTypeFlags::kCredentialLeaked,
+              /*has_script=*/false),
+          ExpectCompromisedCredentialForUI(
+              base::ASCIIToUTF16(kUsername2), base::ASCIIToUTF16("Example App"),
+              "com.example.app", base::nullopt,
+              CompromiseTypeFlags::kCredentialLeaked,
+              /*has_script=*/false)));
 }
 
 TEST_F(PasswordCheckManagerTest, SetsTimestampOnSuccessfulCheck) {
@@ -272,11 +311,11 @@
   RunUntilIdle();
 
   // Pretend to start the check so that the manager thinks a check is running.
-  manager_->StartCheck();
+  manager().StartCheck();
 
   // Change the state to idle to simulate a successful check finish.
   service()->set_state_and_notify(State::kIdle);
-  EXPECT_NE(0.0, manager_->GetLastCheckTimestamp().ToDoubleT());
+  EXPECT_NE(0.0, manager().GetLastCheckTimestamp().ToDoubleT());
 }
 
 TEST_F(PasswordCheckManagerTest, DoesntRecordTimestampOfUnsuccessfulCheck) {
@@ -285,9 +324,77 @@
   RunUntilIdle();
 
   // Pretend to start the check so that the manager thinks a check is running.
-  manager_->StartCheck();
+  manager().StartCheck();
 
   // Change the state to an error state to simulate a unsuccessful check finish.
   service()->set_state_and_notify(State::kSignedOut);
-  EXPECT_EQ(0.0, manager_->GetLastCheckTimestamp().ToDoubleT());
+  EXPECT_EQ(0.0, manager().GetLastCheckTimestamp().ToDoubleT());
+}
+
+TEST_F(PasswordCheckManagerTest, CorrectlyCreatesUIStructWithPasswordScripts) {
+  InitializeManager();
+  feature_list().InitAndEnableFeature(
+      password_manager::features::kPasswordChangeInSettings);
+  store().AddLogin(MakeSavedPassword(kExampleCom, kUsername1));
+  store().AddCompromisedCredentials(MakeCompromised(kExampleCom, kUsername1));
+
+  RunUntilIdle();
+  EXPECT_CALL(fetcher(), RefreshScriptsIfNecessary)
+      .WillOnce(Invoke(
+          [](base::OnceClosure callback) { std::move(callback).Run(); }));
+
+  manager().RefreshScripts();
+
+  EXPECT_CALL(fetcher(), IsScriptAvailable).WillRepeatedly(Return(true));
+  EXPECT_THAT(
+      manager().GetCompromisedCredentials(),
+      ElementsAre(ExpectCompromisedCredentialForUI(
+          base::ASCIIToUTF16(kUsername1), base::ASCIIToUTF16("example.com"),
+          base::nullopt, "https://example.com/",
+          CompromiseTypeFlags::kCredentialLeaked, /*has_script=*/true)));
+}
+
+TEST_F(PasswordCheckManagerTest, GetCompromisedCredentialsOrder) {
+  InitializeManager();
+  store().AddLogin(MakeSavedPassword(kExampleCom, kUsername1));
+  store().AddLogin(MakeSavedPassword(kExampleCom, kUsername2));
+  store().AddLogin(MakeSavedPassword(kExampleOrg, kUsername1));
+  store().AddLogin(MakeSavedPassword(kExampleOrg, kUsername2));
+  store().AddCompromisedCredentials(
+      MakeCompromised(kExampleCom, kUsername1, base::TimeDelta::FromMinutes(1),
+                      CompromiseType::kLeaked));
+  store().AddCompromisedCredentials(
+      MakeCompromised(kExampleCom, kUsername2, base::TimeDelta::FromMinutes(5),
+                      CompromiseType::kLeaked));
+  store().AddCompromisedCredentials(
+      MakeCompromised(kExampleCom, kUsername2, base::TimeDelta::FromMinutes(3),
+                      CompromiseType::kPhished));
+  store().AddCompromisedCredentials(
+      MakeCompromised(kExampleOrg, kUsername2, base::TimeDelta::FromMinutes(4),
+                      CompromiseType::kLeaked));
+  store().AddCompromisedCredentials(
+      MakeCompromised(kExampleOrg, kUsername1, base::TimeDelta::FromMinutes(2),
+                      CompromiseType::kPhished));
+  RunUntilIdle();
+  EXPECT_THAT(
+      manager().GetCompromisedCredentials(),
+      ElementsAre(
+          ExpectCompromisedCredentialForUI(
+              base::ASCIIToUTF16(kUsername1), base::ASCIIToUTF16("example.org"),
+              base::nullopt, "http://www.example.org/",
+              CompromiseTypeFlags::kCredentialPhished, /*has_script_=*/false),
+          ExpectCompromisedCredentialForUI(
+              base::ASCIIToUTF16(kUsername2), base::ASCIIToUTF16("example.com"),
+              base::nullopt, "https://example.com/",
+              password_manager::CompromiseTypeFlags::kCredentialLeaked |
+                  password_manager::CompromiseTypeFlags::kCredentialPhished,
+              /*has_script_=*/false),
+          ExpectCompromisedCredentialForUI(
+              base::ASCIIToUTF16(kUsername1), base::ASCIIToUTF16("example.com"),
+              base::nullopt, "https://example.com/",
+              CompromiseTypeFlags::kCredentialLeaked, /*has_script_=*/false),
+          ExpectCompromisedCredentialForUI(
+              base::ASCIIToUTF16(kUsername2), base::ASCIIToUTF16("example.org"),
+              base::nullopt, "http://www.example.org/",
+              CompromiseTypeFlags::kCredentialLeaked, /*has_script_=*/false)));
 }
diff --git a/chrome/browser/password_manager/android/BUILD.gn b/chrome/browser/password_manager/android/BUILD.gn
index 48075506..aac2d9d5 100644
--- a/chrome/browser/password_manager/android/BUILD.gn
+++ b/chrome/browser/password_manager/android/BUILD.gn
@@ -7,6 +7,7 @@
 android_library("java") {
   deps = [
     "//base:base_java",
+    "//base:jni_java",
     "//chrome/browser/settings:java",
     "//components/password_manager/core/browser:password_manager_java_enums",
     "//third_party/android_deps:androidx_annotation_annotation_java",
@@ -14,9 +15,16 @@
   ]
   sources = [
     "java/src/org/chromium/chrome/browser/password_manager/PasswordManagerHelper.java",
+    "java/src/org/chromium/chrome/browser/password_manager/PasswordScriptsFetcherBridge.java",
     "java/src/org/chromium/chrome/browser/password_manager/settings/PasswordReauthenticationFragment.java",
     "java/src/org/chromium/chrome/browser/password_manager/settings/ReauthenticationManager.java",
   ]
+  annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
+}
+
+generate_jni("jni_headers") {
+  visibility = [ "//chrome/browser" ]
+  sources = [ "java/src/org/chromium/chrome/browser/password_manager/PasswordScriptsFetcherBridge.java" ]
 }
 
 junit_binary("password_manager_junit_tests") {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/password_manager/PasswordScriptsFetcherBridge.java b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordScriptsFetcherBridge.java
similarity index 61%
rename from chrome/android/java/src/org/chromium/chrome/browser/password_manager/PasswordScriptsFetcherBridge.java
rename to chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordScriptsFetcherBridge.java
index 779b51e..d591ca1a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/password_manager/PasswordScriptsFetcherBridge.java
+++ b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordScriptsFetcherBridge.java
@@ -6,20 +6,18 @@
 
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.base.annotations.NativeMethods;
-import org.chromium.chrome.browser.profiles.Profile;
-import org.chromium.components.embedder_support.browser_context.BrowserContextHandle;
 
 /**
  * Android bridge to |PasswordScriptsFetcher|.
  */
 @JNINamespace("password_manager")
 public class PasswordScriptsFetcherBridge {
-    public static void prewarmCache(Profile profile) {
-        PasswordScriptsFetcherBridgeJni.get().prewarmCache(profile);
+    public static void prewarmCache() {
+        PasswordScriptsFetcherBridgeJni.get().prewarmCache();
     }
 
     @NativeMethods
     interface Natives {
-        void prewarmCache(BrowserContextHandle browserContext);
+        void prewarmCache();
     }
 }
diff --git a/chrome/browser/password_manager/android/password_scripts_fetcher_android.cc b/chrome/browser/password_manager/android/password_scripts_fetcher_android.cc
new file mode 100644
index 0000000..d02cd59
--- /dev/null
+++ b/chrome/browser/password_manager/android/password_scripts_fetcher_android.cc
@@ -0,0 +1,22 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <jni.h>
+
+#include "base/android/scoped_java_ref.h"
+#include "chrome/browser/password_manager/android/jni_headers/PasswordScriptsFetcherBridge_jni.h"
+#include "chrome/browser/password_manager/password_scripts_fetcher_factory.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "components/password_manager/core/browser/password_scripts_fetcher.h"
+
+namespace password_manager {
+
+// static
+void JNI_PasswordScriptsFetcherBridge_PrewarmCache(JNIEnv* env) {
+  PasswordScriptsFetcherFactory::GetInstance()
+      ->GetForBrowserContext(ProfileManager::GetLastUsedProfile())
+      ->PrewarmCache();
+}
+
+}  // namespace password_manager
diff --git a/chrome/browser/pdf/pdf_extension_test.cc b/chrome/browser/pdf/pdf_extension_test.cc
index d3ffae0..ec83b79b 100644
--- a/chrome/browser/pdf/pdf_extension_test.cc
+++ b/chrome/browser/pdf/pdf_extension_test.cc
@@ -2988,8 +2988,7 @@
         if (base::StartsWith(trimmed_line, allow_str,
                              base::CompareCase::SENSITIVE)) {
           property_filters->push_back(PropertyFilter(
-              base::UTF8ToUTF16(trimmed_line.substr(allow_str.size())),
-              PropertyFilter::ALLOW));
+              trimmed_line.substr(allow_str.size()), PropertyFilter::ALLOW));
         }
       }
     }
@@ -3091,8 +3090,7 @@
   void AddPropertyFilter(std::vector<PropertyFilter>* property_filters,
                          std::string filter,
                          PropertyFilter::Type type = PropertyFilter::ALLOW) {
-    property_filters->push_back(
-        PropertyFilter(base::ASCIIToUTF16(filter), type));
+    property_filters->push_back(PropertyFilter(filter, type));
   }
 
   content::AccessibilityTreeFormatter::TestPass test_pass_;
diff --git a/chrome/browser/policy/android/BUILD.gn b/chrome/browser/policy/android/BUILD.gn
index cae1178..1b899db8 100644
--- a/chrome/browser/policy/android/BUILD.gn
+++ b/chrome/browser/policy/android/BUILD.gn
@@ -13,6 +13,7 @@
     "//base:jni_java",
     "//chrome/browser/profiles/android:java",
     "//components/policy/android:policy_java",
+    "//third_party/android_deps:androidx_annotation_annotation_java",
   ]
   annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
   sources = _jni_sources
diff --git a/chrome/browser/policy/android/java/src/org/chromium/chrome/browser/policy/PolicyServiceFactory.java b/chrome/browser/policy/android/java/src/org/chromium/chrome/browser/policy/PolicyServiceFactory.java
index a3ef3b3b..8884526 100644
--- a/chrome/browser/policy/android/java/src/org/chromium/chrome/browser/policy/PolicyServiceFactory.java
+++ b/chrome/browser/policy/android/java/src/org/chromium/chrome/browser/policy/PolicyServiceFactory.java
@@ -4,6 +4,8 @@
 
 package org.chromium.chrome.browser.policy;
 
+import androidx.annotation.VisibleForTesting;
+
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.base.annotations.NativeMethods;
 import org.chromium.chrome.browser.profiles.Profile;
@@ -16,12 +18,16 @@
  */
 @JNINamespace("policy::android")
 public class PolicyServiceFactory {
+    private static PolicyService sPolicyServiceForTest;
+
     /**
      * Returns the PolicyService instance that contains browser policies.
      * The associated C++ instance is deleted during shutdown.
      */
     public static PolicyService getGlobalPolicyService() {
-        return PolicyServiceFactoryJni.get().getGlobalPolicyService();
+        return sPolicyServiceForTest == null
+                ? PolicyServiceFactoryJni.get().getGlobalPolicyService()
+                : sPolicyServiceForTest;
     }
 
     /**
@@ -30,7 +36,17 @@
      * deletion.
      */
     public static PolicyService getProfilePolicyService(Profile profile) {
-        return PolicyServiceFactoryJni.get().getProfilePolicyService(profile);
+        return sPolicyServiceForTest == null
+                ? PolicyServiceFactoryJni.get().getProfilePolicyService(profile)
+                : sPolicyServiceForTest;
+    }
+
+    /**
+     * @param policyService Mock {@link PolicyService} for testing.
+     */
+    @VisibleForTesting
+    public static void setPolicyServiceForTest(PolicyService policyService) {
+        sPolicyServiceForTest = policyService;
     }
 
     @NativeMethods
diff --git a/chrome/browser/policy/configuration_policy_handler_list_factory.cc b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
index 6569cce2..c9a2a58b 100644
--- a/chrome/browser/policy/configuration_policy_handler_list_factory.cc
+++ b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
@@ -942,9 +942,6 @@
   { key::kSchedulerConfiguration,
     prefs::kSchedulerConfiguration,
     base::Value::Type::STRING },
-  { key::kNativePrintersBulkBlacklist,
-    prefs::kRecommendedNativePrintersBlacklist,
-    base::Value::Type::LIST },
   { key::kNativePrintersBulkWhitelist,
     prefs::kRecommendedNativePrintersWhitelist,
     base::Value::Type::LIST },
@@ -1736,6 +1733,13 @@
       std::make_unique<SimplePolicyHandler>(
           key::kPrintersBulkAccessMode, prefs::kRecommendedPrintersAccessMode,
           base::Value::Type::INTEGER)));
+  handlers->AddHandler(std::make_unique<SimpleDeprecatingPolicyHandler>(
+      std::make_unique<SimplePolicyHandler>(
+          key::kNativePrintersBulkBlacklist,
+          prefs::kRecommendedPrintersBlocklist, base::Value::Type::LIST),
+      std::make_unique<SimplePolicyHandler>(
+          key::kPrintersBulkBlocklist, prefs::kRecommendedPrintersBlocklist,
+          base::Value::Type::LIST)));
   handlers->AddHandler(
       std::make_unique<ExternalDataPolicyHandler>(key::kExternalPrintServers));
   handlers->AddHandler(std::make_unique<ExternalDataPolicyHandler>(
diff --git a/chrome/browser/printing/print_browsertest.cc b/chrome/browser/printing/print_browsertest.cc
index ce485040..3678dd8 100644
--- a/chrome/browser/printing/print_browsertest.cc
+++ b/chrome/browser/printing/print_browsertest.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 <algorithm>
 #include <memory>
 
 #include "base/auto_reset.h"
@@ -404,6 +405,10 @@
 class BackForwardCachePrintBrowserTest : public PrintBrowserTest {
  public:
   BackForwardCachePrintBrowserTest() = default;
+  BackForwardCachePrintBrowserTest(const BackForwardCachePrintBrowserTest&) =
+      delete;
+  BackForwardCachePrintBrowserTest& operator=(
+      const BackForwardCachePrintBrowserTest&) = delete;
   ~BackForwardCachePrintBrowserTest() override = default;
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
@@ -416,7 +421,7 @@
             {"TimeToLiveInBackForwardCacheInSeconds", "3600"},
         });
 
-    InProcessBrowserTest::SetUpCommandLine(command_line);
+    PrintBrowserTest::SetUpCommandLine(command_line);
   }
 
   content::WebContents* web_contents() const {
@@ -448,8 +453,6 @@
         << location.ToString();
   }
 
-  base::HistogramTester histogram_tester_;
-
  private:
   void AddSampleToBuckets(std::vector<base::Bucket>* buckets,
                           base::HistogramBase::Sample sample) {
@@ -463,10 +466,9 @@
     }
   }
 
+  base::HistogramTester histogram_tester_;
   std::vector<base::Bucket> expected_blocklisted_features_;
   base::test::ScopedFeatureList scoped_feature_list_;
-
-  DISALLOW_COPY_AND_ASSIGN(BackForwardCachePrintBrowserTest);
 };
 
 constexpr char IsolateOriginsPrintBrowserTest::kIsolatedSite[];
@@ -789,7 +791,8 @@
   ASSERT_TRUE(embedded_test_server()->Started());
 
   // 1) Navigate to A and trigger printing.
-  GURL url(embedded_test_server()->GetURL("a.com", "/printing/test1.html"));
+  GURL url(embedded_test_server()->GetURL(
+      "a.com", "/back_forward_cache/no-favicon.html"));
   ui_test_utils::NavigateToURL(browser(), url);
   content::RenderFrameHost* rfh_a = current_frame_host();
   content::RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
@@ -797,7 +800,8 @@
 
   // 2) Navigate to B.
   // The first page is not cached because printing preview was open.
-  GURL url_2(embedded_test_server()->GetURL("b.com", "/printing/test2.html"));
+  GURL url_2(embedded_test_server()->GetURL(
+      "b.com", "/back_forward_cache/no-favicon.html"));
   ui_test_utils::NavigateToURL(browser(), url_2);
   delete_observer_rfh_a.WaitUntilDeleted();
 
diff --git a/chrome/browser/renderer_context_menu/render_view_context_menu.cc b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
index 36258d63..4a50496 100644
--- a/chrome/browser/renderer_context_menu/render_view_context_menu.cc
+++ b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
@@ -2393,9 +2393,6 @@
       NavigateToManagePasswordsPage(
           GetBrowser(),
           password_manager::ManagePasswordsReferrer::kPasswordContextMenu);
-      password_manager::metrics_util::LogContextOfShowAllSavedPasswordsAccepted(
-          password_manager::metrics_util::ShowAllSavedPasswordsContext::
-              kContextMenu);
       break;
 
     case IDC_CONTENT_CONTEXT_PICTUREINPICTURE:
diff --git a/chrome/browser/resources/chromeos/login/screen_gaia_signin.js b/chrome/browser/resources/chromeos/login/screen_gaia_signin.js
index 343cc204..9d12f7f 100644
--- a/chrome/browser/resources/chromeos/login/screen_gaia_signin.js
+++ b/chrome/browser/resources/chromeos/login/screen_gaia_signin.js
@@ -523,7 +523,7 @@
    */
   onBackButtonClicked_() {
     if (!this.canGoBack_()) {
-      if (this.childSpecificSigninFeatureEnabled_) {
+      if (!this.isSaml_ && this.childSpecificSigninFeatureEnabled_) {
         this.userActed('back');
       } else {
         this.cancel();
diff --git a/chrome/browser/resources/management/management_browser_proxy.js b/chrome/browser/resources/management/management_browser_proxy.js
index 0bfe0ed3..4636c7d 100644
--- a/chrome/browser/resources/management/management_browser_proxy.js
+++ b/chrome/browser/resources/management/management_browser_proxy.js
@@ -40,7 +40,8 @@
  *   threatProtectionDescription: string,
  *   showUpdateRequiredEol: boolean,
  *   eolMessage: string,
- *   eolAdminMessage: string
+ *   eolAdminMessage: string,
+ *   showProxyServerPrivacyDisclosure: boolean
  * }}
  */
 let ManagedDataResponse;
@@ -79,7 +80,6 @@
   USERNAME: 'username',
   EXTENSION: 'extension',
   ANDROID_APPLICATION: 'android application',
-  PROXY_SERVER: 'proxy server'
 };
 
 
diff --git a/chrome/browser/resources/management/management_ui.html b/chrome/browser/resources/management/management_ui.html
index 4d369f7..feee055 100644
--- a/chrome/browser/resources/management/management_ui.html
+++ b/chrome/browser/resources/management/management_ui.html
@@ -275,6 +275,10 @@
               if="[[showDeviceReportingInfo_(deviceReportingInfo_)]]">
             <section>
               <h2 class="cr-title-text">$i18n{deviceReporting}</h2>
+              <div class="subtitle"
+                hidden="[[!showProxyServerPrivacyDisclosure_]]">
+                $i18n{proxyServerPrivacyDisclosure}
+              </div>
               <div class="subtitle">
                 $i18n{deviceConfiguration}
               </div>
diff --git a/chrome/browser/resources/management/management_ui.js b/chrome/browser/resources/management/management_ui.js
index 8efffc5..050271b1 100644
--- a/chrome/browser/resources/management/management_ui.js
+++ b/chrome/browser/resources/management/management_ui.js
@@ -81,6 +81,9 @@
     /** @private */
     eolMessage_: String,
 
+    /** @private */
+    showProxyServerPrivacyDisclosure_: Boolean,
+
     // </if>
 
     /** @private */
@@ -267,8 +270,6 @@
         return 'cr:extension';
       case DeviceReportingType.ANDROID_APPLICATION:
         return 'management:play-store';
-      case DeviceReportingType.PROXY_SERVER:
-        return 'management:vpn-lock';
       default:
         return 'cr:computer';
     }
@@ -346,6 +347,8 @@
       this.customerLogo_ = data.customerLogo;
       this.managementOverview_ = data.overview;
       this.eolMessage_ = data.eolMessage;
+      this.showProxyServerPrivacyDisclosure_ =
+          data.showProxyServerPrivacyDisclosure;
       try {
         // Sanitizing the message could throw an error if it contains non
         // supported markup.
diff --git a/chrome/browser/resources/nearby_internals/contact_tab.html b/chrome/browser/resources/nearby_internals/contact_tab.html
index b6e4e99c..9f4ec6741 100644
--- a/chrome/browser/resources/nearby_internals/contact_tab.html
+++ b/chrome/browser/resources/nearby_internals/contact_tab.html
@@ -3,10 +3,6 @@
     --standard-border: 1px solid black;
   }
 
-  #contact-list:last-child {
-    border-bottom: var(--standard-border);
-  }
-
   #clearButton {
     float: right;
   }
@@ -22,10 +18,10 @@
     id="clearButton" disabled="[[!contactList_.length]]">
  Clear Messages
 </cr-button>
-<iron-list items="[[contactList_]]" as="contact" id="contact-list"
+<doom-repeat items="[[contactList_]]" as="contact" id="contact-list"
     hidden="[[!contactList_.length]]">
   <template>
     <contact-object item="[[contact]]">
     </contact-object>
   </template>
-</iron-list>
+</dom-repeat>
diff --git a/chrome/browser/resources/nearby_internals/http_message_object.html b/chrome/browser/resources/nearby_internals/http_message_object.html
index d636534..d8eee7c 100644
--- a/chrome/browser/resources/nearby_internals/http_message_object.html
+++ b/chrome/browser/resources/nearby_internals/http_message_object.html
@@ -1,10 +1,12 @@
-<style>
+<style include="shared-style">
   :host {
     --standard-border: 1px solid black;
   }
 
   #item {
-    border: var(--standard-border);
+    border-left: var(--standard-border);
+    border-right: var(--standard-border);
+    border-top: var(--standard-border);
   }
 
   #header {
diff --git a/chrome/browser/resources/nearby_internals/http_tab.html b/chrome/browser/resources/nearby_internals/http_tab.html
index 561db62..8f10ecf2 100644
--- a/chrome/browser/resources/nearby_internals/http_tab.html
+++ b/chrome/browser/resources/nearby_internals/http_tab.html
@@ -20,7 +20,6 @@
     id="clearButton" disabled="[[!httpMessageList_.length]]">
  Clear Messages
 </cr-button>
-
 <dom-repeat items="[[httpMessageList_]]" as="http-message"
     hidden="[[!httpMessageList_.length]]">
   <template>
diff --git a/chrome/browser/resources/nearby_internals/shared_style.html b/chrome/browser/resources/nearby_internals/shared_style.html
index 30ec9f47..cb589e3 100644
--- a/chrome/browser/resources/nearby_internals/shared_style.html
+++ b/chrome/browser/resources/nearby_internals/shared_style.html
@@ -2,12 +2,14 @@
   <style include="cr-shared-style">
     :host {
       cursor: default;
-      font-family: Roboto, Roboto, sans-serif;
+      font-family: monospace;
+      font-size: 12px;
     }
 
     .internals-button {
       background-color: rgb(0, 134, 179);
       color: white;
+      font-family: Roboto;
       margin: 5px;
     }
   </style>
diff --git a/chrome/browser/resources/print_preview/ui/destination_dialog.html b/chrome/browser/resources/print_preview/ui/destination_dialog.html
index adb8923..bc7f30d 100644
--- a/chrome/browser/resources/print_preview/ui/destination_dialog.html
+++ b/chrome/browser/resources/print_preview/ui/destination_dialog.html
@@ -91,6 +91,24 @@
     font-size: calc(10 / 13 * 1em);
     font-weight: 500;
   }
+
+  .destinations-status {
+    align-items: center;
+    display: flex;
+    margin-bottom: 16px;
+  }
+
+  iron-icon {
+    --google-orange-600: rgb(232, 113, 10);  /* e8710a */
+    fill: var(--google-orange-600);
+    flex-shrink: 0;
+    padding-inline-end: 8px;
+    padding-inline-start: 4px;
+  }
+
+  #warning-message {
+    color: var(--cr-primary-text-color);
+  }
 </style>
 <cr-dialog id="dialog" on-close="onCloseOrCancel_">
   <div slot="title" id="header">$i18n{destinationSearchTitle}</div>
@@ -105,6 +123,13 @@
         <option value="">$i18n{addAccountTitle}</option>
       </select>
     </div>
+    <div class="destinations-status" hidden$="[[!showCloudPrintWarning_]]">
+      <iron-icon icon="cr:warning" aria-label="$i18n{warningIconAriaLabel}">
+      </iron-icon>
+      <span id="warning-message">
+        $i18nRaw{cloudPrintingNotSupportedWarning}
+      </span>
+    </div>
     <print-preview-search-box id="searchBox"
         label="$i18n{searchBoxPlaceholder}" search-query="{{searchQuery_}}"
         autofocus>
diff --git a/chrome/browser/resources/print_preview/ui/destination_dialog.js b/chrome/browser/resources/print_preview/ui/destination_dialog.js
index 52810038..986d909 100644
--- a/chrome/browser/resources/print_preview/ui/destination_dialog.js
+++ b/chrome/browser/resources/print_preview/ui/destination_dialog.js
@@ -9,6 +9,8 @@
 import 'chrome://resources/js/action_link.js';
 import 'chrome://resources/cr_elements/action_link_css.m.js';
 import 'chrome://resources/cr_elements/md_select_css.m.js';
+import 'chrome://resources/cr_elements/icons.m.js';
+import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
 import '../print_preview_utils.js';
 import './destination_list.js';
 import './print_preview_search_box.js';
@@ -79,6 +81,13 @@
       value: false,
     },
 
+    /** @private {boolean} */
+    showCloudPrintWarning_: {
+      type: Boolean,
+      computed: 'computeShowCloudPrintWarning_(destinations_.splices)',
+      value: false,
+    },
+
     /** @private {?RegExp} */
     searchQuery_: {
       type: Object,
@@ -110,6 +119,18 @@
   },
 
   /**
+   * @return {boolean} Whether the destinations dialog should show a Cloud Print
+   *     deprecation warning.
+   * @private
+   */
+  computeShowCloudPrintWarning_() {
+    return this.destinations_.some(destination => {
+      return destination.shouldShowSaveToDriveWarning ||
+          destination.shouldShowDeprecatedPrinterWarning;
+    });
+  },
+
+  /**
    * @param {!KeyboardEvent} e Event containing the key
    * @private
    */
diff --git a/chrome/browser/resources/print_preview/ui/destination_select_cros.html b/chrome/browser/resources/print_preview/ui/destination_select_cros.html
index 257fe50..5d53269 100644
--- a/chrome/browser/resources/print_preview/ui/destination_select_cros.html
+++ b/chrome/browser/resources/print_preview/ui/destination_select_cros.html
@@ -1,11 +1,12 @@
 <style include="print-preview-shared throbber md-select destination-select cr-hidden-style">
-
-:host([is-current-destination-cros-local_]) #statusText {
-  color: var(--google-red-600);
-  font-size: calc(10 / 13 * 1em);
-  padding: 0;
-}
-
+  :host([is-current-destination-cros-local_]) #statusText {
+    color: var(--google-red-600);
+    font-size: calc(10 / 13 * 1em);
+    overflow: hidden;
+    padding: 0;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
 </style>
 <print-preview-settings-section>
   <span slot="title">$i18n{destinationLabel}</span>
@@ -60,8 +61,7 @@
   <div slot="title"></div>
   <div slot="controls">
     <div id="statusText" class="destination-status" title="[[statusText_]]"
-        aria-hidden="true">
-        [[statusText_]]
+        aria-hidden="[[isCurrentDestinationCrosLocal_]]">
     </div>
   </div>
 </print-preview-settings-section>
diff --git a/chrome/browser/resources/print_preview/ui/destination_select_cros.js b/chrome/browser/resources/print_preview/ui/destination_select_cros.js
index 5f131d9..0c0ccee 100644
--- a/chrome/browser/resources/print_preview/ui/destination_select_cros.js
+++ b/chrome/browser/resources/print_preview/ui/destination_select_cros.js
@@ -70,6 +70,7 @@
       type: String,
       computed:
           'computeStatusText_(destination, destination.printerStatusReason)',
+      observer: 'onStatusTextSet_',
     },
 
     /** @private {string} */
@@ -351,9 +352,18 @@
 
     // Cloudprint destinations contain their own status text.
     if (CloudOrigins.some(origin => origin === this.destination.origin)) {
-      return this.destination.shouldShowInvalidCertificateError ?
-          this.i18n('noLongerSupportedFragment') :
-          this.destination.connectionStatusText;
+      if (this.destination.shouldShowInvalidCertificateError) {
+        return this.i18n('noLongerSupportedFragment');
+      }
+      if (this.destination.connectionStatusText) {
+        return this.destination.connectionStatusText;
+      }
+    }
+
+    if (this.destination.origin !== DestinationOrigin.CROS) {
+      return this.destination.shouldShowDeprecatedPrinterWarning ?
+          this.i18nAdvanced('printerNotSupportedWarning') :
+          '';
     }
 
     // Only when the flag is enabled do we need to fetch a local printer status
@@ -372,6 +382,11 @@
     return this.getErrorString_(printerStatusReason);
   },
 
+  /** @private */
+  onStatusTextSet_() {
+    this.$$('#statusText').innerHTML = this.statusText_;
+  },
+
   /**
    * @param {!PrinterStatusReason} printerStatusReason
    * @return {!string}
diff --git a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_subpage.html b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_subpage.html
index 3b5bef5..242c3d7 100644
--- a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_subpage.html
+++ b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_subpage.html
@@ -112,18 +112,16 @@
         </settings-crostini-disk-resize-confirmation-dialog>
       </template>
     </template>
-    <template is="dom-if" if="[[showCrostiniMic_]]">
-      <div class="settings-box">
-        <div id="crostini-mic-sharing-label" class="start" aria-hidden="true">
-          $i18n{crostiniMicTitle}
-        </div>
-        <cr-toggle id="crostini-mic-sharing-toggle"
-            checked="[[crostiniMicSharingEnabled_]]"
-            on-change="onMicSharingChange_"
-            aria-label="$i18n{crostiniMicTitle}">
-        </cr-toggle>
+    <div class="settings-box">
+      <div id="crostini-mic-sharing-label" class="start" aria-hidden="true">
+        $i18n{crostiniMicTitle}
       </div>
-    </template>
+      <cr-toggle id="crostini-mic-sharing-toggle"
+          checked="[[crostiniMicSharingEnabled_]]"
+          on-change="onMicSharingChange_"
+          aria-describedby="crostini-mic-sharing-label">
+      </cr-toggle>
+    </div>
     <template is="dom-if" if="[[!hideCrostiniUninstall_]]">
       <div id="remove" class="settings-box">
         <div id="removeCrostiniLabel" class="start" aria-hidden="true">
diff --git a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_subpage.js b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_subpage.js
index ae4cf07c..70581a98 100644
--- a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_subpage.js
+++ b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_subpage.js
@@ -105,17 +105,6 @@
       value: false,
     },
 
-    /**
-     * Whether the toggle to share the mic with Crostini should be shown.
-     * @private {boolean}
-     */
-    showCrostiniMic_: {
-      type: Boolean,
-      value() {
-        return loadTimeData.getBoolean('showCrostiniMic');
-      },
-    },
-
     /*
      * Whether the installer is showing.
      * @private {boolean}
diff --git a/chrome/browser/resources/signin/signin_reauth/BUILD.gn b/chrome/browser/resources/signin/signin_reauth/BUILD.gn
index 5b8112b..46d31bd 100644
--- a/chrome/browser/resources/signin/signin_reauth/BUILD.gn
+++ b/chrome/browser/resources/signin/signin_reauth/BUILD.gn
@@ -17,7 +17,6 @@
   deps = [
     ":signin_reauth_browser_proxy",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-    "//ui/webui/resources/js:i18n_behavior.m",
     "//ui/webui/resources/js:load_time_data.m",
     "//ui/webui/resources/js:web_ui_listener_behavior.m",
   ]
diff --git a/chrome/browser/resources/signin/signin_reauth/signin_reauth_app.html b/chrome/browser/resources/signin/signin_reauth/signin_reauth_app.html
index 7782702..c6e739c6 100644
--- a/chrome/browser/resources/signin/signin_reauth/signin_reauth_app.html
+++ b/chrome/browser/resources/signin/signin_reauth/signin_reauth_app.html
@@ -86,7 +86,7 @@
   </paper-spinner-lite>
   <cr-button id="confirmButton" class="action-button" on-click="onConfirm_"
       hidden="[[confirmButtonHidden_]]" consent-confirmation>
-    [[confirmButtonLabel_]]
+    $i18n{signinReauthConfirmLabel}
   </cr-button>
   <cr-button id="cancelButton" on-click="onCancel_"
       hidden="[[cancelButtonHidden_]]">
diff --git a/chrome/browser/resources/signin/signin_reauth/signin_reauth_app.js b/chrome/browser/resources/signin/signin_reauth/signin_reauth_app.js
index 6ce8e1f..f5f10a6 100644
--- a/chrome/browser/resources/signin/signin_reauth/signin_reauth_app.js
+++ b/chrome/browser/resources/signin/signin_reauth/signin_reauth_app.js
@@ -9,7 +9,6 @@
 import './signin_shared_css.js';
 
 import {assert, assertNotReached} from 'chrome://resources/js/assert.m.js';
-import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {WebUIListenerBehavior} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
 import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
@@ -21,7 +20,7 @@
 
   _template: html`{__html_template__}`,
 
-  behaviors: [I18nBehavior, WebUIListenerBehavior],
+  behaviors: [WebUIListenerBehavior],
 
   properties: {
     /** @private */
@@ -33,9 +32,6 @@
     },
 
     /** @private */
-    confirmButtonLabel_: String,
-
-    /** @private */
     confirmButtonHidden_: {type: Boolean, value: true},
 
     /** @private */
@@ -76,10 +72,7 @@
   onReauthTypeReceived_(requiresReauth) {
     this.confirmButtonHidden_ = false;
     this.$.confirmButton.focus();
-    this.cancelButtonHidden_ = requiresReauth;
-    this.confirmButtonLabel_ = requiresReauth ?
-        this.i18n('signinReauthNextLabel') :
-        this.i18n('signinReauthConfirmLabel');
+    this.cancelButtonHidden_ = false;
   },
 
   /** @return {!Array<string>} Text of the consent description elements. */
diff --git a/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckCoordinator.java b/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckCoordinator.java
index bde4849..517c6c2 100644
--- a/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckCoordinator.java
+++ b/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckCoordinator.java
@@ -9,6 +9,8 @@
 import androidx.lifecycle.LifecycleOwner;
 import androidx.lifecycle.Observer;
 
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.browser.password_manager.PasswordScriptsFetcherBridge;
 import org.chromium.chrome.browser.settings.SettingsLauncher;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
@@ -32,6 +34,10 @@
     public static void create(SafetyCheckSettingsFragment settingsFragment,
             SafetyCheckUpdatesDelegate updatesClient, SettingsLauncher settingsLauncher) {
         new SafetyCheckCoordinator(settingsFragment, updatesClient, settingsLauncher);
+        if (ChromeFeatureList.isEnabled(ChromeFeatureList.PASSWORD_CHANGE_IN_SETTINGS)) {
+            // Triggers pre-fetching the list of password change scripts.
+            PasswordScriptsFetcherBridge.prewarmCache();
+        }
     }
 
     private SafetyCheckCoordinator(SafetyCheckSettingsFragment settingsFragment,
diff --git a/chrome/browser/sharesheet/share_action.h b/chrome/browser/sharesheet/share_action.h
index 1e6a34aa..37119d41 100644
--- a/chrome/browser/sharesheet/share_action.h
+++ b/chrome/browser/sharesheet/share_action.h
@@ -20,6 +20,7 @@
 
   virtual const base::string16 GetActionName() = 0;
 
+  // Icon DIP (Density Independent Pixel) size must be 40 x 40.
   virtual const gfx::ImageSkia GetActionIcon() = 0;
 
   // LaunchAction should synchronously create all UI needed and fill
diff --git a/chrome/browser/sharesheet/sharesheet_action_cache.cc b/chrome/browser/sharesheet/sharesheet_action_cache.cc
index 906757b..a0f0c04 100644
--- a/chrome/browser/sharesheet/sharesheet_action_cache.cc
+++ b/chrome/browser/sharesheet/sharesheet_action_cache.cc
@@ -5,10 +5,12 @@
 #include "chrome/browser/sharesheet/sharesheet_action_cache.h"
 
 #include "chrome/browser/sharesheet/share_action.h"
+#include "chrome/browser/sharesheet/sharesheet_types.h"
 
 namespace sharesheet {
 
 SharesheetActionCache::SharesheetActionCache() = default;
+// ShareActions will be initialised here by calling AddShareAction.
 
 SharesheetActionCache::~SharesheetActionCache() = default;
 
@@ -30,4 +32,10 @@
   return nullptr;
 }
 
+void SharesheetActionCache::AddShareAction(
+    std::unique_ptr<ShareAction> action) {
+  DCHECK_EQ(action->GetActionIcon().size(), gfx::Size(kIconSize, kIconSize));
+  share_actions_.push_back(std::move(action));
+}
+
 }  // namespace sharesheet
diff --git a/chrome/browser/sharesheet/sharesheet_action_cache.h b/chrome/browser/sharesheet/sharesheet_action_cache.h
index 1e41194..40fea14 100644
--- a/chrome/browser/sharesheet/sharesheet_action_cache.h
+++ b/chrome/browser/sharesheet/sharesheet_action_cache.h
@@ -29,6 +29,8 @@
   const std::vector<std::unique_ptr<ShareAction>>& GetShareActions();
 
  private:
+  void AddShareAction(std::unique_ptr<ShareAction> action);
+
   std::vector<std::unique_ptr<ShareAction>> share_actions_;
 };
 
diff --git a/chrome/browser/sharesheet/sharesheet_controller.h b/chrome/browser/sharesheet/sharesheet_controller.h
index 239ccbe..993d517 100644
--- a/chrome/browser/sharesheet/sharesheet_controller.h
+++ b/chrome/browser/sharesheet/sharesheet_controller.h
@@ -17,6 +17,10 @@
   // different invocations of the sharesheet.
   virtual uint32_t GetId() = 0;
 
+  // When called will set the bubble size to |width| and |height|.
+  // |width| and |height| must be set to a positive int.
+  virtual void SetSharesheetSize(const int& width, const int& height) = 0;
+
   // Called by ShareAction to notify SharesheetBubbleView that ShareAction
   // has completed.
   virtual void CloseSharesheet() = 0;
diff --git a/chrome/browser/sharesheet/sharesheet_service.cc b/chrome/browser/sharesheet/sharesheet_service.cc
index 9d885c3..0f79520 100644
--- a/chrome/browser/sharesheet/sharesheet_service.cc
+++ b/chrome/browser/sharesheet/sharesheet_service.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "base/optional.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
@@ -20,11 +21,6 @@
 #include "ui/display/types/display_constants.h"
 #include "ui/views/view.h"
 
-namespace {
-// In px.
-constexpr int kIconSize = 40;
-}  // namespace
-
 namespace sharesheet {
 
 SharesheetService::SharesheetService(Profile* profile)
@@ -118,24 +114,24 @@
 
 bool SharesheetService::HasShareTargets(const apps::mojom::IntentPtr& intent) {
   auto& actions = sharesheet_action_cache_->GetShareActions();
-  std::vector<apps::AppIdAndActivityName> app_id_and_activities =
+  std::vector<apps::IntentLaunchInfo> intent_launch_info =
       app_service_proxy_->GetAppsForIntent(intent);
 
-  return !actions.empty() || !app_id_and_activities.empty();
+  return !actions.empty() || !intent_launch_info.empty();
 }
 
 void SharesheetService::LoadAppIcons(
-    std::vector<apps::AppIdAndActivityName> app_id_and_activities,
+    std::vector<apps::IntentLaunchInfo> intent_launch_info,
     std::vector<TargetInfo> targets,
     size_t index,
     base::OnceCallback<void(std::vector<TargetInfo> targets)> callback) {
-  if (index >= app_id_and_activities.size()) {
+  if (index >= intent_launch_info.size()) {
     std::move(callback).Run(std::move(targets));
     return;
   }
 
-  // Making a copy because we move |app_id_and_activities| out below.
-  auto app_id = app_id_and_activities[index].app_id;
+  // Making a copy because we move |intent_launch_info| out below.
+  auto app_id = intent_launch_info[index].app_id;
   auto app_type = app_service_proxy_->AppRegistryCache().GetAppType(app_id);
   auto icon_type =
       (base::FeatureList::IsEnabled(features::kAppServiceAdaptiveIcon))
@@ -145,23 +141,23 @@
   app_service_proxy_->LoadIcon(
       app_type, app_id, icon_type, kIconSize, allow_placeholder_icon,
       base::BindOnce(&SharesheetService::OnIconLoaded,
-                     weak_factory_.GetWeakPtr(),
-                     std::move(app_id_and_activities), std::move(targets),
-                     index, std::move(callback)));
+                     weak_factory_.GetWeakPtr(), std::move(intent_launch_info),
+                     std::move(targets), index, std::move(callback)));
 }
 
 void SharesheetService::OnIconLoaded(
-    std::vector<apps::AppIdAndActivityName> app_id_and_activities,
+    std::vector<apps::IntentLaunchInfo> intent_launch_info,
     std::vector<TargetInfo> targets,
     size_t index,
     base::OnceCallback<void(std::vector<TargetInfo> targets)> callback,
     apps::mojom::IconValuePtr icon_value) {
-  const auto& app_id_and_activity = app_id_and_activities[index];
+  const auto& launch_entry = intent_launch_info[index];
   targets.emplace_back(TargetType::kApp, icon_value->uncompressed,
-                       base::UTF8ToUTF16(app_id_and_activity.app_id),
-                       base::UTF8ToUTF16(app_id_and_activity.activity_name));
+                       base::UTF8ToUTF16(launch_entry.app_id),
+                       base::UTF8ToUTF16(launch_entry.activity_label),
+                       launch_entry.activity_name);
 
-  LoadAppIcons(std::move(app_id_and_activities), std::move(targets), index + 1,
+  LoadAppIcons(std::move(intent_launch_info), std::move(targets), index + 1,
                std::move(callback));
 }
 
@@ -181,13 +177,14 @@
   auto iter = actions.begin();
   while (iter != actions.end()) {
     targets.emplace_back(TargetType::kAction, (*iter)->GetActionIcon(),
-                         (*iter)->GetActionName(), (*iter)->GetActionName());
+                         (*iter)->GetActionName(), (*iter)->GetActionName(),
+                         base::nullopt);
     ++iter;
   }
 
-  std::vector<apps::AppIdAndActivityName> app_id_and_activities =
+  std::vector<apps::IntentLaunchInfo> intent_launch_info =
       app_service_proxy_->GetAppsForIntent(intent);
-  LoadAppIcons(std::move(app_id_and_activities), std::move(targets), 0,
+  LoadAppIcons(std::move(intent_launch_info), std::move(targets), 0,
                base::BindOnce(&SharesheetService::OnAppIconsLoaded,
                               weak_factory_.GetWeakPtr(), std::move(delegate),
                               std::move(intent)));
diff --git a/chrome/browser/sharesheet/sharesheet_service.h b/chrome/browser/sharesheet/sharesheet_service.h
index d4abbba..60bcec21 100644
--- a/chrome/browser/sharesheet/sharesheet_service.h
+++ b/chrome/browser/sharesheet/sharesheet_service.h
@@ -19,7 +19,7 @@
 class Profile;
 
 namespace apps {
-struct AppIdAndActivityName;
+struct IntentLaunchInfo;
 class AppServiceProxy;
 }
 
@@ -65,18 +65,16 @@
   using SharesheetServiceIconLoaderCallback =
       base::OnceCallback<void(std::vector<TargetInfo> targets)>;
 
-  void LoadAppIcons(
-      std::vector<apps::AppIdAndActivityName> app_id_and_activities,
-      std::vector<TargetInfo> targets,
-      size_t index,
-      SharesheetServiceIconLoaderCallback callback);
+  void LoadAppIcons(std::vector<apps::IntentLaunchInfo> intent_launch_info,
+                    std::vector<TargetInfo> targets,
+                    size_t index,
+                    SharesheetServiceIconLoaderCallback callback);
 
-  void OnIconLoaded(
-      std::vector<apps::AppIdAndActivityName> app_id_and_activities,
-      std::vector<TargetInfo> targets,
-      size_t index,
-      SharesheetServiceIconLoaderCallback callback,
-      apps::mojom::IconValuePtr icon_value);
+  void OnIconLoaded(std::vector<apps::IntentLaunchInfo> intent_launch_info,
+                    std::vector<TargetInfo> targets,
+                    size_t index,
+                    SharesheetServiceIconLoaderCallback callback,
+                    apps::mojom::IconValuePtr icon_value);
 
   void OnAppIconsLoaded(std::unique_ptr<SharesheetServiceDelegate> delegate,
                         apps::mojom::IntentPtr intent,
diff --git a/chrome/browser/sharesheet/sharesheet_service_delegate.cc b/chrome/browser/sharesheet/sharesheet_service_delegate.cc
index f0dc40e..b54dbe7 100644
--- a/chrome/browser/sharesheet/sharesheet_service_delegate.cc
+++ b/chrome/browser/sharesheet/sharesheet_service_delegate.cc
@@ -63,6 +63,13 @@
   return id_;
 }
 
+void SharesheetServiceDelegate::SetSharesheetSize(const int& width,
+                                                  const int& height) {
+  DCHECK_GT(width, 0);
+  DCHECK_GT(height, 0);
+  sharesheet_bubble_view_->ResizeBubble(width, height);
+}
+
 void SharesheetServiceDelegate::CloseSharesheet() {
   sharesheet_bubble_view_->CloseBubble();
 }
diff --git a/chrome/browser/sharesheet/sharesheet_service_delegate.h b/chrome/browser/sharesheet/sharesheet_service_delegate.h
index fc17172..37bb1a8 100644
--- a/chrome/browser/sharesheet/sharesheet_service_delegate.h
+++ b/chrome/browser/sharesheet/sharesheet_service_delegate.h
@@ -52,6 +52,7 @@
 
   // SharesheetController overrides
   uint32_t GetId() override;
+  void SetSharesheetSize(const int& width, const int& height) override;
   void CloseSharesheet() override;
 
  private:
diff --git a/chrome/browser/sharesheet/sharesheet_types.cc b/chrome/browser/sharesheet/sharesheet_types.cc
index c728886..1d1399a 100644
--- a/chrome/browser/sharesheet/sharesheet_types.cc
+++ b/chrome/browser/sharesheet/sharesheet_types.cc
@@ -9,11 +9,15 @@
 TargetInfo::TargetInfo(TargetType type,
                        const gfx::ImageSkia& icon,
                        const base::string16& launch_name,
-                       const base::string16& display_name)
+                       const base::string16& display_name,
+                       const base::Optional<std::string>& activity_name)
     : type(type),
       icon(icon),
       launch_name(launch_name),
-      display_name(display_name) {}
+      display_name(display_name),
+      activity_name(activity_name) {}
+
+TargetInfo::~TargetInfo() = default;
 
 TargetInfo::TargetInfo(TargetInfo&& other) = default;
 
diff --git a/chrome/browser/sharesheet/sharesheet_types.h b/chrome/browser/sharesheet/sharesheet_types.h
index 79f11bbb..133bacd3 100644
--- a/chrome/browser/sharesheet/sharesheet_types.h
+++ b/chrome/browser/sharesheet/sharesheet_types.h
@@ -5,11 +5,15 @@
 #ifndef CHROME_BROWSER_SHARESHEET_SHARESHEET_TYPES_H_
 #define CHROME_BROWSER_SHARESHEET_SHARESHEET_TYPES_H_
 
+#include "base/optional.h"
 #include "base/strings/string16.h"
 #include "ui/gfx/image/image_skia.h"
 
 namespace sharesheet {
 
+// In DIP (Density Independent Pixel).
+constexpr int kIconSize = 40;
+
 // The type of a target.
 enum class TargetType {
   kUnknown = 0,
@@ -21,7 +25,10 @@
   TargetInfo(TargetType type,
              const gfx::ImageSkia& icon,
              const base::string16& launch_name,
-             const base::string16& display_name);
+             const base::string16& display_name,
+             const base::Optional<std::string>& activity_name);
+  ~TargetInfo();
+
   // Allow move.
   TargetInfo(TargetInfo&& other);
   TargetInfo& operator=(TargetInfo&& other);
@@ -34,6 +41,7 @@
   TargetType type;
 
   // The icon to be displayed for this target in the sharesheet bubble.
+  // DIP size must be kIconSize
   gfx::ImageSkia icon;
 
   // The string used to launch this target. Represents an Android package name
@@ -43,6 +51,10 @@
   // The string shown to the user to identify this target in the sharesheet
   // bubble.
   base::string16 display_name;
+
+  // The activity of the app for the target. This only applies when the app type
+  // is kArc.
+  base::Optional<std::string> activity_name;
 };
 
 }  // namespace sharesheet
diff --git a/chrome/browser/spellchecker/spell_check_host_chrome_impl.cc b/chrome/browser/spellchecker/spell_check_host_chrome_impl.cc
index 1415d9b..36a519f 100644
--- a/chrome/browser/spellchecker/spell_check_host_chrome_impl.cc
+++ b/chrome/browser/spellchecker/spell_check_host_chrome_impl.cc
@@ -243,6 +243,13 @@
   DCHECK(dictionaries_loaded_callback_);
   SpellcheckService* spellcheck = GetSpellcheckService();
 
+  if (!spellcheck) {  // Teardown.
+    std::move(dictionaries_loaded_callback_)
+        .Run(/*dictionaries=*/{}, /*custom_words=*/{},
+             /*enable=*/false);
+    return;
+  }
+
   const bool enable = spellcheck->IsSpellcheckEnabled();
 
   std::vector<spellcheck::mojom::SpellCheckBDictLanguagePtr> dictionaries;
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 8ed9ff0..854b05c 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -742,7 +742,6 @@
       "android/passwords/password_generation_dialog_view_android.h",
       "android/passwords/password_generation_editing_popup_view_android.cc",
       "android/passwords/password_generation_editing_popup_view_android.h",
-      "android/passwords/password_scripts_fetcher_android.cc",
       "android/safe_browsing/password_reuse_dialog_view_android.cc",
       "android/safe_browsing/password_reuse_dialog_view_android.h",
       "android/simple_message_box_android.cc",
@@ -3115,6 +3114,7 @@
       "//ui/base:wm_role_names",
       "//ui/base/ime",
       "//ui/events:dom_keycode_converter",
+      "//ui/platform_window",
     ]
 
     if (use_dbus) {
@@ -3164,14 +3164,12 @@
         ]
       }
       sources += [ "views/frame/native_browser_frame_factory_ozone.cc" ]
-      deps += [ "//ui/platform_window" ]
     }
     if (is_desktop_linux) {
       sources += [
         "views/frame/browser_desktop_window_tree_host_linux.cc",
         "views/frame/browser_desktop_window_tree_host_linux.h",
       ]
-      deps += [ "//ui/platform_window/extensions" ]
     }
     if (use_gtk) {
       # This is the only component that can interact with gtk.
diff --git a/chrome/browser/ui/android/passwords/password_scripts_fetcher_android.cc b/chrome/browser/ui/android/passwords/password_scripts_fetcher_android.cc
deleted file mode 100644
index e081d4f..0000000
--- a/chrome/browser/ui/android/passwords/password_scripts_fetcher_android.cc
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include <jni.h>
-
-#include "base/android/scoped_java_ref.h"
-#include "chrome/android/chrome_jni_headers/PasswordScriptsFetcherBridge_jni.h"
-#include "chrome/browser/password_manager/password_scripts_fetcher_factory.h"
-#include "components/embedder_support/android/browser_context/browser_context_handle.h"
-#include "components/password_manager/core/browser/password_scripts_fetcher.h"
-#include "content/public/browser/browser_context.h"
-
-namespace password_manager {
-
-// static
-void JNI_PasswordScriptsFetcherBridge_PrewarmCache(
-    JNIEnv* env,
-    const base::android::JavaParamRef<jobject>& jbrowser_context) {
-  content::BrowserContext* context =
-      browser_context::BrowserContextFromJavaHandle(jbrowser_context);
-
-  DCHECK(context);
-  PasswordScriptsFetcherFactory::GetInstance()
-      ->GetForBrowserContext(context)
-      ->PrewarmCache();
-}
-
-}  // namespace password_manager
diff --git a/chrome/browser/ui/ash/ambient/ambient_client_impl.cc b/chrome/browser/ui/ash/ambient/ambient_client_impl.cc
index 096619e1..770f3086 100644
--- a/chrome/browser/ui/ash/ambient/ambient_client_impl.cc
+++ b/chrome/browser/ui/ash/ambient/ambient_client_impl.cc
@@ -13,7 +13,6 @@
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
-#include "chrome/common/channel_info.h"
 #include "chromeos/constants/chromeos_features.h"
 #include "components/account_id/account_id.h"
 #include "components/prefs/pref_service.h"
@@ -24,7 +23,6 @@
 #include "components/signin/public/identity_manager/scope_set.h"
 #include "components/user_manager/user.h"
 #include "components/user_manager/user_manager.h"
-#include "components/version_info/channel.h"
 #include "content/public/browser/device_service.h"
 #include "google_apis/gaia/gaia_auth_util.h"
 #include "google_apis/gaia/google_service_auth_error.h"
@@ -166,12 +164,3 @@
                             /*expiration_time=*/base::Time::Now());
   }
 }
-
-bool AmbientClientImpl::ShouldUseProdServer() {
-  if (chromeos::features::IsAmbientModeDevUseProdEnabled())
-    return true;
-
-  auto channel = chrome::GetChannel();
-  return channel == version_info::Channel::STABLE ||
-         channel == version_info::Channel::BETA;
-}
diff --git a/chrome/browser/ui/ash/ambient/ambient_client_impl.h b/chrome/browser/ui/ash/ambient/ambient_client_impl.h
index ad4bd74..f73956e 100644
--- a/chrome/browser/ui/ash/ambient/ambient_client_impl.h
+++ b/chrome/browser/ui/ash/ambient/ambient_client_impl.h
@@ -30,7 +30,6 @@
   scoped_refptr<network::SharedURLLoaderFactory> GetURLLoaderFactory() override;
   void RequestWakeLockProvider(
       mojo::PendingReceiver<device::mojom::WakeLockProvider> receiver) override;
-  bool ShouldUseProdServer() override;
 
  private:
   void GetAccessToken(GetAccessTokenCallback callback,
diff --git a/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.cc b/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.cc
index fad9441..982b637 100644
--- a/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.cc
+++ b/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.cc
@@ -119,10 +119,6 @@
   cast_config_controller_media_router_ =
       std::make_unique<CastConfigControllerMediaRouter>();
 
-  // Needed by AmbientController in ash.
-  if (chromeos::features::IsAmbientModeEnabled())
-    ambient_client_ = std::make_unique<AmbientClientImpl>();
-
   ash_shell_init_ = std::make_unique<AshShellInit>();
 
   screen_orientation_delegate_ =
@@ -211,6 +207,9 @@
 
 void ChromeBrowserMainExtraPartsAsh::PostBrowserStart() {
   mobile_data_notifications_ = std::make_unique<MobileDataNotifications>();
+
+  if (chromeos::features::IsAmbientModeEnabled())
+    ambient_client_ = std::make_unique<AmbientClientImpl>();
 }
 
 void ChromeBrowserMainExtraPartsAsh::PostMainMessageLoopRun() {
@@ -220,6 +219,9 @@
   exo_parts_.reset();
 #endif
 
+  if (chromeos::features::IsAmbientModeEnabled())
+    ambient_client_.reset();
+
   night_light_client_.reset();
   mobile_data_notifications_.reset();
   chrome_launcher_controller_initializer_.reset();
@@ -244,8 +246,6 @@
   // needs to be released before destroying the profile.
   app_list_client_.reset();
   ash_shell_init_.reset();
-  ambient_client_.reset();
-
   cast_config_controller_media_router_.reset();
   if (chromeos::NetworkConnect::IsInitialized())
     chromeos::NetworkConnect::Shutdown();
diff --git a/chrome/browser/ui/chrome_pages.cc b/chrome/browser/ui/chrome_pages.cc
index 4b0fa08b..0c581c91 100644
--- a/chrome/browser/ui/chrome_pages.cc
+++ b/chrome/browser/ui/chrome_pages.cc
@@ -151,8 +151,21 @@
   ShowSingletonTab(displayer->browser(), url);
 }
 
-void LaunchReleaseNotesImpl(Profile* profile) {
+void LaunchReleaseNotesImpl(Profile* profile,
+                            apps::mojom::LaunchSource source) {
   base::RecordAction(UserMetricsAction("ReleaseNotes.ShowReleaseNotes"));
+  // If the flag is enabled, launch the Help app and show the release notes.
+  if (base::FeatureList::IsEnabled(chromeos::features::kHelpAppReleaseNotes)) {
+    // Note that AppServiceProxy is null for off-the-record profiles. For more
+    // context, see https://crbug.com/1112197.
+    apps::AppServiceProxy* proxy = apps::AppServiceProxyFactory::GetForProfile(
+        profile->GetOriginalProfile());
+    proxy->LaunchAppWithUrl(
+        chromeos::default_web_apps::kHelpAppId, ui::EventFlags::EF_NONE,
+        GURL("chrome://help-app/updates"), source, display::kDefaultDisplayId);
+    return;
+  }
+
   auto* provider = web_app::WebAppProviderBase::GetProviderBase(profile);
   if (provider && provider->registrar().IsInstalled(
                       chromeos::default_web_apps::kReleaseNotesAppId)) {
@@ -341,9 +354,9 @@
   ShowHelpImpl(NULL, profile, source);
 }
 
-void LaunchReleaseNotes(Profile* profile) {
+void LaunchReleaseNotes(Profile* profile, apps::mojom::LaunchSource source) {
 #if defined(OS_CHROMEOS) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
-  LaunchReleaseNotesImpl(profile);
+  LaunchReleaseNotesImpl(profile, source);
 #endif
 }
 
diff --git a/chrome/browser/ui/chrome_pages.h b/chrome/browser/ui/chrome_pages.h
index 53b84e26..8fc4560a 100644
--- a/chrome/browser/ui/chrome_pages.h
+++ b/chrome/browser/ui/chrome_pages.h
@@ -11,6 +11,7 @@
 
 #include "build/build_config.h"
 #include "components/content_settings/core/common/content_settings_types.h"
+#include "components/services/app_service/public/mojom/types.mojom.h"
 #include "url/gurl.h"
 
 #if !defined(OS_ANDROID)
@@ -96,7 +97,7 @@
 
 void ShowHelp(Browser* browser, HelpSource source);
 void ShowHelpForProfile(Profile* profile, HelpSource source);
-void LaunchReleaseNotes(Profile* profile);
+void LaunchReleaseNotes(Profile* profile, apps::mojom::LaunchSource source);
 void ShowBetaForum(Browser* browser);
 void ShowPolicy(Browser* browser);
 void ShowSlow(Browser* browser);
diff --git a/chrome/browser/ui/views/passwords/move_to_account_store_bubble_view.cc b/chrome/browser/ui/views/passwords/move_to_account_store_bubble_view.cc
index ced8dd1..4464f58a 100644
--- a/chrome/browser/ui/views/passwords/move_to_account_store_bubble_view.cc
+++ b/chrome/browser/ui/views/passwords/move_to_account_store_bubble_view.cc
@@ -242,10 +242,12 @@
       /*from_view=*/std::move(computer_view),
       /*to_view=*/std::move(avatar_view)));
 
-  SetButtonLabel(ui::DIALOG_BUTTON_OK,
-                 l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_MOVE_BUTTON));
+  SetButtonLabel(
+      ui::DIALOG_BUTTON_OK,
+      l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_MOVE_BUBBLE_OK_BUTTON));
   SetButtonLabel(ui::DIALOG_BUTTON_CANCEL,
-                 l10n_util::GetStringUTF16(IDS_DECLINE_RECOVERY));
+                 l10n_util::GetStringUTF16(
+                     IDS_PASSWORD_MANAGER_MOVE_BUBBLE_CANCEL_BUTTON));
   SetAcceptCallback(
       base::BindOnce(&MoveToAccountStoreBubbleController::AcceptMove,
                      base::Unretained(&controller_)));
diff --git a/chrome/browser/ui/views/passwords/move_to_account_store_bubble_view_unittest.cc b/chrome/browser/ui/views/passwords/move_to_account_store_bubble_view_unittest.cc
index 94577a2..43805e7 100644
--- a/chrome/browser/ui/views/passwords/move_to_account_store_bubble_view_unittest.cc
+++ b/chrome/browser/ui/views/passwords/move_to_account_store_bubble_view_unittest.cc
@@ -70,10 +70,12 @@
   CreateViewAndShow();
   ASSERT_TRUE(view_->GetOkButton());
   ASSERT_TRUE(view_->GetCancelButton());
-  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_MOVE_BUTTON),
-            view_->GetDialogButtonLabel(ui::DIALOG_BUTTON_OK));
-  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_DECLINE_RECOVERY),
-            view_->GetDialogButtonLabel(ui::DIALOG_BUTTON_CANCEL));
+  EXPECT_EQ(
+      l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_MOVE_BUBBLE_OK_BUTTON),
+      view_->GetDialogButtonLabel(ui::DIALOG_BUTTON_OK));
+  EXPECT_EQ(
+      l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_MOVE_BUBBLE_CANCEL_BUTTON),
+      view_->GetDialogButtonLabel(ui::DIALOG_BUTTON_CANCEL));
 }
 
 // Flaky on Windows due to http://crbug.com/968222
diff --git a/chrome/browser/ui/views/profiles/profile_menu_view_browsertest.cc b/chrome/browser/ui/views/profiles/profile_menu_view_browsertest.cc
index 6894969..562e60e10 100644
--- a/chrome/browser/ui/views/profiles/profile_menu_view_browsertest.cc
+++ b/chrome/browser/ui/views/profiles/profile_menu_view_browsertest.cc
@@ -672,8 +672,15 @@
     // there are no other buttons at the end.
     ProfileMenuViewBase::ActionableItem::kPasswordsButton};
 
+#if defined(OS_WIN)
+// TODO(crbug.com/1021930): Failure on Windows
+#define MAYBE_ProfileMenuClickTest_SyncError \
+  DISABLED_ProfileMenuClickTest_SyncError
+#else
+#define MAYBE_ProfileMenuClickTest_SyncError ProfileMenuClickTest_SyncError
+#endif
 PROFILE_MENU_CLICK_TEST(kActionableItems_SyncError,
-                        ProfileMenuClickTest_SyncError) {
+                        MAYBE_ProfileMenuClickTest_SyncError) {
   ASSERT_TRUE(sync_harness()->SignInPrimaryAccount());
   // Check that the setup was successful.
   ASSERT_TRUE(identity_manager()->HasPrimaryAccount());
diff --git a/chrome/browser/ui/views/sharesheet_bubble_view.cc b/chrome/browser/ui/views/sharesheet_bubble_view.cc
index 12ebb1d..fde41dbc 100644
--- a/chrome/browser/ui/views/sharesheet_bubble_view.cc
+++ b/chrome/browser/ui/views/sharesheet_bubble_view.cc
@@ -48,7 +48,7 @@
 constexpr int kMaxTargetsPerRow = 4;
 // TargetViewHeight is 2*kButtonHeight + kButtonPadding
 constexpr int kTargetViewHeight = 216;
-constexpr int kBubbleWidth = 416;
+constexpr int kDefaultBubbleWidth = 416;
 constexpr int kShortSpacing = 20;
 constexpr int kSpacing = 24;
 constexpr int kTitleLineHeight = 24;
@@ -129,18 +129,8 @@
           ->GetCurrentAppWindowForApp(extension_misc::kFilesManagerAppId)
           ->GetNativeWindow();
   set_parent_window(parent);
-  View* parent_view =
-      views::Widget::GetWidgetForNativeWindow(parent)->GetRootView();
-
-  // Horizontally centered
-  int x_within_parent_view = parent_view->GetMirroredXInView(
-      (parent_view->bounds().width() - kBubbleWidth) / 2);
-  // Get position in screen, taking parent view origin into account. This is
-  // 0,0 in fullscreen on the primary display, but not on secondary displays, or
-  // in Hosted App windows.
-  gfx::Point origin = parent_view->GetBoundsInScreen().origin();
-  origin += gfx::Vector2d(x_within_parent_view, kBubbleTopPaddingFromWindow);
-  SetAnchorRect(gfx::Rect(origin, gfx::Size()));
+  parent_view_ = views::Widget::GetWidgetForNativeWindow(parent)->GetRootView();
+  UpdateAnchorPosition();
 
   CreateBubble();
 }
@@ -213,6 +203,9 @@
   views::Widget* widget = views::BubbleDialogDelegateView::CreateBubble(this);
   GetWidget()->GetRootView()->Layout();
   widget->Show();
+
+  SetToDefaultBubbleSizing();
+  UpdateAnchorPosition();
 }
 
 void SharesheetBubbleView::ShowActionView() {
@@ -220,9 +213,20 @@
   share_action_view_->SetVisible(true);
 }
 
+void SharesheetBubbleView::ResizeBubble(const int& width, const int& height) {
+  width_ = width;
+  height_ = height;
+  UpdateAnchorPosition();
+}
+
 void SharesheetBubbleView::CloseBubble() {
   views::Widget* widget = View::GetWidget();
   widget->CloseWithReason(views::Widget::ClosedReason::kAcceptButtonClicked);
+  // Reset all bubble values.
+  targets_.clear();
+  active_target_ = base::string16();
+  intent_.reset();
+  SetToDefaultBubbleSizing();
 }
 
 void SharesheetBubbleView::ButtonPressed(views::Button* sender,
@@ -230,6 +234,8 @@
   auto type = targets_[sender->tag()].type;
   if (type == sharesheet::TargetType::kAction) {
     active_target_ = targets_[sender->tag()].launch_name;
+  } else {
+    intent_->activity_name = targets_[sender->tag()].activity_name;
   }
   delegate_->OnTargetSelected(targets_[sender->tag()].launch_name, type,
                               std::move(intent_), share_action_view_);
@@ -253,7 +259,7 @@
 }
 
 gfx::Size SharesheetBubbleView::CalculatePreferredSize() const {
-  return gfx::Size(kBubbleWidth, GetHeightForWidth(kBubbleWidth));
+  return gfx::Size(width_, height_);
 }
 
 void SharesheetBubbleView::CreateBubble() {
@@ -280,3 +286,27 @@
   share_action_view_ = AddChildView(std::move(share_action_view));
   share_action_view_->SetVisible(false);
 }
+
+void SharesheetBubbleView::UpdateAnchorPosition() {
+  // If |width_| is not set, set to default value.
+  if (width_ == 0) {
+    SetToDefaultBubbleSizing();
+  }
+
+  // Horizontally centered
+  int x_within_parent_view = parent_view_->GetMirroredXInView(
+      (parent_view_->bounds().width() - width_) / 2);
+  // Get position in screen, taking parent view origin into account. This is
+  // 0,0 in fullscreen on the primary display, but not on secondary displays, or
+  // in Hosted App windows.
+  gfx::Point origin = parent_view_->GetBoundsInScreen().origin();
+  origin += gfx::Vector2d(x_within_parent_view, kBubbleTopPaddingFromWindow);
+
+  // SetAnchorRect will CalculatePreferredSize when called.
+  SetAnchorRect(gfx::Rect(origin, gfx::Size()));
+}
+
+void SharesheetBubbleView::SetToDefaultBubbleSizing() {
+  width_ = kDefaultBubbleWidth;
+  height_ = GetHeightForWidth(width_);
+}
diff --git a/chrome/browser/ui/views/sharesheet_bubble_view.h b/chrome/browser/ui/views/sharesheet_bubble_view.h
index a3f629f0..7a4f8a2 100644
--- a/chrome/browser/ui/views/sharesheet_bubble_view.h
+++ b/chrome/browser/ui/views/sharesheet_bubble_view.h
@@ -36,6 +36,7 @@
   void ShowBubble(std::vector<TargetInfo> targets,
                   apps::mojom::IntentPtr intent);
   void ShowActionView();
+  void ResizeBubble(const int& width, const int& height);
   void CloseBubble();
 
   // views::ButtonListener overrides
@@ -51,15 +52,21 @@
 
  private:
   void CreateBubble();
+  void UpdateAnchorPosition();
+  void SetToDefaultBubbleSizing();
+
   // Owns this class.
   sharesheet::SharesheetServiceDelegate* delegate_;
   std::vector<TargetInfo> targets_;
   base::string16 active_target_;
   apps::mojom::IntentPtr intent_;
+  int width_ = 0;
+  int height_ = 0;
 
   views::View* root_view_ = nullptr;
   views::View* main_view_ = nullptr;
   views::View* share_action_view_ = nullptr;
+  views::View* parent_view_ = nullptr;
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_SHARESHEET_BUBBLE_VIEW_H_
diff --git a/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc
index 7e92e35f..2378d47 100644
--- a/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc
@@ -553,6 +553,26 @@
   }
 
   params.SetString("gaiaUrl", GaiaUrls::GetInstance()->gaia_url().spec());
+  switch (gaia_path_) {
+    case GaiaPath::kDefault:
+      // Use the default gaia signin path embedded/setup/v2/chromeos which is
+      // set in authenticator.js
+      break;
+    case GaiaPath::kChildSignup:
+      params.SetString("gaiaPath",
+                       GaiaUrls::GetInstance()
+                           ->embedded_setup_chromeos_kid_signup_url()
+                           .path()
+                           .substr(1));
+      break;
+    case GaiaPath::kChildSignin:
+      params.SetString("gaiaPath",
+                       GaiaUrls::GetInstance()
+                           ->embedded_setup_chromeos_kid_signin_url()
+                           .path()
+                           .substr(1));
+      break;
+  }
 
   // We only send |chromeos_board| Gaia URL parameter if user has opted into
   // sending device statistics.
@@ -1316,6 +1336,10 @@
   BaseScreenHandler::SetBaseScreen(nullptr);
 }
 
+void GaiaScreenHandler::SetGaiaPath(GaiaScreenHandler::GaiaPath gaia_path) {
+  gaia_path_ = gaia_path;
+}
+
 void GaiaScreenHandler::LoadGaiaAsync(const AccountId& account_id) {
   if (account_id.is_valid())
     populated_account_id_ = account_id;
diff --git a/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h b/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h
index 03fdb96..a312ac51 100644
--- a/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h
+++ b/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h
@@ -48,6 +48,12 @@
 
 class GaiaView {
  public:
+  enum class GaiaPath {
+    kDefault,
+    kChildSignup,
+    kChildSignin,
+  };
+
   constexpr static StaticOobeScreenId kScreenId{"gaia-signin"};
 
   GaiaView() = default;
@@ -74,6 +80,8 @@
   virtual void Bind(GaiaScreen* screen) = 0;
   // Unbinds the screen from the view.
   virtual void Unbind() = 0;
+  // Sets Gaia path for sign-in, child sign-in or child sign-up.
+  virtual void SetGaiaPath(GaiaPath gaia_path) = 0;
 
   // Show sign-in screen for the given credentials. |services| is a list of
   // services returned by userInfo call as JSON array. Should be an empty array
@@ -132,6 +140,7 @@
   void Hide() override;
   void Bind(GaiaScreen* screen) override;
   void Unbind() override;
+  void SetGaiaPath(GaiaPath gaia_path) override;
   void ShowSigninScreenForTest(const std::string& username,
                                const std::string& password,
                                const std::string& services) override;
@@ -455,6 +464,8 @@
   // canceled by the user.
   bool was_security_token_pin_canceled_ = false;
 
+  GaiaPath gaia_path_ = GaiaPath::kDefault;
+
   bool hidden_ = true;
 
   // Handler for |samlChallengeMachineKey| request.
diff --git a/chrome/browser/ui/webui/management_ui.cc b/chrome/browser/ui/webui/management_ui.cc
index fcf7ad7..c3214cf 100644
--- a/chrome/browser/ui/webui/management_ui.cc
+++ b/chrome/browser/ui/webui/management_ui.cc
@@ -72,7 +72,8 @@
     {kManagementReportExtensions, IDS_MANAGEMENT_REPORT_EXTENSIONS},
     {kManagementReportAndroidApplications,
      IDS_MANAGEMENT_REPORT_ANDROID_APPLICATIONS},
-    {kManagementReportProxyServer, IDS_MANAGEMENT_REPORT_PROXY_SERVER},
+    {"proxyServerPrivacyDisclosure",
+     IDS_MANAGEMENT_PROXY_SERVER_PRIVACY_DISCLOSURE},
 #endif  // defined(OS_CHROMEOS)
     {"browserReporting", IDS_MANAGEMENT_BROWSER_REPORTING},
     {"browserReportingExplanation",
diff --git a/chrome/browser/ui/webui/management_ui_handler.cc b/chrome/browser/ui/webui/management_ui_handler.cc
index 9e927dd2..d3fc82f 100644
--- a/chrome/browser/ui/webui/management_ui_handler.cc
+++ b/chrome/browser/ui/webui/management_ui_handler.cc
@@ -170,7 +170,6 @@
 const char kManagementCrostini[] = "managementCrostini";
 const char kManagementCrostiniContainerConfiguration[] =
     "managementCrostiniContainerConfiguration";
-const char kManagementReportProxyServer[] = "managementReportProxyServer";
 const char kAccountManagedInfo[] = "accountManagedInfo";
 const char kDeviceManagedInfo[] = "deviceManagedInfo";
 const char kOverview[] = "overview";
@@ -213,8 +212,7 @@
   kCrostini,
   kUsername,
   kExtensions,
-  kAndroidApplication,
-  kProxyServer
+  kAndroidApplication
 };
 
 // Corresponds to DeviceReportingType in management_browser_proxy.js
@@ -244,8 +242,6 @@
       return "extension";
     case DeviceReportingType::kAndroidApplication:
       return "android application";
-    case DeviceReportingType::kProxyServer:
-      return "proxy server";
     default:
       NOTREACHED() << "Unknown device reporting type";
       return "device";
@@ -609,33 +605,6 @@
                               kManagementReportAndroidApplications,
                               DeviceReportingType::kAndroidApplication);
   }
-
-  chromeos::NetworkHandler* network_handler = chromeos::NetworkHandler::Get();
-  base::Value proxy_settings(base::Value::Type::DICTIONARY);
-  // |ui_proxy_config_service| may be missing in tests. If the device is offline
-  // (no network connected) the |DefaultNetwork| is null.
-  if (network_handler->has_ui_proxy_config_service() &&
-      network_handler->network_state_handler()->DefaultNetwork()) {
-    // Check if proxy is enforced by user policy, a forced install extension or
-    // ONC policies. This will only read managed settings.
-    network_handler->ui_proxy_config_service()->MergeEnforcedProxyConfig(
-        network_handler->network_state_handler()->DefaultNetwork()->guid(),
-        &proxy_settings);
-  }
-  if (!proxy_settings.DictEmpty()) {
-    // Proxies can be specified by web server url, via a PAC script or via the
-    // web proxy auto-discovery protocol. Chrome also supports the "direct"
-    // mode, in which no proxy is used.
-    base::Value* proxy_specification_mode = proxy_settings.FindPath(
-        {::onc::network_config::kType, ::onc::kAugmentationActiveSetting});
-    bool use_proxy =
-        proxy_specification_mode &&
-        proxy_specification_mode->GetString() != ::onc::proxy::kDirect;
-    if (use_proxy) {
-      AddDeviceReportingElement(report_sources, kManagementReportProxyServer,
-                                DeviceReportingType::kProxyServer);
-    }
-  }
 }
 
 bool ManagementUIHandler::IsUpdateRequiredEol() const {
@@ -663,6 +632,35 @@
       chromeos::kDeviceMinimumVersionAueMessage, &eol_admin_message);
   response->SetStringPath("eolAdminMessage", eol_admin_message);
 }
+
+void ManagementUIHandler::AddProxyServerPrivacyDisclosure(
+    base::Value* response) const {
+  bool showProxyDisclosure = false;
+  chromeos::NetworkHandler* network_handler = chromeos::NetworkHandler::Get();
+  base::Value proxy_settings(base::Value::Type::DICTIONARY);
+  // |ui_proxy_config_service| may be missing in tests. If the device is offline
+  // (no network connected) the |DefaultNetwork| is null.
+  if (network_handler->has_ui_proxy_config_service() &&
+      network_handler->network_state_handler()->DefaultNetwork()) {
+    // Check if proxy is enforced by user policy, a forced install extension or
+    // ONC policies. This will only read managed settings.
+    network_handler->ui_proxy_config_service()->MergeEnforcedProxyConfig(
+        network_handler->network_state_handler()->DefaultNetwork()->guid(),
+        &proxy_settings);
+  }
+  if (!proxy_settings.DictEmpty()) {
+    // Proxies can be specified by web server url, via a PAC script or via the
+    // web proxy auto-discovery protocol. Chrome also supports the "direct"
+    // mode, in which no proxy is used.
+    base::Value* proxy_specification_mode = proxy_settings.FindPath(
+        {::onc::network_config::kType, ::onc::kAugmentationActiveSetting});
+    showProxyDisclosure =
+        proxy_specification_mode &&
+        proxy_specification_mode->GetString() != ::onc::proxy::kDirect;
+  }
+  response->SetBoolPath("showProxyServerPrivacyDisclosure",
+                        showProxyDisclosure);
+}
 #endif
 
 base::Value ManagementUIHandler::GetContextualManagedData(Profile* profile) {
@@ -672,6 +670,7 @@
   if (management_domain.empty())
     management_domain = GetAccountDomain(profile);
   AddUpdateRequiredEolInfo(&response);
+  AddProxyServerPrivacyDisclosure(&response);
 #else
   std::string management_domain = GetAccountDomain(profile);
 
diff --git a/chrome/browser/ui/webui/management_ui_handler.h b/chrome/browser/ui/webui/management_ui_handler.h
index 59543bd..ea700bbe 100644
--- a/chrome/browser/ui/webui/management_ui_handler.h
+++ b/chrome/browser/ui/webui/management_ui_handler.h
@@ -39,7 +39,6 @@
 extern const char kManagementCrostiniContainerConfiguration[];
 extern const char kManagementReportExtensions[];
 extern const char kManagementReportAndroidApplications[];
-extern const char kManagementReportProxyServer[];
 #endif  // defined(OS_CHROMEOS)
 
 extern const char kCloudReportingExtensionId[];
@@ -154,6 +153,11 @@
   // as per device policy but the device cannot be updated due to End of Life
   // (Auto Update Expiration).
   void AddUpdateRequiredEolInfo(base::Value* response) const;
+
+  // Adds a boolean which indicates if there's a proxy on the device enforced by
+  // the admin. If true, a warning will be added to the transparency panel to
+  // inform the user that the admin may be able to see their network traffic.
+  void AddProxyServerPrivacyDisclosure(base::Value* response) const;
 #endif  // defined(OS_CHROMEOS)
  private:
   void GetManagementStatus(Profile* profile, base::Value* status) const;
diff --git a/chrome/browser/ui/webui/management_ui_handler_unittest.cc b/chrome/browser/ui/webui/management_ui_handler_unittest.cc
index f39e275..6a550c0 100644
--- a/chrome/browser/ui/webui/management_ui_handler_unittest.cc
+++ b/chrome/browser/ui/webui/management_ui_handler_unittest.cc
@@ -1,3 +1,4 @@
+
 // Copyright 2019 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
@@ -85,6 +86,7 @@
 #if defined(OS_CHROMEOS)
   base::string16 management_overview;
   base::string16 update_required_eol;
+  bool show_proxy_server_privacy_disclosure;
 #else
   base::string16 browser_management_notice;
 #endif  // defined(OS_CHROMEOS)
@@ -299,6 +301,10 @@
 #if defined(OS_CHROMEOS)
     extracted_.management_overview = ExtractPathFromDict(data, "overview");
     extracted_.update_required_eol = ExtractPathFromDict(data, "eolMessage");
+    base::Optional<bool> showProxyDisclosure =
+        data.FindBoolPath("showProxyServerPrivacyDisclosure");
+    extracted_.show_proxy_server_privacy_disclosure =
+        showProxyDisclosure.has_value() && showProxyDisclosure.value();
 #else
     extracted_.browser_management_notice =
         ExtractPathFromDict(data, "browserManagementNotice");
@@ -459,6 +465,10 @@
   base::string16 GetUpdateRequiredEolMessage() const {
     return extracted_.update_required_eol;
   }
+
+  bool GetShowProxyServerPrivacyDisclosure() const {
+    return extracted_.show_proxy_server_privacy_disclosure;
+  }
 #else
 
   base::string16 GetBrowserManagementNotice() const {
@@ -918,32 +928,28 @@
                       expected_elements);
 }
 
-TEST_F(ManagementUIHandlerTests, ProxyServerShowReport) {
+TEST_F(ManagementUIHandlerTests, ShowProxyServerDisclosure) {
+  // Set pref to use a proxy.
   PrefProxyConfigTrackerImpl::RegisterProfilePrefs(user_prefs_.registry());
   chromeos::NetworkHandler::Get()->InitializePrefServices(&user_prefs_,
                                                           &local_state_);
-  // Set pref to use a proxy.
   base::Value policy_prefs_config = ProxyConfigDictionary::CreateAutoDetect();
   user_prefs_.SetUserPref(
       proxy_config::prefs::kProxy,
       base::Value::ToUniquePtrValue(std::move(policy_prefs_config)));
   base::RunLoop().RunUntilIdle();
 
-  ResetTestConfig(false);
-  const base::Value info = SetUpForReportingInfo();
+  GetTestConfig().managed_device = true;
+  SetUpProfileAndHandler();
 
-  const std::map<std::string, std::string> expected_elements = {
-      {kManagementReportProxyServer, "proxy server"}};
-
-  ASSERT_PRED_FORMAT2(ReportingElementsToBeEQ, info.GetList(),
-                      expected_elements);
+  EXPECT_TRUE(GetShowProxyServerPrivacyDisclosure());
 }
 
-TEST_F(ManagementUIHandlerTests, ProxyServerShowReportDeviceOffline) {
+TEST_F(ManagementUIHandlerTests, ProxyServerDisclosureDeviceOffline) {
+  // Simulate network disconnected state.
   PrefProxyConfigTrackerImpl::RegisterProfilePrefs(user_prefs_.registry());
   chromeos::NetworkHandler::Get()->InitializePrefServices(&user_prefs_,
                                                           &local_state_);
-  // Simulate network disconnected state.
   chromeos::NetworkStateHandler::NetworkStateList networks;
   chromeos::NetworkHandler::Get()
       ->network_state_handler()
@@ -962,33 +968,30 @@
   }
   base::RunLoop().RunUntilIdle();
 
-  ResetTestConfig(false);
-  const base::Value info = SetUpForReportingInfo();
+  GetTestConfig().managed_device = true;
+  SetUpProfileAndHandler();
 
-  const std::map<std::string, std::string> expected_elements = {};
+  EXPECT_FALSE(GetShowProxyServerPrivacyDisclosure());
 
-  ASSERT_PRED_FORMAT2(ReportingElementsToBeEQ, info.GetList(),
-                      expected_elements);
   chromeos::NetworkHandler::Get()->NetworkHandler::ShutdownPrefServices();
 }
 
-TEST_F(ManagementUIHandlerTests, ProxyServerHideReportForDirectProxy) {
+TEST_F(ManagementUIHandlerTests, HideProxyServerDisclosureForDirectProxy) {
+  // Set pref not to use proxy.
   PrefProxyConfigTrackerImpl::RegisterProfilePrefs(user_prefs_.registry());
   chromeos::NetworkHandler::Get()->InitializePrefServices(&user_prefs_,
                                                           &local_state_);
-  // Set pref not to use proxy.
   base::Value policy_prefs_config = ProxyConfigDictionary::CreateDirect();
   user_prefs_.SetUserPref(
       proxy_config::prefs::kProxy,
       base::Value::ToUniquePtrValue(std::move(policy_prefs_config)));
   base::RunLoop().RunUntilIdle();
 
-  ResetTestConfig(false);
-  const base::Value info = SetUpForReportingInfo();
+  GetTestConfig().managed_device = true;
+  SetUpProfileAndHandler();
 
-  const std::map<std::string, std::string> expected_elements = {};
-  ASSERT_PRED_FORMAT2(ReportingElementsToBeEQ, info.GetList(),
-                      expected_elements);
+  EXPECT_FALSE(GetShowProxyServerPrivacyDisclosure());
+
   chromeos::NetworkHandler::Get()->NetworkHandler::ShutdownPrefServices();
 }
 
diff --git a/chrome/browser/ui/webui/print_preview/print_preview_ui.cc b/chrome/browser/ui/webui/print_preview/print_preview_ui.cc
index 1f4304f..48dfc6e 100644
--- a/chrome/browser/ui/webui/print_preview/print_preview_ui.cc
+++ b/chrome/browser/ui/webui/print_preview/print_preview_ui.cc
@@ -315,6 +315,7 @@
     {"title", IDS_PRINT_PREVIEW_TITLE},
     {"top", IDS_PRINT_PREVIEW_TOP_MARGIN_LABEL},
     {"unsupportedCloudPrinter", IDS_PRINT_PREVIEW_UNSUPPORTED_CLOUD_PRINTER},
+    {"warningIconAriaLabel", IDS_WARNING_ICON_ARIA_LABEL},
 #if defined(OS_CHROMEOS)
     {"configuringFailedText", IDS_PRINT_CONFIGURING_FAILED_TEXT},
     {"configuringInProgressText", IDS_PRINT_CONFIGURING_IN_PROGRESS_TEXT},
@@ -352,10 +353,18 @@
 
   const bool is_enterprise_managed = webui::IsEnterpriseManaged();
   if (is_enterprise_managed) {
+    source->AddLocalizedString(
+        "cloudPrintingNotSupportedWarning",
+        IDS_CLOUD_PRINTING_NOT_SUPPORTED_WARNING_ENTERPRISE);
     source->AddLocalizedString("printerNotSupportedWarning",
                                IDS_PRINTER_NOT_SUPPORTED_WARNING_ENTERPRISE);
   } else {
     source->AddString(
+        "cloudPrintingNotSupportedWarning",
+        l10n_util::GetStringFUTF16(
+            IDS_CLOUD_PRINTING_NOT_SUPPORTED_WARNING,
+            base::ASCIIToUTF16(cloud_devices::kCloudPrintDeprecationHelpURL)));
+    source->AddString(
         "printerNotSupportedWarning",
         l10n_util::GetStringFUTF16(
             IDS_PRINTER_NOT_SUPPORTED_WARNING,
diff --git a/chrome/browser/ui/webui/settings/about_handler.cc b/chrome/browser/ui/webui/settings/about_handler.cc
index 70ed6fe..0f802b0 100644
--- a/chrome/browser/ui/webui/settings/about_handler.cc
+++ b/chrome/browser/ui/webui/settings/about_handler.cc
@@ -445,6 +445,18 @@
 
 void AboutHandler::HandleLaunchReleaseNotes(const base::ListValue* args) {
   DCHECK(args->empty());
+  // If the flag is enabled, we can always show the release notes since the Help
+  // app caches it, or can show an appropriate error state (e.g. No internet
+  // connection).
+  if (base::FeatureList::IsEnabled(chromeos::features::kHelpAppReleaseNotes)) {
+    base::RecordAction(
+        base::UserMetricsAction("ReleaseNotes.LaunchedAboutPage"));
+    chrome::LaunchReleaseNotes(profile_,
+                               apps::mojom::LaunchSource::kFromOtherApp);
+    return;
+  }
+
+  // If the flag is disabled, we need connectivity to load the PWA.
   chromeos::NetworkStateHandler* network_state_handler =
       chromeos::NetworkHandler::Get()->network_state_handler();
   const chromeos::NetworkState* network =
@@ -452,7 +464,8 @@
   if (network && network->IsOnline()) {
     base::RecordAction(
         base::UserMetricsAction("ReleaseNotes.LaunchedAboutPage"));
-    chrome::LaunchReleaseNotes(profile_);
+    chrome::LaunchReleaseNotes(profile_,
+                               apps::mojom::LaunchSource::kFromOtherApp);
   }
 }
 
diff --git a/chrome/browser/ui/webui/settings/chromeos/crostini_section.cc b/chrome/browser/ui/webui/settings/chromeos/crostini_section.cc
index 2d4ac4b..84640a58 100644
--- a/chrome/browser/ui/webui/settings/chromeos/crostini_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/crostini_section.cc
@@ -73,6 +73,14 @@
         IDS_OS_SETTINGS_TAG_CROSTINI_SHARED_FOLDERS_ALT2,
         IDS_OS_SETTINGS_TAG_CROSTINI_SHARED_FOLDERS_ALT3,
         SearchConcept::kAltTagEnd}},
+      {IDS_OS_SETTINGS_TAG_CROSTINI_MIC_ACCESS,
+       mojom::kCrostiniDetailsSubpagePath,
+       mojom::SearchResultIcon::kPenguin,
+       mojom::SearchResultDefaultRank::kMedium,
+       mojom::SearchResultType::kSetting,
+       {.setting = mojom::Setting::kCrostiniMicAccess},
+       {IDS_OS_SETTINGS_TAG_CROSTINI_MIC_ACCESS_ALT1,
+        SearchConcept::kAltTagEnd}},
   });
   return *tags;
 }
@@ -175,20 +183,6 @@
   return *tags;
 }
 
-const std::vector<SearchConcept>& GetCrostiniMicSearchConcepts() {
-  static const base::NoDestructor<std::vector<SearchConcept>> tags({
-      {IDS_OS_SETTINGS_TAG_CROSTINI_MIC_ACCESS,
-       mojom::kCrostiniDetailsSubpagePath,
-       mojom::SearchResultIcon::kPenguin,
-       mojom::SearchResultDefaultRank::kMedium,
-       mojom::SearchResultType::kSetting,
-       {.setting = mojom::Setting::kCrostiniMicAccess},
-       {IDS_OS_SETTINGS_TAG_CROSTINI_MIC_ACCESS_ALT1,
-        SearchConcept::kAltTagEnd}},
-  });
-  return *tags;
-}
-
 bool IsProfileManaged(Profile* profile) {
   return profile->GetProfilePolicyConnector()->IsManaged();
 }
@@ -205,10 +199,6 @@
   return base::FeatureList::IsEnabled(features::kCrostiniDiskResizing);
 }
 
-bool IsMicSettingAllowed() {
-  return base::FeatureList::IsEnabled(features::kCrostiniShowMicSetting);
-}
-
 }  // namespace
 
 CrostiniSection::CrostiniSection(Profile* profile,
@@ -433,7 +423,6 @@
   html_source->AddBoolean("showCrostiniContainerUpgrade",
                           IsContainerUpgradeAllowed());
   html_source->AddBoolean("showCrostiniDiskResize", IsDiskResizingAllowed());
-  html_source->AddBoolean("showCrostiniMic", IsMicSettingAllowed());
 }
 
 void CrostiniSection::AddHandlers(content::WebUI* web_ui) {
@@ -553,7 +542,6 @@
   updater.RemoveSearchTags(GetCrostiniPortForwardingSearchConcepts());
   updater.RemoveSearchTags(GetCrostiniContainerUpgradeSearchConcepts());
   updater.RemoveSearchTags(GetCrostiniDiskResizingSearchConcepts());
-  updater.RemoveSearchTags(GetCrostiniMicSearchConcepts());
 
   if (!IsCrostiniAllowed())
     return;
@@ -581,9 +569,6 @@
 
   if (IsDiskResizingAllowed())
     updater.AddSearchTags(GetCrostiniDiskResizingSearchConcepts());
-
-  if (IsMicSettingAllowed())
-    updater.AddSearchTags(GetCrostiniMicSearchConcepts());
 }
 
 }  // namespace settings
diff --git a/chrome/browser/ui/webui/signin/signin_reauth_ui.cc b/chrome/browser/ui/webui/signin/signin_reauth_ui.cc
index 56975af..92d8dbb 100644
--- a/chrome/browser/ui/webui/signin/signin_reauth_ui.cc
+++ b/chrome/browser/ui/webui/signin/signin_reauth_ui.cc
@@ -53,44 +53,11 @@
              : profiles::GetPlaceholderAvatarIconUrl();
 }
 
-int GetReauthTitleStringId(signin_metrics::ReauthAccessPoint access_point) {
-  switch (access_point) {
-    case signin_metrics::ReauthAccessPoint::kUnknown:
-    case signin_metrics::ReauthAccessPoint::kAutofillDropdown:
-    case signin_metrics::ReauthAccessPoint::kPasswordSettings:
-      return IDS_ACCOUNT_PASSWORDS_REAUTH_SHOW_TITLE;
-    case signin_metrics::ReauthAccessPoint::kGeneratePasswordDropdown:
-    case signin_metrics::ReauthAccessPoint::kGeneratePasswordContextMenu:
-    case signin_metrics::ReauthAccessPoint::kPasswordSaveBubble:
-    case signin_metrics::ReauthAccessPoint::kPasswordMoveBubble:
-      return IDS_ACCOUNT_PASSWORDS_REAUTH_SAVE_TITLE;
-  }
-}
-
-int GetReauthConfirmButtonLabelStringId(
-    signin_metrics::ReauthAccessPoint access_point) {
-  switch (access_point) {
-    case signin_metrics::ReauthAccessPoint::kUnknown:
-    case signin_metrics::ReauthAccessPoint::kAutofillDropdown:
-    case signin_metrics::ReauthAccessPoint::kPasswordSettings:
-      return IDS_ACCOUNT_PASSWORDS_REAUTH_SHOW_BUTTON_LABEL;
-    case signin_metrics::ReauthAccessPoint::kGeneratePasswordDropdown:
-    case signin_metrics::ReauthAccessPoint::kGeneratePasswordContextMenu:
-    case signin_metrics::ReauthAccessPoint::kPasswordSaveBubble:
-    case signin_metrics::ReauthAccessPoint::kPasswordMoveBubble:
-      return IDS_ACCOUNT_PASSWORDS_REAUTH_SAVE_BUTTON_LABEL;
-  }
-}
-
 }  // namespace
 
 SigninReauthUI::SigninReauthUI(content::WebUI* web_ui)
     : SigninWebDialogUI(web_ui) {
   Profile* profile = Profile::FromWebUI(web_ui);
-  signin_metrics::ReauthAccessPoint access_point =
-      signin::GetReauthAccessPointForReauthConfirmationURL(
-          web_ui->GetWebContents()->GetVisibleURL());
-
   content::WebUIDataSource* source =
       content::WebUIDataSource::Create(chrome::kChromeUISigninReauthHost);
   source->UseStringsJs();
@@ -118,13 +85,11 @@
       "images/signin_reauth_illustration_dark.svg",
       IDR_SIGNIN_REAUTH_IMAGES_ACCOUNT_PASSWORDS_REAUTH_ILLUSTRATION_DARK_SVG);
   AddStringResource(source, "signinReauthTitle",
-                    GetReauthTitleStringId(access_point));
+                    IDS_ACCOUNT_PASSWORDS_REAUTH_TITLE);
   AddStringResource(source, "signinReauthDesc",
                     IDS_ACCOUNT_PASSWORDS_REAUTH_DESC);
   AddStringResource(source, "signinReauthConfirmLabel",
-                    GetReauthConfirmButtonLabelStringId(access_point));
-  AddStringResource(source, "signinReauthNextLabel",
-                    IDS_ACCOUNT_PASSWORDS_REAUTH_NEXT_BUTTON_LABEL);
+                    IDS_ACCOUNT_PASSWORDS_REAUTH_CONFIRM_BUTTON_LABEL);
   AddStringResource(source, "signinReauthCloseLabel",
                     IDS_ACCOUNT_PASSWORDS_REAUTH_CLOSE_BUTTON_LABEL);
 
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index ac60c13..af00b4d0 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-master-1597795195-73a3893f6ac2e232e6f2551f9490f3476218900c.profdata
+chrome-mac-master-1597838234-9c863d7c9dc15ae374a8358c943ba78053f4a35c.profdata
diff --git a/chrome/common/custom_handlers/protocol_handler.cc b/chrome/common/custom_handlers/protocol_handler.cc
index 55368f4..0bd39ad 100644
--- a/chrome/common/custom_handlers/protocol_handler.cc
+++ b/chrome/common/custom_handlers/protocol_handler.cc
@@ -98,7 +98,7 @@
   std::string translatedUrlSpec(url_.spec());
   base::ReplaceFirstSubstringAfterOffset(
       &translatedUrlSpec, 0, "%s",
-      net::EscapeQueryParamValue(url.spec(), true));
+      net::EscapeQueryParamValue(url.spec(), false));
   return GURL(translatedUrlSpec);
 }
 
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index 89feef33..3e5d0cc 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -1397,7 +1397,7 @@
     "native_printing.recommended_printers_access_mode";
 
 // List of printer ids which are explicitly disallowed.  List of strings.
-const char kRecommendedNativePrintersBlacklist[] =
+const char kRecommendedPrintersBlocklist[] =
     "native_printing.recommended_printers_blacklist";
 
 // List of printer ids that are allowed.  List of strings.
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index 444ff686..ed7dcbb 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -447,7 +447,7 @@
 extern const char kDeviceExternalPrintServersAllowlist[];
 extern const char kRecommendedPrinters[];
 extern const char kRecommendedPrintersAccessMode[];
-extern const char kRecommendedNativePrintersBlacklist[];
+extern const char kRecommendedPrintersBlocklist[];
 extern const char kRecommendedNativePrintersWhitelist[];
 extern const char kUserPrintersAllowed[];
 
diff --git a/chrome/renderer/BUILD.gn b/chrome/renderer/BUILD.gn
index acd11f1..cbf9a60c3 100644
--- a/chrome/renderer/BUILD.gn
+++ b/chrome/renderer/BUILD.gn
@@ -38,6 +38,7 @@
   ]
   if (is_chromeos) {
     deps += [
+      "//chromeos/components/remote_apps/mojom:mojom_js",
       "//chromeos/services/ime/public/mojom:mojom_js",
       "//chromeos/services/tts/public/mojom:mojom_js",
     ]
diff --git a/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc b/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc
index 8515cff..4f5a719 100644
--- a/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc
+++ b/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc
@@ -178,6 +178,14 @@
   source_map->RegisterSource("chromeos.tts.mojom.tts_stream.mojom",
                              IDR_TTS_STREAM_MOJOM_JS);
   source_map->RegisterSource("chromeos.tts.stream", IDR_TTS_STREAM_BINDINGS_JS);
+
+  // Imprivata API.
+  source_map->RegisterSource("chromeos.remote_apps.mojom-lite",
+                             IDR_REMOTE_APPS_MOJOM_LITE_JS);
+  source_map->RegisterSource("chromeos.remote_apps",
+                             IDR_REMOTE_APPS_BINDINGS_JS);
+  source_map->RegisterSource("url/mojom/url.mojom-lite",
+                             IDR_MOJO_URL_MOJOM_LITE_JS);
 #endif  // defined(OS_CHROMEOS)
 
   source_map->RegisterSource(
diff --git a/chrome/renderer/resources/extensions/remote_apps/OWNERS b/chrome/renderer/resources/extensions/remote_apps/OWNERS
new file mode 100644
index 0000000..fc0c3ac
--- /dev/null
+++ b/chrome/renderer/resources/extensions/remote_apps/OWNERS
@@ -0,0 +1,2 @@
+hendrich@chromium.org
+jityao@google.com
diff --git a/chrome/renderer/resources/extensions/remote_apps/remote_apps_bindings.js b/chrome/renderer/resources/extensions/remote_apps/remote_apps_bindings.js
new file mode 100644
index 0000000..4c5ee06
--- /dev/null
+++ b/chrome/renderer/resources/extensions/remote_apps/remote_apps_bindings.js
@@ -0,0 +1,61 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+if ((typeof mojo === 'undefined') || !mojo.bindingsLibraryInitialized) {
+  loadScript('mojo_bindings_lite');
+}
+
+loadScript('url/mojom/url.mojom-lite');
+loadScript('chromeos.remote_apps.mojom-lite');
+
+class RemoteAppsAdapter {
+  constructor() {
+    const factory = chromeos.remoteApps.mojom.RemoteAppsFactory.getRemote();
+
+    this.remoteApps_ = new chromeos.remoteApps.mojom.RemoteAppsRemote();
+    this.callbackRouter_ =
+        new chromeos.remoteApps.mojom.RemoteAppLaunchObserverCallbackRouter();
+    factory.create(
+        this.remoteApps_.$.bindNewPipeAndPassReceiver(),
+        this.callbackRouter_.$.bindNewPipeAndPassRemote());
+  }
+
+  /**
+   * Adds a folder to the launcher. Note that empty folders are not shown in
+   * the launcher.
+   * @param {string} name name of the added folder
+   * @return {!Promise<!{folderId: string, error: string}>} ID for the added
+   *     folder
+   */
+  addFolder(name) {
+    return this.remoteApps_.addFolder(name);
+  }
+
+  /**
+   * Adds an app to the launcher.
+   * @param {string} name name of the added app
+   * @param {string} folderId Id of the parent folder. An empty string
+   *     indicates the app does not have a parent folder.
+   * @param {string} iconUrl URL to an image representing the app's icon
+   * @return {!Promise<!{appId: string, error: string}>} ID for the
+   *     added app.
+   */
+  addApp(name, folderId, iconUrl) {
+    return this.remoteApps_.addApp(name, folderId, {url: iconUrl});
+  }
+
+  /**
+   * Adds a callback for remote app launch events.
+   * @param {function(string)} callback called when a remote app is launched
+   *     with the app ID as argument.
+   * @return {!Promise<void>}
+   */
+  addRemoteAppLaunchObserver(callback) {
+    return this.callbackRouter_.onRemoteAppLaunched.addListener(callback);
+  }
+}
+
+exports.$set('returnValue', new RemoteAppsAdapter());
diff --git a/chrome/renderer/resources/renderer_resources.grd b/chrome/renderer/resources/renderer_resources.grd
index cadb90c..a0574ab 100644
--- a/chrome/renderer/resources/renderer_resources.grd
+++ b/chrome/renderer/resources/renderer_resources.grd
@@ -63,10 +63,15 @@
 
           <!-- ChromeOS IME Mojo service and bindings. -->
           <include name="IDR_IME_SERVICE_BINDINGS_JS" file="extensions\chromeos_ime_service_bindings.js" type="BINDATA" />
-          <include name="IDR_IME_SERVICE_MOJOM_JS" file="${mojom_root}\chromeos/services/ime/public/mojom/input_engine.mojom.js" use_base_dir="false" type="BINDATA" />
+          <include name="IDR_IME_SERVICE_MOJOM_JS" file="${mojom_root}\chromeos\services\ime\public\mojom\input_engine.mojom.js" use_base_dir="false" type="BINDATA" />
+
+          <!-- Remote Apps bindings. -->
+          <include name="IDR_REMOTE_APPS_BINDINGS_JS" file="extensions\remote_apps\remote_apps_bindings.js" type="BINDATA" />
+          <include name="IDR_REMOTE_APPS_MOJOM_LITE_JS" file="${mojom_root}\chromeos\components\remote_apps\mojom\remote_apps.mojom-lite.js" use_base_dir="false" type="BINDATA" />
+          <include name="IDR_MOJO_URL_MOJOM_LITE_JS" file="${mojom_root}\url\mojom\url.mojom-lite.js" use_base_dir="false" type="BINDATA" />
 
           <include name="IDR_TTS_STREAM_BINDINGS_JS" file="extensions\chromeos_tts_stream_bindings.js" type="BINDATA" />
-          <include name="IDR_TTS_STREAM_MOJOM_JS" file="${mojom_root}\chromeos/services/tts/public/mojom/tts_service.mojom.js" use_base_dir="false" type="BINDATA" />
+          <include name="IDR_TTS_STREAM_MOJOM_JS" file="${mojom_root}\chromeos\services\tts\public\mojom\tts_service.mojom.js" use_base_dir="false" type="BINDATA" />
         </if>
         <!-- Media Router Mojo service and bindings. -->
         <include name="IDR_MEDIA_CONTROLLER_MOJOM_JS" file="${mojom_root}\chrome\common\media_router\mojom\media_controller.mojom.js" use_base_dir="false" type="BINDATA" />
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 13ac2d94..c212c59a 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -970,7 +970,7 @@
       "../browser/media/test_license_server_config.h",
       "../browser/media/unified_autoplay_browsertest.cc",
       "../browser/media/webrtc/media_stream_devices_controller_browsertest.cc",
-      "../browser/media/webrtc/media_stream_infobar_browsertest.cc",
+      "../browser/media/webrtc/media_stream_permission_browsertest.cc",
       "../browser/media/webrtc/test_stats_dictionary.cc",
       "../browser/media/webrtc/test_stats_dictionary.h",
       "../browser/media/webrtc/test_stats_dictionary_unittest.cc",
@@ -2493,6 +2493,7 @@
         "../browser/chromeos/printing/test_printer_configurer.cc",
         "../browser/chromeos/printing/test_printer_configurer.h",
         "../browser/chromeos/profiles/profile_helper_browsertest.cc",
+        "../browser/chromeos/remote_apps/remote_apps_impl_browsertest.cc",
         "../browser/chromeos/remote_apps/remote_apps_manager_browsertest.cc",
         "../browser/chromeos/shutdown_policy_browsertest.cc",
         "../browser/chromeos/startup_settings_cache_browsertest.cc",
diff --git a/chrome/test/data/extensions/remote_apps/extension/manifest.json b/chrome/test/data/extensions/remote_apps/extension/manifest.json
new file mode 100644
index 0000000..e73a462
--- /dev/null
+++ b/chrome/test/data/extensions/remote_apps/extension/manifest.json
@@ -0,0 +1,9 @@
+{
+  "name": "Remote Apps",
+  "version": "0.1",
+  "manifest_version": 2,
+  "description": "Browser test for Remote Apps Mojo Private API",
+  "background": {
+    "scripts": ["test.js"]
+  }
+}
diff --git a/chrome/test/data/extensions/remote_apps/extension/test.js b/chrome/test/data/extensions/remote_apps/extension/test.js
new file mode 100644
index 0000000..ef350b7
--- /dev/null
+++ b/chrome/test/data/extensions/remote_apps/extension/test.js
@@ -0,0 +1,66 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+let api;
+
+const testCases = [
+  async function AddApp() {
+    const result1 = await api.addApp('App 1', '', '');
+    chrome.test.assertFalse(!!result1.error);
+    chrome.test.assertEq('Id 1', result1.appId);
+
+    const result2 = await api.addApp('App 2', 'missing', '');
+    chrome.test.assertEq('Folder ID provided does not exist', result2.error);
+
+    chrome.test.succeed();
+  },
+  async function AddFolderAndApps() {
+    const result1 = await api.addFolder('Folder 1');
+    const folderId = result1.folderId;
+    chrome.test.assertFalse(!!result1.error);
+    chrome.test.assertEq('Id 1', folderId);
+
+    const result2 = await api.addApp('App 1', folderId, '');
+    chrome.test.assertFalse(!!result2.error);
+    chrome.test.assertEq('Id 2', result2.appId);
+
+    const result3 = await api.addApp('App 2', folderId, '');
+    chrome.test.assertFalse(!!result3.error);
+    chrome.test.assertEq('Id 3', result3.appId);
+
+    chrome.test.succeed();
+  },
+  async function OnRemoteAppLaunched() {
+    let actualId = '';
+    await new Promise(async (resolve) => {
+      await api.addRemoteAppLaunchObserver(id => {
+        actualId = id;
+        resolve();
+      });
+      await api.addApp('App 1', '', '');
+      chrome.test.sendMessage('Remote app added');
+    });
+
+    chrome.test.assertEq('Id 1', actualId);
+    chrome.test.succeed();
+  },
+];
+
+chrome.test.getConfig(async (config) => {
+  try {
+    api = await chrome.mojoPrivate.requireAsync('chromeos.remote_apps');
+  } catch (e) {
+    chrome.test.notifyFail('Could not get mojoPrivate bindings: ' + e.message);
+    return;
+  }
+
+  const testName = config.customArg;
+  const testCase = testCases.find((f) => f.name === testName);
+  if (!testCase) {
+    chrome.test.notifyFail('Test case \'' + testName + '\' not found');
+    return;
+  }
+
+  chrome.test.runTests([testCase]);
+});
diff --git a/chrome/test/data/extensions/remote_apps/remote_apps.pem b/chrome/test/data/extensions/remote_apps/remote_apps.pem
new file mode 100644
index 0000000..2285fbc7
--- /dev/null
+++ b/chrome/test/data/extensions/remote_apps/remote_apps.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDrhtzVny69zwz3
+F0n517RrPaeuiXF9f7TXf+K+xrL3p0NDTrP6pj5A9tV2rbhCrMqEd6ub5WRhl7Y8
+JlnWcAs6UikeJaQU7uSSVXxw2/ZJdEgTrcjL6DvfyyGpWf33TQn8yHVHpqiRxXr5
+NV2ciA0Vv8abNgbi5EUQrM+yKXi3FrCGBewXNoffA8tUV3jYEdHEJirE3y1s65r9
+xD9RMfPlGt6lV63EOGy+Ti88YldKPB6HiRHD9GmIH3BPkan9YGlmb4RBWLfoUNZS
+jrrUAlQzkUaWLyqTYHZqtpNHaKClCAX6DNAodgchzd+YNWW/Ysev6et6IkAO5dbl
+v4gxEkAPAgMBAAECggEAKiKv6kW2mn1qr9/KO7jDzbWzhG2RUKbipvT5jzDD/rs9
+NNLlLu/DzmJ6UOeGQeNgva8dE+BHg5AdKYig5NSZpZ7iPUL1pksQuD8z6orndj+n
+z2F1PUl4QLK5/G6dmTr+kOsZ1C40FRQTynaqHyFV2fC7qrPRKpE06+VGqPRzZKmC
+HjeLUTevBgOYmVB/bRn1uc7yZN2YpuDoJ9BtU1HopyqVFVxTvJorsCAYD+94hS6I
++lKzW+X1U0JDGjTRaRZqHaOc7kgKJswD1B6xVKv58ZO4mRhmKbKtaKRF3IVLz9vb
+RyoClY2Qrn57hOMSgKyh+jxN8jpiW76bTHOi2GofVQKBgQD6PhSHzqpDAVVg+ivR
+JykRjfVtG5mOAYPB4PAZX+8KuKXxtFuh9xN98zJiB/EaqJNP0Kcnd09enkqHN4Cm
+D8WEBzjokIxJQf55+DzeVtC1DkCAzuWdfXeJDjOQ/e447L9xeo0ms9ivjsWpl2T+
+p6jfx7nk1N1cPZUGyIso0m1oVQKBgQDw8huFWKEM4K5TXMF9Z/keoS/q6uVoeSkB
+335S/iZMEmnv8eG46y4ID30Mg4E4gye9U4Jc6wauorQYm1lOpfVlYxsVh7TaMFLk
+mQ+jUWttIeURye3qn3t9YCSsIdKydNUrkkJd4nCCr31rPSQhRWa5PQUVjtOqI4B/
+siafBDo60wKBgQCpC6by1zlNamkyyc0vzTSBF1TkD/D7bSqEnl+TxKrGo1X2odAE
+6dPREajHcHX/fEGHeXxxvLdxQ501GtldVOoo9ngLIxqhomM2Iet8h0kWBjqsyRdz
+/H3zqBRNrjxvV/87uX4A1x1Z+yisGAmxvbDm+xUo8GNZHIC/xFm9iek+wQKBgDQj
+0DzM7x0ASfkUK3Ld2xT7wIjPiBFRlsQm/wkqolL38SDRcQ05J17rKx5YHtCB4Umh
+FqbQ3UNRRjPE+lCArVfhWG0STtqgdm+th6rJ5btaCF4PGoMZO/nnokf1kci4a6Dg
+J6h1Ze+B1lwsgPMKN66CO+VsYPWCdT4s6RqkKY2tAoGBAOiyRyW3RfpdGO36mv7p
+r7qZ++tH+ZnTZ6CAp/+MTcx0XpCUnmSOGt0fU83TI9ujBeVJSoafg2MB/Th5UX11
+RkN67zkTqeyej5scAzoTB+FD4+/zcrCfLze30dLCvtGp/IdwyLv9X+Yd4uxPIugm
+CGqT0AcAo852wwzRPS/7eB6J
+-----END PRIVATE KEY-----
diff --git a/chrome/test/data/policy/policy_test_cases.json b/chrome/test/data/policy/policy_test_cases.json
index 2982337..ab82e48 100644
--- a/chrome/test/data/policy/policy_test_cases.json
+++ b/chrome/test/data/policy/policy_test_cases.json
@@ -669,6 +669,7 @@
   },
 
   "NativePrintersBulkBlacklist": {
+    "note": "This policy is deprecated, see http://crbug.com/1098049.",
     "os": ["chromeos"],
     "policy_pref_mapping_test": [
       {
@@ -678,6 +679,16 @@
     ]
   },
 
+  "PrintersBulkBlocklist": {
+    "os": ["chromeos"],
+    "policy_pref_mapping_test": [
+      {
+        "policies": { "PrintersBulkBlocklist": ["id4", "id7", "id10"] },
+        "prefs": { "native_printing.recommended_printers_blacklist": {} }
+      }
+    ]
+  },
+
   "NativePrintersBulkWhitelist": {
     "os": ["chromeos"],
     "policy_pref_mapping_test": [
diff --git a/chrome/test/data/webrtc/video_detector.js b/chrome/test/data/webrtc/video_detector.js
index 4c96610..f759bd3 100644
--- a/chrome/test/data/webrtc/video_detector.js
+++ b/chrome/test/data/webrtc/video_detector.js
@@ -68,14 +68,33 @@
       if (!allElementsRoughlyEqualTo_(gFingerprints, gFingerprints[0])) {
         clearInterval(gDetectorInterval);
         gDetectorInterval = null;
-        returnToTest('video-playing');
+        silentReturnToTest('video-playing');
         return;
       }
     }
   } catch (exception) {
     throw failTest('Failed to detect video: ' + exception.message);
   }
-  returnToTest('video-not-playing');
+  silentReturnToTest('video-not-playing');
+}
+
+/**
+ * Checks if the video has stopped
+ *
+ * @return {string} video-stopped or video-not-stopped.
+ */
+function isVideoStopped() {
+  // Video is considered to be stopped if the last 5 fingerprints are the same.
+  // We only check for rough equality though to account for rounding errors.
+  if (gFingerprints.length < 5)
+    silentReturnToTest('video-not-stopped');
+
+  if (allElementsRoughlyEqualTo_(gFingerprints.slice(-5),
+                                 gFingerprints[gFingerprints.length - 1])) {
+    silentReturnToTest('video-stopped');
+  }
+
+  silentReturnToTest('video-not-stopped');
 }
 
 /**
diff --git a/chrome/test/data/webui/print_preview/destination_dialog_test.js b/chrome/test/data/webui/print_preview/destination_dialog_test.js
index 893fee5..b51f57e8 100644
--- a/chrome/test/data/webui/print_preview/destination_dialog_test.js
+++ b/chrome/test/data/webui/print_preview/destination_dialog_test.js
@@ -2,8 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {Destination, DestinationStore, InvitationStore, LocalDestinationInfo, makeRecentDestination, NativeLayerImpl, RecentDestination} from 'chrome://print/print_preview.js';
+import {Destination, DestinationConnectionStatus, DestinationOrigin, DestinationStore, DestinationType, InvitationStore, LocalDestinationInfo, makeRecentDestination, NativeLayerImpl, RecentDestination} from 'chrome://print/print_preview.js';
 import {assert} from 'chrome://resources/js/assert.m.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {keyEventOn} from 'chrome://resources/polymer/v3_0/iron-test-helpers/mock-interactions.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
@@ -22,6 +23,15 @@
   PrinterList: 'PrinterList',
   ShowProvisionalDialog: 'ShowProvisionalDialog',
   UserAccounts: 'UserAccounts',
+  CloudPrinterDeprecationWarnings: 'CloudPrinterDeprecationWarnings',
+  CloudPrinterDeprecationWarningsSuppressed:
+      'CloudPrinterDeprecationWarningsSuppressed',
+  SaveToDriveDeprecationWarnings: 'SaveToDriveDeprecationWarnings',
+  SaveToDriveDeprecationWarningsSuppressed:
+      'SaveToDriveDeprecationWarningsSuppressed',
+  SaveToDriveDeprecationWarningsCros: 'SaveToDriveDeprecationWarningsCros',
+  SaveToDriveDeprecationWarningsSuppressedCros:
+      'SaveToDriveDeprecationWarningsSuppressedCros',
 };
 
 suite(destination_dialog_test.suiteName, function() {
@@ -285,4 +295,111 @@
     // Cloud print should have been queried again for the new account.
     assertEquals(3, cloudPrintInterface.getCallCount('search'));
   });
+
+  /**
+   * @param {boolean} warningsSuppressed Whether warnings are suppressed.
+   * @return{!function()} Async function that tests whether Cloud print
+   *     deprecation warnings show when they're supposed to.
+   */
+  function testCloudPrinterDeprecationWarnings(warningsSuppressed) {
+    return async () => {
+      loadTimeData.overrideValues(
+          {cloudPrintDeprecationWarningsSuppressed: warningsSuppressed});
+
+      // Set up the cloud print interface with a Cloud printer for an account.
+      const user = 'foo@chromium.org';
+      cloudPrintInterface.setPrinter(
+          new Destination(
+              'ID', DestinationType.GOOGLE, DestinationOrigin.COOKIES,
+              'Cloud Printer', DestinationConnectionStatus.ONLINE,
+              {account: user, isOwned: true}),
+      );
+
+      await finishSetup();
+
+      // Nobody is signed in. There are no cloud printers.
+      assertSignedInState('', 0);
+      const statusEl = dialog.$$('.destinations-status');
+      assertTrue(statusEl.hidden);
+
+      // Set an active user. There is a cloud printer.
+      destinationStore.setActiveUser(user);
+      destinationStore.reloadUserCookieBasedDestinations(user);
+      dialog.activeUser = user;
+      dialog.users = [user];
+      flush();
+      assertSignedInState(user, 1);
+
+      assertEquals(warningsSuppressed, statusEl.hidden);
+    };
+  }
+
+  // Test that Cloud print deprecation warnings appear.
+  test(
+      assert(destination_dialog_test.TestNames.CloudPrinterDeprecationWarnings),
+      testCloudPrinterDeprecationWarnings(false));
+
+  // Test that Cloud print deprecation warnings are suppressed.
+  test(
+      assert(destination_dialog_test.TestNames
+                 .CloudPrinterDeprecationWarningsSuppressed),
+      testCloudPrinterDeprecationWarnings(true));
+
+  /**
+   * @param {boolean} warningsSuppressed Whether warnings are suppressed.
+   * @return{!function()} Async function that tests whether Cloud print
+   *     deprecation warnings show when they're supposed to.
+   */
+  function testSaveToDriveDeprecationWarnings(warningsSuppressed, isChromeOS) {
+    return async () => {
+      loadTimeData.overrideValues(
+          {cloudPrintDeprecationWarningsSuppressed: warningsSuppressed});
+
+      // Set up the cloud print interface with Google Drive printer for an
+      // account.
+      const user = 'foo@chromium.org';
+      cloudPrintInterface.setPrinter(getGoogleDriveDestination(user));
+
+      await finishSetup();
+
+      // Nobody is signed in. There are no cloud printers.
+      assertSignedInState('', 0);
+      const statusEl = dialog.$$('.destinations-status');
+      assertTrue(statusEl.hidden);
+
+      // Set an active user. There is a cloud printer (Save to Drive).
+      destinationStore.setActiveUser(user);
+      destinationStore.reloadUserCookieBasedDestinations(user);
+      dialog.activeUser = user;
+      dialog.users = [user];
+      flush();
+      assertSignedInState(user, 1);
+
+      // Warning should never appear on ChromeOS.
+      assertEquals(warningsSuppressed || isChromeOS, statusEl.hidden);
+    };
+  }
+
+  // Test that Save to Drive deprecation warnings appear.
+  test(
+      assert(destination_dialog_test.TestNames.SaveToDriveDeprecationWarnings),
+      testSaveToDriveDeprecationWarnings(false, false));
+
+  // Test that Save to Drive deprecation warnings are suppressed.
+  test(
+      assert(destination_dialog_test.TestNames
+                 .SaveToDriveDeprecationWarningsSuppressed),
+      testSaveToDriveDeprecationWarnings(true, false));
+
+  // Test that Save to Drive deprecation warnings never appear.
+  test(
+      assert(
+          destination_dialog_test.TestNames.SaveToDriveDeprecationWarningsCros),
+      testSaveToDriveDeprecationWarnings(false, true));
+
+  // Test that Save to Drive deprecation warnings are suppressed.
+  test(
+      assert(destination_dialog_test.TestNames
+                 .SaveToDriveDeprecationWarningsSuppressedCros),
+      testSaveToDriveDeprecationWarnings(true, true));
 });
diff --git a/chrome/test/data/webui/print_preview/destination_select_test_cros.js b/chrome/test/data/webui/print_preview/destination_select_test_cros.js
index df4facfe..519a6093 100644
--- a/chrome/test/data/webui/print_preview/destination_select_test_cros.js
+++ b/chrome/test/data/webui/print_preview/destination_select_test_cros.js
@@ -453,6 +453,10 @@
               'printer-not-supported';
 
           compareIcon(selectEl, dest3Icon);
+
+          // Update destination.
+          destinationSelect.destination = recentDestinationList[2];
+          compareIcon(selectEl, dest3Icon);
         });
   }
 
@@ -465,6 +469,7 @@
   function testUpdateStatus(cloudPrintDeprecationWarningsSuppressed) {
     loadTimeData.overrideValues({
       offline: 'offline',
+      printerNotSupportedWarning: 'printerNotSupportedWarning',
     });
 
     assertFalse(destinationSelect.$$('.throbber-container').hidden);
@@ -482,22 +487,26 @@
     destinationSelect.destination = getGoogleDriveDestination(account);
     destinationSelect.updateDestination();
     assertTrue(additionalInfoEl.hidden);
-    assertEquals('', statusEl.innerHTML.trim());
+    assertEquals('', statusEl.innerHTML);
 
     destinationSelect.destination = recentDestinationList[0];
     destinationSelect.updateDestination();
     assertTrue(additionalInfoEl.hidden);
-    assertEquals('', statusEl.innerHTML.trim());
+    assertEquals('', statusEl.innerHTML);
 
     destinationSelect.destination = recentDestinationList[1];
     destinationSelect.updateDestination();
     assertFalse(additionalInfoEl.hidden);
-    assertEquals('offline', statusEl.innerHTML.trim());
+    assertEquals('offline', statusEl.innerHTML);
 
     destinationSelect.destination = recentDestinationList[2];
     destinationSelect.updateDestination();
-    assertTrue(additionalInfoEl.hidden);
-    assertEquals('', statusEl.innerHTML.trim());
+    assertEquals(
+        cloudPrintDeprecationWarningsSuppressed, additionalInfoEl.hidden);
+    const dest3Status = cloudPrintDeprecationWarningsSuppressed ?
+        '' :
+        'printerNotSupportedWarning';
+    assertEquals(dest3Status, statusEl.innerHTML);
   }
 
   test(assert(destination_select_test_cros.TestNames.UpdateStatus), function() {
diff --git a/chrome/test/data/webui/print_preview/print_preview_ui_browsertest.js b/chrome/test/data/webui/print_preview/print_preview_ui_browsertest.js
index 2e1e50b..8e2cafd4 100644
--- a/chrome/test/data/webui/print_preview/print_preview_ui_browsertest.js
+++ b/chrome/test/data/webui/print_preview/print_preview_ui_browsertest.js
@@ -655,6 +655,52 @@
   this.runMochaTest(destination_dialog_test.TestNames.UserAccounts);
 });
 
+TEST_F(
+    'PrintPreviewDestinationDialogTest', 'CloudPrinterDeprecationWarnings',
+    function() {
+      this.runMochaTest(
+          destination_dialog_test.TestNames.CloudPrinterDeprecationWarnings);
+    });
+
+TEST_F(
+    'PrintPreviewDestinationDialogTest',
+    'CloudPrinterDeprecationWarningsSuppressed', function() {
+      this.runMochaTest(destination_dialog_test.TestNames
+                            .CloudPrinterDeprecationWarningsSuppressed);
+    });
+
+// TODO(crbug.com/1111985): Different tests are needed because |isChromeOS| from
+// cr.m.js does not match the behavior of the |OS_CHROMEOS| macro on Lacros.
+GEN('#if defined(OS_CHROMEOS)');
+TEST_F(
+    'PrintPreviewDestinationDialogTest', 'SaveToDriveDeprecationWarningsCros',
+    function() {
+      this.runMochaTest(
+          destination_dialog_test.TestNames.SaveToDriveDeprecationWarningsCros);
+    });
+
+TEST_F(
+    'PrintPreviewDestinationDialogTest',
+    'SaveToDriveDeprecationWarningsSuppressedCros', function() {
+      this.runMochaTest(destination_dialog_test.TestNames
+                            .SaveToDriveDeprecationWarningsSuppressedCros);
+    });
+GEN('#else');
+TEST_F(
+    'PrintPreviewDestinationDialogTest', 'SaveToDriveDeprecationWarnings',
+    function() {
+      this.runMochaTest(
+          destination_dialog_test.TestNames.SaveToDriveDeprecationWarnings);
+    });
+
+TEST_F(
+    'PrintPreviewDestinationDialogTest',
+    'SaveToDriveDeprecationWarningsSuppressed', function() {
+      this.runMochaTest(destination_dialog_test.TestNames
+                            .SaveToDriveDeprecationWarningsSuppressed);
+    });
+GEN('#endif');
+
 // eslint-disable-next-line no-var
 var PrintPreviewAdvancedDialogTest = class extends PrintPreviewTest {
   /** @override */
diff --git a/chrome/test/data/webui/settings/chromeos/crostini_page_test.js b/chrome/test/data/webui/settings/chromeos/crostini_page_test.js
index 3ea6061..61aa753 100644
--- a/chrome/test/data/webui/settings/chromeos/crostini_page_test.js
+++ b/chrome/test/data/webui/settings/chromeos/crostini_page_test.js
@@ -100,7 +100,6 @@
         showCrostiniExportImport: true,
         showCrostiniContainerUpgrade: true,
         showCrostiniPortForwarding: true,
-        showCrostiniMic: true,
         showCrostiniDiskResize: true,
       });
 
diff --git a/chrome/test/data/webui/signin/signin_reauth_test.js b/chrome/test/data/webui/signin/signin_reauth_test.js
index 6935dd06..17e776bb 100644
--- a/chrome/test/data/webui/signin/signin_reauth_test.js
+++ b/chrome/test/data/webui/signin/signin_reauth_test.js
@@ -38,7 +38,7 @@
   test('LoadPage', function() {
     assertDefaultLocale();
     assertEquals(
-        'Save this and other passwords in your Google Account?',
+        'Use your Google Account to save and fill passwords?',
         app.$.signinReauthTitle.textContent.trim());
   });
 
@@ -52,41 +52,33 @@
     return browserProxy.whenCalled('cancel');
   });
 
-  test('RequiresReauth', async () => {
-    await browserProxy.whenCalled('initialize');
-    assertFalse(isVisible(app.$.confirmButton));
-    assertFalse(isVisible(app.$.cancelButton));
-    assertTrue(isVisible(app.$$('paper-spinner-lite')));
+  const requires_reauth_test_params = [
+    {
+      requires_reauth: true,
+    },
+    {
+      requires_reauth: false,
+    },
+  ];
 
-    webUIListenerCallback('reauth-type-received', true);
-    flush();
+  requires_reauth_test_params.forEach(function(params) {
+    test('ButtonsVisibilityAndFocus', async () => {
+      await browserProxy.whenCalled('initialize');
+      assertFalse(isVisible(app.$.confirmButton));
+      assertFalse(isVisible(app.$.cancelButton));
+      assertTrue(isVisible(app.$$('paper-spinner-lite')));
 
-    assertTrue(isVisible(app.$.confirmButton));
-    assertFalse(isVisible(app.$.cancelButton));
-    assertFalse(isVisible(app.$$('paper-spinner-lite')));
+      webUIListenerCallback('reauth-type-received', params.requires_reauth);
+      flush();
 
-    assertEquals(getDeepActiveElement(), app.$.confirmButton);
+      assertTrue(isVisible(app.$.confirmButton));
+      assertTrue(isVisible(app.$.cancelButton));
+      assertFalse(isVisible(app.$$('paper-spinner-lite')));
 
-    assertDefaultLocale();
-    assertEquals('Next', app.$.confirmButton.textContent.trim());
-  });
+      assertEquals(getDeepActiveElement(), app.$.confirmButton);
 
-  test('DoesNotRequireReauth', async () => {
-    await browserProxy.whenCalled('initialize');
-    assertFalse(isVisible(app.$.confirmButton));
-    assertFalse(isVisible(app.$.cancelButton));
-    assertTrue(isVisible(app.$$('paper-spinner-lite')));
-
-    webUIListenerCallback('reauth-type-received', false);
-    flush();
-
-    assertTrue(isVisible(app.$.confirmButton));
-    assertTrue(isVisible(app.$.cancelButton));
-    assertFalse(isVisible(app.$$('paper-spinner-lite')));
-
-    assertEquals(getDeepActiveElement(), app.$.confirmButton);
-
-    assertDefaultLocale();
-    assertEquals('Save', app.$.confirmButton.textContent.trim());
+      assertDefaultLocale();
+      assertEquals('Yes', app.$.confirmButton.textContent.trim());
+    });
   });
 });
diff --git a/chromeos/BUILD.gn b/chromeos/BUILD.gn
index 5d16951a..4f2a4d4 100644
--- a/chromeos/BUILD.gn
+++ b/chromeos/BUILD.gn
@@ -322,9 +322,6 @@
 
       # crbug.com/1114752
       "arc.ContainerMount",
-
-      # crbug.com/1117324
-      "session.RetrieveActiveSessions",
     ]
   }
 
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index 76329f5c8..bd08417 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-13416.0.0
\ No newline at end of file
+13417.0.0
\ No newline at end of file
diff --git a/chromeos/components/media_app_ui/resources/js/receiver.js b/chromeos/components/media_app_ui/resources/js/receiver.js
index 39be702..6dad6e9 100644
--- a/chromeos/components/media_app_ui/resources/js/receiver.js
+++ b/chromeos/components/media_app_ui/resources/js/receiver.js
@@ -77,6 +77,9 @@
     const renameResponse =
         /** @type {!RenameFileResponse} */ (await parentMessagePipe.sendMessage(
             Message.RENAME_FILE, {token: this.token, newFilename: newName}));
+    if (renameResponse.renameResult === RenameResult.SUCCESS) {
+      this.name = newName;
+    }
     return renameResponse.renameResult;
   }
 
diff --git a/chromeos/components/media_app_ui/test/driver_api.js b/chromeos/components/media_app_ui/test/driver_api.js
index 5d3de65..79d78fe 100644
--- a/chromeos/components/media_app_ui/test/driver_api.js
+++ b/chromeos/components/media_app_ui/test/driver_api.js
@@ -16,6 +16,7 @@
  * @typedef {{
  *     deleteLastFile: (boolean|undefined),
  *     getFileErrors: (boolean|undefined),
+ *     getLastFileName: (boolean|undefined),
  *     navigate: (string|undefined),
  *     overwriteLastFile: (string|undefined),
  *     pathToRoot: (!Array<string>|undefined),
diff --git a/chromeos/components/media_app_ui/test/guest_query_receiver.js b/chromeos/components/media_app_ui/test/guest_query_receiver.js
index 5ad1fe98..fc74cb7f 100644
--- a/chromeos/components/media_app_ui/test/guest_query_receiver.js
+++ b/chromeos/components/media_app_ui/test/guest_query_receiver.js
@@ -125,6 +125,8 @@
         assertCast(lastReceivedFileList).files.map(file => file.error).join();
   } else if (data.openFile) {
     await DELEGATE.openFile();
+  } else if (data.getLastFileName) {
+    result = firstReceivedItem().name;
   }
   return {testQueryResult: result, testQueryResultData: extraResultData};
 }
diff --git a/chromeos/components/media_app_ui/test/media_app_ui_browsertest.js b/chromeos/components/media_app_ui/test/media_app_ui_browsertest.js
index e66c031..ef02893 100644
--- a/chromeos/components/media_app_ui/test/media_app_ui_browsertest.js
+++ b/chromeos/components/media_app_ui/test/media_app_ui_browsertest.js
@@ -783,8 +783,14 @@
       testResponse.testQueryResult, 'renameOriginalFile resolved success');
   // The original file that was renamed got deleted.
   assertEquals(file2Handle, directory.lastDeleted);
+  // The new file has the right name in the trusted context.
   assertEquals(directory.files.length, 2);
   assertEquals(directory.files[entryIndex].name, 'new_file_name.png');
+  assertEquals(currentFiles[entryIndex].handle.name, 'new_file_name.png');
+  assertEquals(currentFiles[entryIndex].file.name, 'new_file_name.png');
+  // The new file has the right name in the untrusted context.
+  testResponse = await sendTestMessage({getLastFileName: true});
+  assertEquals(testResponse.testQueryResult, 'new_file_name.png');
   // The new file uses the same token as the old file.
   assertEquals(currentFiles[entryIndex].token, file2Token);
   // Check the new file written has the correct data.
diff --git a/chromeos/components/remote_apps/mojom/BUILD.gn b/chromeos/components/remote_apps/mojom/BUILD.gn
new file mode 100644
index 0000000..f91de8a
--- /dev/null
+++ b/chromeos/components/remote_apps/mojom/BUILD.gn
@@ -0,0 +1,10 @@
+import("//mojo/public/tools/bindings/mojom.gni")
+
+mojom("mojom") {
+  sources = [ "remote_apps.mojom" ]
+
+  public_deps = [
+    "//mojo/public/mojom/base",
+    "//url/mojom:url_mojom_gurl",
+  ]
+}
diff --git a/chromeos/components/remote_apps/mojom/OWNERS b/chromeos/components/remote_apps/mojom/OWNERS
new file mode 100644
index 0000000..08850f4
--- /dev/null
+++ b/chromeos/components/remote_apps/mojom/OWNERS
@@ -0,0 +1,2 @@
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
diff --git a/chromeos/components/remote_apps/mojom/remote_apps.mojom b/chromeos/components/remote_apps/mojom/remote_apps.mojom
new file mode 100644
index 0000000..594fffe
--- /dev/null
+++ b/chromeos/components/remote_apps/mojom/remote_apps.mojom
@@ -0,0 +1,50 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module chromeos.remote_apps.mojom;
+
+import "url/mojom/url.mojom";
+
+// Interface for communication between an extension and the Remote Apps
+// Manager.
+interface RemoteApps {
+  // Adds a Remote Apps folder to the launcher. Empty folders are not shown in
+  // the launcher.
+  //
+  // Input parameters:
+  // - |name|: the name of the folder.
+  //
+  // Output parameters:
+  // - |folder_id|: the ID of the newly added folder.
+  // - |error|: A string describing the error if any.
+  AddFolder(string name) => (string? folder_id, string? error);
+
+  // Adds a Remote Apps app to the launcher.
+  //
+  // Input parameters:
+  // - |name|: the name of the app.
+  // - |folder_id|: the ID of the parent folder. An empty string indicates the
+  //                app has no parent folder.
+  // - |icon_url|: a URL pointing to an image which represents the app's icon.
+  //
+  // Output parameters:
+  // - |app_id|: the ID of the newly added app.
+  // - |error|: A string describing the error if any.
+  AddApp(string name, string folder_id, url.mojom.Url icon_url) =>
+      (string? app_id, string? error);
+};
+
+// Factory for creating an instance of RemoteApps.
+interface RemoteAppsFactory {
+  // Creates an instance of RemoteApps.
+  Create(pending_receiver<RemoteApps> remote_apps,
+         pending_remote<RemoteAppLaunchObserver> observer);
+};
+
+// A RemoteAppLaunchObserver gets notifications when a remote app is launched.
+interface RemoteAppLaunchObserver {
+  // Invoked when a remote app is launched. |app_id| is the ID of the app which
+  // was launched.
+  OnRemoteAppLaunched(string app_id);
+};
diff --git a/chromeos/components/telemetry_extension_ui/BUILD.gn b/chromeos/components/telemetry_extension_ui/BUILD.gn
index 87ed39a..00e068c 100644
--- a/chromeos/components/telemetry_extension_ui/BUILD.gn
+++ b/chromeos/components/telemetry_extension_ui/BUILD.gn
@@ -40,6 +40,7 @@
 source_set("unit_tests") {
   testonly = true
   sources = [
+    "diagnostics_service_converters_unittest.cc",
     "probe_service_converters_unittest.cc",
     "probe_service_unittest.cc",
   ]
diff --git a/chromeos/components/telemetry_extension_ui/diagnostics_service.cc b/chromeos/components/telemetry_extension_ui/diagnostics_service.cc
index ad1f7a3..89347c3 100644
--- a/chromeos/components/telemetry_extension_ui/diagnostics_service.cc
+++ b/chromeos/components/telemetry_extension_ui/diagnostics_service.cc
@@ -48,4 +48,21 @@
       std::move(callback)));
 }
 
+void DiagnosticsService::GetRoutineUpdate(
+    int32_t id,
+    health::mojom::DiagnosticRoutineCommandEnum command,
+    bool include_output,
+    GetRoutineUpdateCallback callback) {
+  GetService()->GetRoutineUpdate(
+      id, diagnostics_service_converters::Convert(command), include_output,
+      base::BindOnce(
+          [](health::mojom::DiagnosticsService::GetRoutineUpdateCallback
+                 callback,
+             cros_healthd::mojom::RoutineUpdatePtr ptr) {
+            std::move(callback).Run(
+                diagnostics_service_converters::ConvertPtr(std::move(ptr)));
+          },
+          std::move(callback)));
+}
+
 }  // namespace chromeos
diff --git a/chromeos/components/telemetry_extension_ui/diagnostics_service.h b/chromeos/components/telemetry_extension_ui/diagnostics_service.h
index 6991f95..9251698c 100644
--- a/chromeos/components/telemetry_extension_ui/diagnostics_service.h
+++ b/chromeos/components/telemetry_extension_ui/diagnostics_service.h
@@ -27,6 +27,10 @@
 
  private:
   void GetAvailableRoutines(GetAvailableRoutinesCallback callback) override;
+  void GetRoutineUpdate(int32_t id,
+                        health::mojom::DiagnosticRoutineCommandEnum command,
+                        bool include_output,
+                        GetRoutineUpdateCallback callback) override;
 
   // Ensures that |service_| created and connected to the
   // CrosHealthdProbeService.
diff --git a/chromeos/components/telemetry_extension_ui/diagnostics_service_converters.cc b/chromeos/components/telemetry_extension_ui/diagnostics_service_converters.cc
index afc6456..15a5b40 100644
--- a/chromeos/components/telemetry_extension_ui/diagnostics_service_converters.cc
+++ b/chromeos/components/telemetry_extension_ui/diagnostics_service_converters.cc
@@ -12,7 +12,85 @@
 namespace chromeos {
 namespace diagnostics_service_converters {
 
-namespace {
+namespace unchecked {
+
+health::mojom::RoutineUpdatePtr UncheckedConvertPtr(
+    cros_healthd::mojom::RoutineUpdatePtr input) {
+  return health::mojom::RoutineUpdate::New(
+      input->progress_percent, Convert(std::move(input->output)),
+      ConvertPtr(std::move(input->routine_update_union)));
+}
+
+health::mojom::RoutineUpdateUnionPtr UncheckedConvertPtr(
+    cros_healthd::mojom::RoutineUpdateUnionPtr input) {
+  switch (input->which()) {
+    case cros_healthd::mojom::RoutineUpdateUnion::Tag::INTERACTIVE_UPDATE:
+      return health::mojom::RoutineUpdateUnion::NewInteractiveUpdate(
+          ConvertPtr(std::move(input->get_interactive_update())));
+    case cros_healthd::mojom::RoutineUpdateUnion::Tag::NONINTERACTIVE_UPDATE:
+      return health::mojom::RoutineUpdateUnion::NewNoninteractiveUpdate(
+          ConvertPtr(std::move(input->get_noninteractive_update())));
+  }
+}
+
+health::mojom::InteractiveRoutineUpdatePtr UncheckedConvertPtr(
+    cros_healthd::mojom::InteractiveRoutineUpdatePtr input) {
+  return health::mojom::InteractiveRoutineUpdate::New(
+      Convert(input->user_message));
+}
+
+health::mojom::NonInteractiveRoutineUpdatePtr UncheckedConvertPtr(
+    cros_healthd::mojom::NonInteractiveRoutineUpdatePtr input) {
+  return health::mojom::NonInteractiveRoutineUpdate::New(
+      Convert(input->status), std::move(input->status_message));
+}
+
+}  // namespace unchecked
+
+health::mojom::DiagnosticRoutineStatusEnum Convert(
+    cros_healthd::mojom::DiagnosticRoutineStatusEnum input) {
+  switch (input) {
+    case cros_healthd::mojom::DiagnosticRoutineStatusEnum::kReady:
+      return health::mojom::DiagnosticRoutineStatusEnum::kReady;
+    case cros_healthd::mojom::DiagnosticRoutineStatusEnum::kRunning:
+      return health::mojom::DiagnosticRoutineStatusEnum::kRunning;
+    case cros_healthd::mojom::DiagnosticRoutineStatusEnum::kWaiting:
+      return health::mojom::DiagnosticRoutineStatusEnum::kWaiting;
+    case cros_healthd::mojom::DiagnosticRoutineStatusEnum::kPassed:
+      return health::mojom::DiagnosticRoutineStatusEnum::kPassed;
+    case cros_healthd::mojom::DiagnosticRoutineStatusEnum::kFailed:
+      return health::mojom::DiagnosticRoutineStatusEnum::kFailed;
+    case cros_healthd::mojom::DiagnosticRoutineStatusEnum::kError:
+      return health::mojom::DiagnosticRoutineStatusEnum::kError;
+    case cros_healthd::mojom::DiagnosticRoutineStatusEnum::kCancelled:
+      return health::mojom::DiagnosticRoutineStatusEnum::kCancelled;
+    case cros_healthd::mojom::DiagnosticRoutineStatusEnum::kFailedToStart:
+      return health::mojom::DiagnosticRoutineStatusEnum::kFailedToStart;
+    case cros_healthd::mojom::DiagnosticRoutineStatusEnum::kRemoved:
+      return health::mojom::DiagnosticRoutineStatusEnum::kRemoved;
+    case cros_healthd::mojom::DiagnosticRoutineStatusEnum::kCancelling:
+      return health::mojom::DiagnosticRoutineStatusEnum::kCancelling;
+  }
+  NOTREACHED();
+  return static_cast<health::mojom::DiagnosticRoutineStatusEnum>(
+      static_cast<int>(health::mojom::DiagnosticRoutineStatusEnum::kMaxValue) +
+      1);
+}
+
+health::mojom::DiagnosticRoutineUserMessageEnum Convert(
+    cros_healthd::mojom::DiagnosticRoutineUserMessageEnum input) {
+  switch (input) {
+    case cros_healthd::mojom::DiagnosticRoutineUserMessageEnum::kUnplugACPower:
+      return health::mojom::DiagnosticRoutineUserMessageEnum::kUnplugACPower;
+    case cros_healthd::mojom::DiagnosticRoutineUserMessageEnum::kPlugInACPower:
+      return health::mojom::DiagnosticRoutineUserMessageEnum::kPlugInACPower;
+  }
+  NOTREACHED();
+  return static_cast<health::mojom::DiagnosticRoutineUserMessageEnum>(
+      static_cast<int>(
+          health::mojom::DiagnosticRoutineUserMessageEnum::kMaxValue) +
+      1);
+}
 
 base::Optional<health::mojom::DiagnosticRoutineEnum> Convert(
     cros_healthd::mojom::DiagnosticRoutineEnum input) {
@@ -48,7 +126,29 @@
   return base::nullopt;
 }
 
-}  // namespace
+std::string Convert(mojo::ScopedHandle handle) {
+  // TODO (b:162051831): Read from handle and put contents in a string
+  return std::string();
+}
+
+cros_healthd::mojom::DiagnosticRoutineCommandEnum Convert(
+    health::mojom::DiagnosticRoutineCommandEnum input) {
+  switch (input) {
+    case health::mojom::DiagnosticRoutineCommandEnum::kContinue:
+      return cros_healthd::mojom::DiagnosticRoutineCommandEnum::kContinue;
+    case health::mojom::DiagnosticRoutineCommandEnum::kCancel:
+      return cros_healthd::mojom::DiagnosticRoutineCommandEnum::kCancel;
+    case health::mojom::DiagnosticRoutineCommandEnum::kGetStatus:
+      return cros_healthd::mojom::DiagnosticRoutineCommandEnum::kGetStatus;
+    case health::mojom::DiagnosticRoutineCommandEnum::kRemove:
+      return cros_healthd::mojom::DiagnosticRoutineCommandEnum::kRemove;
+  }
+  NOTREACHED();
+  return static_cast<cros_healthd::mojom::DiagnosticRoutineCommandEnum>(
+      static_cast<int>(
+          cros_healthd::mojom::DiagnosticRoutineCommandEnum::kMaxValue) +
+      1);
+}
 
 std::vector<health::mojom::DiagnosticRoutineEnum> Convert(
     const std::vector<cros_healthd::mojom::DiagnosticRoutineEnum>& input) {
diff --git a/chromeos/components/telemetry_extension_ui/diagnostics_service_converters.h b/chromeos/components/telemetry_extension_ui/diagnostics_service_converters.h
index be10cd6..b1f9307 100644
--- a/chromeos/components/telemetry_extension_ui/diagnostics_service_converters.h
+++ b/chromeos/components/telemetry_extension_ui/diagnostics_service_converters.h
@@ -9,10 +9,13 @@
 #error Diagnostics service should only be included in unofficial builds.
 #endif
 
+#include <string>
+#include <utility>
 #include <vector>
 
 #include "chromeos/components/telemetry_extension_ui/mojom/diagnostics_service.mojom-forward.h"
 #include "chromeos/services/cros_healthd/public/mojom/cros_healthd_diagnostics.mojom-forward.h"
+#include "mojo/public/cpp/system/handle.h"
 
 namespace chromeos {
 namespace diagnostics_service_converters {
@@ -20,9 +23,42 @@
 // This file contains helper functions used by DiagnosticsService to convert its
 // types to/from cros_healthd DiagnosticsService types.
 
+namespace unchecked {
+
+health::mojom::RoutineUpdatePtr UncheckedConvertPtr(
+    cros_healthd::mojom::RoutineUpdatePtr input);
+
+health::mojom::RoutineUpdateUnionPtr UncheckedConvertPtr(
+    cros_healthd::mojom::RoutineUpdateUnionPtr input);
+
+health::mojom::InteractiveRoutineUpdatePtr UncheckedConvertPtr(
+    cros_healthd::mojom::InteractiveRoutineUpdatePtr input);
+
+health::mojom::NonInteractiveRoutineUpdatePtr UncheckedConvertPtr(
+    cros_healthd::mojom::NonInteractiveRoutineUpdatePtr input);
+
+}  // namespace unchecked
+
 std::vector<health::mojom::DiagnosticRoutineEnum> Convert(
     const std::vector<cros_healthd::mojom::DiagnosticRoutineEnum>& input);
 
+health::mojom::DiagnosticRoutineUserMessageEnum Convert(
+    cros_healthd::mojom::DiagnosticRoutineUserMessageEnum input);
+
+health::mojom::DiagnosticRoutineStatusEnum Convert(
+    cros_healthd::mojom::DiagnosticRoutineStatusEnum input);
+
+cros_healthd::mojom::DiagnosticRoutineCommandEnum Convert(
+    health::mojom::DiagnosticRoutineCommandEnum input);
+
+std::string Convert(mojo::ScopedHandle handle);
+
+template <class InputT>
+auto ConvertPtr(InputT input) {
+  return (!input.is_null()) ? unchecked::UncheckedConvertPtr(std::move(input))
+                            : nullptr;
+}
+
 }  // namespace diagnostics_service_converters
 }  // namespace chromeos
 
diff --git a/chromeos/components/telemetry_extension_ui/diagnostics_service_converters_unittest.cc b/chromeos/components/telemetry_extension_ui/diagnostics_service_converters_unittest.cc
new file mode 100644
index 0000000..5519c58
--- /dev/null
+++ b/chromeos/components/telemetry_extension_ui/diagnostics_service_converters_unittest.cc
@@ -0,0 +1,68 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/components/telemetry_extension_ui/diagnostics_service_converters.h"
+
+#include "chromeos/components/telemetry_extension_ui/mojom/diagnostics_service.mojom.h"
+#include "chromeos/services/cros_healthd/public/mojom/cros_healthd_diagnostics.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromeos {
+namespace diagnostics_service_converters {
+
+TEST(DiagnosticsServiceConvertersTest, ConvertDiagnosticRoutineStatusEnum) {
+  namespace cros_healthd = ::chromeos::cros_healthd::mojom;
+  namespace health = ::chromeos::health::mojom;
+
+  EXPECT_EQ(Convert(cros_healthd::DiagnosticRoutineStatusEnum::kReady),
+            health::DiagnosticRoutineStatusEnum::kReady);
+  EXPECT_EQ(Convert(cros_healthd::DiagnosticRoutineStatusEnum::kRunning),
+            health::DiagnosticRoutineStatusEnum::kRunning);
+  EXPECT_EQ(Convert(cros_healthd::DiagnosticRoutineStatusEnum::kWaiting),
+            health::DiagnosticRoutineStatusEnum::kWaiting);
+  EXPECT_EQ(Convert(cros_healthd::DiagnosticRoutineStatusEnum::kPassed),
+            health::DiagnosticRoutineStatusEnum::kPassed);
+  EXPECT_EQ(Convert(cros_healthd::DiagnosticRoutineStatusEnum::kFailed),
+            health::DiagnosticRoutineStatusEnum::kFailed);
+  EXPECT_EQ(Convert(cros_healthd::DiagnosticRoutineStatusEnum::kError),
+            health::DiagnosticRoutineStatusEnum::kError);
+  EXPECT_EQ(Convert(cros_healthd::DiagnosticRoutineStatusEnum::kCancelled),
+            health::DiagnosticRoutineStatusEnum::kCancelled);
+  EXPECT_EQ(Convert(cros_healthd::DiagnosticRoutineStatusEnum::kFailedToStart),
+            health::DiagnosticRoutineStatusEnum::kFailedToStart);
+  EXPECT_EQ(Convert(cros_healthd::DiagnosticRoutineStatusEnum::kRemoved),
+            health::DiagnosticRoutineStatusEnum::kRemoved);
+  EXPECT_EQ(Convert(cros_healthd::DiagnosticRoutineStatusEnum::kCancelling),
+            health::DiagnosticRoutineStatusEnum::kCancelling);
+}
+
+TEST(DiagnosticsServiceConvertersTest,
+     ConvertDiagnosticRoutineUserMessageEnum) {
+  namespace cros_healthd = ::chromeos::cros_healthd::mojom;
+  namespace health = ::chromeos::health::mojom;
+
+  EXPECT_EQ(
+      Convert(cros_healthd::DiagnosticRoutineUserMessageEnum::kUnplugACPower),
+      health::DiagnosticRoutineUserMessageEnum::kUnplugACPower);
+  EXPECT_EQ(
+      Convert(cros_healthd::DiagnosticRoutineUserMessageEnum::kPlugInACPower),
+      health::DiagnosticRoutineUserMessageEnum::kPlugInACPower);
+}
+
+TEST(DiagnosticsServiceConvertersTest, ConvertDiagnosticRoutineCommandEnum) {
+  namespace cros_healthd = ::chromeos::cros_healthd::mojom;
+  namespace health = ::chromeos::health::mojom;
+
+  EXPECT_EQ(Convert(health::DiagnosticRoutineCommandEnum::kContinue),
+            cros_healthd::DiagnosticRoutineCommandEnum::kContinue);
+  EXPECT_EQ(Convert(health::DiagnosticRoutineCommandEnum::kCancel),
+            cros_healthd::DiagnosticRoutineCommandEnum::kCancel);
+  EXPECT_EQ(Convert(health::DiagnosticRoutineCommandEnum::kGetStatus),
+            cros_healthd::DiagnosticRoutineCommandEnum::kGetStatus);
+  EXPECT_EQ(Convert(health::DiagnosticRoutineCommandEnum::kRemove),
+            cros_healthd::DiagnosticRoutineCommandEnum::kRemove);
+}
+
+}  // namespace diagnostics_service_converters
+}  // namespace chromeos
diff --git a/chromeos/components/telemetry_extension_ui/mojom/diagnostics_service.mojom b/chromeos/components/telemetry_extension_ui/mojom/diagnostics_service.mojom
index 7b4c6bd..68f2367 100644
--- a/chromeos/components/telemetry_extension_ui/mojom/diagnostics_service.mojom
+++ b/chromeos/components/telemetry_extension_ui/mojom/diagnostics_service.mojom
@@ -22,6 +22,24 @@
   // Returns an array of all diagnostic routines that the platform supports.
   GetAvailableRoutines()
       => (array<DiagnosticRoutineEnum> available_routines);
+
+  // Sends commands to an existing routine. Also returns status information for
+  // the routine.
+  //
+  // The request:
+  // * |id| - specifies which routine the command will be sent to. This must be
+  //          the same id that was returned from the RunSomeRoutine function
+  //          call used to create the routine.
+  // * |command| - command to send the routine.
+  // * |include_output| - whether or not the response should include any output
+  //                      accumulated from the routine.
+  //
+  // The response:
+  // * |routine_update| - status information for the specified routine. See
+  //                      cros_healthd_diagnostics.mojom for the structure.
+  GetRoutineUpdate(int32 id, DiagnosticRoutineCommandEnum command,
+                   bool include_output)
+      => (RoutineUpdate routine_update);
 };
 
 // Enumeration of each of the diagnostics routines the platform may support.
@@ -41,3 +59,77 @@
   kPrimeSearch = 11,
   kBatteryDischarge = 12,
 };
+
+// Enumeration of each of the possible statuses for a diagnostics routine.
+[Extensible]
+enum DiagnosticRoutineStatusEnum {
+  kReady = 0,  // Routine is ready to start.
+  kRunning = 1,  // Routine is currently running.
+  kWaiting = 2,  // Routine needs user input to continue.
+  kPassed = 3,  // Routine completed and passed.
+  kFailed = 4,  // Routine completed and failed.
+  kError = 5,  // An error prevented the routine from completing.
+  kCancelled = 6,  // Routine was cancelled before completion. A cancelled
+                   // routine's information can still be accessed with a
+                   // GetRoutineUpdateRequest.
+  kFailedToStart = 7,  // Routine could not be created.
+  kRemoved = 8,  // Routine has been removed and is no longer valid.
+  kCancelling = 9,  // Routine is in the process of being cancelled.
+};
+
+// Enumeration of each of the messages a diagnostics routine can pass back.
+// These messages prompt interaction from the user of the routine.
+[Extensible]
+enum DiagnosticRoutineUserMessageEnum {
+  kUnplugACPower = 0,  // The user needs to unplug the AC power cord.
+  kPlugInACPower = 1,  // The user needs to plug in the AC power cord.
+};
+
+// Enumeration of the possible commands to send a diagnostics routine.
+[Extensible]
+enum DiagnosticRoutineCommandEnum {
+  kContinue = 0,  // Resume a routine that is waiting.
+  kCancel = 1,  // Cancelled routines must still be removed before the routine
+                // is destroyed.
+  kGetStatus = 2,  // Used to get status but not otherwise control a routine.
+  kRemove = 3,  // All routines which started successfully must be removed,
+                // otherwise they will persist on the system. This makes sure
+                // the routine is terminated before removing it.
+};
+
+// Status fields specific to interactive routines.
+struct InteractiveRoutineUpdate {
+  // Request for user action. This message should be localized and displayed to
+  // the user.
+  DiagnosticRoutineUserMessageEnum user_message;
+};
+
+// Status fields specific to noninteractive routines.
+struct NonInteractiveRoutineUpdate {
+  // Current status of the routine.
+  DiagnosticRoutineStatusEnum status;
+  // More detailed status - for example, if |status| was kError,
+  // |status_message| would describe the error encountered, like "failed to
+  // read file."
+  string status_message;
+};
+
+// Responses will be either interactive or noninteractive.
+union RoutineUpdateUnion {
+  InteractiveRoutineUpdate interactive_update;
+  NonInteractiveRoutineUpdate noninteractive_update;
+};
+
+// Response type for GetRoutineUpdate.
+struct RoutineUpdate {
+  // Percent complete, must be between 0 and 100, inclusive.
+  uint32 progress_percent;
+  // Any accumulated output, like logs, from the routine. This field is only
+  // valid when GetRoutineUpdate (see cros_healthd.mojom) is called with
+  // include_output = true.
+  string? output;
+
+  // Information specific to the type of response - interactive or
+  // noninteractive.
+  RoutineUpdateUnion routine_update_union;
+};
diff --git a/chromeos/components/telemetry_extension_ui/resources/message_types.js b/chromeos/components/telemetry_extension_ui/resources/message_types.js
index c56361b..bfb8c3f 100644
--- a/chromeos/components/telemetry_extension_ui/resources/message_types.js
+++ b/chromeos/components/telemetry_extension_ui/resources/message_types.js
@@ -19,6 +19,7 @@
  */
 dpsl_internal.Message = {
   DIAGNOSTICS_AVAILABLE_ROUTINES: 'DiagnosticsService.GetAvailableRoutines',
+  DIAGNOSTICS_ROUTINE_UPDATE: 'DiagnosticsService.GetRoutineUpdate',
   PROBE_TELEMETRY_INFO: 'ProbeService.ProbeTelemetryInfo',
 };
 
@@ -37,6 +38,23 @@
 dpsl_internal.DiagnosticsGetAvailableRoutinesResponse;
 
 /**
+ * Request message sent by the unprivileged context to the privileged
+ * context to request a routine update.
+ * @typedef {{
+ *   routineId: !number,
+ *   command: !string,
+ *   includeOutput: !boolean}}
+ */
+dpsl_internal.DiagnosticsGetRoutineUpdateRequest;
+
+/**
+ * Response message sent by the privileged context containing diagnostic
+ * routine update information.
+ * @typedef { !Object | !Error }
+ */
+dpsl_internal.DiagnosticsGetRoutineUpdateResponse;
+
+/**
  * Request message sent by the unprivileged context to request the privileged
  * context to probe telemetry information
  * @typedef { !Array<!string> }
diff --git a/chromeos/components/telemetry_extension_ui/resources/trusted.js b/chromeos/components/telemetry_extension_ui/resources/trusted.js
index c373206..52205a1 100644
--- a/chromeos/components/telemetry_extension_ui/resources/trusted.js
+++ b/chromeos/components/telemetry_extension_ui/resources/trusted.js
@@ -42,7 +42,7 @@
      * @type { !Map<!chromeos.health.mojom.DiagnosticRoutineEnum, !string> }
      * @const
      */
-    this.routineToEnum_ = new Map([
+    this.enumToRoutineName_ = new Map([
       [routineEnum.kBatteryCapacity, 'battery-capacity'],
       [routineEnum.kBatteryHealth, 'battery-health'],
       [routineEnum.kUrandom, 'urandom'],
@@ -58,8 +58,69 @@
       [routineEnum.kBatteryDischarge, 'battery-discharge'],
     ]);
 
-    if (this.routineToEnum_.size != routineEnum.MAX_VALUE + 1) {
-      throw RangeError('routineToEnum_ does not contain all items from enum!');
+    if (this.enumToRoutineName_.size !== routineEnum.MAX_VALUE + 1) {
+      throw RangeError(
+          'enumToRoutineName_ does not contain all items from enum!');
+    }
+
+    const commandEnum = chromeos.health.mojom.DiagnosticRoutineCommandEnum;
+
+    /**
+     * @type { !Map<!string,
+     *     !chromeos.health.mojom.DiagnosticRoutineCommandEnum> }
+     * @const
+     */
+    this.commandToEnum_ = new Map([
+      ['continue', commandEnum.kContinue],
+      ['cancel', commandEnum.kCancel],
+      ['get-status', commandEnum.kGetStatus],
+      ['remove', commandEnum.kRemove],
+    ]);
+
+    if (this.commandToEnum_.size !== commandEnum.MAX_VALUE + 1) {
+      throw RangeError('commandToEnum_ does not contain all items from enum!');
+    }
+
+    const statusEnum = chromeos.health.mojom.DiagnosticRoutineStatusEnum;
+
+    /**
+     * @type { !Map<!chromeos.health.mojom.DiagnosticRoutineStatusEnum, !string>
+     *     }
+     * @const
+     */
+    this.enumToStatus_ = new Map([
+      [statusEnum.kReady, 'ready'],
+      [statusEnum.kRunning, 'running'],
+      [statusEnum.kWaiting, 'waiting'],
+      [statusEnum.kPassed, 'passed'],
+      [statusEnum.kFailed, 'failed'],
+      [statusEnum.kError, 'error'],
+      [statusEnum.kCancelled, 'cancelled'],
+      [statusEnum.kFailedToStart, 'failed-to-start'],
+      [statusEnum.kRemoved, 'removed'],
+      [statusEnum.kCancelling, 'cancelling'],
+    ]);
+
+    if (this.enumToStatus_.size !== statusEnum.MAX_VALUE + 1) {
+      throw RangeError('enumToStatus_ does not contain all items from enum!');
+    }
+
+    const userMessageEnum =
+        chromeos.health.mojom.DiagnosticRoutineUserMessageEnum;
+
+    /**
+     * @type { !Map<!chromeos.health.mojom.DiagnosticRoutineUserMessageEnum,
+     *     !string> }
+     * @const
+     */
+    this.enumToUserMessage_ = new Map([
+      [userMessageEnum.kUnplugACPower, 'unplug-ac-power'],
+      [userMessageEnum.kPlugInACPower, 'plug-in-ac-power'],
+    ]);
+
+    if (this.enumToUserMessage_.size !== userMessageEnum.MAX_VALUE + 1) {
+      throw RangeError(
+          'enumToUserMessage_ does not contain all items from enum!');
     }
   }
 
@@ -69,10 +130,10 @@
    */
   convertRoutines(routines) {
     return routines.map((routine) => {
-      if (!this.routineToEnum_.has(routine)) {
+      if (!this.enumToRoutineName_.has(routine)) {
         throw TypeError(`Diagnostic routine '${routine}' is unknown.`);
       }
-      return this.routineToEnum_.get(routine);
+      return this.enumToRoutineName_.get(routine);
     });
   }
 
@@ -85,6 +146,113 @@
         await getOrCreateDiagnosticsService().getAvailableRoutines();
     return this.convertRoutines(availableRoutines.availableRoutines);
   };
+
+  /**
+   * @param { !number } id
+   * @return { !number }
+   */
+  convertRoutineId(id) {
+    if (id < -2147483648 || id > 2147483647) {
+      throw RangeError(`Diagnostic routine id '${id}' is out of int32 range.`);
+    }
+    return id;
+  }
+
+  /**
+   * @param { !string } command
+   * @return { !chromeos.health.mojom.DiagnosticRoutineCommandEnum }
+   */
+  convertCommandToEnum(command) {
+    if (!this.commandToEnum_.has(command)) {
+      throw TypeError(`Diagnostic command '${command}' is unknown.`);
+    }
+
+    return this.commandToEnum_.get(command);
+  }
+
+  /**
+   * @param { !chromeos.health.mojom.DiagnosticRoutineStatusEnum } status
+   * @return { !string | null }
+   */
+  convertStatus(status) {
+    if (!this.enumToStatus_.has(status)) {
+      return null;
+    }
+
+    return this.enumToStatus_.get(status);
+  }
+
+  /**
+   * @param { !chromeos.health.mojom.DiagnosticRoutineUserMessageEnum }
+   *     userMessage
+   * @return { !string | null }
+   */
+  convertUserMessage(userMessage) {
+    if (!this.enumToUserMessage_.has(userMessage)) {
+      return null;
+    }
+
+    return this.enumToUserMessage_.get(userMessage);
+  }
+
+  /**
+   * @param { !chromeos.health.mojom.RoutineUpdate } routineUpdate
+   * @return { !Object }
+   */
+  convertRoutineUpdate(routineUpdate) {
+    let result = {
+      progressPercent: routineUpdate.progressPercent,
+      output: routineUpdate.output,
+      routineUpdateUnion: {}
+    };
+
+    const updateUnion = routineUpdate.routineUpdateUnion;
+
+    if (typeof updateUnion.noninteractiveUpdate !== 'undefined' &&
+        updateUnion.noninteractiveUpdate !== null) {
+      let status = this.convertStatus(updateUnion.noninteractiveUpdate.status);
+
+      result.routineUpdateUnion = {
+        noninteractiveUpdate: {
+          status: status,
+          statusMessage: updateUnion.noninteractiveUpdate.statusMessage
+        }
+      };
+    }
+
+    if (typeof updateUnion.interactiveUpdate !== 'undefined' &&
+        updateUnion.interactiveUpdate !== null) {
+      let message =
+          this.convertUserMessage(updateUnion.interactiveUpdate.userMessage);
+      result.routineUpdateUnion = {interactiveUpdate: {userMessage: message}};
+    }
+
+    return result;
+  }
+
+  /**
+   * Runs a command on a routine.
+   * @param { !Object } message
+   * @return { !Promise<dpsl_internal.DiagnosticsGetRoutineUpdateResponse> }
+   */
+  async handleGetRoutineUpdate(message) {
+    const request =
+        /** @type {dpsl_internal.DiagnosticsGetRoutineUpdateRequest} */ (
+            message);
+
+    let routine, command;
+    try {
+      routine = this.convertRoutineId(request.routineId);
+      command = this.convertCommandToEnum(request.command);
+    } catch (/** @type {!Error} */ error) {
+      return error;
+    }
+
+    const response = await getOrCreateDiagnosticsService().getRoutineUpdate(
+        routine, command, request.includeOutput);
+
+    return this.convertRoutineUpdate(response.routineUpdate);
+  };
 };
 
 const diagnosticsProxy = new DiagnosticsProxy();
@@ -145,11 +313,12 @@
       return null;
     }
 
-    // After this closure compiler knows that input is {!Object}.
     if (typeof input !== 'object') {
       return input;
     }
 
+    // At this point, closure compiler knows that the input is {!Object}.
+
     // 1 rule: convert objects like { value: X } to X, where X is a number.
     if (Object.entries(input).length === 1 &&
         typeof input['value'] === 'number') {
@@ -209,5 +378,9 @@
     () => diagnosticsProxy.handleGetAvailableRoutines());
 
 untrustedMessagePipe.registerHandler(
+    dpsl_internal.Message.DIAGNOSTICS_ROUTINE_UPDATE,
+    (message) => diagnosticsProxy.handleGetRoutineUpdate(message));
+
+untrustedMessagePipe.registerHandler(
     dpsl_internal.Message.PROBE_TELEMETRY_INFO,
     (message) => telemetryProxy.handleProbeTelemetryInfo(message));
diff --git a/chromeos/components/telemetry_extension_ui/resources/untrusted.js b/chromeos/components/telemetry_extension_ui/resources/untrusted.js
index a9250050..27c71794 100644
--- a/chromeos/components/telemetry_extension_ui/resources/untrusted.js
+++ b/chromeos/components/telemetry_extension_ui/resources/untrusted.js
@@ -50,6 +50,30 @@
                   dpsl_internal.Message.DIAGNOSTICS_AVAILABLE_ROUTINES));
       return response;
     }
+
+    /**
+     * Requests a command to be run on a diagnostic routine.
+     * @param { !number } routineId
+     * @param { !string } command
+     * @param { !boolean } includeOutput
+     * @return { !Promise<!Object> }
+     * @public
+     */
+    async sendCommandToRoutine(routineId, command, includeOutput) {
+      const message =
+          /** @type {dpsl_internal.DiagnosticsGetRoutineUpdateRequest} */ ({
+            routineId: routineId,
+            command: command,
+            includeOutput: includeOutput,
+          });
+      const response =
+          /** @type {!Object} */ (await messagePipe.sendMessage(
+              dpsl_internal.Message.DIAGNOSTICS_ROUTINE_UPDATE, message));
+      if (response instanceof Error) {
+        throw response;
+      }
+      return response;
+    }
   };
 
   /**
diff --git a/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.cc b/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.cc
index 329b528..d065c644 100644
--- a/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.cc
+++ b/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.cc
@@ -61,6 +61,50 @@
   SandboxedWebUiAppTestBase::SetUpCommandLine(command_line);
 }
 
+void TelemetryExtensionUiBrowserTest::
+    ConfigureDiagnosticsForInteractiveUpdate() {
+  namespace cros_healthd = ::chromeos::cros_healthd::mojom;
+
+  auto input = cros_healthd::RoutineUpdate::New();
+  auto routineUpdateUnion = cros_healthd::RoutineUpdateUnion::New();
+  auto interactiveRoutineUpdate = cros_healthd::InteractiveRoutineUpdate::New();
+
+  interactiveRoutineUpdate->user_message =
+      cros_healthd::DiagnosticRoutineUserMessageEnum::kUnplugACPower;
+
+  routineUpdateUnion->set_interactive_update(
+      std::move(interactiveRoutineUpdate));
+
+  input->progress_percent = 0;
+  input->routine_update_union = std::move(routineUpdateUnion);
+
+  chromeos::cros_healthd::FakeCrosHealthdClient::Get()
+      ->SetGetRoutineUpdateResponseForTesting(input);
+}
+
+void TelemetryExtensionUiBrowserTest::
+    ConfigureDiagnosticsForNonInteractiveUpdate() {
+  namespace cros_healthd = ::chromeos::cros_healthd::mojom;
+
+  auto input = cros_healthd::RoutineUpdate::New();
+  auto routineUpdateUnion = cros_healthd::RoutineUpdateUnion::New();
+  auto nonInteractiveRoutineUpdate =
+      cros_healthd::NonInteractiveRoutineUpdate::New();
+
+  nonInteractiveRoutineUpdate->status =
+      cros_healthd::DiagnosticRoutineStatusEnum::kReady;
+  nonInteractiveRoutineUpdate->status_message = "Routine ran by Google.";
+
+  routineUpdateUnion->set_noninteractive_update(
+      std::move(nonInteractiveRoutineUpdate));
+
+  input->progress_percent = 3147483771;
+  input->routine_update_union = std::move(routineUpdateUnion);
+
+  chromeos::cros_healthd::FakeCrosHealthdClient::Get()
+      ->SetGetRoutineUpdateResponseForTesting(input);
+}
+
 void TelemetryExtensionUiBrowserTest::SetUpOnMainThread() {
   {
     namespace cros_diagnostics = ::chromeos::cros_healthd::mojom;
diff --git a/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.h b/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.h
index 43597adcf..cf06e1a 100644
--- a/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.h
+++ b/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.h
@@ -18,6 +18,9 @@
   TelemetryExtensionUiBrowserTest& operator=(
       const TelemetryExtensionUiBrowserTest&) = delete;
 
+  void ConfigureDiagnosticsForInteractiveUpdate();
+  void ConfigureDiagnosticsForNonInteractiveUpdate();
+
   // SandboxedWebUiAppTestBase overrides:
   void SetUpCommandLine(base::CommandLine* command_line) override;
   void SetUpOnMainThread() override;
diff --git a/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.js b/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.js
index f818f0e..89dc67b 100644
--- a/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.js
+++ b/chromeos/components/telemetry_extension_ui/test/telemetry_extension_ui_browsertest.js
@@ -67,6 +67,55 @@
   testDone();
 });
 
+TEST_F('TelemetryExtensionUIBrowserTest', 'ConvertDiagnosticsEnums', () => {
+  // Unit tests for convertRoutineId
+  assertEquals(diagnosticsProxy.convertRoutineId(234089591), 234089591);
+
+  // Unit tests for convertCommandToEnum
+  const commandEnum = chromeos.health.mojom.DiagnosticRoutineCommandEnum;
+
+  assertEquals(
+      diagnosticsProxy.convertCommandToEnum('continue'), commandEnum.kContinue);
+  assertEquals(
+      diagnosticsProxy.convertCommandToEnum('cancel'), commandEnum.kCancel);
+  assertEquals(
+      diagnosticsProxy.convertCommandToEnum('get-status'),
+      commandEnum.kGetStatus);
+  assertEquals(
+      diagnosticsProxy.convertCommandToEnum('remove'), commandEnum.kRemove);
+
+  // Unit tests for convertStatus
+  const statusEnum = chromeos.health.mojom.DiagnosticRoutineStatusEnum;
+
+  assertEquals(diagnosticsProxy.convertStatus(statusEnum.kReady), 'ready');
+  assertEquals(diagnosticsProxy.convertStatus(statusEnum.kRunning), 'running');
+  assertEquals(diagnosticsProxy.convertStatus(statusEnum.kWaiting), 'waiting');
+  assertEquals(diagnosticsProxy.convertStatus(statusEnum.kPassed), 'passed');
+  assertEquals(diagnosticsProxy.convertStatus(statusEnum.kFailed), 'failed');
+  assertEquals(diagnosticsProxy.convertStatus(statusEnum.kError), 'error');
+  assertEquals(
+      diagnosticsProxy.convertStatus(statusEnum.kCancelled), 'cancelled');
+  assertEquals(
+      diagnosticsProxy.convertStatus(statusEnum.kFailedToStart),
+      'failed-to-start');
+  assertEquals(diagnosticsProxy.convertStatus(statusEnum.kRemoved), 'removed');
+  assertEquals(
+      diagnosticsProxy.convertStatus(statusEnum.kCancelling), 'cancelling');
+
+  // Unit tests for convertUserMessage
+  const userMessageEnum =
+      chromeos.health.mojom.DiagnosticRoutineUserMessageEnum;
+
+  assertEquals(
+      diagnosticsProxy.convertUserMessage(userMessageEnum.kUnplugACPower),
+      'unplug-ac-power');
+  assertEquals(
+      diagnosticsProxy.convertUserMessage(userMessageEnum.kPlugInACPower),
+      'plug-in-ac-power');
+
+  testDone();
+});
+
 // Tests that Telemetry.convert method correctly converts Mojo types into WebIDL
 // types.
 TEST_F(
@@ -130,6 +179,14 @@
 
 TEST_F(
     'TelemetryExtensionUIBrowserTest',
+    'UntrustedDiagnosticsRequestRoutineUpdateUnknownArguments', async () => {
+      await runTestInUntrusted(
+          'UntrustedDiagnosticsRequestRoutineUpdateUnknownArguments');
+      testDone();
+    });
+
+TEST_F(
+    'TelemetryExtensionUIBrowserTest',
     'UntrustedRequestTelemetryInfoUnknownCategory', async () => {
       await runTestInUntrusted('UntrustedRequestTelemetryInfoUnknownCategory');
       testDone();
@@ -142,6 +199,38 @@
       testDone();
     });
 
+var DiagnosticsInteractiveRoutineUpdate =
+    class extends TelemetryExtensionUIBrowserTest {
+  /** @override */
+  testGenPreamble() {
+    GEN('ConfigureDiagnosticsForInteractiveUpdate();');
+  }
+}
+
+TEST_F(
+    'DiagnosticsInteractiveRoutineUpdate',
+    'UntrustedDiagnosticsRequestInteractiveRoutineUpdate', async () => {
+      await runTestInUntrusted(
+          'UntrustedDiagnosticsRequestInteractiveRoutineUpdate');
+      testDone();
+    });
+
+var DiagnosticsNonInteractiveRoutineUpdate =
+    class extends TelemetryExtensionUIBrowserTest {
+  /** @override */
+  testGenPreamble() {
+    GEN('ConfigureDiagnosticsForNonInteractiveUpdate();');
+  }
+}
+
+TEST_F(
+    'DiagnosticsNonInteractiveRoutineUpdate',
+    'UntrustedDiagnosticsRequestNonInteractiveRoutineUpdate', async () => {
+      await runTestInUntrusted(
+          'UntrustedDiagnosticsRequestNonInteractiveRoutineUpdate');
+      testDone();
+    });
+
 /**
  * @implements {chromeos.health.mojom.ProbeServiceInterface}
  */
diff --git a/chromeos/components/telemetry_extension_ui/test/untrusted_browsertest.js b/chromeos/components/telemetry_extension_ui/test/untrusted_browsertest.js
index a5971c9..2a7dbc1 100644
--- a/chromeos/components/telemetry_extension_ui/test/untrusted_browsertest.js
+++ b/chromeos/components/telemetry_extension_ui/test/untrusted_browsertest.js
@@ -88,6 +88,48 @@
   ]);
 });
 
+// Tests that sendCommandToRoutine throws the correct errors
+// when unknown routines or commands are passed as input.
+UNTRUSTED_TEST(
+    'UntrustedDiagnosticsRequestRoutineUpdateUnknownArguments', async () => {
+      let caughtError;
+      try {
+        await chromeos.diagnostics.sendCommandToRoutine(
+            9007199254740991, 'remove', true);
+      } catch (error) {
+        caughtError = error;
+      }
+
+      assertEquals(caughtError.name, 'RangeError');
+      assertEquals(
+          caughtError.message,
+          `Diagnostic routine id '9007199254740991' is out of int32 range.`);
+
+      try {
+        await chromeos.diagnostics.sendCommandToRoutine(
+            -9007199254740991, 'remove', true);
+      } catch (error) {
+        caughtError = error;
+      }
+
+      assertEquals(caughtError.name, 'RangeError');
+      assertEquals(
+          caughtError.message,
+          `Diagnostic routine id '-9007199254740991' is out of int32 range.`);
+
+      try {
+        await chromeos.diagnostics.sendCommandToRoutine(
+            123456789, 'this-command-must-not-exist', true);
+      } catch (error) {
+        caughtError = error;
+      }
+
+      assertEquals(caughtError.name, 'TypeError');
+      assertEquals(
+          caughtError.message,
+          `Diagnostic command \'this-command-must-not-exist\' is unknown.`);
+    });
+
 // Tests that TelemetryInfo can be successfully requested from
 // from chrome-untrusted://.
 UNTRUSTED_TEST('UntrustedRequestTelemetryInfo', async () => {
@@ -118,6 +160,36 @@
   });
 });
 
+// Tests that sendCommandToRoutine returns the correct Object
+// for an interactive routine.
+UNTRUSTED_TEST(
+    'UntrustedDiagnosticsRequestInteractiveRoutineUpdate', async () => {
+      const response = await chromeos.diagnostics.sendCommandToRoutine(
+          987654321, 'remove', true);
+      assertDeepEquals(response, {
+        progressPercent: 0,
+        output: '',
+        routineUpdateUnion:
+            {interactiveUpdate: {userMessage: 'unplug-ac-power'}}
+      });
+    });
+
+// Tests that sendCommandToRoutine returns the correct Object
+// for a non-interactive routine.
+UNTRUSTED_TEST(
+    'UntrustedDiagnosticsRequestNonInteractiveRoutineUpdate', async () => {
+      const response = await chromeos.diagnostics.sendCommandToRoutine(
+          135797531, 'remove', true);
+      assertDeepEquals(response, {
+        progressPercent: 3147483771,
+        output: '',
+        routineUpdateUnion: {
+          noninteractiveUpdate:
+              {status: 'ready', statusMessage: 'Routine ran by Google.'}
+        }
+      });
+    });
+
 // Tests that TelemetryInfo can be successfully requested from
 // from chrome-untrusted://.
 UNTRUSTED_TEST('UntrustedRequestTelemetryInfoWithInterceptor', async () => {
diff --git a/chromeos/components/web_applications/js2gtest_support.externs.js b/chromeos/components/web_applications/js2gtest_support.externs.js
index 1304827..8786121 100644
--- a/chromeos/components/web_applications/js2gtest_support.externs.js
+++ b/chromeos/components/web_applications/js2gtest_support.externs.js
@@ -26,6 +26,7 @@
 const testing = {
   Test: class {
     get browsePreload() {}
+    get testGenPreamble() {}
     get extraLibraries() {}
     get isAsync() {}
     get featureList() {}
diff --git a/chromeos/constants/chromeos_features.cc b/chromeos/constants/chromeos_features.cc
index 4d7052f..018fd32 100644
--- a/chromeos/constants/chromeos_features.cc
+++ b/chromeos/constants/chromeos_features.cc
@@ -29,11 +29,6 @@
 const base::Feature kAmbientModePhotoPreviewFeature{
     "ChromeOSAmbientModePhotoPreview", base::FEATURE_DISABLED_BY_DEFAULT};
 
-// Controls whether to allow Dev channel to use Prod server feature.
-const base::Feature kAmbientModeDevUseProdFeature{
-    "ChromeOSAmbientModeDevChannelUseProdServer",
-    base::FEATURE_DISABLED_BY_DEFAULT};
-
 // Controls whether to enable ARC ADB sideloading support.
 const base::Feature kArcAdbSideloadingFeature{
     "ArcAdbSideloading", base::FEATURE_DISABLED_BY_DEFAULT};
@@ -109,10 +104,6 @@
 const base::Feature kImeOptionsInSettings{"ImeOptionsInSettings",
                                           base::FEATURE_DISABLED_BY_DEFAULT};
 
-// Enables or disables Crostini port forwarding.
-const base::Feature kCrostiniPortForwarding{"CrostiniPortForwarding",
-                                            base::FEATURE_ENABLED_BY_DEFAULT};
-
 // Enables or disables Crostini Disk Resizing.
 const base::Feature kCrostiniDiskResizing{"CrostiniDiskResizing",
                                           base::FEATURE_ENABLED_BY_DEFAULT};
@@ -121,10 +112,6 @@
 const base::Feature kCrostiniUseBusterImage{"CrostiniUseBusterImage",
                                             base::FEATURE_ENABLED_BY_DEFAULT};
 
-// Enables the option to share the mic with Crostini or not
-const base::Feature kCrostiniShowMicSetting{"CrostiniShowMicSetting",
-                                            base::FEATURE_ENABLED_BY_DEFAULT};
-
 // Enables or disables Crostini GPU support.
 const base::Feature kCrostiniGpuSupport{"CrostiniGpuSupport",
                                         base::FEATURE_DISABLED_BY_DEFAULT};
@@ -539,10 +526,6 @@
   return base::FeatureList::IsEnabled(kAmbientModePhotoPreviewFeature);
 }
 
-bool IsAmbientModeDevUseProdEnabled() {
-  return base::FeatureList::IsEnabled(kAmbientModeDevUseProdFeature);
-}
-
 bool IsBetterUpdateEnabled() {
   return base::FeatureList::IsEnabled(kBetterUpdateScreen);
 }
diff --git a/chromeos/constants/chromeos_features.h b/chromeos/constants/chromeos_features.h
index 1d45c026..8792a67 100644
--- a/chromeos/constants/chromeos_features.h
+++ b/chromeos/constants/chromeos_features.h
@@ -22,8 +22,6 @@
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kAmbientModePhotoPreviewFeature;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
-extern const base::Feature kAmbientModeDevUseProdFeature;
-COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kArcAdbSideloadingFeature;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kArcManagedAdbSideloadingSupport;
@@ -54,8 +52,6 @@
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kChildSpecificSignin;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
-extern const base::Feature kCrostiniPortForwarding;
-COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kCrostiniDiskResizing;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kCrostiniUseBusterImage;
@@ -64,8 +60,6 @@
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kCrostiniUsbAllowUnsupported;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
-extern const base::Feature kCrostiniShowMicSetting;
-COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kCrostiniUseDlc;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kDisableCryptAuthV1DeviceSync;
@@ -234,7 +228,6 @@
 
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) bool IsAmbientModeEnabled();
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) bool IsAmbientModePhotoPreviewEnabled();
-COMPONENT_EXPORT(CHROMEOS_CONSTANTS) bool IsAmbientModeDevUseProdEnabled();
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) bool IsAssistantEnabled();
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) bool IsBetterUpdateEnabled();
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) bool IsChildSpecificSigninEnabled();
diff --git a/chromeos/profiles/airmont.afdo.newest.txt b/chromeos/profiles/airmont.afdo.newest.txt
index 28b14e0..0c095f78 100644
--- a/chromeos/profiles/airmont.afdo.newest.txt
+++ b/chromeos/profiles/airmont.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-airmont-86-4183.59-1597659923-benchmark-86.0.4232.0-r1-redacted.afdo.xz
+chromeos-chrome-amd64-airmont-86-4183.59-1597659923-benchmark-86.0.4237.0-r1-redacted.afdo.xz
diff --git a/chromeos/profiles/broadwell.afdo.newest.txt b/chromeos/profiles/broadwell.afdo.newest.txt
index af5b304..038b8c5 100644
--- a/chromeos/profiles/broadwell.afdo.newest.txt
+++ b/chromeos/profiles/broadwell.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-broadwell-86-4183.59-1597658380-benchmark-86.0.4232.0-r1-redacted.afdo.xz
+chromeos-chrome-amd64-broadwell-86-4183.59-1597658380-benchmark-86.0.4237.0-r1-redacted.afdo.xz
diff --git a/chromeos/profiles/silvermont.afdo.newest.txt b/chromeos/profiles/silvermont.afdo.newest.txt
index 164a488..a16c8b39 100644
--- a/chromeos/profiles/silvermont.afdo.newest.txt
+++ b/chromeos/profiles/silvermont.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-silvermont-86-4183.59-1597657121-benchmark-86.0.4232.0-r1-redacted.afdo.xz
+chromeos-chrome-amd64-silvermont-86-4183.59-1597657121-benchmark-86.0.4237.0-r1-redacted.afdo.xz
diff --git a/components/arc/intent_helper/intent_filter.cc b/components/arc/intent_helper/intent_filter.cc
index 54dd2a0..dc179b8b 100644
--- a/components/arc/intent_helper/intent_filter.cc
+++ b/components/arc/intent_helper/intent_filter.cc
@@ -39,6 +39,7 @@
 IntentFilter::IntentFilter(
     const std::string& package_name,
     const std::string& activity_name,
+    const std::string& activity_label,
     std::vector<std::string> actions,
     std::vector<IntentFilter::AuthorityEntry> authorities,
     std::vector<IntentFilter::PatternMatcher> paths,
@@ -46,6 +47,7 @@
     std::vector<std::string> mime_types)
     : package_name_(package_name),
       activity_name_(activity_name),
+      activity_label_(activity_label),
       actions_(std::move(actions)),
       authorities_(std::move(authorities)),
       schemes_(std::move(schemes)),
diff --git a/components/arc/intent_helper/intent_filter.h b/components/arc/intent_helper/intent_filter.h
index 508de2b3..6569176 100644
--- a/components/arc/intent_helper/intent_filter.h
+++ b/components/arc/intent_helper/intent_filter.h
@@ -77,6 +77,7 @@
                std::vector<std::string> mime_types);
   IntentFilter(const std::string& package_name,
                const std::string& activity_name,
+               const std::string& activity_label,
                std::vector<std::string> actions,
                std::vector<IntentFilter::AuthorityEntry> authorities,
                std::vector<IntentFilter::PatternMatcher> paths,
@@ -90,6 +91,7 @@
 
   const std::string& package_name() const { return package_name_; }
   const std::string& activity_name() const { return activity_name_; }
+  const std::string& activity_label() const { return activity_label_; }
   const std::vector<std::string>& actions() const { return actions_; }
   const std::vector<AuthorityEntry>& authorities() const {
     return authorities_;
@@ -104,6 +106,7 @@
 
   std::string package_name_;
   std::string activity_name_;
+  std::string activity_label_;
   std::vector<std::string> actions_;
   std::vector<AuthorityEntry> authorities_;
   std::vector<PatternMatcher> paths_;
diff --git a/components/arc/intent_helper/intent_filter_mojom_traits.cc b/components/arc/intent_helper/intent_filter_mojom_traits.cc
index 719ac2a..1dc5ff8 100644
--- a/components/arc/intent_helper/intent_filter_mojom_traits.cc
+++ b/components/arc/intent_helper/intent_filter_mojom_traits.cc
@@ -43,9 +43,14 @@
   if (!data.ReadActivityName(&activity_name))
     return false;
 
-  *out = arc::IntentFilter(package_name, activity_name, std::move(actions),
-                           std::move(authorities), std::move(paths),
-                           std::move(schemes), std::move(mime_types));
+  std::string activity_label;
+  if (!data.ReadActivityLabel(&activity_label))
+    return false;
+
+  *out = arc::IntentFilter(package_name, activity_name, activity_label,
+                           std::move(actions), std::move(authorities),
+                           std::move(paths), std::move(schemes),
+                           std::move(mime_types));
   return true;
 }
 
diff --git a/components/arc/intent_helper/intent_filter_mojom_traits.h b/components/arc/intent_helper/intent_filter_mojom_traits.h
index 0c9ce34..16b4b622 100644
--- a/components/arc/intent_helper/intent_filter_mojom_traits.h
+++ b/components/arc/intent_helper/intent_filter_mojom_traits.h
@@ -54,6 +54,10 @@
     return r.activity_name();
   }
 
+  static const std::string& activity_label(const arc::IntentFilter& r) {
+    return r.activity_label();
+  }
+
   static bool Read(arc::mojom::IntentFilterDataView data,
                    arc::IntentFilter* out);
 };
diff --git a/components/arc/mojom/intent_helper.mojom b/components/arc/mojom/intent_helper.mojom
index 6751cbb..930a517 100644
--- a/components/arc/mojom/intent_helper.mojom
+++ b/components/arc/mojom/intent_helper.mojom
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 //
-// Next MinVersion: 41
+// Next MinVersion: 42
 
 module arc.mojom;
 
@@ -87,6 +87,9 @@
 
   // Activity which registered the filter.
   [MinVersion=39] string? activity_name;
+
+  // The label shown to the user for this activity.
+  [MinVersion=41] string? activity_label;
 };
 
 // Describes a package that can handle an intent.
diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleWebsiteSettings.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleWebsiteSettings.java
index a43f713..6db148e 100644
--- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleWebsiteSettings.java
+++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleWebsiteSettings.java
@@ -141,8 +141,6 @@
     // The Preference key for chooser object permissions.
     private static final String CHOOSER_PERMISSION_PREFERENCE_KEY = "chooser_permission_list";
 
-    // The maximum order of a permission preference.
-    private int mMaxPermissionOrder;
     // The number of user and policy chosen object permissions displayed.
     private int mObjectUserPermissionCount;
     private int mObjectPolicyPermissionCount;
@@ -350,7 +348,7 @@
         }
         SettingsUtils.addPreferencesFromResource(this, R.xml.single_website_preferences);
 
-        mMaxPermissionOrder = 0;
+        int maxPermissionOrder = 0;
         PreferenceScreen preferenceScreen = getPreferenceScreen();
         // Iterate over preferences in reverse order because some preferences will be removed during
         // this setup, causing indices of later preferences to change.
@@ -359,10 +357,10 @@
             setUpPreference(preference);
             if (getContentSettingsTypeFromPreferenceKey(preference.getKey())
                     != ContentSettingsType.DEFAULT) {
-                mMaxPermissionOrder = Math.max(mMaxPermissionOrder, preference.getOrder());
+                maxPermissionOrder = Math.max(maxPermissionOrder, preference.getOrder());
             }
         }
-        setUpChosenObjectPreferences(mMaxPermissionOrder);
+        setUpChosenObjectPreferences(maxPermissionOrder);
         setUpOsWarningPreferences();
 
         setUpAdsInformationalBanner();
@@ -795,7 +793,13 @@
 
     private boolean hasPermissionsPreferences() {
         if (mObjectUserPermissionCount > 0 || mObjectPolicyPermissionCount > 0) return true;
-        if (mMaxPermissionOrder > 0) return true;
+        PreferenceScreen preferenceScreen = getPreferenceScreen();
+        for (int i = 0; i < preferenceScreen.getPreferenceCount(); i++) {
+            String key = preferenceScreen.getPreference(i).getKey();
+            if (getContentSettingsTypeFromPreferenceKey(key) != ContentSettingsType.DEFAULT) {
+                return true;
+            }
+        }
         return false;
     }
 
diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/Website.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/Website.java
index ed57ec1..58413d2e2 100644
--- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/Website.java
+++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/Website.java
@@ -41,7 +41,6 @@
 
     private LocalStorageInfo mLocalStorageInfo;
     private final List<StorageInfo> mStorageInfo = new ArrayList<>();
-    private int mStorageInfoCallbacksLeft;
 
     // The collection of chooser-based permissions (e.g. USB device access) granted to this site.
     // Each entry declares its own ContentSettingsType and so depending on how this object was
@@ -265,9 +264,9 @@
             BrowserContextHandle browserContextHandle, final StoredDataClearedCallback callback) {
         // Wait for callbacks from each mStorageInfo and another callback from
         // mLocalStorageInfo.
-        mStorageInfoCallbacksLeft = mStorageInfo.size() + 1;
+        int[] storageInfoCallbacksLeft = {mStorageInfo.size() + 1};
         StorageInfoClearedCallback clearedCallback = () -> {
-            if (--mStorageInfoCallbacksLeft == 0) callback.onStoredDataCleared();
+            if (--storageInfoCallbacksLeft[0] == 0) callback.onStoredDataCleared();
         };
         if (mLocalStorageInfo != null) {
             mLocalStorageInfo.clear(browserContextHandle, clearedCallback);
diff --git a/components/management_strings.grdp b/components/management_strings.grdp
index 7893c0e..8500595 100644
--- a/components/management_strings.grdp
+++ b/components/management_strings.grdp
@@ -83,6 +83,9 @@
     <message name="IDS_MANAGEMENT_DEVICE_REPORTING" desc="Title of the types of device reporting section of the page">
       Device
     </message>
+    <message name="IDS_MANAGEMENT_PROXY_SERVER_PRIVACY_DISCLOSURE" desc="Message stating that administrators can see user's traffic when connected to a proxy server.">
+      Administrators of this device have configured your network connection, which may allow them to see your network traffic, including which websites you visit.
+    </message>
     <message name="IDS_MANAGEMENT_DEVICE_CONFIGURATION" desc="Message telling users that their administrator has set some specific policies on their device">
       Your administrator can see:
     </message>
@@ -122,9 +125,6 @@
     <message name="IDS_MANAGEMENT_REPORT_ANDROID_APPLICATIONS" desc="Message stating that administrators can see user's installed Android applications.">
       Which Google Play apps you have installed
     </message>
-    <message name="IDS_MANAGEMENT_REPORT_PROXY_SERVER" desc="Message stating that administrators can see user's traffic when connected to a proxy server.">
-      Websites you visit and the contents of not secure pages
-    </message>
     <message name="IDS_MANAGEMENT_REPORT_PLUGIN_VM" desc="Message telling users that Plugin VM can collect data.">
       Your administrator has allowed <ph name="APP_NAME">$1<ex>Plugin VM</ex></ph> to collect diagnostics data to improve the product experience. See <ph name="BEGIN_LINK">&lt;a target="_blank" href="https://www.parallels.com/pcep"&gt;</ph>https://www.parallels.com/pcep<ph name="END_LINK">&lt;/a&gt;</ph> for more information.
     </message>
diff --git a/components/management_strings_grdp/IDS_MANAGEMENT_PROXY_SERVER_PRIVACY_DISCLOSURE.png.sha1 b/components/management_strings_grdp/IDS_MANAGEMENT_PROXY_SERVER_PRIVACY_DISCLOSURE.png.sha1
new file mode 100644
index 0000000..5ce6d4a
--- /dev/null
+++ b/components/management_strings_grdp/IDS_MANAGEMENT_PROXY_SERVER_PRIVACY_DISCLOSURE.png.sha1
@@ -0,0 +1 @@
+b0b948edf91fae1dd9a31455eab621918025fa36
\ No newline at end of file
diff --git a/components/management_strings_grdp/IDS_MANAGEMENT_REPORT_PROXY_SERVER.png.sha1 b/components/management_strings_grdp/IDS_MANAGEMENT_REPORT_PROXY_SERVER.png.sha1
deleted file mode 100644
index bcf287d..0000000
--- a/components/management_strings_grdp/IDS_MANAGEMENT_REPORT_PROXY_SERVER.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-a35ceef3ad7c9e9ad08ac68788efc2a63720c506
\ No newline at end of file
diff --git a/components/page_load_metrics/browser/observers/core_page_load_metrics_observer.cc b/components/page_load_metrics/browser/observers/core_page_load_metrics_observer.cc
index b36f6189..56ee2b95 100644
--- a/components/page_load_metrics/browser/observers/core_page_load_metrics_observer.cc
+++ b/components/page_load_metrics/browser/observers/core_page_load_metrics_observer.cc
@@ -324,20 +324,6 @@
 const char kHistogramBackForwardCacheEvent[] =
     "PageLoad.BackForwardCache.Event";
 
-const char kHistogramFontPreloadFirstPaint[] =
-    "PageLoad.Clients.FontPreload.PaintTiming.NavigationToFirstPaint";
-const char kHistogramFontPreloadFirstContentfulPaint[] =
-    "PageLoad.Clients.FontPreload.PaintTiming.NavigationToFirstContentfulPaint";
-const char kHistogramFontPreloadLargestContentfulPaint[] =
-    "PageLoad.Clients.FontPreload.PaintTiming."
-    "NavigationToLargestContentfulPaint";
-const char kHistogramFontPreloadLargestImagePaint[] =
-    "PageLoad.Clients.FontPreload.PaintTiming."
-    "NavigationToLargestImagePaint";
-const char kHistogramFontPreloadLargestTextPaint[] =
-    "PageLoad.Clients.FontPreload.PaintTiming."
-    "NavigationToLargestTextPaint";
-
 // Navigation metrics from the navigation start.
 const char kHistogramNavigationTimingNavigationStartToFirstRequestStart[] =
     "PageLoad.Experimental.NavigationTiming.NavigationStartToFirstRequestStart";
@@ -465,12 +451,6 @@
     PAGE_LOAD_HISTOGRAM(internal::kHistogramFirstPaint,
                         timing.paint_timing->first_paint.value());
 
-    // Note: This depends on PageLoadMetrics internally processing loading
-    // behavior before timing metrics if they come in the same IPC update.
-    if (font_preload_started_before_rendering_observed_) {
-      PAGE_LOAD_HISTOGRAM(internal::kHistogramFontPreloadFirstPaint,
-                          timing.paint_timing->first_paint.value());
-    }
     if (timing.input_to_navigation_start) {
       PAGE_LOAD_HISTOGRAM(internal::kHistogramInputToFirstPaint,
                           timing.input_to_navigation_start.value() +
@@ -516,13 +496,6 @@
                         timing.paint_timing->first_contentful_paint.value() -
                             timing.parse_timing->parse_start.value());
 
-    // Note: This depends on PageLoadMetrics internally processing loading
-    // behavior before timing metrics if they come in the same IPC update.
-    if (font_preload_started_before_rendering_observed_) {
-      PAGE_LOAD_HISTOGRAM(internal::kHistogramFontPreloadFirstContentfulPaint,
-                          timing.paint_timing->first_contentful_paint.value());
-    }
-
     // Emit a trace event to highlight a long navigation to first contentful
     // paint.
     if (timing.paint_timing->first_contentful_paint >
@@ -992,30 +965,6 @@
   }
 
   const page_load_metrics::ContentfulPaintTimingInfo&
-      main_frame_largest_image_paint = GetDelegate()
-                                           .GetLargestContentfulPaintHandler()
-                                           .MainFrameLargestImagePaint();
-  if (main_frame_largest_image_paint.ContainsValidTime() &&
-      WasStartedInForegroundOptionalEventInForeground(
-          main_frame_largest_image_paint.Time(), GetDelegate()) &&
-      font_preload_started_before_rendering_observed_) {
-    PAGE_LOAD_HISTOGRAM(internal::kHistogramFontPreloadLargestImagePaint,
-                        main_frame_largest_image_paint.Time().value());
-  }
-
-  const page_load_metrics::ContentfulPaintTimingInfo&
-      main_frame_largest_text_paint = GetDelegate()
-                                          .GetLargestContentfulPaintHandler()
-                                          .MainFrameLargestTextPaint();
-  if (main_frame_largest_text_paint.ContainsValidTime() &&
-      WasStartedInForegroundOptionalEventInForeground(
-          main_frame_largest_text_paint.Time(), GetDelegate()) &&
-      font_preload_started_before_rendering_observed_) {
-    PAGE_LOAD_HISTOGRAM(internal::kHistogramFontPreloadLargestTextPaint,
-                        main_frame_largest_text_paint.Time().value());
-  }
-
-  const page_load_metrics::ContentfulPaintTimingInfo&
       main_frame_largest_contentful_paint =
           GetDelegate()
               .GetLargestContentfulPaintHandler()
@@ -1048,13 +997,6 @@
         GetDelegate().GetNavigationStart() +
             all_frames_largest_contentful_paint.Time().value(),
         "data", all_frames_largest_contentful_paint.DataAsTraceValue());
-
-    // Note: This depends on PageLoadMetrics internally processing loading
-    // behavior before timing metrics if they come in the same IPC update.
-    if (font_preload_started_before_rendering_observed_) {
-      PAGE_LOAD_HISTOGRAM(internal::kHistogramFontPreloadLargestContentfulPaint,
-                          all_frames_largest_contentful_paint.Time().value());
-    }
   }
 
   const page_load_metrics::ContentfulPaintTimingInfo&
@@ -1260,12 +1202,3 @@
       internal::kHistogramBackForwardCacheEvent,
       internal::PageLoadBackForwardCacheEvent::kRestoreFromBackForwardCache);
 }
-
-void CorePageLoadMetricsObserver::OnLoadingBehaviorObserved(
-    content::RenderFrameHost* rfh,
-    int behavior_flag) {
-  if (behavior_flag & blink::LoadingBehaviorFlag::
-                          kLoadingBehaviorFontPreloadStartedBeforeRendering) {
-    font_preload_started_before_rendering_observed_ = true;
-  }
-}
diff --git a/components/page_load_metrics/browser/observers/core_page_load_metrics_observer.h b/components/page_load_metrics/browser/observers/core_page_load_metrics_observer.h
index 7eab43d..04414c4 100644
--- a/components/page_load_metrics/browser/observers/core_page_load_metrics_observer.h
+++ b/components/page_load_metrics/browser/observers/core_page_load_metrics_observer.h
@@ -103,15 +103,6 @@
 extern const char kBackgroundHistogramInputToFirstContentfulPaint[];
 extern const char kHistogramBackForwardCacheEvent[];
 
-// Split histograms recorded only when the first rendering cycle has been
-// delayed for web font preloading.
-// See design doc https://bit.ly/36E8UKB for details.
-extern const char kHistogramFontPreloadFirstPaint[];
-extern const char kHistogramFontPreloadFirstContentfulPaint[];
-extern const char kHistogramFontPreloadLargestContentfulPaint[];
-extern const char kHistogramFontPreloadLargestImagePaint[];
-extern const char kHistogramFontPreloadLargestTextPaint[];
-
 // Navigation metrics from the navigation start.
 extern const char
     kHistogramNavigationTimingNavigationStartToFirstRequestStart[];
@@ -218,8 +209,6 @@
   void OnRestoreFromBackForwardCache(
       const page_load_metrics::mojom::PageLoadTiming& timing,
       content::NavigationHandle* navigation_handle) override;
-  void OnLoadingBehaviorObserved(content::RenderFrameHost* rfh,
-                                 int behavior_flags) override;
 
  private:
   void RecordNavigationTimingHistograms();
@@ -264,10 +253,6 @@
   // True if we've received a scroll input after first paint has happened.
   bool received_scroll_input_after_first_paint_ = false;
 
-  // True if the first rendering cycle has been delayed due to web font
-  // preloading.
-  bool font_preload_started_before_rendering_observed_ = false;
-
   base::TimeTicks first_paint_;
 
   // Tracks user input clicks for possible click burst.
diff --git a/components/page_load_metrics/browser/observers/core_page_load_metrics_observer_unittest.cc b/components/page_load_metrics/browser/observers/core_page_load_metrics_observer_unittest.cc
index 123f97f..e3da0ae4 100644
--- a/components/page_load_metrics/browser/observers/core_page_load_metrics_observer_unittest.cc
+++ b/components/page_load_metrics/browser/observers/core_page_load_metrics_observer_unittest.cc
@@ -1419,135 +1419,3 @@
   tester()->histogram_tester().ExpectUniqueSample(
       internal::kHistogramPageLoadUnfinishedBytes, 10, 1);
 }
-
-// PageLoad.Clients.FontPreload.* shouldn't be recorded when the behavior is not
-// observed.
-TEST_F(CorePageLoadMetricsObserverTest, FontPreloadHistogramsNotObserved) {
-  page_load_metrics::mojom::PageLoadTiming timing;
-  page_load_metrics::InitPageLoadTimingForTest(&timing);
-  timing.navigation_start = base::Time::FromDoubleT(1);
-  timing.parse_timing->parse_start = base::TimeDelta::FromMilliseconds(1);
-  timing.parse_timing->parse_stop = base::TimeDelta::FromMilliseconds(100);
-  timing.paint_timing->first_paint = base::TimeDelta::FromMilliseconds(150);
-  timing.paint_timing->first_contentful_paint =
-      base::TimeDelta::FromMilliseconds(200);
-  timing.paint_timing->largest_contentful_paint->largest_text_paint =
-      base::TimeDelta::FromMilliseconds(250);
-  timing.paint_timing->largest_contentful_paint->largest_text_paint_size = 100u;
-  timing.paint_timing->largest_contentful_paint->largest_image_paint =
-      base::TimeDelta::FromMilliseconds(250);
-  timing.paint_timing->largest_contentful_paint->largest_image_paint_size =
-      100u;
-  PopulateRequiredTimingFields(&timing);
-
-  NavigateAndCommit(GURL("https://www.google.com/"));
-  tester()->SimulateTimingUpdate(timing);
-
-  // Simulate closing the tab.
-  DeleteContents();
-
-  tester()->histogram_tester().ExpectTotalCount(
-      internal::kHistogramFontPreloadFirstPaint, 0);
-  tester()->histogram_tester().ExpectTotalCount(
-      internal::kHistogramFontPreloadFirstContentfulPaint, 0);
-  tester()->histogram_tester().ExpectTotalCount(
-      internal::kHistogramFontPreloadLargestContentfulPaint, 0);
-  tester()->histogram_tester().ExpectTotalCount(
-      internal::kHistogramFontPreloadLargestImagePaint, 0);
-  tester()->histogram_tester().ExpectTotalCount(
-      internal::kHistogramFontPreloadLargestTextPaint, 0);
-}
-
-// PageLoad.Clients.FontPreload.* should be recorded when the behavior is
-// observed.
-TEST_F(CorePageLoadMetricsObserverTest, FontPreloadHistogramsObserved) {
-  page_load_metrics::mojom::PageLoadTiming timing;
-  page_load_metrics::InitPageLoadTimingForTest(&timing);
-  timing.navigation_start = base::Time::FromDoubleT(1);
-  timing.parse_timing->parse_start = base::TimeDelta::FromMilliseconds(1);
-  timing.parse_timing->parse_stop = base::TimeDelta::FromMilliseconds(100);
-  timing.paint_timing->first_paint = base::TimeDelta::FromMilliseconds(150);
-  timing.paint_timing->first_contentful_paint =
-      base::TimeDelta::FromMilliseconds(200);
-  timing.paint_timing->largest_contentful_paint->largest_text_paint =
-      base::TimeDelta::FromMilliseconds(250);
-  timing.paint_timing->largest_contentful_paint->largest_text_paint_size = 100u;
-  timing.paint_timing->largest_contentful_paint->largest_image_paint =
-      base::TimeDelta::FromMilliseconds(250);
-  timing.paint_timing->largest_contentful_paint->largest_image_paint_size =
-      100u;
-  PopulateRequiredTimingFields(&timing);
-
-  // Note: PageLoadMetrics internally processes loading behavior before timing
-  // metrics if they come in the same IPC update.
-  page_load_metrics::mojom::FrameMetadata metadata;
-  metadata.behavior_flags |= blink::LoadingBehaviorFlag::
-      kLoadingBehaviorFontPreloadStartedBeforeRendering;
-  NavigateAndCommit(GURL("https://www.google.com/"));
-  tester()->SimulateTimingAndMetadataUpdate(timing, metadata);
-
-  // Similate closing the tab.
-  DeleteContents();
-
-  tester()->histogram_tester().ExpectTotalCount(
-      internal::kHistogramFontPreloadFirstPaint, 1);
-  tester()->histogram_tester().ExpectTotalCount(
-      internal::kHistogramFontPreloadFirstContentfulPaint, 1);
-  tester()->histogram_tester().ExpectTotalCount(
-      internal::kHistogramFontPreloadLargestContentfulPaint, 1);
-  tester()->histogram_tester().ExpectTotalCount(
-      internal::kHistogramFontPreloadLargestImagePaint, 1);
-  tester()->histogram_tester().ExpectTotalCount(
-      internal::kHistogramFontPreloadLargestTextPaint, 1);
-}
-
-// PageLoad.Clients.FontPreload.* depends on the order that, we need to first
-// observe the load behavior and then observe the paint timings. This should
-// have been guaranteed by the renderer. However, If the ordering is wrong, we
-// can't observe the histograms correctly.
-TEST_F(CorePageLoadMetricsObserverTest,
-       FontPreloadHistogramsNotObservedOnWrongDispatchOrder) {
-  page_load_metrics::mojom::PageLoadTiming timing;
-  page_load_metrics::InitPageLoadTimingForTest(&timing);
-  timing.navigation_start = base::Time::FromDoubleT(1);
-  timing.parse_timing->parse_start = base::TimeDelta::FromMilliseconds(1);
-  timing.parse_timing->parse_stop = base::TimeDelta::FromMilliseconds(100);
-  timing.paint_timing->first_paint = base::TimeDelta::FromMilliseconds(150);
-  timing.paint_timing->first_contentful_paint =
-      base::TimeDelta::FromMilliseconds(200);
-  timing.paint_timing->largest_contentful_paint->largest_text_paint =
-      base::TimeDelta::FromMilliseconds(250);
-  timing.paint_timing->largest_contentful_paint->largest_text_paint_size = 100u;
-  timing.paint_timing->largest_contentful_paint->largest_image_paint =
-      base::TimeDelta::FromMilliseconds(250);
-  timing.paint_timing->largest_contentful_paint->largest_image_paint_size =
-      100u;
-  PopulateRequiredTimingFields(&timing);
-
-  // Note: PageLoadMetrics internally processes loading behavior before timing
-  // metrics if they come in the same IPC update.
-  page_load_metrics::mojom::FrameMetadata metadata;
-  metadata.behavior_flags |= blink::LoadingBehaviorFlag::
-      kLoadingBehaviorFontPreloadStartedBeforeRendering;
-
-  NavigateAndCommit(GURL("https://www.google.com/"));
-  tester()->SimulateTimingUpdate(timing);
-  tester()->SimulateMetadataUpdate(metadata, main_rfh());
-
-  // Similate closing the tab.
-  DeleteContents();
-
-  tester()->histogram_tester().ExpectTotalCount(
-      internal::kHistogramFontPreloadFirstPaint, 0);
-  tester()->histogram_tester().ExpectTotalCount(
-      internal::kHistogramFontPreloadFirstContentfulPaint, 0);
-
-  // Largest*Paint is recorded on page complete/closing, so they can still be
-  // observed.
-  tester()->histogram_tester().ExpectTotalCount(
-      internal::kHistogramFontPreloadLargestContentfulPaint, 1);
-  tester()->histogram_tester().ExpectTotalCount(
-      internal::kHistogramFontPreloadLargestImagePaint, 1);
-  tester()->histogram_tester().ExpectTotalCount(
-      internal::kHistogramFontPreloadLargestTextPaint, 1);
-}
diff --git a/components/password_manager/core/browser/password_autofill_manager.cc b/components/password_manager/core/browser/password_autofill_manager.cc
index 63f2d11..4fefb75 100644
--- a/components/password_manager/core/browser/password_autofill_manager.cc
+++ b/components/password_manager/core/browser/password_autofill_manager.cc
@@ -396,8 +396,6 @@
                  autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_EMPTY) {
     password_client_->NavigateToManagePasswordsPage(
         ManagePasswordsReferrer::kPasswordDropdown);
-    metrics_util::LogContextOfShowAllSavedPasswordsAccepted(
-        metrics_util::ShowAllSavedPasswordsContext::kPassword);
     metrics_util::LogPasswordDropdownItemSelected(
         PasswordDropdownSelectedOption::kShowAll,
         password_client_->IsIncognito());
@@ -628,17 +626,12 @@
       metrics_util::PasswordDropdownState::kStandard;
   for (const auto& suggestion : suggestions) {
     switch (suggestion.frontend_id) {
-      case autofill::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY:
-      case autofill::PopupItemId::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_EMPTY:
-        metrics_util::LogContextOfShowAllSavedPasswordsShown(
-            metrics_util::ShowAllSavedPasswordsContext::kPassword);
-        continue;
       case autofill::POPUP_ITEM_ID_GENERATE_PASSWORD_ENTRY:
         // TODO(crbug.com/1062709): Revisit metrics for the "opt in and
         // generate" button.
       case autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN_AND_GENERATE:
         dropdown_state = metrics_util::PasswordDropdownState::kStandardGenerate;
-        continue;
+        break;
     }
   }
   metrics_util::LogPasswordDropdownShown(dropdown_state,
diff --git a/components/password_manager/core/browser/password_autofill_manager_unittest.cc b/components/password_manager/core/browser/password_autofill_manager_unittest.cc
index e23bdd2..a6da3a4d 100644
--- a/components/password_manager/core/browser/password_autofill_manager_unittest.cc
+++ b/components/password_manager/core/browser/password_autofill_manager_unittest.cc
@@ -1183,10 +1183,6 @@
 // Tests that the "Manage passwords" suggestion is shown along with the password
 // popup.
 TEST_F(PasswordAutofillManagerTest, ShowAllPasswordsOptionOnPasswordField) {
-  constexpr char kShownContextHistogram[] =
-      "PasswordManager.ShowAllSavedPasswordsShownContext";
-  constexpr char kAcceptedContextHistogram[] =
-      "PasswordManager.ShowAllSavedPasswordsAcceptedContext";
   base::HistogramTester histograms;
 
   NiceMock<MockAutofillClient> autofill_client;
@@ -1216,10 +1212,6 @@
               SuggestionVectorValuesAre(
                   ElementsAre(test_username_, GetManagePasswordsTitle())));
 
-  // Expect a sample only in the shown histogram.
-  histograms.ExpectUniqueSample(
-      kShownContextHistogram,
-      metrics_util::ShowAllSavedPasswordsContext::kPassword, 1);
   // Clicking at the "Show all passwords row" should trigger a call to open
   // the Password Manager settings page and hide the popup.
   EXPECT_CALL(*client, NavigateToManagePasswordsPage(
@@ -1229,13 +1221,6 @@
       HideAutofillPopup(autofill::PopupHidingReason::kAcceptSuggestion));
   password_autofill_manager_->DidAcceptSuggestion(
       base::string16(), autofill::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY, 0);
-  // Expect a sample in both the shown and accepted histogram.
-  histograms.ExpectUniqueSample(
-      kShownContextHistogram,
-      metrics_util::ShowAllSavedPasswordsContext::kPassword, 1);
-  histograms.ExpectUniqueSample(
-      kAcceptedContextHistogram,
-      metrics_util::ShowAllSavedPasswordsContext::kPassword, 1);
   histograms.ExpectUniqueSample(
       kDropdownSelectedHistogram,
       metrics_util::PasswordDropdownSelectedOption::kShowAll, 1);
diff --git a/components/password_manager/core/browser/password_manager_metrics_util.cc b/components/password_manager/core/browser/password_manager_metrics_util.cc
index 7b1cef8..5bb73efc 100644
--- a/components/password_manager/core/browser/password_manager_metrics_util.cc
+++ b/components/password_manager/core/browser/password_manager_metrics_util.cc
@@ -210,18 +210,6 @@
                                 PasswordType::PASSWORD_TYPE_COUNT);
 }
 
-void LogContextOfShowAllSavedPasswordsShown(
-    ShowAllSavedPasswordsContext context) {
-  base::UmaHistogramEnumeration(
-      "PasswordManager.ShowAllSavedPasswordsShownContext", context);
-}
-
-void LogContextOfShowAllSavedPasswordsAccepted(
-    ShowAllSavedPasswordsContext context) {
-  base::UmaHistogramEnumeration(
-      "PasswordManager.ShowAllSavedPasswordsAcceptedContext", context);
-}
-
 void LogPasswordDropdownShown(PasswordDropdownState state,
                               bool off_the_record) {
   base::UmaHistogramEnumeration("PasswordManager.PasswordDropdownShown", state);
diff --git a/components/password_manager/core/browser/password_manager_metrics_util.h b/components/password_manager/core/browser/password_manager_metrics_util.h
index 7b405e4..6dfc5ca 100644
--- a/components/password_manager/core/browser/password_manager_metrics_util.h
+++ b/components/password_manager/core/browser/password_manager_metrics_util.h
@@ -296,26 +296,6 @@
 };
 #endif
 
-// Specifies the context in which the "Show all saved passwords" fallback is
-// shown or accepted.
-// Metrics:
-// - PasswordManager.ShowAllSavedPasswordsAcceptedContext
-// - PasswordManager.ShowAllSavedPasswordsShownContext
-//
-// These values are persisted to logs. Entries should not be renumbered and
-// numeric values should never be reused.
-enum class ShowAllSavedPasswordsContext {
-  kNone = 0,
-  // The "Show all saved passwords..." fallback is shown below a list of
-  // available passwords.
-  kPassword = 1,
-  // Obsolete.
-  kManualFallbackDeprecated = 2,
-  // The "Show all saved  passwords..." fallback is shown in context menu.
-  kContextMenu = 3,
-  kMaxValue = kContextMenu,
-};
-
 // These values are persisted to logs. Entries should not be renumbered and
 // numeric values should never be reused.
 // Metrics: "PasswordManager.CertificateErrorsWhileSeeingForms"
@@ -547,15 +527,6 @@
                       bool password_field_detected,
                       PasswordType reused_password_type);
 
-// Log the context in which the "Show all saved passwords" fallback was shown.
-void LogContextOfShowAllSavedPasswordsShown(
-    ShowAllSavedPasswordsContext context);
-
-// Log the context in which the "Show all saved passwords" fallback was
-// accepted.
-void LogContextOfShowAllSavedPasswordsAccepted(
-    ShowAllSavedPasswordsContext context);
-
 // Log the type of the password dropdown when it's shown.
 void LogPasswordDropdownShown(PasswordDropdownState state, bool off_the_record);
 
diff --git a/components/password_manager/core/browser/password_manager_util.cc b/components/password_manager/core/browser/password_manager_util.cc
index c9ce5ab..d8fe4ca4 100644
--- a/components/password_manager/core/browser/password_manager_util.cc
+++ b/components/password_manager/core/browser/password_manager_util.cc
@@ -137,10 +137,6 @@
       !client->IsFillingFallbackEnabled(driver->GetLastCommittedURL()))
     return false;
 
-  LogContextOfShowAllSavedPasswordsShown(
-      password_manager::metrics_util::ShowAllSavedPasswordsContext::
-          kContextMenu);
-
   return true;
 }
 
diff --git a/components/password_manager/core/browser/password_scripts_fetcher.h b/components/password_manager/core/browser/password_scripts_fetcher.h
index 80fecf0..ad18f9ac 100644
--- a/components/password_manager/core/browser/password_scripts_fetcher.h
+++ b/components/password_manager/core/browser/password_scripts_fetcher.h
@@ -27,7 +27,8 @@
   // |PrewarmCache| was supposed to fetch the data in advance). In case of
   // several calls of the method, the callbacks will be called one after
   // another.
-  virtual void Fetch(base::OnceClosure fetch_finished_callback) = 0;
+  virtual void RefreshScriptsIfNecessary(
+      base::OnceClosure fetch_finished_callback) = 0;
   // Returns whether there is a password change script for |origin| via
   // |callback|. If the cache was never set or is stale, it triggers a re-fetch.
   // In case of a network error, the verdict will default to no script being
diff --git a/components/password_manager/core/browser/password_scripts_fetcher_impl.cc b/components/password_manager/core/browser/password_scripts_fetcher_impl.cc
index 89c8357..22a724ba 100644
--- a/components/password_manager/core/browser/password_scripts_fetcher_impl.cc
+++ b/components/password_manager/core/browser/password_scripts_fetcher_impl.cc
@@ -86,7 +86,7 @@
     StartFetch();
 }
 
-void PasswordScriptsFetcherImpl::Fetch(
+void PasswordScriptsFetcherImpl::RefreshScriptsIfNecessary(
     base::OnceClosure fetch_finished_callback) {
   CacheState state = IsCacheStale()
                          ? (url_loader_ ? CacheState::kWaiting
diff --git a/components/password_manager/core/browser/password_scripts_fetcher_impl.h b/components/password_manager/core/browser/password_scripts_fetcher_impl.h
index 1a97f66..53ae331 100644
--- a/components/password_manager/core/browser/password_scripts_fetcher_impl.h
+++ b/components/password_manager/core/browser/password_scripts_fetcher_impl.h
@@ -64,7 +64,8 @@
 
   // PasswordScriptsFetcher:
   void PrewarmCache() override;
-  void Fetch(base::OnceClosure fetch_finished_callback) override;
+  void RefreshScriptsIfNecessary(
+      base::OnceClosure fetch_finished_callback) override;
   void FetchScriptAvailability(const url::Origin& origin,
                                ResponseCallback callback) override;
   bool IsScriptAvailable(const url::Origin& origin) const override;
diff --git a/components/password_manager/core/browser/password_scripts_fetcher_impl_unittests.cc b/components/password_manager/core/browser/password_scripts_fetcher_impl_unittests.cc
index 9341c93..c598b28 100644
--- a/components/password_manager/core/browser/password_scripts_fetcher_impl_unittests.cc
+++ b/components/password_manager/core/browser/password_scripts_fetcher_impl_unittests.cc
@@ -88,7 +88,7 @@
 
   void StartBulkCheck() {
     pending_fetch_finished_callbacks_++;
-    fetcher()->Fetch(
+    fetcher()->RefreshScriptsIfNecessary(
         base::BindOnce(&PasswordScriptsFetcherImplTest::RecordFetchFinished,
                        base::Unretained(this)));
     RequestSingleScriptAvailability(GetOriginWithScript1());
diff --git a/components/password_manager/core/browser/password_store.cc b/components/password_manager/core/browser/password_store.cc
index 8b08c01..91883485 100644
--- a/components/password_manager/core/browser/password_store.cc
+++ b/components/password_manager/core/browser/password_store.cc
@@ -83,6 +83,12 @@
 
 }  // namespace
 
+void PasswordStore::Observer::OnLoginsChangedIn(
+    PasswordStore* store,
+    const PasswordStoreChangeList& changes) {
+  OnLoginsChanged(changes);
+}
+
 #if defined(SYNC_PASSWORD_REUSE_DETECTION_ENABLED)
 PasswordStore::CheckReuseRequest::CheckReuseRequest(
     PasswordReuseDetectorConsumer* consumer)
@@ -731,7 +737,8 @@
     const PasswordStoreChangeList& changes) {
   DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
   if (!changes.empty()) {
-    observers_->Notify(FROM_HERE, &Observer::OnLoginsChanged, changes);
+    observers_->Notify(FROM_HERE, &Observer::OnLoginsChangedIn,
+                       base::RetainedRef(this), changes);
     if (sync_bridge_)
       sync_bridge_->ActOnPasswordStoreChanges(changes);
 
diff --git a/components/password_manager/core/browser/password_store.h b/components/password_manager/core/browser/password_store.h
index 374f27f..17f9baf 100644
--- a/components/password_manager/core/browser/password_store.h
+++ b/components/password_manager/core/browser/password_store.h
@@ -89,6 +89,14 @@
     // the UI thread.
     virtual void OnLoginsChanged(const PasswordStoreChangeList& changes) = 0;
 
+    // Like OnLoginsChanged(), but also receives the originating PasswordStore
+    // as a parameter. This is useful for observers that observe changes in both
+    // the profile-scoped and the account-scoped store. The default
+    // implementation simply calls OnLoginsChanged(), so observers that don't
+    // care about the store can just ignore this.
+    virtual void OnLoginsChangedIn(PasswordStore* store,
+                                   const PasswordStoreChangeList& changes);
+
    protected:
     virtual ~Observer() = default;
   };
diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json
index 5e1a65e4..389e6a6 100644
--- a/components/policy/resources/policy_templates.json
+++ b/components/policy/resources/policy_templates.json
@@ -1044,6 +1044,7 @@
         'Printers',
         'PrintersBulkConfiguration',
         'PrintersBulkAccessMode',
+        'PrintersBulkBlocklist',
         'DeviceNativePrinters',
         'DeviceNativePrintersAccessMode',
         'DeviceNativePrintersBlacklist',
@@ -10041,11 +10042,14 @@
         'dynamic_refresh': True,
         'per_profile': False,
       },
+      'deprecated': True,
       'example_value': True,
       'id': 688,
       'caption': '''Reduce Managed-guest session auto-launch notifications''',
       'tags': [],
-      'desc': ''' Control the auto launch notification of the managed guest session on <ph name="PRODUCT_OS_NAME">$2<ex>Google Chrome OS</ex></ph>.
+      'desc': ''' Note that this policy is deprecated and will be removed in <ph name="PRODUCT_OS_NAME">$2<ex>Google Chrome OS</ex></ph> version 89. Please use <ph name="MANAGED_GUEST_SESSION_PRIVACY_WARNINGS_POLICY_NAME">ManagedGuestSessionPrivacyWarningsEnabled</ph> to configure the privacy warnings of managed-guest sessions instead.
+
+      Control the auto launch notification of the managed guest session on <ph name="PRODUCT_OS_NAME">$2<ex>Google Chrome OS</ex></ph>.
 
       If this policy is set to True, the privacy warning notification will be closed after some seconds.
 
@@ -15238,13 +15242,13 @@
 
       The file is downloaded and cached. It will be re-downloaded whenever the URL or the hash changes.
 
-      If this policy is set, <ph name="PRODUCT_OS_NAME">$2<ex>Google Chrome OS</ex></ph> will download the file for printer configurations and make printers available in accordance with <ph name="BULK_PRINTERS_ACCESS_MODE">NativePrintersBulkAccessMode</ph>, <ph name="BULK_PRINTERS_WHITELIST">NativePrintersBulkWhitelist</ph>, and <ph name="BULK_PRINTERS_BLACKLIST">NativePrintersBulkBlacklist</ph>.
+      If this policy is set, <ph name="PRODUCT_OS_NAME">$2<ex>Google Chrome OS</ex></ph> will download the file for printer configurations and make printers available in accordance with <ph name="BULK_PRINTERS_ACCESS_MODE_POLICY_NAME">NativePrintersBulkAccessMode</ph>, <ph name="BULK_PRINTERS_WHITELIST">NativePrintersBulkWhitelist</ph>, and <ph name="BULK_PRINTERS_BLACKLIST">NativePrintersBulkBlacklist</ph>.
 
       If you set this policy, users cannot change or override it.
 
       This policy has no effect on whether users can configure printers on individual devices.  It is intended to be supplementary to the configuration of printers by individual users.
 
-      This policy is deprecated, please use <ph name="PRINTERS_BULK_CONFIGURATION">PrintersBulkConfiguration</ph> instead.''',
+      This policy is deprecated, please use <ph name="PRINTERS_BULK_CONFIGURATION_POLICY_NAME">PrintersBulkConfiguration</ph> instead.''',
     },
     {
       'name': 'PrintersBulkConfiguration',
@@ -15278,7 +15282,7 @@
 
       The file is downloaded and cached. It will be re-downloaded whenever the URL or the hash changes.
 
-      If this policy is set, <ph name="PRODUCT_OS_NAME">$2<ex>Google Chrome OS</ex></ph> will download the file for printer configurations and make printers available in accordance with <ph name="PRINTERS_BULK_ACCESS_MODE">PrintersBulkAccessMode</ph>, <ph name="PRINTERS_BULK_ALLOWLIST">PrintersBulkAllowlist</ph>, and <ph name="PRINTERS_BULK_BLOCKLIST">PrintersBulkBlocklist</ph>.
+      If this policy is set, <ph name="PRODUCT_OS_NAME">$2<ex>Google Chrome OS</ex></ph> will download the file for printer configurations and make printers available in accordance with <ph name="PRINTERS_BULK_ACCESS_MODE_POLICY_NAME_POLICY_NAME">PrintersBulkAccessMode</ph>, <ph name="PRINTERS_BULK_ALLOWLIST">PrintersBulkAllowlist</ph>, and <ph name="PRINTERS_BULK_BLOCKLIST">PrintersBulkBlocklist</ph>.
 
       If you set this policy, users cannot change or override it.
 
@@ -15320,13 +15324,13 @@
       'example_value': 1,
       'caption': '''Printer configuration access policy.''',
       'tags': [],
-      'desc': '''Controls which printers from the <ph name="BULK_PRINTERS_POLICY">NativePrintersBulkConfiguration</ph> are available to users.
+      'desc': '''Controls which printers from the <ph name="BULK_PRINTERS_POLICY_NAME">NativePrintersBulkConfiguration</ph> are available to users.
 
       Designates which access policy is used for bulk printer configuration. If <ph name="PRINTERS_ALLOW_ALL">AllowAll</ph> is selected, all printers are shown. If <ph name="PRINTERS_BLACKLIST">BlacklistRestriction</ph> is selected, <ph name="BULK_PRINTERS_BLACKLIST">NativePrintersBulkBlacklist</ph> is used to restrict access to the specified printers.  If <ph name="PRINTERS_WHITELIST">WhitelistPrintersOnly</ph> is selected, <ph name="BULK_PRINTERS_WHITELIST">NativePrintersBulkWhitelist</ph> designates only those printers which are selectable.
 
       If this policy is not set, <ph name="PRINTERS_ALLOW_ALL">AllowAll</ph> is assumed.
 
-      This policy is deprecated, please use <ph name="PRINTERS_BULK_ACCESS_MODE">PrintersBulkAccessMode</ph> instead.
+      This policy is deprecated, please use <ph name="PRINTERS_BULK_ACCESS_MODE_POLICY_NAME_POLICY_NAME">PrintersBulkAccessMode</ph> instead.
       ''',
     },
     {
@@ -15364,7 +15368,7 @@
       'example_value': 1,
       'caption': '''Printer configuration access policy.''',
       'tags': [],
-      'desc': '''Controls which printers from the <ph name="PRINTERS_BULK_CONFIGURATION">PrintersBulkConfiguration</ph> are available to users.
+      'desc': '''Controls which printers from the <ph name="PRINTERS_BULK_CONFIGURATION_POLICY_NAME">PrintersBulkConfiguration</ph> are available to users.
 
       Designates which access policy is used for bulk printer configuration. If <ph name="PRINTERS_ALLOW_ALL">AllowAll</ph> is selected, all printers are shown. If <ph name="PRINTERS_BLOCKLIST">BlocklistRestriction</ph> is selected, <ph name="PRINTERS_BULK_BLOCKLIST">PrintersBulkBlocklist</ph> is used to restrict access to the specified printers.  If <ph name="PRINTERS_ALLOWLIST">AllowlistPrintersOnly</ph> is selected, <ph name="PRINTERS_BULK_ALLOWLIST">PrintersBulkAllowlist</ph> designates only those printers which are selectable.
 
@@ -15385,14 +15389,41 @@
         'dynamic_refresh': True,
         'per_profile': True,
       },
+      'deprecated': True,
       'example_value':  ["id1", "id2", "id3"],
       'caption': '''Disabled enterprise printers''',
       'tags': [],
       'desc': '''Specifies the printers which a user cannot use.
 
-      This policy is only used if <ph name="PRINTERS_BLACKLIST">BlacklistRestriction</ph> is chosen for <ph name="BULK_PRINTERS_ACCESS_MODE">NativePrintersBulkAccessMode</ph>.
+      This policy is only used if <ph name="PRINTERS_BLACKLIST">BlacklistRestriction</ph> is chosen for <ph name="BULK_PRINTERS_ACCESS_MODE_POLICY_NAME">NativePrintersBulkAccessMode</ph>.
 
-      If this policy is used, all printers are provided to the user except for the ids listed in this policy. The ids must correspond to the "id" or "guid" fields in the file specified in <ph name="BULK_PRINTERS_POLICY">NativePrintersBulkConfiguration</ph>.
+      If this policy is used, all printers are provided to the user except for the ids listed in this policy. The ids must correspond to the "id" or "guid" fields in the file specified in <ph name="BULK_PRINTERS_POLICY_NAME">NativePrintersBulkConfiguration</ph>.
+
+      This policy is deprecated, please use <ph name="PRINTERS_BULK_BLOCKLIST">PrintersBulkBlocklist</ph> instead.
+      ''',
+    },
+    {
+      'name': 'PrintersBulkBlocklist',
+      'owners': ['file://chromeos/printing/OWNERS'],
+      'supported_on': ['chrome_os:86-'],
+      'id': 779,
+      'type': 'list',
+      'schema': {
+        'type': 'array',
+        'items': { 'type': 'string' },
+      },
+      'features': {
+        'dynamic_refresh': True,
+        'per_profile': True,
+      },
+      'example_value':  ["id1", "id2", "id3"],
+      'caption': '''Disabled enterprise printers''',
+      'tags': [],
+      'desc': '''Specifies the printers which a user cannot use.
+
+      This policy is only used if <ph name="PRINTERS_BLOCKLIST">BlocklistRestriction</ph> is chosen for <ph name="PRINTERS_BULK_ACCESS_MODE_POLICY_NAME">PrintersBulkAccessMode</ph>.
+
+      If this policy is used, all printers are provided to the user except for the ids listed in this policy. The ids must correspond to the "id" or "guid" fields in the file specified in <ph name="PRINTERS_BULK_CONFIGURATION_POLICY_NAME">PrintersBulkConfiguration</ph>.
       ''',
     },
     {
@@ -15414,9 +15445,9 @@
       'tags': [],
       'desc': '''Specifies the printers which a user can use.
 
-      This policy is only used if <ph name="PRINTERS_WHITELIST">WhitelistPrintersOnly</ph> is chosen for <ph name="BULK_PRINTERS_ACCESS_MODE">NativePrintersBulkAccessMode</ph>.
+      This policy is only used if <ph name="PRINTERS_WHITELIST">WhitelistPrintersOnly</ph> is chosen for <ph name="BULK_PRINTERS_ACCESS_MODE_POLICY_NAME">NativePrintersBulkAccessMode</ph>.
 
-      If this policy is used, only the printers with ids matching the values in this policy are available to the user. The ids must correspond to the "id" or "guid" fields in the file specified in <ph name="BULK_PRINTERS_POLICY">NativePrintersBulkConfiguration</ph>.
+      If this policy is used, only the printers with ids matching the values in this policy are available to the user. The ids must correspond to the "id" or "guid" fields in the file specified in <ph name="BULK_PRINTERS_POLICY_NAME">NativePrintersBulkConfiguration</ph>.
       ''',
     },
     {
@@ -15457,7 +15488,7 @@
 
       This policy has no effect on whether users can configure printers on individual devices.  It is intended to be supplementary to the configuration of printers by individual users.
 
-      This policy is additive to the <ph name="BULK_PRINTERS_POLICY">NativePrintersBulkConfiguration</ph>.
+      This policy is additive to the <ph name="BULK_PRINTERS_POLICY_NAME">NativePrintersBulkConfiguration</ph>.
 
       If this policy is unset, there will be no device printers and the other <ph name="DEVICE_NATIVE_PRINTERS_POLICY_PATTERN">DeviceNativePrinter*</ph> policies will be ignored.
 
@@ -15601,7 +15632,7 @@
 
       This policy has no effect on whether users can configure printers on individual devices.  It is intended to be supplementary to the configuration of printers by individual users.
 
-      This policy is additive to the <ph name="BULK_PRINTERS_POLICY">NativePrintersBulkConfiguration</ph>.
+      This policy is additive to the <ph name="PRINTERS_BULK_CONFIGURATION_POLICY_NAME">PrintersBulkConfiguration</ph>.
 
       If this policy is unset, there will be no device printers and the other <ph name="DEVICE_PRINTERS_POLICY_PATTERN">DevicePrinter*</ph> policies will be ignored.
       ''',
@@ -24086,6 +24117,6 @@
   ],
   'placeholders': [],
   'deleted_policy_ids': [412, 476, 546, 562, 569, 578],
-  'highest_id_currently_used': 778,
+  'highest_id_currently_used': 779,
   'highest_atomic_group_id_currently_used': 40
 }
diff --git a/components/printing/test/print_mock_render_thread.cc b/components/printing/test/print_mock_render_thread.cc
index 4da7c576..6f7f519d 100644
--- a/components/printing/test/print_mock_render_thread.cc
+++ b/components/printing/test/print_mock_render_thread.cc
@@ -86,11 +86,6 @@
   }
 }
 
-void PrintMockRenderThread::OnDidGetPrintedPagesCount(int cookie,
-                                                      int number_pages) {
-  printer_->SetPrintedPagesCount(cookie, number_pages);
-}
-
 void PrintMockRenderThread::OnDidPrintDocument(
     const printing::mojom::DidPrintDocumentParams& params,
     IPC::Message* reply_msg) {
diff --git a/components/printing/test/print_mock_render_thread.h b/components/printing/test/print_mock_render_thread.h
index 8765e4c..ed266ba 100644
--- a/components/printing/test/print_mock_render_thread.h
+++ b/components/printing/test/print_mock_render_thread.h
@@ -76,7 +76,6 @@
   void OnScriptedPrint(const PrintHostMsg_ScriptedPrint_Params& params,
                        PrintMsg_PrintPages_Params* settings);
 
-  void OnDidGetPrintedPagesCount(int cookie, int number_pages);
   void OnDidPrintDocument(const printing::mojom::DidPrintDocumentParams& params,
                           IPC::Message* reply_msg);
 #if BUILDFLAG(ENABLE_PRINT_PREVIEW)
diff --git a/components/services/app_service/public/cpp/intent_test_util.cc b/components/services/app_service/public/cpp/intent_test_util.cc
index 11f3460..10c7c63 100644
--- a/components/services/app_service/public/cpp/intent_test_util.cc
+++ b/components/services/app_service/public/cpp/intent_test_util.cc
@@ -15,7 +15,7 @@
 apps::mojom::IntentFilterPtr CreateIntentFilterForShare(
     const std::string& action,
     const std::string& mime_type,
-    const std::string& activity_name) {
+    const std::string& activity_label) {
   auto intent_filter = apps::mojom::IntentFilter::New();
 
   apps_util::AddSingleValueCondition(
@@ -26,7 +26,7 @@
       apps::mojom::ConditionType::kMimeType, mime_type,
       apps::mojom::PatternMatchType::kMimeType, intent_filter);
 
-  intent_filter->activity_name = activity_name;
+  intent_filter->activity_label = activity_label;
 
   return intent_filter;
 }
@@ -72,15 +72,15 @@
 
 apps::mojom::IntentFilterPtr CreateIntentFilterForSend(
     const std::string& mime_type,
-    const std::string& activity_name) {
+    const std::string& activity_label) {
   return CreateIntentFilterForShare(kIntentActionSend, mime_type,
-                                    activity_name);
+                                    activity_label);
 }
 
 apps::mojom::IntentFilterPtr CreateIntentFilterForSendMultiple(
     const std::string& mime_type,
-    const std::string& activity_name) {
+    const std::string& activity_label) {
   return CreateIntentFilterForShare(kIntentActionSendMultiple, mime_type,
-                                    activity_name);
+                                    activity_label);
 }
 }  // namespace apps_util
diff --git a/components/services/app_service/public/cpp/intent_test_util.h b/components/services/app_service/public/cpp/intent_test_util.h
index 7580110..044042e 100644
--- a/components/services/app_service/public/cpp/intent_test_util.h
+++ b/components/services/app_service/public/cpp/intent_test_util.h
@@ -22,12 +22,12 @@
 // Create intent filter for send action.
 apps::mojom::IntentFilterPtr CreateIntentFilterForSend(
     const std::string& mime_types,
-    const std::string& activity_name = "");
+    const std::string& activity_label = "");
 // Create intent filter for send multiple action.
 
 apps::mojom::IntentFilterPtr CreateIntentFilterForSendMultiple(
     const std::string& mime_types,
-    const std::string& activity_name = "");
+    const std::string& activity_label = "");
 
 }  // namespace apps_util
 
diff --git a/components/services/app_service/public/mojom/types.mojom b/components/services/app_service/public/mojom/types.mojom
index b423ab87..6923766 100644
--- a/components/services/app_service/public/mojom/types.mojom
+++ b/components/services/app_service/public/mojom/types.mojom
@@ -192,26 +192,28 @@
 // should not be re-ordered or removed.
 enum LaunchSource {
   kUnknown = 0,
-  kFromAppListGrid = 1,             // Grid of apps, not the search box.
-  kFromAppListGridContextMenu = 2,  // Grid of apps; context menu.
-  kFromAppListQuery = 3,            // Query-dependent results (larger icons).
-  kFromAppListQueryContextMenu = 4, // Query-dependent results; context menu.
-  kFromAppListRecommendation = 5,   // Query-less recommendations (smaller
-                                    // icons).
-  kFromParentalControls = 6,        // Parental Controls Settings Section and
-                                    // Per App time notification.
-  kFromShelf = 7,                   // Shelf.
-  kFromFileManager = 8,             // FileManager.
-  kFromLink = 9,                    // Left-licking on links in the browser.
-  kFromOmnibox = 10,                // Enter URL in the Omnibox in the browser.
-  kFromChromeInternal = 11,         // Chrome internal call.
-  kFromKeyboard = 12,               // Keyboard shortcut for opening app.
-  kFromOtherApp = 13,               // Clicking link in another app or webui.
-  kFromMenu = 14,                   // Menu.
-  kFromInstalledNotification = 15,  // Installed notification
-  kFromTest = 16,                   // Test
-  kFromArc = 17,                    // Arc.
-  kFromSharesheet = 18,             // Sharesheet.
+  kFromAppListGrid = 1,               // Grid of apps, not the search box.
+  kFromAppListGridContextMenu = 2,    // Grid of apps; context menu.
+  kFromAppListQuery = 3,              // Query-dependent results (larger icons).
+  kFromAppListQueryContextMenu = 4,   // Query-dependent results; context menu.
+  kFromAppListRecommendation = 5,     // Query-less recommendations (smaller
+                                      // icons).
+  kFromParentalControls = 6,          // Parental Controls Settings Section and
+                                      // Per App time notification.
+  kFromShelf = 7,                     // Shelf.
+  kFromFileManager = 8,               // FileManager.
+  kFromLink = 9,                      // Left-licking on links in the browser.
+  kFromOmnibox = 10,                  // Enter URL in the Omnibox in the
+                                      // browser.
+  kFromChromeInternal = 11,           // Chrome internal call.
+  kFromKeyboard = 12,                 // Keyboard shortcut for opening app.
+  kFromOtherApp = 13,                 // Clicking link in another app or webui.
+  kFromMenu = 14,                     // Menu.
+  kFromInstalledNotification = 15,    // Installed notification
+  kFromTest = 16,                     // Test
+  kFromArc = 17,                      // Arc.
+  kFromSharesheet = 18,               // Sharesheet.
+  kFromReleaseNotesNotification = 19, // Release Notes Notification.
 };
 
 enum TriState {
@@ -309,6 +311,9 @@
   // Activity which registered this filter. We only fill this field for ARC
   // share intent filters.
   string? activity_name;
+
+  // The label shown to the user for this activity.
+  string? activity_label;
 };
 
 // Action and resource handling request. This includes the scheme and URL at
@@ -318,6 +323,7 @@
   url.mojom.Url? url; // The URL of the intent. e.g. https://www.google.com/.
   string? mime_type; // MIME type. e.g. text/plain, image/*.
   array<url.mojom.Url>? file_urls; // The URLs of the files to share.
+  string? activity_name; // The activity for the app to launch.
 };
 
 // Represents a group of |app_ids| that is no longer preferred app of their
diff --git a/components/viz/service/display/output_surface.h b/components/viz/service/display/output_surface.h
index 883800b..2772299 100644
--- a/components/viz/service/display/output_surface.h
+++ b/components/viz/service/display/output_surface.h
@@ -85,6 +85,11 @@
     OrientationMode orientation_mode = OrientationMode::kLogic;
     // Whether this OutputSurface supports direct composition layers.
     bool supports_dc_layers = false;
+    // Set RGB10A2 overlay support flags true by force, which is used for
+    // playing hdr video.
+    // TODO(richard.li@intel.com): Remove this when Intel fixs its overlay caps.
+    // checking bug in their driver.
+    bool forces_rgb10a2_overlay_support_flags = false;
     // Whether this OutputSurface should skip DrawAndSwap(). This is true for
     // the unified display on Chrome OS. All drawing is handled by the physical
     // displays so the unified display should skip that work.
diff --git a/components/viz/service/display/overlay_processor_win.cc b/components/viz/service/display/overlay_processor_win.cc
index 03946d8e..1b9c4e4 100644
--- a/components/viz/service/display/overlay_processor_win.cc
+++ b/components/viz/service/display/overlay_processor_win.cc
@@ -77,7 +77,8 @@
   // supporting RGB10 format. Let overlay deal with HDR content in this
   // situation.
   bool supports_rgb10a2_overlay =
-      gl::GetOverlaySupportFlags(DXGI_FORMAT_R10G10B10A2_UNORM) != 0;
+      (gl::GetOverlaySupportFlags(DXGI_FORMAT_R10G10B10A2_UNORM) != 0 ||
+       output_surface_->capabilities().forces_rgb10a2_overlay_support_flags);
   if (root_render_pass->content_color_usage == gfx::ContentColorUsage::kHDR &&
       !supports_rgb10a2_overlay) {
     return;
diff --git a/components/viz/service/display_embedder/skia_output_device_gl.cc b/components/viz/service/display_embedder/skia_output_device_gl.cc
index 42dc6337..920a5f74 100644
--- a/components/viz/service/display_embedder/skia_output_device_gl.cc
+++ b/components/viz/service/display_embedder/skia_output_device_gl.cc
@@ -66,6 +66,9 @@
           .disable_post_sub_buffers_for_onscreen_surfaces) {
     capabilities_.supports_post_sub_buffer = false;
   }
+  if (feature_info->workarounds().force_rgb10a2_overlay_support_flags) {
+    capabilities_.forces_rgb10a2_overlay_support_flags = true;
+  }
   capabilities_.max_frames_pending = gl_surface_->GetBufferCount() - 1;
   capabilities_.supports_commit_overlay_planes =
       gl_surface_->SupportsCommitOverlayPlanes();
diff --git a/content/browser/accessibility/accessibility_tree_formatter_base.cc b/content/browser/accessibility/accessibility_tree_formatter_base.cc
index c7b0358..7b1f918 100644
--- a/content/browser/accessibility/accessibility_tree_formatter_base.cc
+++ b/content/browser/accessibility/accessibility_tree_formatter_base.cc
@@ -43,7 +43,7 @@
   // Property invocation: property_str expected format is
   // prop_name or prop_name(arg1, ... argN).
   PropertyNode root;
-  std::string property_str = base::UTF16ToUTF8(filter.property_str);
+  const std::string& property_str = filter.property_str;
   Parse(&root, property_str.begin(), property_str.end());
 
   PropertyNode* node = &root.parameters[0];
@@ -56,7 +56,7 @@
   // :line_num_1, ... :line_num_N, a comma separated list of line indexes
   // the property should be queried for. For example, ":1,:5,:7" indicates that
   // the property should called for objects placed on 1, 5 and 7 lines only.
-  std::string filter_str = base::UTF16ToUTF8(filter.filter_str);
+  const std::string& filter_str = filter.filter_str;
   if (!filter_str.empty()) {
     node->line_indexes =
         base::SplitString(filter_str, std::string(1, ','),
@@ -269,7 +269,7 @@
     const PropertyFilter&) = default;
 
 AccessibilityTreeFormatter::PropertyFilter::PropertyFilter(
-    const base::string16& str,
+    const std::string& str,
     Type type)
     : match_str(str), type(type) {
   size_t index = str.find(';');
@@ -311,7 +311,7 @@
 
 bool AccessibilityTreeFormatter::MatchesPropertyFilters(
     const std::vector<PropertyFilter>& property_filters,
-    const base::string16& text,
+    const std::string& text,
     bool default_result) {
   bool allow = default_result;
   for (const auto& filter : property_filters) {
@@ -324,14 +324,13 @@
         (filter.match_str.length() > 0 &&
          filter.match_str.find('=') == std::string::npos &&
          filter.match_str[filter.match_str.length() - 1] != '*' &&
-         base::MatchPattern(text,
-                            filter.match_str + base::ASCIIToUTF16("=*")))) {
+         base::MatchPattern(text, filter.match_str + "=*"))) {
       switch (filter.type) {
         case PropertyFilter::ALLOW_EMPTY:
           allow = true;
           break;
         case PropertyFilter::ALLOW:
-          allow = (!base::MatchPattern(text, base::UTF8ToUTF16("*=''")));
+          allow = (!base::MatchPattern(text, "*=''"));
           break;
         case PropertyFilter::DENY:
           allow = false;
@@ -480,8 +479,7 @@
 
 bool AccessibilityTreeFormatterBase::HasMatchAllPropertyFilter() const {
   for (const auto& filter : property_filters_) {
-    if (filter.type == PropertyFilter::ALLOW &&
-        filter.match_str == base::ASCIIToUTF16("*")) {
+    if (filter.type == PropertyFilter::ALLOW && filter.match_str == "*") {
       return true;
     }
   }
@@ -489,7 +487,7 @@
 }
 
 bool AccessibilityTreeFormatterBase::MatchesPropertyFilters(
-    const base::string16& text,
+    const std::string& text,
     bool default_result) const {
   return AccessibilityTreeFormatter::MatchesPropertyFilters(
       property_filters_, text, default_result);
@@ -542,7 +540,7 @@
                                                     base::string16* line) {
   if (attr.empty())
     return false;
-  if (!MatchesPropertyFilters(attr, include_by_default))
+  if (!MatchesPropertyFilters(base::UTF16ToUTF8(attr), include_by_default))
     return false;
   if (!line->empty())
     *line += base::ASCIIToUTF16(" ");
@@ -554,7 +552,7 @@
     std::vector<PropertyFilter>* property_filters,
     std::string filter,
     PropertyFilter::Type type) {
-  property_filters->push_back(PropertyFilter(base::ASCIIToUTF16(filter), type));
+  property_filters->push_back(PropertyFilter(filter, type));
 }
 
 void AccessibilityTreeFormatterBase::AddDefaultFilters(
diff --git a/content/browser/accessibility/accessibility_tree_formatter_base.h b/content/browser/accessibility/accessibility_tree_formatter_base.h
index ab84184..20f52d5 100644
--- a/content/browser/accessibility/accessibility_tree_formatter_base.h
+++ b/content/browser/accessibility/accessibility_tree_formatter_base.h
@@ -210,7 +210,7 @@
                                         base::string16* contents,
                                         int depth = 0);
 
-  bool MatchesPropertyFilters(const base::string16& text,
+  bool MatchesPropertyFilters(const std::string& text,
                               bool default_result) const;
   bool MatchesNodeFilters(const base::DictionaryValue& dict) const;
 
diff --git a/content/browser/accessibility/accessibility_tree_formatter_base_unittest.cc b/content/browser/accessibility/accessibility_tree_formatter_base_unittest.cc
index f66f5c9..fb57124 100644
--- a/content/browser/accessibility/accessibility_tree_formatter_base_unittest.cc
+++ b/content/browser/accessibility/accessibility_tree_formatter_base_unittest.cc
@@ -32,8 +32,7 @@
 
 PropertyNode Parse(const char* input) {
   AccessibilityTreeFormatter::PropertyFilter filter(
-      base::UTF8ToUTF16(input),
-      AccessibilityTreeFormatter::PropertyFilter::ALLOW);
+      input, AccessibilityTreeFormatter::PropertyFilter::ALLOW);
   return PropertyNode::FromPropertyFilter(filter);
 }
 
diff --git a/content/browser/accessibility/accessibility_tree_formatter_mac_browsertest.mm b/content/browser/accessibility/accessibility_tree_formatter_mac_browsertest.mm
index 4d92ee72..e9368443 100644
--- a/content/browser/accessibility/accessibility_tree_formatter_mac_browsertest.mm
+++ b/content/browser/accessibility/accessibility_tree_formatter_mac_browsertest.mm
@@ -67,8 +67,7 @@
 
   for (const char* filter : filters) {
     property_filters.push_back(AccessibilityTreeFormatter::PropertyFilter(
-        base::UTF8ToUTF16(filter),
-        AccessibilityTreeFormatter::PropertyFilter::ALLOW_EMPTY));
+        filter, AccessibilityTreeFormatter::PropertyFilter::ALLOW_EMPTY));
   }
 
   formatter->AddDefaultFilters(&property_filters);
diff --git a/content/browser/accessibility/accessibility_win_browsertest.cc b/content/browser/accessibility/accessibility_win_browsertest.cc
index af162c1..0520950e 100644
--- a/content/browser/accessibility/accessibility_win_browsertest.cc
+++ b/content/browser/accessibility/accessibility_win_browsertest.cc
@@ -145,7 +145,7 @@
   DCHECK(formatter);
   formatter->set_show_ids(true);
   formatter->SetPropertyFilters({AccessibilityTreeFormatter::PropertyFilter(
-      L"*", AccessibilityTreeFormatter::PropertyFilter::ALLOW)});
+      "*", AccessibilityTreeFormatter::PropertyFilter::ALLOW)});
 
   base::string16 str;
   formatter->FormatAccessibilityTreeForTesting(
diff --git a/content/browser/accessibility/dump_accessibility_browsertest_base.cc b/content/browser/accessibility/dump_accessibility_browsertest_base.cc
index e1db266..0664002 100644
--- a/content/browser/accessibility/dump_accessibility_browsertest_base.cc
+++ b/content/browser/accessibility/dump_accessibility_browsertest_base.cc
@@ -145,8 +145,7 @@
 DumpAccessibilityTestBase::DumpUnfilteredAccessibilityTreeAsString() {
   std::unique_ptr<AccessibilityTreeFormatter> formatter(formatter_factory_());
   std::vector<PropertyFilter> property_filters;
-  property_filters.push_back(
-      PropertyFilter(base::ASCIIToUTF16("*"), PropertyFilter::ALLOW));
+  property_filters.emplace_back("*", PropertyFilter::ALLOW);
   formatter->SetPropertyFilters(property_filters);
   formatter->set_show_ids(true);
   base::string16 ax_tree_dump;
@@ -174,18 +173,13 @@
     const std::string& until_str = formatter_->GetRunUntilEventString();
     const std::string& default_action_on_str = "@DEFAULT-ACTION-ON:";
     if (base::StartsWith(line, allow_empty_str, base::CompareCase::SENSITIVE)) {
-      property_filters_.push_back(
-          PropertyFilter(base::UTF8ToUTF16(line.substr(allow_empty_str.size())),
-                         PropertyFilter::ALLOW_EMPTY));
+      property_filters_.emplace_back(
+          line.substr(allow_empty_str.size()), PropertyFilter::ALLOW_EMPTY);
     } else if (base::StartsWith(line, allow_str,
                                 base::CompareCase::SENSITIVE)) {
-      property_filters_.push_back(
-          PropertyFilter(base::UTF8ToUTF16(line.substr(allow_str.size())),
-                         PropertyFilter::ALLOW));
+      property_filters_.emplace_back(line.substr(allow_str.size()), PropertyFilter::ALLOW);
     } else if (base::StartsWith(line, deny_str, base::CompareCase::SENSITIVE)) {
-      property_filters_.push_back(
-          PropertyFilter(base::UTF8ToUTF16(line.substr(deny_str.size())),
-                         PropertyFilter::DENY));
+      property_filters_.emplace_back(line.substr(deny_str.size()), PropertyFilter::DENY);
     } else if (base::StartsWith(line, deny_node_str,
                                 base::CompareCase::SENSITIVE)) {
       const auto& node_filter = line.substr(deny_node_str.size());
diff --git a/content/browser/accessibility/dump_accessibility_events_browsertest.cc b/content/browser/accessibility/dump_accessibility_events_browsertest.cc
index 35b8f41..58d05711 100644
--- a/content/browser/accessibility/dump_accessibility_events_browsertest.cc
+++ b/content/browser/accessibility/dump_accessibility_events_browsertest.cc
@@ -75,16 +75,13 @@
       std::vector<PropertyFilter>* property_filters) override {
     // Suppress spurious focus events on the document object.
     property_filters->push_back(
-        PropertyFilter(base::ASCIIToUTF16("EVENT_OBJECT_FOCUS*DOCUMENT*"),
-                       PropertyFilter::DENY));
-    property_filters->push_back(
-        PropertyFilter(base::ASCIIToUTF16("AutomationFocusChanged*document*"),
-                       PropertyFilter::DENY));
+        PropertyFilter("EVENT_OBJECT_FOCUS*DOCUMENT*", PropertyFilter::DENY));
+    property_filters->push_back(PropertyFilter(
+        "AutomationFocusChanged*document*", PropertyFilter::DENY));
     // Implementing IRawElementProviderAdviseEvents causes Win7 to fire
     // spurious focus events (regardless of what the implementation does).
     property_filters->push_back(PropertyFilter(
-        base::ASCIIToUTF16("AutomationFocusChanged on role=region"),
-        PropertyFilter::DENY));
+        "AutomationFocusChanged on role=region", PropertyFilter::DENY));
   }
 
   std::vector<std::string> Dump(std::vector<std::string>& run_until) override;
@@ -199,7 +196,7 @@
 
     for (size_t i = 0; i < event_logs.size(); ++i) {
       if (AccessibilityTreeFormatter::MatchesPropertyFilters(
-              property_filters_, base::UTF8ToUTF16(event_logs[i]), true)) {
+              property_filters_, event_logs[i], true)) {
         result.push_back(event_logs[i]);
       }
     }
diff --git a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
index de00cdd..d66b389 100644
--- a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
+++ b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
@@ -62,10 +62,9 @@
   void AddDefaultFilters(
       std::vector<PropertyFilter>* property_filters) override;
   void AddPropertyFilter(std::vector<PropertyFilter>* property_filters,
-                         std::string filter,
+                         const std::string& filter,
                          PropertyFilter::Type type = PropertyFilter::ALLOW) {
-    property_filters->push_back(
-        PropertyFilter(base::ASCIIToUTF16(filter), type));
+    property_filters->push_back(PropertyFilter(filter, type));
   }
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
diff --git a/content/browser/accessibility/hit_testing_browsertest.cc b/content/browser/accessibility/hit_testing_browsertest.cc
index 7dfee28..ea53c6c 100644
--- a/content/browser/accessibility/hit_testing_browsertest.cc
+++ b/content/browser/accessibility/hit_testing_browsertest.cc
@@ -268,12 +268,9 @@
       AccessibilityTreeFormatterBlink::CreateBlink();
   accessibility_tree_formatter->set_show_ids(true);
   accessibility_tree_formatter->SetPropertyFilters(
-      {{base::ASCIIToUTF16("name=*"),
-        AccessibilityTreeFormatter::PropertyFilter::ALLOW},
-       {base::ASCIIToUTF16("location=*"),
-        AccessibilityTreeFormatter::PropertyFilter::ALLOW},
-       {base::ASCIIToUTF16("size=*"),
-        AccessibilityTreeFormatter::PropertyFilter::ALLOW}});
+      {{"name=*", AccessibilityTreeFormatter::PropertyFilter::ALLOW},
+       {"location=*", AccessibilityTreeFormatter::PropertyFilter::ALLOW},
+       {"size=*", AccessibilityTreeFormatter::PropertyFilter::ALLOW}});
   base::string16 accessibility_tree;
   accessibility_tree_formatter->FormatAccessibilityTreeForTesting(
       GetRootAndAssertNonNull(), &accessibility_tree);
diff --git a/content/browser/back_forward_cache_browsertest.cc b/content/browser/back_forward_cache_browsertest.cc
index 830201ae..3c608071 100644
--- a/content/browser/back_forward_cache_browsertest.cc
+++ b/content/browser/back_forward_cache_browsertest.cc
@@ -6892,4 +6892,147 @@
   }
 }
 
+// Tests that pagehide and visibilitychange handlers of the old RFH are run for
+// bfcached pages.
+IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
+                       PagehideAndVisibilitychangeRuns) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  GURL url_1(embedded_test_server()->GetURL("a.com", "/title1.html"));
+  GURL url_2(embedded_test_server()->GetURL("b.com", "/title2.html"));
+  GURL url_3(embedded_test_server()->GetURL("a.com", "/title2.html"));
+  WebContentsImpl* web_contents =
+      static_cast<WebContentsImpl*>(shell()->web_contents());
+
+  // 1) Navigate to |url_1|.
+  EXPECT_TRUE(NavigateToURL(shell(), url_1));
+  RenderFrameHostImpl* main_frame_1 = web_contents->GetMainFrame();
+
+  // Create a pagehide handler that sets item "pagehide_storage" and a
+  // visibilitychange handler that sets item "visibilitychange_storage" in
+  // localStorage.
+  EXPECT_TRUE(ExecJs(main_frame_1,
+                     R"(
+    localStorage.setItem('pagehide_storage', 'not_dispatched');
+    var dispatched_pagehide = false;
+    window.onpagehide = function(e) {
+      if (dispatched_pagehide) {
+        // We shouldn't dispatch pagehide more than once.
+        localStorage.setItem('pagehide_storage', 'dispatched_more_than_once');
+      } else if (!e.persisted) {
+        localStorage.setItem('pagehide_storage', 'wrong_persisted');
+      } else {
+        localStorage.setItem('pagehide_storage', 'dispatched_once');
+      }
+      dispatched_pagehide = true;
+    }
+    localStorage.setItem('visibilitychange_storage', 'not_dispatched');
+    var dispatched_visibilitychange = false;
+    document.onvisibilitychange = function(e) {
+      if (dispatched_visibilitychange) {
+        // We shouldn't dispatch visibilitychange more than once.
+        localStorage.setItem('visibilitychange_storage',
+          'dispatched_more_than_once');
+      } else if (document.visibilityState != 'hidden') {
+        // We should dispatch the event when the visibilityState is 'hidden'.
+        localStorage.setItem('visibilitychange_storage', 'not_hidden');
+      } else {
+        localStorage.setItem('visibilitychange_storage', 'dispatched_once');
+      }
+      dispatched_visibilitychange = true;
+    }
+  )"));
+
+  // 2) Navigate cross-site to |url_2|. We need to navigate cross-site to make
+  // sure we won't run pagehide and visibilitychange during new page's commit,
+  // which is tested in ProactivelySwapBrowsingInstancesSameSiteTest.
+  EXPECT_TRUE(NavigateToURL(shell(), url_2));
+
+  // |main_frame_1| should be in the back-forward cache.
+  EXPECT_TRUE(main_frame_1->IsInBackForwardCache());
+
+  // 3) Navigate to |url_3| which is same-origin with |url_1|, so we can check
+  // the localStorage values.
+  EXPECT_TRUE(NavigateToURL(shell(), url_3));
+  RenderFrameHostImpl* main_frame_3 = web_contents->GetMainFrame();
+
+  // Check that the value for 'pagehide_storage' and 'visibilitychange_storage'
+  // are set correctly.
+  EXPECT_EQ("dispatched_once",
+            EvalJs(main_frame_3, "localStorage.getItem('pagehide_storage')"));
+  EXPECT_EQ(
+      "dispatched_once",
+      EvalJs(main_frame_3, "localStorage.getItem('visibilitychange_storage')"));
+}
+
+// Tests that pagehide handlers of the old RFH are run for bfcached pages even
+// if the page is already hidden (and visibilitychange won't run).
+IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
+                       PagehideRunsWhenPageIsHidden) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  GURL url_1(embedded_test_server()->GetURL("a.com", "/title1.html"));
+  GURL url_2(embedded_test_server()->GetURL("b.com", "/title2.html"));
+  GURL url_3(embedded_test_server()->GetURL("a.com", "/title2.html"));
+  WebContentsImpl* web_contents =
+      static_cast<WebContentsImpl*>(shell()->web_contents());
+
+  // 1) Navigate to |url_1| and hide the tab.
+  EXPECT_TRUE(NavigateToURL(shell(), url_1));
+  RenderFrameHostImpl* main_frame_1 = web_contents->GetMainFrame();
+  // We need to set it to Visibility::VISIBLE first in case this is the first
+  // time the visibility is updated.
+  web_contents->UpdateWebContentsVisibility(Visibility::VISIBLE);
+  web_contents->UpdateWebContentsVisibility(Visibility::HIDDEN);
+  EXPECT_EQ(Visibility::HIDDEN, web_contents->GetVisibility());
+
+  // Create a pagehide handler that sets item "pagehide_storage" and a
+  // visibilitychange handler that sets item "visibilitychange_storage" in
+  // localStorage.
+  EXPECT_TRUE(ExecJs(main_frame_1,
+                     R"(
+    localStorage.setItem('pagehide_storage', 'not_dispatched');
+    var dispatched_pagehide = false;
+    window.onpagehide = function(e) {
+      if (dispatched_pagehide) {
+        // We shouldn't dispatch pagehide more than once.
+        localStorage.setItem('pagehide_storage', 'dispatched_more_than_once');
+      } else if (!e.persisted) {
+        localStorage.setItem('pagehide_storage', 'wrong_persisted');
+      } else {
+        localStorage.setItem('pagehide_storage', 'dispatched_once');
+      }
+      dispatched_pagehide = true;
+    }
+    localStorage.setItem('visibilitychange_storage', 'not_dispatched');
+    document.onvisibilitychange = function(e) {
+      localStorage.setItem('visibilitychange_storage',
+        'should_not_be_dispatched');
+    }
+  )"));
+  // |visibilitychange_storage| should be set to its initial correct value.
+  EXPECT_EQ(
+      "not_dispatched",
+      EvalJs(main_frame_1, "localStorage.getItem('visibilitychange_storage')"));
+
+  // 2) Navigate cross-site to |url_2|. We need to navigate cross-site to make
+  // sure we won't run pagehide and visibilitychange during new page's commit,
+  // which is tested in ProactivelySwapBrowsingInstancesSameSiteTest.
+  EXPECT_TRUE(NavigateToURL(shell(), url_2));
+
+  // |main_frame_1| should be in the back-forward cache.
+  EXPECT_TRUE(main_frame_1->IsInBackForwardCache());
+
+  // 3) Navigate to |url_3| which is same-origin with |url_1|, so we can check
+  // the localStorage values.
+  EXPECT_TRUE(NavigateToURL(shell(), url_3));
+  RenderFrameHostImpl* main_frame_3 = web_contents->GetMainFrame();
+
+  // Check that the value for 'pagehide_storage' and 'visibilitychange_storage'
+  // are set correctly.
+  EXPECT_EQ("dispatched_once",
+            EvalJs(main_frame_3, "localStorage.getItem('pagehide_storage')"));
+  EXPECT_EQ(
+      "not_dispatched",
+      EvalJs(main_frame_3, "localStorage.getItem('visibilitychange_storage')"));
+}
+
 }  // namespace content
diff --git a/content/browser/devtools/devtools_session.cc b/content/browser/devtools/devtools_session.cc
index 327ba472..8a96b68 100644
--- a/content/browser/devtools/devtools_session.cc
+++ b/content/browser/devtools/devtools_session.cc
@@ -127,6 +127,7 @@
     std::unique_ptr<protocol::DevToolsDomainHandler> handler) {
   DCHECK(agent_host_);
   handler->Wire(dispatcher_.get());
+  handler->SetSession(this);
   handlers_[handler->name()] = std::move(handler);
 }
 
diff --git a/content/browser/devtools/protocol/devtools_domain_handler.cc b/content/browser/devtools/protocol/devtools_domain_handler.cc
index 802bb71..4b20bb29 100644
--- a/content/browser/devtools/protocol/devtools_domain_handler.cc
+++ b/content/browser/devtools/protocol/devtools_domain_handler.cc
@@ -26,5 +26,17 @@
   return Response::Success();
 }
 
+void DevToolsDomainHandler::SetSession(DevToolsSession* session) {
+  session_ = session;
+}
+
+const std::string& DevToolsDomainHandler::name() const {
+  return name_;
+}
+
+DevToolsSession* DevToolsDomainHandler::session() {
+  return session_;
+}
+
 }  // namespace protocol
 }  // namespace content
diff --git a/content/browser/devtools/protocol/devtools_domain_handler.h b/content/browser/devtools/protocol/devtools_domain_handler.h
index ea48934a..be887506 100644
--- a/content/browser/devtools/protocol/devtools_domain_handler.h
+++ b/content/browser/devtools/protocol/devtools_domain_handler.h
@@ -6,10 +6,12 @@
 #define CONTENT_BROWSER_DEVTOOLS_PROTOCOL_DEVTOOLS_DOMAIN_HANDLER_H_
 
 #include "content/browser/devtools/protocol/forward.h"
+#include "content/browser/devtools/shared_worker_devtools_agent_host.h"
 
 namespace content {
 
 class RenderFrameHostImpl;
+class DevToolsSession;
 
 namespace protocol {
 
@@ -21,12 +23,17 @@
   virtual void SetRenderer(int process_host_id,
                            RenderFrameHostImpl* frame_host);
   virtual void Wire(UberDispatcher* dispatcher);
+  void SetSession(DevToolsSession* session);
   virtual Response Disable();
 
-  const std::string& name() const { return name_; }
+  const std::string& name() const;
+
+ protected:
+  DevToolsSession* session();
 
  private:
   std::string name_;
+  DevToolsSession* session_;
 
   DISALLOW_COPY_AND_ASSIGN(DevToolsDomainHandler);
 };
diff --git a/content/browser/frame_host/back_forward_cache_impl.cc b/content/browser/frame_host/back_forward_cache_impl.cc
index 59ab3d74..9994c06e 100644
--- a/content/browser/frame_host/back_forward_cache_impl.cc
+++ b/content/browser/frame_host/back_forward_cache_impl.cc
@@ -596,10 +596,9 @@
 void BackForwardCacheImpl::DisableForTesting(DisableForTestingReason reason) {
   is_disabled_for_testing_ = true;
 
-  // This could happen if a test populated some entries in the cache, then
-  // called DisableForTesting(). This is not something we currently expect tests
-  // to do.
-  DCHECK(entries_.empty());
+  // Flush all the entries to make sure there are no entries in the cache after
+  // DisableForTesting() is called.
+  Flush();
 }
 
 const std::list<std::unique_ptr<BackForwardCacheImpl::Entry>>&
diff --git a/content/browser/frame_host/back_forward_cache_metrics_browsertest.cc b/content/browser/frame_host/back_forward_cache_metrics_browsertest.cc
index 5fd575ff..d6d9a583 100644
--- a/content/browser/frame_host/back_forward_cache_metrics_browsertest.cc
+++ b/content/browser/frame_host/back_forward_cache_metrics_browsertest.cc
@@ -12,6 +12,7 @@
 #include "content/browser/frame_host/back_forward_cache_metrics.h"
 #include "content/browser/frame_host/navigation_controller_impl.h"
 #include "content/browser/web_contents/web_contents_impl.h"
+#include "content/common/content_navigation_policy.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/web_contents.h"
@@ -566,13 +567,14 @@
   EXPECT_TRUE(NavigateToURL(shell(), url1));
   EXPECT_TRUE(NavigateToURL(shell(), url2));
 
-  {
-    TestNavigationObserver navigation_observer(shell()->web_contents(), 2);
-    shell()->GoBackOrForward(-1);
-    navigation_observer.WaitForNavigationFinished();
-  }
+  web_contents()->GetController().GoBack();
+  EXPECT_TRUE(WaitForLoadStop(web_contents()));
 
-  ASSERT_EQ(navigation_ids_.size(), static_cast<size_t>(5));
+  // When the page is restored from back-forward cache, there is one navigation
+  // corresponding to the bfcache restore. Whereas, when the page is reloaded,
+  // there are two navigations i.e., one loading the main frame and one loading
+  // the subframe.
+  ASSERT_EQ(navigation_ids_.size(), IsBackForwardCacheEnabled() ? 4u : 5u);
   // ukm::SourceId id1 = ToSourceId(navigation_ids_[0]);
   // ukm::SourceId id2 = ToSourceId(navigation_ids_[1]);
   // ukm::SourceId id3 = ToSourceId(navigation_ids_[2]);
diff --git a/content/browser/frame_host/navigation_controller_impl.cc b/content/browser/frame_host/navigation_controller_impl.cc
index 870d677..5735af8 100644
--- a/content/browser/frame_host/navigation_controller_impl.cc
+++ b/content/browser/frame_host/navigation_controller_impl.cc
@@ -3361,7 +3361,8 @@
           std::vector<
               network::mojom::WebClientHintsType>() /* enabled_client_hints */,
           false /* is_cross_browsing_instance */,
-          std::vector<std::string>() /* forced_content_security_policies */);
+          std::vector<std::string>() /* forced_content_security_policies */,
+          nullptr /* old_page_info */);
 #if defined(OS_ANDROID)
   if (ValidateDataURLAsString(params.data_url_as_string)) {
     commit_params->data_url_as_string = params.data_url_as_string->data();
diff --git a/content/browser/frame_host/navigation_entry_impl.cc b/content/browser/frame_host/navigation_entry_impl.cc
index 293dfc6c3..5009ce5 100644
--- a/content/browser/frame_host/navigation_entry_impl.cc
+++ b/content/browser/frame_host/navigation_entry_impl.cc
@@ -788,7 +788,8 @@
           std::vector<
               network::mojom::WebClientHintsType>() /* enabled_client_hints */,
           false /* is_cross_browsing_instance */,
-          std::vector<std::string>() /* forced_content_security_policies */);
+          std::vector<std::string>() /* forced_content_security_policies */,
+          nullptr /* old_page_info */);
 #if defined(OS_ANDROID)
   if (NavigationControllerImpl::ValidateDataURLAsString(GetDataURLAsString())) {
     commit_params->data_url_as_string = GetDataURLAsString()->data();
diff --git a/content/browser/frame_host/navigation_request.cc b/content/browser/frame_host/navigation_request.cc
index 0d9f01a1..49910a3a 100644
--- a/content/browser/frame_host/navigation_request.cc
+++ b/content/browser/frame_host/navigation_request.cc
@@ -882,7 +882,8 @@
           std::vector<
               network::mojom::WebClientHintsType>() /* enabled_client_hints */,
           false /* is_cross_browsing_instance */,
-          std::vector<std::string>() /* forced_content_security_policies */);
+          std::vector<std::string>() /* forced_content_security_policies */,
+          nullptr /* old_page_info */);
 
   // CreateRendererInitiated() should only be triggered when the navigation is
   // initiated by a frame in the same process.
@@ -975,7 +976,8 @@
           std::vector<
               network::mojom::WebClientHintsType>() /* enabled_client_hints */,
           false /* is_cross_browsing_instance */,
-          std::vector<std::string>() /* forced_content_security_policies */
+          std::vector<std::string>() /* forced_content_security_policies */,
+          nullptr /* old_page_info */
       );
   mojom::BeginNavigationParamsPtr begin_params =
       mojom::BeginNavigationParams::New();
@@ -3099,6 +3101,39 @@
                                        error_page_content);
 }
 
+void NavigationRequest::AddOldPageInfoToCommitParamsIfNeeded() {
+  // Add the routing ID and the updated lifecycle state of the old page if we
+  // need to run pagehide and visibilitychange handlers of the old page
+  // when we commit the new page.
+  auto* old_frame_host =
+      frame_tree_node_->render_manager()->current_frame_host();
+  if (!GetRenderFrameHost()
+           ->ShouldDispatchPagehideAndVisibilitychangeDuringCommit(
+               old_frame_host, GetURL())) {
+    return;
+  }
+  DCHECK(!IsSameDocument());
+  // The pagehide event's "persisted" property depends on whether the old page
+  // will be put into the back-forward cache or not. As we won't freeze the
+  // page until after the commit finished, there is no way to know for sure
+  // whether the page will actually be persisted or not after commit, but we
+  // will send our best guess by checking if the page can be persisted at this
+  // point.
+  bool can_store_old_page_in_bfcache =
+      frame_tree_node_->frame_tree()
+          ->controller()
+          ->GetBackForwardCache()
+          .CanPotentiallyStorePageLater(old_frame_host);
+  commit_params_->old_page_info = mojom::OldPageInfo::New();
+  commit_params_->old_page_info->routing_id_for_old_main_frame =
+      old_frame_host->GetRoutingID();
+  auto* page_lifecycle_state_manager =
+      old_frame_host->render_view_host()->GetPageLifecycleStateManager();
+  commit_params_->old_page_info->new_lifecycle_state_for_old_page =
+      page_lifecycle_state_manager->SetPagehideDispatchDuringNewPageCommit(
+          can_store_old_page_in_bfcache /* persisted */);
+}
+
 void NavigationRequest::CommitNavigation() {
   UpdateCommitNavigationParamsHistory();
   DCHECK(NeedsUrlLoader() == !!response_head_ ||
@@ -3106,6 +3141,8 @@
   DCHECK(!common_params_->url.SchemeIs(url::kJavaScriptScheme));
   DCHECK(!IsRendererDebugURL(common_params_->url));
 
+  AddOldPageInfoToCommitParamsIfNeeded();
+
   if (IsServedFromBackForwardCache()) {
     NavigationControllerImpl* controller = GetNavigationController();
     std::unique_ptr<BackForwardCacheImpl::Entry> restored_bfcache_entry =
diff --git a/content/browser/frame_host/navigation_request.h b/content/browser/frame_host/navigation_request.h
index 257eb926..e9819cd 100644
--- a/content/browser/frame_host/navigation_request.h
+++ b/content/browser/frame_host/navigation_request.h
@@ -865,6 +865,13 @@
   // Inform the RenderProcessHost to no longer expect a navigation.
   void ResetExpectedProcess();
 
+  // If this is a same-site main-frame navigation where we did a proactive
+  // BrowsingInstance swap but we're reusing the old page's process, we need
+  // to send the routing ID and the updated lifecycle state of the old page so
+  // that we can run pagehide and visibilitychange handlers of the old page
+  // when we commit the new page.
+  void AddOldPageInfoToCommitParamsIfNeeded();
+
   // Compute the history offset of the new document compared to the current one.
   // See navigation_history_offset_ for more details.
   int EstimateHistoryOffset();
diff --git a/content/browser/frame_host/navigator.cc b/content/browser/frame_host/navigator.cc
index 4ab4593c2..b61e0d3 100644
--- a/content/browser/frame_host/navigator.cc
+++ b/content/browser/frame_host/navigator.cc
@@ -231,20 +231,31 @@
   DCHECK(navigation_request);
   FrameTreeNode* frame_tree_node = render_frame_host->frame_tree_node();
   FrameTree* frame_tree = frame_tree_node->frame_tree();
+  RenderFrameHostImpl* old_frame_host =
+      frame_tree_node->render_manager()->current_frame_host();
 
   bool is_same_document_navigation = controller_->IsURLSameDocumentNavigation(
       params.url, params.origin, was_within_same_document, render_frame_host);
 
   // If a frame claims the navigation was same-document, it must be the current
   // frame, not a pending one.
-  if (is_same_document_navigation &&
-      render_frame_host !=
-          frame_tree_node->render_manager()->current_frame_host()) {
+  if (is_same_document_navigation && render_frame_host != old_frame_host) {
     bad_message::ReceivedBadMessage(render_frame_host->GetProcess(),
                                     bad_message::NI_IN_PAGE_NAVIGATION);
     is_same_document_navigation = false;
   }
 
+  if (auto& old_page_info = navigation_request->commit_params().old_page_info) {
+    // This is a same-site main-frame navigation where we did a proactive
+    // BrowsingInstance swap but we're reusing the old page's process, and we
+    // have dispatched the pagehide and visibilitychange handlers of the old
+    // page when we committed the new page.
+    auto* page_lifecycle_state_manager =
+        old_frame_host->render_view_host()->GetPageLifecycleStateManager();
+    page_lifecycle_state_manager->DidSetPagehideDispatchDuringNewPageCommit(
+        std::move(old_page_info->new_lifecycle_state_for_old_page));
+  }
+
   if (ui::PageTransitionIsMainFrame(params.transition)) {
     if (delegate_) {
       // Run tasks that must execute just before the commit.
diff --git a/content/browser/frame_host/render_document_host_user_data_browsertest.cc b/content/browser/frame_host/render_document_host_user_data_browsertest.cc
index 8e793b1..ca6e52b6 100644
--- a/content/browser/frame_host/render_document_host_user_data_browsertest.cc
+++ b/content/browser/frame_host/render_document_host_user_data_browsertest.cc
@@ -18,6 +18,7 @@
 #include "content/public/browser/site_isolation_policy.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_features.h"
+#include "content/public/test/back_forward_cache_util.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/content_browser_test.h"
@@ -550,6 +551,14 @@
   RenderFrameHostImpl* rfh_a = top_frame_host();
   RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host();
 
+  // Test needs these RenderFrameHosts to be pending deletion after navigating
+  // but it doesn't happen with BackForwardCache as it is stored in cache.
+  // BFCache case is covered explicitly by
+  // "RenderDocumentHostUserDataWithBackForwardCacheTest.BackForwardCacheNavigation"
+  // test.
+  DisableBackForwardCacheForTesting(shell()->web_contents(),
+                                    BackForwardCache::TEST_ASSUMES_NO_CACHING);
+
   // 2) Leave both rfh_a and rfh_b in pending deletion state.
   LeaveInPendingDeletionState(rfh_a);
   LeaveInPendingDeletionState(rfh_b);
@@ -703,6 +712,9 @@
   base::WeakPtr<Data> data = Data::GetForCurrentDocument(rfh_a)->GetWeakPtr();
   EXPECT_TRUE(data);
 
+  DisableBackForwardCacheForTesting(shell()->web_contents(),
+                                    BackForwardCache::TEST_ASSUMES_NO_CACHING);
+
   // 3) Navigate to B.
   EXPECT_TRUE(NavigateToURL(shell(), url_b));
   EXPECT_NE(rfh_a, top_frame_host());
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index 61b373e..9f450a1 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -5882,6 +5882,29 @@
   }
 }
 
+bool RenderFrameHostImpl::ShouldDispatchPagehideAndVisibilitychangeDuringCommit(
+    RenderFrameHostImpl* old_frame_host,
+    const GURL& dest_url) {
+  // Only return true if this is a same-site navigation and we did a proactive
+  // BrowsingInstance swap but we're reusing the old page's renderer process.
+  DCHECK(old_frame_host);
+  if (old_frame_host->GetSiteInstance()->IsRelatedSiteInstance(
+          GetSiteInstance())) {
+    return false;
+  }
+  if (old_frame_host->GetProcess() != GetProcess()) {
+    return false;
+  }
+  if (!frame_tree_node_->render_manager()->IsCurrentlySameSite(old_frame_host,
+                                                               dest_url)) {
+    return false;
+  }
+  DCHECK(frame_tree_node_->IsMainFrame());
+  DCHECK_NE(old_frame_host, this);
+  DCHECK_NE(old_frame_host->GetSiteInstance(), GetSiteInstance());
+  return true;
+}
+
 void RenderFrameHostImpl::CommitNavigation(
     NavigationRequest* navigation_request,
     mojom::CommonNavigationParamsPtr common_params,
@@ -6329,7 +6352,7 @@
     // point just before the navigation commits.
     // TODO(altimin, crbug.com/933147): Remove this logic after we are done with
     // implementing back-forward cache.
-    if (!GetParent() && frame_tree_node()->current_frame_host() == this) {
+    if (!GetParent() && frame_tree_node_->current_frame_host() == this) {
       if (NavigationEntryImpl* last_committed_entry =
               NavigationEntryImpl::FromNavigationEntry(
                   frame_tree()->controller()->GetLastCommittedEntry())) {
diff --git a/content/browser/frame_host/render_frame_host_impl.h b/content/browser/frame_host/render_frame_host_impl.h
index 8928f07..b6aaa33 100644
--- a/content/browser/frame_host/render_frame_host_impl.h
+++ b/content/browser/frame_host/render_frame_host_impl.h
@@ -1725,6 +1725,23 @@
       const base::UnguessableToken& child_frame_token,
       bad_message::BadMessageReason reason);
 
+  // Whether we should run the pagehide/visibilitychange handlers of the
+  // RenderFrameHost we're navigating away from (|old_frame_host|) during the
+  // commit to a new RenderFrameHost (this RenderFrameHost). Should only return
+  // true when we're doing a same-site navigation and we did a proactive
+  // BrowsingInstance swap but we're reusing the old page's renderer process.
+  // We should run pagehide and visibilitychange handlers of the old page during
+  // the commit of the new main frame in those cases because in other same-site
+  // navigations we will run those handlers before the new page finished
+  // committing. Note that unload handlers will still run after the new page
+  // finished committing. Ideally we would run unload handlers alongside
+  // pagehide and visibilitychange handlers at commit time too, but we'd need to
+  // actually unload/freeze the page in that case which is more complex.
+  // TODO(crbug.com/1110744): Support unload-in-commit.
+  bool ShouldDispatchPagehideAndVisibilitychangeDuringCommit(
+      RenderFrameHostImpl* old_frame_host,
+      const GURL& dest_url);
+
   mojo::PendingRemote<network::mojom::CookieAccessObserver>
   CreateCookieAccessObserver();
 
diff --git a/content/browser/frame_host/render_frame_host_impl_browsertest.cc b/content/browser/frame_host/render_frame_host_impl_browsertest.cc
index 9667209..bac6ace 100644
--- a/content/browser/frame_host/render_frame_host_impl_browsertest.cc
+++ b/content/browser/frame_host/render_frame_host_impl_browsertest.cc
@@ -789,10 +789,13 @@
       web_contents()->GetMainFrame()->is_waiting_for_beforeunload_completion());
 
   // The navigation that succeeded was a browser-initiated, main frame
-  // navigation, so it swapped RenderFrameHosts. |main_frame| should now be
-  // pending deletion and waiting for unload ACK, but it should not be waiting
-  // for the beforeunload completion callback.
-  EXPECT_TRUE(main_frame->IsPendingDeletion());
+  // navigation, so it swapped RenderFrameHosts. |main_frame| should either be
+  // in pending deletion and waiting for unload ACK or enter back-forward cache,
+  // but it should not be waiting for the beforeunload completion callback.
+  EXPECT_THAT(
+      main_frame->lifecycle_state(),
+      testing::AnyOf(testing::Eq(LifecycleState::kRunningUnloadHandlers),
+                     testing::Eq(LifecycleState::kInBackForwardCache)));
   EXPECT_FALSE(main_frame->is_waiting_for_beforeunload_completion());
   EXPECT_EQ(0u, main_frame->beforeunload_pending_replies_.size());
   EXPECT_EQ(nullptr, main_frame->GetBeforeUnloadInitiator());
@@ -3630,7 +3633,14 @@
   // 4) Navigate rfh_a to C.
   EXPECT_TRUE(NavigateToURL(shell(), url_c));
   RenderFrameHostImpl* rfh_c = web_contents()->GetMainFrame();
-  EXPECT_EQ(LifecycleState::kRunningUnloadHandlers, rfh_b->lifecycle_state());
+
+  EXPECT_THAT(rfh_a->lifecycle_state(),
+              testing::AnyOf(testing::Eq(LifecycleState::kReadyToBeDeleted),
+                             testing::Eq(LifecycleState::kInBackForwardCache)));
+  EXPECT_THAT(
+      rfh_b->lifecycle_state(),
+      testing::AnyOf(testing::Eq(LifecycleState::kRunningUnloadHandlers),
+                     testing::Eq(LifecycleState::kInBackForwardCache)));
 
   // 5) Check the IsCurrent state of rfh_a, rfh_b and rfh_c after navigating to
   // C.
@@ -3680,7 +3690,10 @@
   RenderFrameHostImpl* rfh_b = root_frame_host();
 
   // 6) Check the LifecycleState of both rfh_a and rfh_b after navigating to B.
-  EXPECT_EQ(LifecycleState::kRunningUnloadHandlers, rfh_a->lifecycle_state());
+  EXPECT_THAT(
+      rfh_a->lifecycle_state(),
+      testing::AnyOf(testing::Eq(LifecycleState::kRunningUnloadHandlers),
+                     testing::Eq(LifecycleState::kInBackForwardCache)));
   EXPECT_EQ(LifecycleState::kActive, rfh_b->lifecycle_state());
 }
 
diff --git a/content/browser/frame_host/render_frame_host_manager.h b/content/browser/frame_host/render_frame_host_manager.h
index 87e02bc..2685002 100644
--- a/content/browser/frame_host/render_frame_host_manager.h
+++ b/content/browser/frame_host/render_frame_host_manager.h
@@ -505,6 +505,10 @@
   scoped_refptr<SiteInstance> GetSiteInstanceForNavigationRequest(
       NavigationRequest* navigation_request);
 
+  // Returns true if |candidate| is currently on the same web site as dest_url.
+  bool IsCurrentlySameSite(RenderFrameHostImpl* candidate,
+                           const GURL& dest_url);
+
   // Helper to initialize the main RenderFrame if it's not initialized.
   // TODO(https://crbug.com/936696): Remove this. For now debug URLs and
   // WebView JS execution are an exception to replacing all crashed frames for
@@ -708,10 +712,6 @@
       const SiteInstanceDescriptor& descriptor,
       SiteInstanceImpl* candidate_instance);
 
-  // Returns true if |candidate| is currently on the same web site as dest_url.
-  bool IsCurrentlySameSite(RenderFrameHostImpl* candidate,
-                           const GURL& dest_url);
-
   // Ensure that we have created all needed proxies for a new RFH with
   // SiteInstance |new_instance|: (1) create swapped-out RVHs and proxies for
   // the new RFH's opener chain if we are staying in the same BrowsingInstance;
diff --git a/content/browser/frame_host/render_frame_host_manager_browsertest.cc b/content/browser/frame_host/render_frame_host_manager_browsertest.cc
index 9bd85f2..01798ed 100644
--- a/content/browser/frame_host/render_frame_host_manager_browsertest.cc
+++ b/content/browser/frame_host/render_frame_host_manager_browsertest.cc
@@ -1942,7 +1942,8 @@
   EXPECT_FALSE(speculative_rfh);
 }
 
-#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID)
+#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID) || \
+    defined(OS_MAC)
 #define MAYBE_DeleteSpeculativeRFHPendingCommitOfPendingEntryOnInterrupted2 \
   DISABLED_DeleteSpeculativeRFHPendingCommitOfPendingEntryOnInterrupted2
 #else
@@ -2665,6 +2666,12 @@
   RenderProcessHostWatcher exit_observer(
       shell()->web_contents()->GetMainFrame()->GetProcess(),
       RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION);
+
+  // With BackForwardCache, old process won't get deleted on navigation as it is
+  // still in use by the bfcached document, disable back-forward cache to ensure
+  // that the process gets deleted.
+  DisableBackForwardCache(BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING);
+
   EXPECT_TRUE(NavigateToURL(shell(), GetCrossSiteURL("/title1.html")));
   exit_observer.Wait();
   EXPECT_FALSE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
@@ -2721,6 +2728,12 @@
   RenderProcessHostWatcher exit_observer(
       wc->GetMainFrame()->GetProcess(),
       RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION);
+
+  // With BackForwardCache, old process won't get deleted on navigation as it is
+  // still in use by the bfcached document, disable back-forward cache to ensure
+  // that the process gets deleted.
+  DisableBackForwardCache(BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING);
+
   EXPECT_TRUE(NavigateToURL(shell(), GetCrossSiteURL("/title1.html")));
   exit_observer.Wait();
   EXPECT_FALSE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
@@ -2884,6 +2897,12 @@
   RenderProcessHostWatcher exit_observer(
       shell()->web_contents()->GetMainFrame()->GetProcess(),
       RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
+
+  // With BackForwardCache, old process won't get deleted on navigation as it is
+  // still in use by the bfcached document, disable back-forward cache to ensure
+  // that the process gets deleted.
+  DisableBackForwardCache(BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING);
+
   EXPECT_TRUE(NavigateToURL(shell(), GetCrossSiteURL("/title1.html")));
   exit_observer.Wait();
   EXPECT_FALSE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
@@ -3008,6 +3027,11 @@
   GURL cross_site_url =
       embedded_test_server()->GetURL("foo.com", "/title2.html");
   RenderFrameHostDestructionObserver rfh_observer(root->current_frame_host());
+
+  // The old RenderFrameHost might have entered the BackForwardCache. Disable
+  // back-forward cache to ensure that the RenderFrameHost gets deleted.
+  DisableBackForwardCache(BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING);
+
   EXPECT_TRUE(NavigateToURL(shell(), cross_site_url));
   rfh_observer.Wait();
 
@@ -3399,6 +3423,10 @@
   EXPECT_TRUE(
       ExecuteScript(root, "window.onunload=function(e){ while(1); };\n"));
 
+  // With BackForwardCache, swapped out RenderFrameHost won't have a
+  // replacement proxy as the document is stored in cache.
+  DisableBackForwardCache(BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING);
+
   // Navigate the tab to a different site, and only wait for commit, not load
   // stop.
   RenderFrameHostImpl* rfh_a = root->current_frame_host();
@@ -3639,11 +3667,9 @@
 // See https://crbug.com/577449.
 IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                        UnloadPushStateOnCrossProcessNavigation) {
-  shell()
-      ->web_contents()
-      ->GetController()
-      .GetBackForwardCache()
-      .DisableForTesting(content::BackForwardCache::TEST_USES_UNLOAD_EVENT);
+  // TODO(sreejakshetty): Replace 'unload' with 'pagehide' and reenable this
+  // test for BackForwardCache.
+  DisableBackForwardCache(content::BackForwardCache::TEST_USES_UNLOAD_EVENT);
 
   StartEmbeddedServer();
   WebContentsImpl* web_contents =
@@ -5181,6 +5207,10 @@
   // There should be two NavigationEntries.
   EXPECT_EQ(2, nav_controller.GetEntryCount());
 
+  // Ensure that previous document won't be restored from the BackForwardCache,
+  // to force a network fetch, which would result in a network error.
+  DisableBackForwardCache(BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING);
+
   // Create an interceptor to cause navigations to url1 to fail and go back
   // in session history.
   std::unique_ptr<URLLoaderInterceptor> url_interceptor =
@@ -6122,17 +6152,12 @@
 // 3. Go back to A1 (should reuse A2's process).
 IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
                        HistoryNavigationReusesProcess) {
-  if (IsBackForwardCacheEnabled()) {
-    // This test expects a renderer process to eventually get deleted when we
-    // navigate away from the page using it, which won't happen if the page is
-    // kept alive in the back-forward cache.  So, we should disable back-forward
-    // cache for this test.
-    shell()
-        ->web_contents()
-        ->GetController()
-        .GetBackForwardCache()
-        .DisableForTesting(BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING);
-  }
+  // This test expects a renderer process to eventually get deleted when we
+  // navigate away from the page using it, which won't happen if the page is
+  // kept alive in the back-forward cache.  So, we should disable back-forward
+  // cache for this test.
+  DisableBackForwardCache(BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING);
+
   ASSERT_TRUE(embedded_test_server()->Start());
   GURL url_1(embedded_test_server()->GetURL("/title1.html"));
   GURL url_2(embedded_test_server()->GetURL("/title2.html"));
@@ -6213,17 +6238,12 @@
 // 3. Go forward to A2 (should reuse A1's process).
 IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
                        HistoryNavigationReusesProcess_SkipSameSiteEntry) {
-  if (IsBackForwardCacheEnabled()) {
-    // This test expects a renderer process to eventually get deleted when we
-    // navigate away from the page using it, which won't happen if the page is
-    // kept alive in the back-forward cache.  So, we should disable back-forward
-    // cache for this test.
-    shell()
-        ->web_contents()
-        ->GetController()
-        .GetBackForwardCache()
-        .DisableForTesting(BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING);
-  }
+  // This test expects a renderer process to eventually get deleted when we
+  // navigate away from the page using it, which won't happen if the page is
+  // kept alive in the back-forward cache.  So, we should disable back-forward
+  // cache for this test.
+  DisableBackForwardCache(BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING);
+
   ASSERT_TRUE(embedded_test_server()->Start());
   GURL url_1(embedded_test_server()->GetURL("/title1.html"));
   GURL url_2(embedded_test_server()->GetURL("/title2.html"));
@@ -6305,17 +6325,12 @@
 // 3. Go forward to B (should use new process).
 IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
                        HistoryNavigationReusesProcess_SkipCrossSiteEntry) {
-  if (IsBackForwardCacheEnabled()) {
-    // This test expects a renderer process to eventually get deleted when we
-    // navigate away from the page using it, which won't happen if the page is
-    // kept alive in the back-forward cache.  So, we should disable back-forward
-    // cache for this test.
-    shell()
-        ->web_contents()
-        ->GetController()
-        .GetBackForwardCache()
-        .DisableForTesting(BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING);
-  }
+  // This test expects a renderer process to eventually get deleted when we
+  // navigate away from the page using it, which won't happen if the page is
+  // kept alive in the back-forward cache.  So, we should disable back-forward
+  // cache for this test.
+  DisableBackForwardCache(BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING);
+
   ASSERT_TRUE(embedded_test_server()->Start());
   GURL url_1(embedded_test_server()->GetURL("/title1.html"));
   GURL cross_site_url(embedded_test_server()->GetURL("b.com", "/title2.html"));
@@ -6385,17 +6400,12 @@
 // used originally).
 IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
                        HistoryNavigationReusesProcessThatIsStillAlive) {
-  if (IsBackForwardCacheEnabled()) {
-    // This test expects a renderer process to eventually get deleted when we
-    // navigate away from the page using it, which won't happen if the page is
-    // kept alive in the back-forward cache.  So, we should disable back-forward
-    // cache for this test.
-    shell()
-        ->web_contents()
-        ->GetController()
-        .GetBackForwardCache()
-        .DisableForTesting(BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING);
-  }
+  // This test expects a renderer process to eventually get deleted when we
+  // navigate away from the page using it, which won't happen if the page is
+  // kept alive in the back-forward cache.  So, we should disable back-forward
+  // cache for this test.
+  DisableBackForwardCache(BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING);
+
   ASSERT_TRUE(embedded_test_server()->Start());
   GURL url_1(embedded_test_server()->GetURL("/title1.html"));
   GURL url_to_open(embedded_test_server()->GetURL("/empty.html"));
@@ -6512,17 +6522,12 @@
 // Note: This test is currently disabled due to crbug.com/1107814.
 IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteCoopTest,
                        DISABLED_HistoryNavigationReusesProcess_COOP) {
-  if (IsBackForwardCacheEnabled()) {
-    // This test expects a renderer process to eventually get deleted when we
-    // navigate away from the page using it, which won't happen if the page is
-    // kept alive in the back-forward cache.  So, we should disable back-forward
-    // cache for this test.
-    shell()
-        ->web_contents()
-        ->GetController()
-        .GetBackForwardCache()
-        .DisableForTesting(BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING);
-  }
+  // This test expects a renderer process to eventually get deleted when we
+  // navigate away from the page using it, which won't happen if the page is
+  // kept alive in the back-forward cache.  So, we should disable back-forward
+  // cache for this test.
+  DisableBackForwardCache(BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING);
+
   GURL url_1(https_server()->GetURL("a.com", "/title1.html"));
   GURL url_2(https_server()->GetURL("a.com", "/title2.html"));
   GURL coop_url(
@@ -6982,6 +6987,357 @@
   SetBrowserClientForTesting(old_client);
 }
 
+// Tests that pagehide handlers of the old RFH are run during the commit
+// of the new RFH when swapping RFH for same-site navigations due to proactive
+// BrowsingInstance swap.
+IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
+                       PagehideRunsDuringCommit) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  GURL url_1(embedded_test_server()->GetURL("/title1.html"));
+  GURL url_2(embedded_test_server()->GetURL("/local_storage.html"));
+  WebContentsImpl* web_contents =
+      static_cast<WebContentsImpl*>(shell()->web_contents());
+
+  // 1) Navigate to title1.html.
+  EXPECT_TRUE(NavigateToURL(shell(), url_1));
+  RenderFrameHostImpl* main_frame_1 = web_contents->GetMainFrame();
+  // Create a pagehide handler that sets item "pagehide_storage" in
+  // localStorage.
+  EXPECT_TRUE(ExecJs(
+      main_frame_1,
+      base::StringPrintf(R"(
+            localStorage.setItem('pagehide_storage', 'not_dispatched');
+            var dispatched_pagehide = false;
+            window.onpagehide = function(e) {
+              if (dispatched_pagehide) {
+                // We shouldn't dispatch pagehide more than once.
+                localStorage.setItem('pagehide_storage',
+                  'dispatched_more_than_once');
+              } else if (e.persisted != %s) {
+                localStorage.setItem('pagehide_storage', 'wrong_persisted');
+              } else {
+                localStorage.setItem('pagehide_storage',
+                  'dispatched_once');
+              }
+              dispatched_pagehide = true;
+            })",
+                         IsBackForwardCacheEnabled() ? "true" : "false")));
+
+  // 2) Navigate to local_storage.html.
+  RenderFrameDeletedObserver main_frame_1_deleted_observer(main_frame_1);
+  EXPECT_TRUE(NavigateToURL(shell(), url_2));
+
+  // Check that title1.html and local_storage.html are in different RFHs.
+  RenderFrameHostImpl* main_frame_2 = web_contents->GetMainFrame();
+  EXPECT_NE(main_frame_1, main_frame_2);
+
+  // Check that the value set by |main_frame_1|'s pagehide handler can be
+  // accessed by |main_frame_2| at load time (the first time the new page runs
+  // scripts), setting the |pagehide_storage_at_load| variable correctly.
+  EXPECT_EQ("dispatched_once",
+            EvalJs(main_frame_2, "pagehide_storage_at_load"));
+
+  // Check that the value for 'pagehide_storage' stays the same after
+  // |main_frame_2| finished loading (or |main_frame_1| deleted if bfcache is
+  // not enabled).
+  if (!IsBackForwardCacheEnabled())
+    main_frame_1_deleted_observer.WaitUntilDeleted();
+  EXPECT_EQ("dispatched_once",
+            EvalJs(main_frame_2, "localStorage.getItem('pagehide_storage')"));
+}
+
+// Tests that visibilitychange handlers of the old RFH are run during the commit
+// of the new RFH when swapping RFH for same-site navigations due to proactive
+// BrowsingInstance swap.
+IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
+                       VisibilitychangeRunsDuringCommit) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  GURL url_1(embedded_test_server()->GetURL("/title1.html"));
+  GURL url_2(embedded_test_server()->GetURL("/local_storage.html"));
+  WebContentsImpl* web_contents =
+      static_cast<WebContentsImpl*>(shell()->web_contents());
+
+  // 1) Navigate to title1.html.
+  EXPECT_TRUE(NavigateToURL(shell(), url_1));
+  RenderFrameHostImpl* main_frame_1 = web_contents->GetMainFrame();
+  // Create a visibilitychange handler that sets item "visibilitychange_storage"
+  // localStorage.
+  EXPECT_TRUE(ExecJs(main_frame_1, R"(
+            localStorage.setItem('visibilitychange_storage', 'not_dispatched');
+            var dispatched_visibilitychange = false;
+            document.onvisibilitychange = function(e) {
+              if (dispatched_visibilitychange) {
+                // We shouldn't dispatch visibilitychange more than once.
+                localStorage.setItem('visibilitychange_storage',
+                  'dispatched_more_than_once');
+              } else if (document.visibilityState != 'hidden') {
+                // We should dispatch the event when the visibilityState is
+                // 'hidden'.
+                localStorage.setItem('visibilitychange_storage', 'not_hidden');
+              } else {
+                localStorage.setItem('visibilitychange_storage',
+                  'dispatched_once');
+              }
+              dispatched_visibilitychange = true;
+            })"));
+
+  // 2) Navigate to local_storage.html.
+  RenderFrameDeletedObserver main_frame_1_deleted_observer(main_frame_1);
+  EXPECT_TRUE(NavigateToURL(shell(), url_2));
+
+  // Check that title1.html and local_storage.html are in different RFHs.
+  RenderFrameHostImpl* main_frame_2 = web_contents->GetMainFrame();
+  EXPECT_NE(main_frame_1, main_frame_2);
+
+  // Check that the value set by |main_frame_1|'s pagehide handler can be
+  // accessed by |main_frame_2| at load time (the first time the new page runs
+  // scripts), setting the |visibilitychange_storage_at_load| variable
+  // correctly.
+  EXPECT_EQ("dispatched_once",
+            EvalJs(main_frame_2, "visibilitychange_storage_at_load"));
+
+  // Check that the value for 'visibilitychange_storage' stays the same after
+  // |main_frame_2| finished loading (or |main_frame_1| got deleted, if bfcache
+  // is not enabled).
+  if (!IsBackForwardCacheEnabled())
+    main_frame_1_deleted_observer.WaitUntilDeleted();
+  EXPECT_EQ(
+      "dispatched_once",
+      EvalJs(main_frame_2, "localStorage.getItem('visibilitychange_storage')"));
+}
+
+// Tests that unload handlers of the old RFH are run during commit of the new
+// RFH when swapping RFH for same-site navigations due to proactive
+// BrowsingInstance swap.
+// TODO(crbug.com/1110744): support this.
+IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
+                       DISABLED_UnloadRunsDuringCommit) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  GURL url_1(embedded_test_server()->GetURL("/title1.html"));
+  GURL url_2(embedded_test_server()->GetURL("/local_storage.html"));
+  WebContentsImpl* web_contents =
+      static_cast<WebContentsImpl*>(shell()->web_contents());
+
+  // 1) Navigate to title1.html.
+  EXPECT_TRUE(NavigateToURL(shell(), url_1));
+  RenderFrameHostImpl* main_frame_1 = web_contents->GetMainFrame();
+  // Create an unload handler that sets item "unload_storage" in localStorage.
+  EXPECT_TRUE(ExecJs(main_frame_1, R"(
+            localStorage.setItem('unload_storage', 'not_dispatched');
+            var dispatched_unload = false;
+            window.onunload = function(e) {
+              if (dispatched_unload) {
+                // We shouldn't dispatch unload more than once.
+                localStorage.setItem('unload_storage',
+                  'dispatched_more_than_once');
+              } else {
+                localStorage.setItem('unload_storage', 'dispatched_once');
+              }
+              dispatched_unload = true;
+            };)"));
+
+  // 2) Navigate to local_storage.html.
+  RenderFrameDeletedObserver main_frame_1_deleted_observer(main_frame_1);
+  EXPECT_TRUE(NavigateToURL(shell(), url_2));
+
+  // Check that title1.html and local_storage.html are in different RFHs.
+  RenderFrameHostImpl* main_frame_2 = web_contents->GetMainFrame();
+  EXPECT_NE(main_frame_1, main_frame_2);
+
+  // Check that the value set by |main_frame_1|'s unload handler can be
+  // accessed by |main_frame_2| at load time (the first time the new page runs
+  // scripts), setting the |unload_storage_at_load| variable correctly.
+  EXPECT_EQ("dispatched_once", EvalJs(main_frame_2, "unload_storage_at_load"));
+
+  // Check that the value for 'unload_storage' stays the same after
+  // |main_frame_2| finished loading (or |main_frame_1| got deleted, if bfcache
+  // is not enabled).
+  if (!IsBackForwardCacheEnabled())
+    main_frame_1_deleted_observer.WaitUntilDeleted();
+  EXPECT_EQ("dispatched_once",
+            EvalJs(main_frame_2, "localStorage.getItem('unload_storage')"));
+}
+
+// Tests that pagehide and visibilitychange handlers of a subframe in the old
+// page are run during the commit of a new main RFH when swapping RFH for
+// same-site navigations due to proactive BrowsingInstance swap.
+IN_PROC_BROWSER_TEST_P(
+    ProactivelySwapBrowsingInstancesSameSiteTest,
+    PagehideAndVisibilitychangeInSubframesAreRunDuringCommit) {
+  if (IsBackForwardCacheEnabled()) {
+    // bfcached subframes with unload/pagehide/visibilitychange handlers will
+    // crash on a failed DCHECK due to crbug.com/1109742.
+    // TODO(crbug.com/1109742): don't skip this test when bfcache is enabled.
+    return;
+  }
+  ASSERT_TRUE(embedded_test_server()->Start());
+  GURL main_url(embedded_test_server()->GetURL(
+      "a.com", "/cross_site_iframe_factory.html?a.com(a.com)"));
+  GURL child_url = embedded_test_server()->GetURL(
+      "a.com", "/cross_site_iframe_factory.html?a.com()");
+  GURL url_2(embedded_test_server()->GetURL("a.com", "/local_storage.html"));
+  WebContentsImpl* web_contents =
+      static_cast<WebContentsImpl*>(shell()->web_contents());
+
+  // 1) Navigate to |main_url|.
+  EXPECT_TRUE(NavigateToURL(shell(), main_url));
+  RenderFrameHostImpl* main_frame_1 = web_contents->GetMainFrame();
+  FrameTreeNode* root = web_contents->GetFrameTree()->root();
+  ASSERT_EQ(1U, root->child_count());
+  // Check if the subframe is navigated to the correct URL.
+  FrameTreeNode* child = root->child_at(0);
+  EXPECT_EQ(child_url, child->current_url());
+
+  // Create a pagehide handler that sets item "pagehide_storage" and a
+  // visibilitychange handler that sets item "visibilitychange_storage" in
+  // localStorage in the subframe.
+  EXPECT_TRUE(ExecJs(
+      child,
+      base::StringPrintf(R"(
+          localStorage.setItem('pagehide_storage', 'not_dispatched');
+          var dispatched_pagehide = false;
+          window.onpagehide = function(e) {
+            if (dispatched_pagehide) {
+              // We shouldn't dispatch pagehide more than once.
+              localStorage.setItem('pagehide_storage',
+                'dispatched_more_than_once');
+            } else if (e.persisted != %s) {
+              localStorage.setItem('pagehide_storage', 'wrong_persisted');
+            } else {
+              localStorage.setItem('pagehide_storage', 'dispatched_once');
+            }
+            dispatched_pagehide = true;
+          }
+
+          localStorage.setItem('visibilitychange_storage', 'not_dispatched');
+          var dispatched_visibilitychange = false;
+          document.onvisibilitychange = function(e) {
+            if (dispatched_visibilitychange) {
+              // We shouldn't dispatch visibilitychange more than once.
+              localStorage.setItem('visibilitychange_storage',
+                'dispatched_more_than_once');
+            } else if (document.visibilityState != 'hidden') {
+              // We should dispatch the event when the visibilityState is
+              // 'hidden'.
+              localStorage.setItem('visibilitychange_storage', 'not_hidden');
+            } else {
+              localStorage.setItem('visibilitychange_storage',
+                'dispatched_once');
+            }
+            dispatched_visibilitychange = true;
+          })",
+                         IsBackForwardCacheEnabled() ? "true" : "false")));
+  // 2) Navigate to local_storage.html.
+  RenderFrameDeletedObserver main_frame_1_deleted_observer(main_frame_1);
+  EXPECT_TRUE(NavigateToURL(shell(), url_2));
+
+  // Check that |main_url| and local_storage.html are in different RFHs.
+  RenderFrameHostImpl* main_frame_2 = web_contents->GetMainFrame();
+  EXPECT_NE(main_frame_1, main_frame_2);
+
+  // Check that the value set by |child|'s pagehide and visibilitychange
+  // handlers can be accessed by |main_frame_2| at load time (the first time the
+  // new page runs scripts), setting the |pagehide_storage_at_load| and
+  // |visibilitychange_storage_at_load| variable correctly.
+  EXPECT_EQ("dispatched_once",
+            EvalJs(main_frame_2, "pagehide_storage_at_load"));
+  EXPECT_EQ("dispatched_once",
+            EvalJs(main_frame_2, "visibilitychange_storage_at_load"));
+
+  // Check that the value for 'pagehide_storage' and 'visibilitychange_storage'
+  // stays the same after |main_frame_2| finished loading (or |main_frame_1| got
+  // deleted, if bfcache is not enabled).
+  if (!IsBackForwardCacheEnabled())
+    main_frame_1_deleted_observer.WaitUntilDeleted();
+  EXPECT_EQ("dispatched_once",
+            EvalJs(main_frame_2, "localStorage.getItem('pagehide_storage')"));
+  EXPECT_EQ(
+      "dispatched_once",
+      EvalJs(main_frame_2, "localStorage.getItem('visibilitychange_storage')"));
+}
+
+// Tests that pagehide handlers of the old RFH are run during the commit
+// of the new RFH when swapping RFH for same-site navigations due to proactive
+// BrowsingInstance swap even if the page is already hidden (and
+// visibilitychange won't run).
+IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
+                       PagehideRunsDuringCommitOfHiddenPage) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  GURL url_1(embedded_test_server()->GetURL("/title1.html"));
+  GURL url_2(embedded_test_server()->GetURL("/local_storage.html"));
+  WebContentsImpl* web_contents =
+      static_cast<WebContentsImpl*>(shell()->web_contents());
+
+  // 1) Navigate to |url_1| and hide the tab.
+  EXPECT_TRUE(NavigateToURL(shell(), url_1));
+  RenderFrameHostImpl* main_frame_1 = web_contents->GetMainFrame();
+  // We need to set it to Visibility::VISIBLE first in case this is the first
+  // time the visibility is updated.
+  web_contents->UpdateWebContentsVisibility(Visibility::VISIBLE);
+  web_contents->UpdateWebContentsVisibility(Visibility::HIDDEN);
+  EXPECT_EQ(Visibility::HIDDEN, web_contents->GetVisibility());
+
+  // Create a pagehide handler that sets item "pagehide_storage" and a
+  // visibilitychange handler that sets item "visibilitychange_storage" in
+  // localStorage.
+  EXPECT_TRUE(ExecJs(
+      main_frame_1,
+      base::StringPrintf(R"(
+          localStorage.setItem('pagehide_storage', 'not_dispatched');
+          var dispatched_pagehide = false;
+          window.onpagehide = function(e) {
+            if (dispatched_pagehide) {
+              // We shouldn't dispatch pagehide more than once.
+              localStorage.setItem('pagehide_storage',
+                'dispatched_more_than_once');
+            } else if (e.persisted != %s) {
+              localStorage.setItem('pagehide_storage', 'wrong_persisted');
+            } else {
+              localStorage.setItem('pagehide_storage', 'dispatched_once');
+            }
+            dispatched_pagehide = true;
+          }
+
+          localStorage.setItem('visibilitychange_storage', 'not_dispatched');
+          document.onvisibilitychange = function(e) {
+            localStorage.setItem('visibilitychange_storage',
+                'should_not_be_dispatched');
+          })",
+                         IsBackForwardCacheEnabled() ? "true" : "false")));
+  // |visibilitychange_storage| should be set to its initial correct value.
+  EXPECT_EQ(
+      "not_dispatched",
+      EvalJs(main_frame_1, "localStorage.getItem('visibilitychange_storage')"));
+
+  // 2) Navigate to local_storage.html.
+  RenderFrameDeletedObserver main_frame_1_deleted_observer(main_frame_1);
+  EXPECT_TRUE(NavigateToURL(shell(), url_2));
+
+  // Check that |url_1| and local_storage.html are in different RFHs.
+  RenderFrameHostImpl* main_frame_2 = web_contents->GetMainFrame();
+  EXPECT_NE(main_frame_1, main_frame_2);
+
+  // Check that the value set by |main_frame_1|'s pagehide handler can be
+  // accessed by |main_frame_2| at load time (the first time the new page runs
+  // scripts), setting the |pagehide_storage_at_load| and variable correctly.
+  EXPECT_EQ("dispatched_once",
+            EvalJs(main_frame_2, "pagehide_storage_at_load"));
+  // |visibilitychange_storage_at_load| should not be modified.
+  EXPECT_EQ("not_dispatched",
+            EvalJs(main_frame_2, "visibilitychange_storage_at_load"));
+
+  // Check that the value for 'pagehide_storage' and 'visibilitychange_storage'
+  // stays the same after |main_frame_2| finished loading (or |main_frame_1| got
+  // deleted, if bfcache is not enabled).
+  if (!IsBackForwardCacheEnabled())
+    main_frame_1_deleted_observer.WaitUntilDeleted();
+  EXPECT_EQ("dispatched_once",
+            EvalJs(main_frame_2, "localStorage.getItem('pagehide_storage')"));
+  EXPECT_EQ(
+      "not_dispatched",
+      EvalJs(main_frame_2, "localStorage.getItem('visibilitychange_storage')"));
+}
+
 // Helper class to simplify testing of unload handlers.  It allows waiting for
 // particular HTTP requests to be made to the embedded_test_server(); the tests
 // use this to wait for termination pings (e.g., navigator.sendBeacon()) made
@@ -7328,6 +7684,12 @@
 
   // Navigate to another page with two subframes.
   RenderFrameDeletedObserver rfh_observer(rfh);
+
+  // Ensure that current document won't enter the BackForwardCache, so that it
+  // properly execute its unload handler. So, we disable back-forward cache for
+  // this test.
+  DisableBackForwardCache(BackForwardCacheImpl::TEST_USES_UNLOAD_EVENT);
+
   GURL second_url(embedded_test_server()->GetURL(
       "b.com", "/cross_site_iframe_factory.html?b(c,b)"));
   EXPECT_TRUE(NavigateToURL(shell(), second_url));
@@ -7633,6 +7995,12 @@
   GURL url3(embedded_test_server()->GetURL("bar.com", "/title1.html"));
   RenderProcessHostWatcher exit_observer(
       process1, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
+
+  // With BackForwardCache, old process won't be deleted on navigation as it is
+  // still in use by the bfcached document, disable back-forward cache to ensure
+  // that the process gets deleted.
+  DisableBackForwardCache(BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING);
+
   EXPECT_TRUE(NavigateToURL(shell(), url3));
   exit_observer.Wait();
 
diff --git a/content/browser/isolated_origin_browsertest.cc b/content/browser/isolated_origin_browsertest.cc
index 3ae310b..ed21069 100644
--- a/content/browser/isolated_origin_browsertest.cc
+++ b/content/browser/isolated_origin_browsertest.cc
@@ -26,6 +26,7 @@
 #include "content/public/browser/site_isolation_policy.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
+#include "content/public/test/back_forward_cache_util.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/content_browser_test.h"
@@ -3390,6 +3391,12 @@
   RenderProcessHostWatcher foo_process_observer(
       web_contents()->GetMainFrame()->GetProcess(),
       RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION);
+
+  // Disable the BackForwardCache to ensure the old process is going to be
+  // released.
+  DisableBackForwardCacheForTesting(web_contents(),
+                                    BackForwardCache::TEST_ASSUMES_NO_CACHING);
+
   GURL isolated_bar_url(
       embedded_test_server()->GetURL("isolated.bar.com", "/title1.html"));
   EXPECT_TRUE(NavigateToURL(shell(), isolated_bar_url));
diff --git a/content/browser/media/capture/web_contents_video_capture_device_browsertest.cc b/content/browser/media/capture/web_contents_video_capture_device_browsertest.cc
index 0331077..b58d213 100644
--- a/content/browser/media/capture/web_contents_video_capture_device_browsertest.cc
+++ b/content/browser/media/capture/web_contents_video_capture_device_browsertest.cc
@@ -400,8 +400,15 @@
 // whether the browser is running with software compositing or GPU-accelerated
 // compositing, whether the WebContents is visible/hidden or occluded/unoccluded
 // and whether the main document contains a cross-site iframe.
+
+// Fails on Linux only. http://crbug.com/1108205
+#if defined(OS_LINUX)
+#define MAYBE_CapturesContentChanges DISABLED_CapturesContentChanges
+#else
+#define MAYBE_CapturesContentChanges CapturesContentChanges
+#endif
 IN_PROC_BROWSER_TEST_P(WebContentsVideoCaptureDeviceBrowserTestP,
-                       CapturesContentChanges) {
+                       MAYBE_CapturesContentChanges) {
   SCOPED_TRACE(testing::Message()
                << "Test parameters: "
                << (IsSoftwareCompositingTest() ? "Software Compositing"
diff --git a/content/browser/renderer_host/frame_token_message_queue.cc b/content/browser/renderer_host/frame_token_message_queue.cc
index 72f07c45..5bb83c8 100644
--- a/content/browser/renderer_host/frame_token_message_queue.cc
+++ b/content/browser/renderer_host/frame_token_message_queue.cc
@@ -5,7 +5,6 @@
 #include "content/browser/renderer_host/frame_token_message_queue.h"
 
 #include "base/bind.h"
-#include "ipc/ipc_message.h"
 
 namespace content {
 
@@ -63,27 +62,10 @@
   callback_map_.insert(std::make_pair(frame_token, std::move(callback)));
 }
 
-void FrameTokenMessageQueue::OnFrameSwapMessagesReceived(
-    uint32_t frame_token,
-    std::vector<IPC::Message> messages) {
-  EnqueueOrRunFrameTokenCallback(
-      frame_token, base::BindOnce(&FrameTokenMessageQueue::ProcessSwapMessages,
-                                  base::Unretained(this), std::move(messages)));
-}
-
 void FrameTokenMessageQueue::Reset() {
   last_received_frame_token_reset_ = last_received_frame_token_;
   last_received_frame_token_ = 0;
   callback_map_.clear();
 }
 
-void FrameTokenMessageQueue::ProcessSwapMessages(
-    std::vector<IPC::Message> messages) {
-  for (const IPC::Message& i : messages) {
-    client_->OnProcessSwapMessage(i);
-    if (i.dispatch_error())
-      client_->OnMessageDispatchError(i);
-  }
-}
-
 }  // namespace content
diff --git a/content/browser/renderer_host/frame_token_message_queue.h b/content/browser/renderer_host/frame_token_message_queue.h
index e8b256d..166ccd0 100644
--- a/content/browser/renderer_host/frame_token_message_queue.h
+++ b/content/browser/renderer_host/frame_token_message_queue.h
@@ -12,10 +12,6 @@
 #include "base/macros.h"
 #include "content/common/content_export.h"
 
-namespace IPC {
-class Message;
-}  // namespace IPC
-
 namespace content {
 
 // The Renderer sends various messages which are not to be processed until after
@@ -25,29 +21,19 @@
 // Viz processes the frames, after which it notifies the Browser of which
 // FrameToken has completed processing.
 //
-// This enqueues all IPC::Messages associated with a FrameToken.
+// Non-IPC callbacks can be enqueued with EnqueueOrRunFrameTokenCallback.
 //
-// Additionally other callbacks can be enqueued with
-// EnqueueOrRunFrameTokenCallback.
-//
-// Upon receipt of DidProcessFrame all IPC::Messages associated with the
-// provided FrameToken are then dispatched, and all enqueued callbacks are ran.
+// Upon receipt of DidProcessFrame all Messages associated with the provided
+// FrameToken are then dispatched, and all enqueued callbacks are ran.
 class CONTENT_EXPORT FrameTokenMessageQueue {
  public:
-  // Notified of errors in processing messages, as well as of the actual
-  // enqueued IPC::Messages which need processing.
+  // Notified of errors in processing messages.
   class Client {
    public:
     ~Client() {}
 
     // Notified when an invalid frame token was received.
     virtual void OnInvalidFrameToken(uint32_t frame_token) = 0;
-
-    // Called when there are dispatching errors in OnMessageReceived().
-    virtual void OnMessageDispatchError(const IPC::Message& message) = 0;
-
-    // Process the enqueued message.
-    virtual void OnProcessSwapMessage(const IPC::Message& message) = 0;
   };
   FrameTokenMessageQueue();
   virtual ~FrameTokenMessageQueue();
@@ -66,21 +52,12 @@
   void EnqueueOrRunFrameTokenCallback(uint32_t frame_token,
                                       base::OnceClosure callback);
 
-  // Enqueues the swap messages.
-  void OnFrameSwapMessagesReceived(uint32_t frame_token,
-                                   std::vector<IPC::Message> messages);
-
   // Called when the renderer process is gone. This will reset our state to be
   // consistent incase a new renderer is created.
   void Reset();
 
   size_t size() const { return callback_map_.size(); }
 
- protected:
-  // Once both the frame and its swap messages arrive, we call this method to
-  // process the messages. Virtual for tests.
-  virtual void ProcessSwapMessages(std::vector<IPC::Message> messages);
-
  private:
   // Not owned.
   Client* client_ = nullptr;
diff --git a/content/browser/renderer_host/frame_token_message_queue_unittest.cc b/content/browser/renderer_host/frame_token_message_queue_unittest.cc
index da59028c..7943ecf9 100644
--- a/content/browser/renderer_host/frame_token_message_queue_unittest.cc
+++ b/content/browser/renderer_host/frame_token_message_queue_unittest.cc
@@ -8,7 +8,6 @@
 
 #include "base/bind.h"
 #include "base/macros.h"
-#include "ipc/ipc_message.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace content {
@@ -25,46 +24,23 @@
   // Resets all method counters.
   void Reset();
 
-  // All subsequent IPC::Messages received in OnProcessSwapMessage will be
-  // marked as having a dispatch error.
-  void SetErrorOnMessageProcess();
-
   // FrameTokenMessageQueue::Client:
   void OnInvalidFrameToken(uint32_t frame_token) override;
-  void OnMessageDispatchError(const IPC::Message& message) override;
-  void OnProcessSwapMessage(const IPC::Message& message) override;
 
   bool invalid_frame_token_called() const {
     return invalid_frame_token_called_;
   }
   uint32_t invalid_frame_token() const { return invalid_frame_token_; }
-  int on_message_dispatch_error_count() const {
-    return on_message_dispatch_error_count_;
-  }
-  int on_process_swap_message_count() const {
-    return on_process_swap_message_count_;
-  }
 
  private:
-  // If true the each IPC::Message received will be marked as having a dispatch
-  // error.
-  bool set_error_on_process_ = false;
   bool invalid_frame_token_called_ = false;
   uint32_t invalid_frame_token_ = 0u;
-  int on_message_dispatch_error_count_ = 0;
-  int on_process_swap_message_count_ = 0;
   DISALLOW_COPY_AND_ASSIGN(TestFrameTokenMessageQueueClient);
 };
 
 void TestFrameTokenMessageQueueClient::Reset() {
   invalid_frame_token_called_ = false;
   invalid_frame_token_ = 0u;
-  on_message_dispatch_error_count_ = 0;
-  on_process_swap_message_count_ = 0;
-}
-
-void TestFrameTokenMessageQueueClient::SetErrorOnMessageProcess() {
-  set_error_on_process_ = true;
 }
 
 void TestFrameTokenMessageQueueClient::OnInvalidFrameToken(
@@ -73,18 +49,6 @@
   invalid_frame_token_ = frame_token;
 }
 
-void TestFrameTokenMessageQueueClient::OnMessageDispatchError(
-    const IPC::Message& message) {
-  ++on_message_dispatch_error_count_;
-}
-
-void TestFrameTokenMessageQueueClient::OnProcessSwapMessage(
-    const IPC::Message& message) {
-  if (set_error_on_process_)
-    message.set_dispatch_error();
-  ++on_process_swap_message_count_;
-}
-
 // Test class which provides FrameTokenCallback() to be used as a closure when
 // enqueueing non-IPC callbacks. This only tracks if the callback was called.
 class TestNonIPCMessageEnqueuer {
@@ -133,124 +97,6 @@
   frame_token_message_queue_.Init(&test_client_);
 }
 
-// Tests that if a valid IPC::Message is enqueued, that it is processed when its
-// matching frame token arrives.
-TEST_F(FrameTokenMessageQueueTest, OnlyIPCMessageCorrectFrameToken) {
-  FrameTokenMessageQueue* queue = frame_token_message_queue();
-  TestFrameTokenMessageQueueClient* client = test_client();
-  ASSERT_EQ(0u, queue->size());
-
-  const uint32_t frame_token = 42;
-  IPC::Message msg(0, 1, IPC::Message::PRIORITY_NORMAL);
-  std::vector<IPC::Message> messages;
-  messages.push_back(msg);
-
-  // Adding to the queue with a new frame token should not cause processing.
-  queue->OnFrameSwapMessagesReceived(frame_token, std::move(messages));
-  EXPECT_EQ(1u, queue->size());
-  EXPECT_EQ(0, client->on_process_swap_message_count());
-
-  queue->DidProcessFrame(frame_token);
-  EXPECT_EQ(0u, queue->size());
-  EXPECT_FALSE(client->invalid_frame_token_called());
-  EXPECT_EQ(1, client->on_process_swap_message_count());
-  EXPECT_EQ(0, client->on_message_dispatch_error_count());
-}
-
-// Tests that if a valid IPC::Message is enqueued after its frame token has
-// arrived that it is processed immediately.
-TEST_F(FrameTokenMessageQueueTest, EnqueueAfterFrameTokenProcesses) {
-  FrameTokenMessageQueue* queue = frame_token_message_queue();
-  TestFrameTokenMessageQueueClient* client = test_client();
-  ASSERT_EQ(0u, queue->size());
-
-  const uint32_t frame_token = 42;
-  IPC::Message msg(0, 1, IPC::Message::PRIORITY_NORMAL);
-  std::vector<IPC::Message> messages;
-  messages.push_back(msg);
-
-  queue->DidProcessFrame(frame_token);
-  EXPECT_EQ(0u, queue->size());
-  EXPECT_FALSE(client->invalid_frame_token_called());
-  EXPECT_EQ(0, client->on_process_swap_message_count());
-  EXPECT_EQ(0, client->on_message_dispatch_error_count());
-
-  // Enqueuing after frame token arrival should immediately process.
-  queue->OnFrameSwapMessagesReceived(frame_token, std::move(messages));
-  EXPECT_EQ(0u, queue->size());
-  EXPECT_FALSE(client->invalid_frame_token_called());
-  EXPECT_EQ(1, client->on_process_swap_message_count());
-  EXPECT_EQ(0, client->on_message_dispatch_error_count());
-}
-
-// Tests that if a valid IPC::Message is enqueued and that subsequently a
-// non-IPC callback is enqueued, that both get called once the frame token
-// arrives.
-TEST_F(FrameTokenMessageQueueTest, EnqueueBothIPCMessageAndNonIPCCallback) {
-  FrameTokenMessageQueue* queue = frame_token_message_queue();
-  TestFrameTokenMessageQueueClient* client = test_client();
-  TestNonIPCMessageEnqueuer* enqueuer = test_non_ipc_enqueuer();
-  ASSERT_EQ(0u, queue->size());
-
-  const uint32_t frame_token = 42;
-  IPC::Message msg(0, 1, IPC::Message::PRIORITY_NORMAL);
-  std::vector<IPC::Message> messages;
-  messages.push_back(msg);
-
-  // Adding to the queue with a new frame token should not cause processing.
-  queue->OnFrameSwapMessagesReceived(frame_token, std::move(messages));
-  EXPECT_EQ(1u, queue->size());
-  EXPECT_EQ(0, client->on_process_swap_message_count());
-
-  queue->EnqueueOrRunFrameTokenCallback(
-      frame_token,
-      base::BindOnce(&TestNonIPCMessageEnqueuer::FrameTokenCallback,
-                     base::Unretained(enqueuer)));
-  EXPECT_FALSE(enqueuer->frame_token_callback_called());
-  EXPECT_FALSE(client->invalid_frame_token_called());
-
-  queue->DidProcessFrame(frame_token);
-  EXPECT_EQ(0u, queue->size());
-  EXPECT_FALSE(client->invalid_frame_token_called());
-  EXPECT_EQ(1, client->on_process_swap_message_count());
-  EXPECT_EQ(0, client->on_message_dispatch_error_count());
-  EXPECT_TRUE(enqueuer->frame_token_callback_called());
-}
-
-// Tests that if a valid non-IPC callback is enqueued before an IPC::Message,
-// that both get called once the frame token arrives.
-TEST_F(FrameTokenMessageQueueTest, EnqueueNonIPCCallbackFirst) {
-  FrameTokenMessageQueue* queue = frame_token_message_queue();
-  TestFrameTokenMessageQueueClient* client = test_client();
-  TestNonIPCMessageEnqueuer* enqueuer = test_non_ipc_enqueuer();
-  ASSERT_EQ(0u, queue->size());
-
-  const uint32_t frame_token = 42;
-  IPC::Message msg(0, 1, IPC::Message::PRIORITY_NORMAL);
-  std::vector<IPC::Message> messages;
-  messages.push_back(msg);
-
-  queue->EnqueueOrRunFrameTokenCallback(
-      frame_token,
-      base::BindOnce(&TestNonIPCMessageEnqueuer::FrameTokenCallback,
-                     base::Unretained(enqueuer)));
-  EXPECT_FALSE(enqueuer->frame_token_callback_called());
-  EXPECT_EQ(0, client->on_process_swap_message_count());
-
-  // We should be able to enqueue even though it is for the same frame token.
-  queue->OnFrameSwapMessagesReceived(frame_token, std::move(messages));
-  EXPECT_EQ(2u, queue->size());
-  EXPECT_EQ(0, client->on_process_swap_message_count());
-  EXPECT_FALSE(client->invalid_frame_token_called());
-
-  queue->DidProcessFrame(frame_token);
-  EXPECT_EQ(0u, queue->size());
-  EXPECT_FALSE(client->invalid_frame_token_called());
-  EXPECT_EQ(1, client->on_process_swap_message_count());
-  EXPECT_EQ(0, client->on_message_dispatch_error_count());
-  EXPECT_TRUE(enqueuer->frame_token_callback_called());
-}
-
 // Tests that if we only have a non-IPC callback enqueued that it is called once
 // the frame token arrive.
 TEST_F(FrameTokenMessageQueueTest, EnqueueOnlyNonIPC) {
@@ -264,80 +110,16 @@
       frame_token,
       base::BindOnce(&TestNonIPCMessageEnqueuer::FrameTokenCallback,
                      base::Unretained(enqueuer)));
-  EXPECT_FALSE(enqueuer->frame_token_callback_called());
-  EXPECT_EQ(0, client->on_process_swap_message_count());
   EXPECT_EQ(1u, queue->size());
+  EXPECT_FALSE(enqueuer->frame_token_callback_called());
+  EXPECT_FALSE(client->invalid_frame_token_called());
 
   queue->DidProcessFrame(frame_token);
   EXPECT_EQ(0u, queue->size());
   EXPECT_FALSE(client->invalid_frame_token_called());
-  EXPECT_EQ(0, client->on_process_swap_message_count());
-  EXPECT_EQ(0, client->on_message_dispatch_error_count());
   EXPECT_TRUE(enqueuer->frame_token_callback_called());
 }
 
-// Tests that if we have messages enqueued, and receive a frame token that is
-// larger, that we still process the messages.
-TEST_F(FrameTokenMessageQueueTest, MessagesWhereFrameTokenSkipped) {
-  FrameTokenMessageQueue* queue = frame_token_message_queue();
-  TestFrameTokenMessageQueueClient* client = test_client();
-  TestNonIPCMessageEnqueuer* enqueuer = test_non_ipc_enqueuer();
-  ASSERT_EQ(0u, queue->size());
-
-  const uint32_t frame_token = 42;
-  IPC::Message msg(0, 1, IPC::Message::PRIORITY_NORMAL);
-  std::vector<IPC::Message> messages;
-  messages.push_back(msg);
-
-  queue->EnqueueOrRunFrameTokenCallback(
-      frame_token,
-      base::BindOnce(&TestNonIPCMessageEnqueuer::FrameTokenCallback,
-                     base::Unretained(enqueuer)));
-  EXPECT_FALSE(enqueuer->frame_token_callback_called());
-  EXPECT_EQ(0, client->on_process_swap_message_count());
-
-  // We should be able to enqueue even though it is for the same frame token.
-  queue->OnFrameSwapMessagesReceived(frame_token, std::move(messages));
-  EXPECT_EQ(2u, queue->size());
-  EXPECT_EQ(0, client->on_process_swap_message_count());
-  EXPECT_FALSE(client->invalid_frame_token_called());
-
-  const uint32_t larger_frame_token = 1337;
-  queue->DidProcessFrame(larger_frame_token);
-  EXPECT_EQ(0u, queue->size());
-  EXPECT_FALSE(client->invalid_frame_token_called());
-  EXPECT_EQ(1, client->on_process_swap_message_count());
-  EXPECT_EQ(0, client->on_message_dispatch_error_count());
-  EXPECT_TRUE(enqueuer->frame_token_callback_called());
-}
-
-// Verifies that if there are multiple IPC::Messages that they are all
-// processed.
-TEST_F(FrameTokenMessageQueueTest, MultipleIPCMessages) {
-  FrameTokenMessageQueue* queue = frame_token_message_queue();
-  TestFrameTokenMessageQueueClient* client = test_client();
-  ASSERT_EQ(0u, queue->size());
-
-  const uint32_t frame_token = 42;
-  IPC::Message msg1(0, 1, IPC::Message::PRIORITY_NORMAL);
-  IPC::Message msg2(1, 2, IPC::Message::PRIORITY_LOW);
-  std::vector<IPC::Message> messages;
-  messages.push_back(msg1);
-  messages.push_back(msg2);
-
-  // Adding to the queue with a new frame token should not cause processing.
-  queue->OnFrameSwapMessagesReceived(frame_token, std::move(messages));
-  // All IPCs are enqueued as one.
-  EXPECT_EQ(1u, queue->size());
-  EXPECT_EQ(0, client->on_process_swap_message_count());
-
-  queue->DidProcessFrame(frame_token);
-  EXPECT_EQ(0u, queue->size());
-  EXPECT_FALSE(client->invalid_frame_token_called());
-  EXPECT_EQ(2, client->on_process_swap_message_count());
-  EXPECT_EQ(0, client->on_message_dispatch_error_count());
-}
-
 // Verifies that if there are multiple non-IPC messages enqueued that they are
 // all called.
 TEST_F(FrameTokenMessageQueueTest, MultipleNonIPCMessages) {
@@ -393,52 +175,6 @@
   EXPECT_TRUE(enqueuer->frame_token_callback_called());
 }
 
-// Tests that if IPC::Messages are enqueued for different frame tokens, that
-// we only process the messages associated with the arriving token, and keep the
-// others enqueued.
-TEST_F(FrameTokenMessageQueueTest, DifferentFrameTokensEnqueuedIPC) {
-  FrameTokenMessageQueue* queue = frame_token_message_queue();
-  TestFrameTokenMessageQueueClient* client = test_client();
-  ASSERT_EQ(0u, queue->size());
-
-  const uint32_t frame_token_1 = 42;
-  IPC::Message msg1(0, 1, IPC::Message::PRIORITY_NORMAL);
-  std::vector<IPC::Message> messages_1;
-  messages_1.push_back(msg1);
-
-  queue->OnFrameSwapMessagesReceived(frame_token_1, std::move(messages_1));
-  EXPECT_EQ(1u, queue->size());
-  EXPECT_EQ(0, client->on_process_swap_message_count());
-
-  const uint32_t frame_token_2 = 1337;
-  IPC::Message msg2(1, 2, IPC::Message::PRIORITY_LOW);
-  std::vector<IPC::Message> messages_2;
-  messages_2.push_back(msg2);
-
-  queue->OnFrameSwapMessagesReceived(frame_token_2, std::move(messages_2));
-  // With no frame token yet the second set of IPC::Messages should be enqueud
-  // separately.
-  EXPECT_EQ(2u, queue->size());
-  EXPECT_EQ(0, client->on_process_swap_message_count());
-
-  // We should only process the first IPC::Message.
-  queue->DidProcessFrame(frame_token_1);
-  EXPECT_EQ(1u, queue->size());
-  EXPECT_FALSE(client->invalid_frame_token_called());
-  EXPECT_EQ(1, client->on_process_swap_message_count());
-  EXPECT_EQ(0, client->on_message_dispatch_error_count());
-
-  // Clear the counts from the first token.
-  client->Reset();
-
-  // The second IPC::Message should be processed.
-  queue->DidProcessFrame(frame_token_2);
-  EXPECT_EQ(0u, queue->size());
-  EXPECT_FALSE(client->invalid_frame_token_called());
-  EXPECT_EQ(1, client->on_process_swap_message_count());
-  EXPECT_EQ(0, client->on_message_dispatch_error_count());
-}
-
 // Test that if non-IPC callbacks are enqueued for different frame tokens, that
 // we only process the messages associated with the arriving token, and keep the
 // others enqueued.
@@ -476,37 +212,6 @@
   EXPECT_TRUE(second_enqueuer.frame_token_callback_called());
 }
 
-
-// Tests that when adding an IPC::Message for an earlier frame token, that it is
-// enqueued.
-TEST_F(FrameTokenMessageQueueTest, EarlierTokenForIPCMessageIsNotRejected) {
-  FrameTokenMessageQueue* queue = frame_token_message_queue();
-  TestFrameTokenMessageQueueClient* client = test_client();
-  ASSERT_EQ(0u, queue->size());
-
-  const uint32_t valid_frame_token = 42;
-  IPC::Message msg1(0, 1, IPC::Message::PRIORITY_NORMAL);
-  std::vector<IPC::Message> messages_1;
-  messages_1.push_back(msg1);
-
-  // Adding to the queue with a new frame token should not cause processing.
-  queue->OnFrameSwapMessagesReceived(valid_frame_token, std::move(messages_1));
-  EXPECT_EQ(1u, queue->size());
-  EXPECT_EQ(0, client->on_process_swap_message_count());
-
-  const uint32_t earlier_frame_token = 1;
-  IPC::Message msg2(1, 2, IPC::Message::PRIORITY_NORMAL);
-  std::vector<IPC::Message> messages_2;
-  messages_2.push_back(msg1);
-
-  // Adding an earlier frame token should be enqueued.
-  queue->OnFrameSwapMessagesReceived(earlier_frame_token,
-                                     std::move(messages_2));
-  EXPECT_EQ(2u, queue->size());
-  EXPECT_FALSE(client->invalid_frame_token_called());
-  EXPECT_EQ(0, client->on_process_swap_message_count());
-}
-
 // Tests that if DidProcessFrame is called with an invalid token, that it is
 // rejected, and that no callbacks are processed.
 TEST_F(FrameTokenMessageQueueTest, InvalidDidProcessFrameTokenNotProcessed) {
@@ -516,14 +221,6 @@
   ASSERT_EQ(0u, queue->size());
 
   const uint32_t frame_token = 42;
-  IPC::Message msg(0, 1, IPC::Message::PRIORITY_NORMAL);
-  std::vector<IPC::Message> messages;
-  messages.push_back(msg);
-
-  queue->OnFrameSwapMessagesReceived(frame_token, std::move(messages));
-  EXPECT_EQ(1u, queue->size());
-  EXPECT_EQ(0, client->on_process_swap_message_count());
-
   queue->EnqueueOrRunFrameTokenCallback(
       frame_token,
       base::BindOnce(&TestNonIPCMessageEnqueuer::FrameTokenCallback,
@@ -534,11 +231,9 @@
   // Empty token should be invalid even with no process frames processed.
   const uint32_t invalid_frame_token = 0;
   queue->DidProcessFrame(invalid_frame_token);
-  EXPECT_EQ(2u, queue->size());
+  EXPECT_EQ(1u, queue->size());
   EXPECT_TRUE(client->invalid_frame_token_called());
   EXPECT_EQ(invalid_frame_token, client->invalid_frame_token());
-  EXPECT_EQ(0, client->on_process_swap_message_count());
-  EXPECT_EQ(0, client->on_message_dispatch_error_count());
   EXPECT_FALSE(enqueuer->frame_token_callback_called());
 }
 
@@ -555,58 +250,24 @@
   queue->DidProcessFrame(earlier_frame_token);
 
   const uint32_t frame_token = 1337;
-  IPC::Message msg(0, 1, IPC::Message::PRIORITY_NORMAL);
-  std::vector<IPC::Message> messages;
-  messages.push_back(msg);
-
-  queue->OnFrameSwapMessagesReceived(frame_token, std::move(messages));
-  EXPECT_EQ(1u, queue->size());
-  EXPECT_EQ(0, client->on_process_swap_message_count());
-
   queue->EnqueueOrRunFrameTokenCallback(
       frame_token,
       base::BindOnce(&TestNonIPCMessageEnqueuer::FrameTokenCallback,
                      base::Unretained(enqueuer)));
   EXPECT_FALSE(enqueuer->frame_token_callback_called());
   EXPECT_FALSE(client->invalid_frame_token_called());
+  EXPECT_EQ(1u, queue->size());
 
   // Using a frame token that is earlier than the last received should be
   // rejected.
   const uint32_t invalid_frame_token = earlier_frame_token - 1;
   queue->DidProcessFrame(invalid_frame_token);
-  EXPECT_EQ(2u, queue->size());
+  EXPECT_EQ(1u, queue->size());
   EXPECT_TRUE(client->invalid_frame_token_called());
   EXPECT_EQ(invalid_frame_token, client->invalid_frame_token());
-  EXPECT_EQ(0, client->on_process_swap_message_count());
-  EXPECT_EQ(0, client->on_message_dispatch_error_count());
   EXPECT_FALSE(enqueuer->frame_token_callback_called());
 }
 
-// Tests that if an IPC::Message has a dispatch error that the client is
-// notified.
-TEST_F(FrameTokenMessageQueueTest, DispatchError) {
-  FrameTokenMessageQueue* queue = frame_token_message_queue();
-  TestFrameTokenMessageQueueClient* client = test_client();
-  ASSERT_EQ(0u, queue->size());
-
-  const uint32_t frame_token = 42;
-  IPC::Message msg(0, 1, IPC::Message::PRIORITY_NORMAL);
-  std::vector<IPC::Message> messages;
-  messages.push_back(msg);
-
-  queue->OnFrameSwapMessagesReceived(frame_token, std::move(messages));
-  EXPECT_EQ(1u, queue->size());
-  EXPECT_EQ(0, client->on_process_swap_message_count());
-
-  // Dispatch error should be notified during processing.
-  client->SetErrorOnMessageProcess();
-  queue->DidProcessFrame(frame_token);
-  EXPECT_EQ(0u, queue->size());
-  EXPECT_FALSE(client->invalid_frame_token_called());
-  EXPECT_EQ(1, client->on_process_swap_message_count());
-  EXPECT_EQ(1, client->on_message_dispatch_error_count());
-}
-
 // Tests that if we have already enqueued a callback for a frame token, that if
 // a request for an earlier frame token arrives, that it is still enqueued. Then
 // once the large frame token arrives, both are processed.
@@ -625,23 +286,10 @@
   EXPECT_FALSE(enqueuer->frame_token_callback_called());
   EXPECT_FALSE(client->invalid_frame_token_called());
 
-  const uint32_t smaller_frame_token = 42;
-  IPC::Message msg(0, 1, IPC::Message::PRIORITY_NORMAL);
-  std::vector<IPC::Message> messages;
-  messages.push_back(msg);
-
-  // Enqueuing for a smaller token, which has not yet arrived, should still
-  // enqueue.
-  queue->OnFrameSwapMessagesReceived(smaller_frame_token, std::move(messages));
-  EXPECT_EQ(2u, queue->size());
-  EXPECT_EQ(0, client->on_process_swap_message_count());
-
   // Process both with the larger frame token arriving.
   queue->DidProcessFrame(larger_frame_token);
   EXPECT_EQ(0u, queue->size());
   EXPECT_FALSE(client->invalid_frame_token_called());
-  EXPECT_EQ(1, client->on_process_swap_message_count());
-  EXPECT_EQ(0, client->on_message_dispatch_error_count());
   EXPECT_TRUE(enqueuer->frame_token_callback_called());
 }
 
diff --git a/content/browser/renderer_host/media/media_stream_manager.cc b/content/browser/renderer_host/media/media_stream_manager.cc
index e31ae09d..f659e68 100644
--- a/content/browser/renderer_host/media/media_stream_manager.cc
+++ b/content/browser/renderer_host/media/media_stream_manager.cc
@@ -33,6 +33,7 @@
 #include "content/browser/gpu/gpu_process_host.h"
 #include "content/browser/media/capture/desktop_capture_device_uma_types.h"
 #include "content/browser/media/media_devices_permission_checker.h"
+#include "content/browser/permissions/permission_controller_impl.h"
 #include "content/browser/renderer_host/media/audio_input_device_manager.h"
 #include "content/browser/renderer_host/media/audio_service_listener.h"
 #include "content/browser/renderer_host/media/in_process_video_capture_provider.h"
@@ -667,6 +668,10 @@
 
   std::string tab_capture_device_id;
 
+  int audio_subscription_id = PermissionControllerImpl::kNoPendingOperation;
+
+  int video_subscription_id = PermissionControllerImpl::kNoPendingOperation;
+
  private:
   std::vector<MediaRequestState> state_;
   std::unique_ptr<MediaStreamRequest> ui_request_;
@@ -1319,7 +1324,18 @@
   for (auto request_it = requests_.begin(); request_it != requests_.end();
        ++request_it) {
     if (request_it->first == label) {
+      // Clean up permission controller subscription.
+      GetUIThreadTaskRunner({})->PostTask(
+          FROM_HERE,
+          base::BindOnce(&MediaStreamManager::
+                             UnsubscribeFromPermissionControllerOnUIThread,
+                         request_it->second->requesting_process_id,
+                         request_it->second->requesting_frame_id,
+                         request_it->second->audio_subscription_id,
+                         request_it->second->video_subscription_id));
+
       requests_.erase(request_it);
+
       return;
     }
   }
@@ -1762,6 +1778,19 @@
       NOTREACHED();
   }
 
+  // Subscribe to follow permission changes in order to close streams when the
+  // user denies mic/camera.
+  // It is safe to bind base::Unretained(this) because MediaStreamManager is
+  // owned by BrowserMainLoop.
+  GetUIThreadTaskRunner({})->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          &MediaStreamManager::SubscribeToPermissionControllerOnUIThread,
+          base::Unretained(this), label, request->requesting_process_id,
+          request->requesting_frame_id, request->requester_id,
+          request->page_request_id, audio_devices.size() > 0,
+          video_devices.size() > 0, request->salt_and_origin.origin.GetURL()));
+
   // It is safe to bind base::Unretained(this) because MediaStreamManager is
   // owned by BrowserMainLoop and so outlives the IO thread.
   GetUIThreadTaskRunner({})->PostTaskAndReplyWithResult(
@@ -2614,4 +2643,144 @@
   }
 }
 
+// static
+PermissionControllerImpl* MediaStreamManager::GetPermissionController(
+    int requesting_process_id,
+    int requesting_frame_id) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+  RenderFrameHost* rfh =
+      RenderFrameHost::FromID(requesting_process_id, requesting_frame_id);
+  if (!rfh)
+    return nullptr;
+
+  return PermissionControllerImpl::FromBrowserContext(rfh->GetBrowserContext());
+}
+
+void MediaStreamManager::SubscribeToPermissionControllerOnUIThread(
+    const std::string& label,
+    int requesting_process_id,
+    int requesting_frame_id,
+    int requester_id,
+    int page_request_id,
+    bool is_audio_request,
+    bool is_video_request,
+    const GURL& origin) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+  PermissionControllerImpl* controller =
+      GetPermissionController(requesting_process_id, requesting_frame_id);
+  if (!controller)
+    return;
+
+  int audio_subscription_id = PermissionControllerImpl::kNoPendingOperation;
+  int video_subscription_id = PermissionControllerImpl::kNoPendingOperation;
+
+  if (is_audio_request) {
+    // It is safe to bind base::Unretained(this) because MediaStreamManager is
+    // owned by BrowserMainLoop.
+    audio_subscription_id = controller->SubscribePermissionStatusChange(
+        PermissionType::AUDIO_CAPTURE,
+        RenderFrameHost::FromID(requesting_process_id, requesting_frame_id),
+        origin,
+        base::BindRepeating(&MediaStreamManager::PermissionChangedCallback,
+                            base::Unretained(this), requesting_process_id,
+                            requesting_frame_id, requester_id,
+                            page_request_id));
+  }
+
+  if (is_video_request) {
+    // It is safe to bind base::Unretained(this) because MediaStreamManager is
+    // owned by BrowserMainLoop.
+    video_subscription_id = controller->SubscribePermissionStatusChange(
+        PermissionType::VIDEO_CAPTURE,
+        RenderFrameHost::FromID(requesting_process_id, requesting_frame_id),
+        origin,
+        base::BindRepeating(&MediaStreamManager::PermissionChangedCallback,
+                            base::Unretained(this), requesting_process_id,
+                            requesting_frame_id, requester_id,
+                            page_request_id));
+  }
+
+  // It is safe to bind base::Unretained(this) because MediaStreamManager is
+  // owned by BrowserMainLoop.
+  GetIOThreadTaskRunner({})->PostTask(
+      FROM_HERE,
+      base::BindOnce(&MediaStreamManager::SetPermissionSubscriptionIDs,
+                     base::Unretained(this), label, requesting_process_id,
+                     requesting_frame_id, audio_subscription_id,
+                     video_subscription_id));
+}
+
+void MediaStreamManager::SetPermissionSubscriptionIDs(
+    const std::string& label,
+    int requesting_process_id,
+    int requesting_frame_id,
+    int audio_subscription_id,
+    int video_subscription_id) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
+  DeviceRequest* const request = FindRequest(label);
+  if (!request) {
+    // Something happened with the request while the permission subscription was
+    // created, unsubscribe to clean up.
+    // It is safe to bind base::Unretained(this) because MediaStreamManager is
+    // owned by BrowserMainLoop.
+    GetUIThreadTaskRunner({})->PostTask(
+        FROM_HERE,
+        base::BindOnce(
+            &MediaStreamManager::UnsubscribeFromPermissionControllerOnUIThread,
+            requesting_process_id, requesting_frame_id, audio_subscription_id,
+            video_subscription_id));
+
+    return;
+  }
+
+  request->audio_subscription_id = audio_subscription_id;
+  request->video_subscription_id = video_subscription_id;
+}
+
+// static
+void MediaStreamManager::UnsubscribeFromPermissionControllerOnUIThread(
+    int requesting_process_id,
+    int requesting_frame_id,
+    int audio_subscription_id,
+    int video_subscription_id) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+  PermissionControllerImpl* controller =
+      GetPermissionController(requesting_process_id, requesting_frame_id);
+  if (!controller)
+    return;
+
+  controller->UnsubscribePermissionStatusChange(audio_subscription_id);
+  controller->UnsubscribePermissionStatusChange(video_subscription_id);
+}
+
+void MediaStreamManager::PermissionChangedCallback(
+    int requesting_process_id,
+    int requesting_frame_id,
+    int requester_id,
+    int page_request_id,
+    blink::mojom::PermissionStatus status) {
+  if (status == blink::mojom::PermissionStatus::GRANTED)
+    return;
+
+  if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
+    // It is safe to bind base::Unretained(this) because MediaStreamManager is
+    // owned by BrowserMainLoop.
+    GetIOThreadTaskRunner({})->PostTask(
+        FROM_HERE,
+        base::BindOnce(&MediaStreamManager::PermissionChangedCallback,
+                       base::Unretained(this), requesting_process_id,
+                       requesting_frame_id, requester_id, page_request_id,
+                       status));
+
+    return;
+  }
+
+  CancelRequest(requesting_process_id, requesting_frame_id, requester_id,
+                page_request_id);
+}
+
 }  // namespace content
diff --git a/content/browser/renderer_host/media/media_stream_manager.h b/content/browser/renderer_host/media/media_stream_manager.h
index 4d1e4877..045e5f5 100644
--- a/content/browser/renderer_host/media/media_stream_manager.h
+++ b/content/browser/renderer_host/media/media_stream_manager.h
@@ -55,6 +55,7 @@
 #include "third_party/blink/public/common/mediastream/media_stream_controls.h"
 #include "third_party/blink/public/common/mediastream/media_stream_request.h"
 #include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
+#include "third_party/blink/public/mojom/permissions/permission_status.mojom.h"
 
 namespace media {
 class AudioSystem;
@@ -72,6 +73,7 @@
 class MediaStreamUIProxy;
 class VideoCaptureManager;
 class VideoCaptureProvider;
+class PermissionControllerImpl;
 
 // MediaStreamManager is used to generate and close new media devices, not to
 // start the media flow. The classes requesting new media streams are answered
@@ -534,6 +536,48 @@
   // Activate the specified tab and bring it to the front.
   void ActivateTabOnUIThread(const DesktopMediaID source);
 
+  // Get the permission controller for a particular RFH. Must be called on the
+  // UI thread.
+  static PermissionControllerImpl* GetPermissionController(
+      int requesting_process_id,
+      int requesting_frame_id);
+
+  // Subscribe to the permission controller in order to monitor camera/mic
+  // permission updates for a particular DeviceRequest. All the additional
+  // information is needed because `FindRequest` can't be called on the UI
+  // thread.
+  void SubscribeToPermissionControllerOnUIThread(const std::string& label,
+                                                 int requesting_process_id,
+                                                 int requesting_frame_id,
+                                                 int requester_id,
+                                                 int page_request_id,
+                                                 bool is_audio_request,
+                                                 bool is_video_request,
+                                                 const GURL& origin);
+
+  // Store the subscription ids on a DeviceRequest in order to allow
+  // unsubscribing when the request is deleted.
+  void SetPermissionSubscriptionIDs(const std::string& label,
+                                    int requesting_process_id,
+                                    int requesting_frame_id,
+                                    int audio_subscription_id,
+                                    int video_subscription_id);
+
+  // Unsubscribe from following permission updates for the two specified
+  // subscription IDs. Called when a request is deleted.
+  static void UnsubscribeFromPermissionControllerOnUIThread(
+      int requesting_process_id,
+      int requesting_frame_id,
+      int audio_subscription_id,
+      int video_subscription_id);
+
+  // Callback that the PermissionController calls when a permission is updated.
+  void PermissionChangedCallback(int requesting_process_id,
+                                 int requesting_frame_id,
+                                 int requester_id,
+                                 int page_request_id,
+                                 blink::mojom::PermissionStatus status);
+
   media::AudioSystem* const audio_system_;  // not owned
   scoped_refptr<AudioInputDeviceManager> audio_input_device_manager_;
   scoped_refptr<VideoCaptureManager> video_capture_manager_;
diff --git a/content/browser/renderer_host/mock_render_widget_host.cc b/content/browser/renderer_host/mock_render_widget_host.cc
index fb78b02..cd0b249 100644
--- a/content/browser/renderer_host/mock_render_widget_host.cc
+++ b/content/browser/renderer_host/mock_render_widget_host.cc
@@ -7,28 +7,6 @@
 #include "components/viz/test/mock_compositor_frame_sink_client.h"
 #include "content/browser/renderer_host/frame_token_message_queue.h"
 
-namespace {
-class TestFrameTokenMessageQueue : public content::FrameTokenMessageQueue {
- public:
-  TestFrameTokenMessageQueue() = default;
-  ~TestFrameTokenMessageQueue() override = default;
-
-  uint32_t processed_frame_messages_count() {
-    return processed_frame_messages_count_;
-  }
-
- protected:
-  void ProcessSwapMessages(std::vector<IPC::Message> messages) override {
-    processed_frame_messages_count_++;
-  }
-
- private:
-  uint32_t processed_frame_messages_count_ = 0;
-
-  DISALLOW_COPY_AND_ASSIGN(TestFrameTokenMessageQueue);
-};
-}  // namespace
-
 namespace content {
 
 MockRenderWidgetHost::~MockRenderWidgetHost() {}
@@ -59,13 +37,6 @@
   input_router_.reset(new MockInputRouter(this));
 }
 
-uint32_t MockRenderWidgetHost::processed_frame_messages_count() {
-  CHECK(frame_token_message_queue_);
-  return static_cast<TestFrameTokenMessageQueue*>(
-             frame_token_message_queue_.get())
-      ->processed_frame_messages_count();
-}
-
 // static
 MockRenderWidgetHost* MockRenderWidgetHost::Create(
     RenderWidgetHostDelegate* delegate,
@@ -106,7 +77,7 @@
                            process,
                            routing_id,
                            /*hidden=*/false,
-                           std::make_unique<TestFrameTokenMessageQueue>()),
+                           std::make_unique<FrameTokenMessageQueue>()),
       new_content_rendering_timeout_fired_(false),
       fling_scheduler_(std::make_unique<FlingScheduler>(this)) {
   acked_touch_event_type_ = blink::WebInputEvent::Type::kUndefined;
diff --git a/content/browser/renderer_host/mock_render_widget_host.h b/content/browser/renderer_host/mock_render_widget_host.h
index 8e65251..c0ac15d 100644
--- a/content/browser/renderer_host/mock_render_widget_host.h
+++ b/content/browser/renderer_host/mock_render_widget_host.h
@@ -58,8 +58,6 @@
 
   InputRouter* input_router() { return input_router_.get(); }
 
-  uint32_t processed_frame_messages_count();
-
   static MockRenderWidgetHost* Create(RenderWidgetHostDelegate* delegate,
                                       RenderProcessHost* process,
                                       int32_t routing_id);
diff --git a/content/browser/renderer_host/page_lifecycle_state_manager.cc b/content/browser/renderer_host/page_lifecycle_state_manager.cc
index 4ac9c39..3a379ccf 100644
--- a/content/browser/renderer_host/page_lifecycle_state_manager.cc
+++ b/content/browser/renderer_host/page_lifecycle_state_manager.cc
@@ -74,10 +74,44 @@
                 &PageLifecycleStateManager::OnBackForwardCacheTimeout,
                 weak_ptr_factory_.GetWeakPtr()),
             kBackForwardCacheTimeoutInSeconds);
+    pagehide_dispatch_ = blink::mojom::PagehideDispatch::kDispatchedPersisted;
+  } else {
+    // When a page is restored from the back-forward cache, we should reset the
+    // |pagehide_dispatch_| state so that we'd dispatch the
+    // events again the next time we navigate away from the page.
+    pagehide_dispatch_ = blink::mojom::PagehideDispatch::kNotDispatched;
   }
   SendUpdatesToRendererIfNeeded(navigation_start);
 }
 
+blink::mojom::PageLifecycleStatePtr
+PageLifecycleStateManager::SetPagehideDispatchDuringNewPageCommit(
+    bool persisted) {
+  pagehide_dispatch_ =
+      persisted ? blink::mojom::PagehideDispatch::kDispatchedPersisted
+                : blink::mojom::PagehideDispatch::kDispatchedNotPersisted;
+  // We've only modified |pagehide_dispatch_| here, but the "visibility"
+  // property of |last_state_sent_to_renderer_| calculated from
+  // CalculatePageLifecycleState() below will be set to kHidden because it
+  // depends on the value of |pagehide_dispatch_|.
+  last_state_sent_to_renderer_ = CalculatePageLifecycleState();
+  DCHECK_EQ(last_state_sent_to_renderer_->visibility,
+            blink::mojom::PageVisibilityState::kHidden);
+
+  // We don't need to call SendUpdatesToRendererIfNeeded() because the update
+  // will be sent through an OldPageInfo parameter in the CommitNavigation IPC.
+  return last_state_sent_to_renderer_.Clone();
+}
+
+void PageLifecycleStateManager::DidSetPagehideDispatchDuringNewPageCommit(
+    blink::mojom::PageLifecycleStatePtr acknowledged_state) {
+  DCHECK_EQ(acknowledged_state->visibility,
+            blink::mojom::PageVisibilityState::kHidden);
+  DCHECK_NE(acknowledged_state->pagehide_dispatch,
+            blink::mojom::PagehideDispatch::kNotDispatched);
+  OnPageLifecycleChangedAck(std::move(acknowledged_state));
+}
+
 void PageLifecycleStateManager::SendUpdatesToRendererIfNeeded(
     base::Optional<base::TimeTicks> navigation_start) {
   if (!render_view_host_impl_->GetAssociatedPageBroadcast()) {
@@ -112,9 +146,15 @@
   auto state = blink::mojom::PageLifecycleState::New();
   state->is_in_back_forward_cache = is_in_back_forward_cache_;
   state->is_frozen = is_in_back_forward_cache_ ? true : is_set_frozen_called_;
-  state->visibility = is_in_back_forward_cache_
-                          ? blink::mojom::PageVisibilityState::kHidden
-                          : web_contents_visibility_;
+  state->pagehide_dispatch = pagehide_dispatch_;
+  // If a page is stored in the back-forward cache, or we have already
+  // dispatched/are dispatching pagehide for the page, it should be treated as
+  // "hidden" regardless of what |web_contents_visibility_| is set to.
+  state->visibility =
+      (is_in_back_forward_cache_ ||
+       pagehide_dispatch_ != blink::mojom::PagehideDispatch::kNotDispatched)
+          ? blink::mojom::PageVisibilityState::kHidden
+          : web_contents_visibility_;
   return state;
 }
 
diff --git a/content/browser/renderer_host/page_lifecycle_state_manager.h b/content/browser/renderer_host/page_lifecycle_state_manager.h
index 889f88b7..12f3c3c 100644
--- a/content/browser/renderer_host/page_lifecycle_state_manager.h
+++ b/content/browser/renderer_host/page_lifecycle_state_manager.h
@@ -44,6 +44,21 @@
       bool is_in_back_forward_cache,
       base::Optional<base::TimeTicks> navigation_start);
 
+  // Called when we're committing main-frame same-site navigations where we did
+  // a proactive BrowsingInstance swap and we're reusing the old page's renderer
+  // process, where we will run pagehide & visibilitychange handlers of the old
+  // page from within the new page's commit call. Returns the newly updated
+  // PageLifecycleState for this page (after we've set |pagehide_dispatch_| to
+  // the appropriate value based on |persisted|).
+  blink::mojom::PageLifecycleStatePtr SetPagehideDispatchDuringNewPageCommit(
+      bool persisted);
+
+  // See above, called when we've finished committing the new page (which means
+  // we've finished running pagehide and visibilitychange handlers of the old
+  // page) for certain cases.
+  void DidSetPagehideDispatchDuringNewPageCommit(
+      blink::mojom::PageLifecycleStatePtr acknowledged_state);
+
   // Calculates the per-page lifecycle state based on the per-tab / web contents
   // lifecycle state saved in this instance.
   blink::mojom::PageLifecycleStatePtr CalculatePageLifecycleState();
@@ -82,6 +97,9 @@
   // |web_contents_visibility_|.
   blink::mojom::PageVisibilityState web_contents_visibility_;
 
+  blink::mojom::PagehideDispatch pagehide_dispatch_ =
+      blink::mojom::PagehideDispatch::kNotDispatched;
+
   RenderViewHostImpl* render_view_host_impl_;
 
   // This is the per-page state computed based on web contents / tab lifecycle
diff --git a/content/browser/renderer_host/render_process_host_browsertest.cc b/content/browser/renderer_host/render_process_host_browsertest.cc
index 4141096..7564965 100644
--- a/content/browser/renderer_host/render_process_host_browsertest.cc
+++ b/content/browser/renderer_host/render_process_host_browsertest.cc
@@ -26,6 +26,7 @@
 #include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/common/url_constants.h"
+#include "content/public/test/back_forward_cache_util.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/content_browser_test.h"
@@ -1111,6 +1112,11 @@
   RenderProcessHostImpl* rph =
       static_cast<RenderProcessHostImpl*>(rfh->GetProcess());
 
+  // Disable the BackForwardCache to ensure the old process is going to be
+  // released.
+  DisableBackForwardCacheForTesting(shell()->web_contents(),
+                                    BackForwardCache::TEST_ASSUMES_NO_CACHING);
+
   host_destructions_ = 0;
   process_exits_ = 0;
   rph->AddObserver(this);
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
index 8ad8a713..ec8d2a06 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -718,8 +718,6 @@
     IPC_MESSAGE_HANDLER(WidgetHostMsg_RequestSetBounds, OnRequestSetBounds)
     IPC_MESSAGE_HANDLER(DragHostMsg_StartDragging, OnStartDragging)
     IPC_MESSAGE_HANDLER(DragHostMsg_UpdateDragCursor, OnUpdateDragCursor)
-    IPC_MESSAGE_HANDLER(WidgetHostMsg_FrameSwapMessages,
-                        OnFrameSwapMessagesReceived)
     IPC_MESSAGE_UNHANDLED(handled = false)
   IPC_END_MESSAGE_MAP()
 
@@ -2066,13 +2064,6 @@
     view->UpdateDragCursor(current_op);
 }
 
-void RenderWidgetHostImpl::OnFrameSwapMessagesReceived(
-    uint32_t frame_token,
-    std::vector<IPC::Message> messages) {
-  frame_token_message_queue_->OnFrameSwapMessagesReceived(frame_token,
-                                                          std::move(messages));
-}
-
 void RenderWidgetHostImpl::RendererExited() {
   if (!renderer_initialized_)
     return;
@@ -2738,16 +2729,6 @@
                                   bad_message::RWH_INVALID_FRAME_TOKEN);
 }
 
-void RenderWidgetHostImpl::OnMessageDispatchError(const IPC::Message& message) {
-  RenderProcessHost* rph = GetProcess();
-  rph->OnBadMessageReceived(message);
-}
-
-void RenderWidgetHostImpl::OnProcessSwapMessage(const IPC::Message& message) {
-  RenderProcessHost* rph = GetProcess();
-  rph->OnMessageReceived(message);
-}
-
 bool RenderWidgetHostImpl::RequestKeyboardLock(
     base::Optional<base::flat_set<ui::DomCode>> codes) {
   if (!delegate_) {
diff --git a/content/browser/renderer_host/render_widget_host_impl.h b/content/browser/renderer_host/render_widget_host_impl.h
index 87097ad0b..ffeeca6e 100644
--- a/content/browser/renderer_host/render_widget_host_impl.h
+++ b/content/browser/renderer_host/render_widget_host_impl.h
@@ -723,8 +723,6 @@
 
   // FrameTokenMessageQueue::Client:
   void OnInvalidFrameToken(uint32_t frame_token) override;
-  void OnMessageDispatchError(const IPC::Message& message) override;
-  void OnProcessSwapMessage(const IPC::Message& message) override;
 
   void ProgressFlingIfNeeded(base::TimeTicks current_time);
   void StopFling();
@@ -914,8 +912,6 @@
                        const gfx::Vector2d& bitmap_offset_in_dip,
                        const DragEventSourceInfo& event_info);
   void OnUpdateDragCursor(blink::WebDragOperation current_op);
-  void OnFrameSwapMessagesReceived(uint32_t frame_token,
-                                   std::vector<IPC::Message> messages);
 
   // blink::mojom::FrameWidgetHost overrides.
   void AnimateDoubleTapZoomInMainFrame(const gfx::Point& tap_point,
diff --git a/content/browser/renderer_host/render_widget_host_unittest.cc b/content/browser/renderer_host/render_widget_host_unittest.cc
index 99ea711..f6050fe 100644
--- a/content/browser/renderer_host/render_widget_host_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_unittest.cc
@@ -2070,137 +2070,6 @@
   ASSERT_FALSE(host_->input_router()->HasPendingEvents());
 }
 
-// Check that if messages of a frame arrive earlier than the frame itself, we
-// queue the messages until the frame arrives and then process them.
-TEST_F(RenderWidgetHostTest, FrameToken_MessageThenFrame) {
-  constexpr uint32_t frame_token = 1;
-  std::vector<IPC::Message> messages;
-  messages.push_back(WidgetHostMsg_Close(5));
-
-  EXPECT_EQ(0u, host_->frame_token_message_queue_->size());
-  EXPECT_EQ(0u, host_->processed_frame_messages_count());
-
-  host_->OnMessageReceived(
-      WidgetHostMsg_FrameSwapMessages(0, frame_token, messages));
-  EXPECT_EQ(1u, host_->frame_token_message_queue_->size());
-  EXPECT_EQ(0u, host_->processed_frame_messages_count());
-
-  view_->OnFrameTokenChanged(frame_token);
-  EXPECT_EQ(0u, host_->frame_token_message_queue_->size());
-  EXPECT_EQ(1u, host_->processed_frame_messages_count());
-}
-
-// Check that if a frame arrives earlier than its messages, we process the
-// messages immedtiately.
-TEST_F(RenderWidgetHostTest, FrameToken_FrameThenMessage) {
-  constexpr uint32_t frame_token = 1;
-  std::vector<IPC::Message> messages;
-  messages.push_back(WidgetHostMsg_Close(5));
-
-  EXPECT_EQ(0u, host_->frame_token_message_queue_->size());
-  EXPECT_EQ(0u, host_->processed_frame_messages_count());
-
-  view_->OnFrameTokenChanged(frame_token);
-  EXPECT_EQ(0u, host_->frame_token_message_queue_->size());
-  EXPECT_EQ(0u, host_->processed_frame_messages_count());
-
-  host_->OnMessageReceived(
-      WidgetHostMsg_FrameSwapMessages(0, frame_token, messages));
-  EXPECT_EQ(0u, host_->frame_token_message_queue_->size());
-  EXPECT_EQ(1u, host_->processed_frame_messages_count());
-}
-
-// Check that if messages of multiple frames arrive before the frames, we
-// process each message once it frame arrives.
-TEST_F(RenderWidgetHostTest, FrameToken_MultipleMessagesThenTokens) {
-  constexpr uint32_t frame_token1 = 1;
-  constexpr uint32_t frame_token2 = 2;
-  std::vector<IPC::Message> messages1;
-  std::vector<IPC::Message> messages2;
-  messages1.push_back(WidgetHostMsg_Close(5));
-  messages2.push_back(WidgetHostMsg_Close(6));
-
-  EXPECT_EQ(0u, host_->frame_token_message_queue_->size());
-  EXPECT_EQ(0u, host_->processed_frame_messages_count());
-
-  host_->OnMessageReceived(
-      WidgetHostMsg_FrameSwapMessages(0, frame_token1, messages1));
-  EXPECT_EQ(1u, host_->frame_token_message_queue_->size());
-  EXPECT_EQ(0u, host_->processed_frame_messages_count());
-
-  host_->OnMessageReceived(
-      WidgetHostMsg_FrameSwapMessages(0, frame_token2, messages2));
-  EXPECT_EQ(2u, host_->frame_token_message_queue_->size());
-  EXPECT_EQ(0u, host_->processed_frame_messages_count());
-
-  view_->OnFrameTokenChanged(frame_token1);
-  EXPECT_EQ(1u, host_->frame_token_message_queue_->size());
-  EXPECT_EQ(1u, host_->processed_frame_messages_count());
-  view_->OnFrameTokenChanged(frame_token2);
-  EXPECT_EQ(0u, host_->frame_token_message_queue_->size());
-  EXPECT_EQ(2u, host_->processed_frame_messages_count());
-}
-
-// Check that if multiple frames arrive before their messages, each message is
-// processed immediately as soon as it arrives.
-TEST_F(RenderWidgetHostTest, FrameToken_MultipleTokensThenMessages) {
-  constexpr uint32_t frame_token1 = 1;
-  constexpr uint32_t frame_token2 = 2;
-  std::vector<IPC::Message> messages1;
-  std::vector<IPC::Message> messages2;
-  messages1.push_back(WidgetHostMsg_Close(5));
-  messages2.push_back(WidgetHostMsg_Close(6));
-
-  EXPECT_EQ(0u, host_->frame_token_message_queue_->size());
-  EXPECT_EQ(0u, host_->processed_frame_messages_count());
-
-  view_->OnFrameTokenChanged(frame_token1);
-  EXPECT_EQ(0u, host_->frame_token_message_queue_->size());
-  EXPECT_EQ(0u, host_->processed_frame_messages_count());
-
-  view_->OnFrameTokenChanged(frame_token2);
-  EXPECT_EQ(0u, host_->frame_token_message_queue_->size());
-  EXPECT_EQ(0u, host_->processed_frame_messages_count());
-
-  host_->OnMessageReceived(
-      WidgetHostMsg_FrameSwapMessages(0, frame_token1, messages1));
-  EXPECT_EQ(0u, host_->frame_token_message_queue_->size());
-  EXPECT_EQ(1u, host_->processed_frame_messages_count());
-
-  host_->OnMessageReceived(
-      WidgetHostMsg_FrameSwapMessages(0, frame_token2, messages2));
-  EXPECT_EQ(0u, host_->frame_token_message_queue_->size());
-  EXPECT_EQ(2u, host_->processed_frame_messages_count());
-}
-
-// Check that if one frame is lost but its messages arrive, we process the
-// messages on the arrival of the next frame.
-TEST_F(RenderWidgetHostTest, FrameToken_DroppedFrame) {
-  constexpr uint32_t frame_token1 = 1;
-  constexpr uint32_t frame_token2 = 2;
-  std::vector<IPC::Message> messages1;
-  std::vector<IPC::Message> messages2;
-  messages1.push_back(WidgetHostMsg_Close(5));
-  messages2.push_back(WidgetHostMsg_Close(6));
-
-  EXPECT_EQ(0u, host_->frame_token_message_queue_->size());
-  EXPECT_EQ(0u, host_->processed_frame_messages_count());
-
-  host_->OnMessageReceived(
-      WidgetHostMsg_FrameSwapMessages(0, frame_token1, messages1));
-  EXPECT_EQ(1u, host_->frame_token_message_queue_->size());
-  EXPECT_EQ(0u, host_->processed_frame_messages_count());
-
-  host_->OnMessageReceived(
-      WidgetHostMsg_FrameSwapMessages(0, frame_token2, messages2));
-  EXPECT_EQ(2u, host_->frame_token_message_queue_->size());
-  EXPECT_EQ(0u, host_->processed_frame_messages_count());
-
-  view_->OnFrameTokenChanged(frame_token2);
-  EXPECT_EQ(0u, host_->frame_token_message_queue_->size());
-  EXPECT_EQ(2u, host_->processed_frame_messages_count());
-}
-
 // If a navigation happens while the widget is hidden, we shouldn't show
 // contents of the previous page when we become visible.
 TEST_F(RenderWidgetHostTest, NavigateInBackgroundShowsBlank) {
diff --git a/content/browser/renderer_host/render_widget_host_view_aura.cc b/content/browser/renderer_host/render_widget_host_view_aura.cc
index dc010ee9..8eeff7b2 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura.cc
+++ b/content/browser/renderer_host/render_widget_host_view_aura.cc
@@ -1852,13 +1852,13 @@
 
 void RenderWidgetHostViewAura::OnWindowFocused(aura::Window* gained_focus,
                                                aura::Window* lost_focus) {
-  if (window_ == gained_focus) {
-    // We need to honor input bypass if the associated tab is does not want
-    // input. This gives the current focused window a chance to be the text
-    // input client and handle events.
-    if (host()->IsIgnoringInputEvents())
-      return;
+  // We need to honor input bypass if the associated tab is does not want
+  // input. This gives the current focused window a chance to be the text
+  // input client and handle events.
+  if (host()->IsIgnoringInputEvents())
+    return;
 
+  if (window_ == gained_focus) {
     host()->GotFocus();
     host()->SetActive(true);
 
diff --git a/content/browser/security_exploit_browsertest.cc b/content/browser/security_exploit_browsertest.cc
index 77dacf25..19ceb50 100644
--- a/content/browser/security_exploit_browsertest.cc
+++ b/content/browser/security_exploit_browsertest.cc
@@ -45,6 +45,7 @@
 #include "content/public/common/content_switches.h"
 #include "content/public/common/navigation_policy.h"
 #include "content/public/common/url_constants.h"
+#include "content/public/test/back_forward_cache_util.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/content_browser_test.h"
@@ -1186,6 +1187,11 @@
   RenderFrameDeletedObserver initial_frame_deleted_observer(
       shell()->web_contents()->GetMainFrame());
 
+  // Test assumes the initial RenderFrameHost to be deleted. Disable
+  // back-forward cache to ensure that it doesn't get preserved in the cache.
+  DisableBackForwardCacheForTesting(shell()->web_contents(),
+                                    BackForwardCache::TEST_ASSUMES_NO_CACHING);
+
   // Navigate to foo.com initially.
   GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
   EXPECT_TRUE(NavigateToURL(shell(), foo_url));
diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc
index c8f4300..29d4dbeb 100644
--- a/content/browser/site_per_process_browsertest.cc
+++ b/content/browser/site_per_process_browsertest.cc
@@ -97,6 +97,7 @@
 #include "content/public/common/content_switches.h"
 #include "content/public/common/url_constants.h"
 #include "content/public/common/use_zoom_for_dsf_policy.h"
+#include "content/public/test/back_forward_cache_util.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/content_browser_test_utils.h"
@@ -5676,9 +5677,17 @@
   commit_observer.WaitForCommit();
   EXPECT_FALSE(deleted_observer.deleted());
 
-  // Since the FrameHostMsg_Unload_ACK for A->B is dropped, the first page's
-  // RenderFrameHost should be pending deletion after the last navigation.
-  EXPECT_TRUE(rfh->IsPendingDeletion());
+  // The previous RFH should be either:
+  // 1) In the BackForwardCache, if back-forward cache is enabled.
+  // 2) Pending deletion otherwise, since the FrameHostMsg_Unload_ACK for A->B
+  // is dropped.
+  EXPECT_THAT(
+      rfh->lifecycle_state(),
+      testing::AnyOf(
+          testing::Eq(
+              RenderFrameHostImpl::LifecycleState::kRunningUnloadHandlers),
+          testing::Eq(
+              RenderFrameHostImpl::LifecycleState::kInBackForwardCache)));
 
   // Without the FrameHostMsg_Unload_ACK and timer, the process A will never
   // shutdown. Simulate the process being killed now.
@@ -6263,6 +6272,11 @@
     process->GetRendererInterface()->CreateFrame(std::move(params));
   }
 
+  // Disable the BackForwardCache to ensure the old process is going to be
+  // released.
+  DisableBackForwardCacheForTesting(web_contents(),
+                                    BackForwardCache::TEST_ASSUMES_NO_CACHING);
+
   // The test must wait for the process to exit, but if there is no leak, the
   // RenderFrame will be properly created and there will be no crash.
   // Therefore, navigate the main frame to completely different site, which
@@ -11380,10 +11394,19 @@
       GURL(embedded_test_server()->GetURL("b.com", "/title1.html")));
   commit_observer.WaitForCommit();
 
-  // The previous RFH should still be pending deletion, as we wait for either
-  // the FrameHostMsg_Unload_ACK or a timeout.
+  // The previous RFH should be either:
+  // 1) In the BackForwardCache, or
+  // 2) Pending deletion, waiting for the FrameHostMsg_Unload_ACK.
+  // As a result, it must still be alive.
   ASSERT_TRUE(rfh->IsRenderFrameLive());
-  ASSERT_TRUE(rfh->IsPendingDeletion());
+  EXPECT_THAT(
+      rfh->lifecycle_state(),
+      testing::AnyOf(
+          testing::Eq(
+              RenderFrameHostImpl::LifecycleState::kRunningUnloadHandlers),
+          testing::Eq(
+              RenderFrameHostImpl::LifecycleState::kInBackForwardCache)));
+
   ASSERT_FALSE(rfh_observer.deleted());
 
   // Check sandbox flags on old RFH -- they should be unchanged.
diff --git a/content/browser/site_per_process_unload_browsertest.cc b/content/browser/site_per_process_unload_browsertest.cc
index e708ecb..0aa570fc 100644
--- a/content/browser/site_per_process_unload_browsertest.cc
+++ b/content/browser/site_per_process_unload_browsertest.cc
@@ -33,9 +33,9 @@
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "content/common/content_navigation_policy.h"
 #include "content/common/frame_messages.h"
-#include "content/public/browser/back_forward_cache.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/common/url_constants.h"
+#include "content/public/test/back_forward_cache_util.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/content_browser_test_utils.h"
@@ -308,6 +308,12 @@
   auto unload_ack_filter = base::MakeRefCounted<ObserveMessageFilter>(
       FrameMsgStart, FrameHostMsg_Unload_ACK::ID);
   rfh->GetProcess()->AddFilter(unload_ack_filter.get());
+
+  // Disable the BackForwardCache to ensure the old process is going to be
+  // released.
+  DisableBackForwardCacheForTesting(web_contents(),
+                                    BackForwardCache::TEST_ASSUMES_NO_CACHING);
+
   GURL cross_site_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
   EXPECT_TRUE(NavigateToURLFromRenderer(shell(), cross_site_url));
   watcher.Wait();
@@ -476,6 +482,11 @@
       web_contents()->GetFrameTree()->root()->child_at(0)->current_frame_host();
   rfh_b->DoNotDeleteForTesting();
 
+  // With BackForwardCache, old frame doesn't fire unload handlers as the page
+  // is stored in BackForwardCache on navigating.
+  DisableBackForwardCacheForTesting(web_contents(),
+                                    BackForwardCache::TEST_USES_UNLOAD_EVENT);
+
   // 3) Navigate and check the old frame is deleted after some time.
   FrameTreeNode* root = web_contents()->GetFrameTree()->root();
   RenderFrameDeletedObserver deleted_observer(root->current_frame_host());
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index b18917a..4dca843 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -7845,10 +7845,6 @@
     ClipboardPasteAllowed allowed) {
   std::move(callback).Run(allowed);
   --suppress_unresponsive_renderer_count_;
-
-  // Focus |this| in case the paste was async and lost focus before this
-  // callback got called.
-  Focus();
 }
 
 bool WebContentsImpl::HasSeenRecentScreenOrientationChange() {
diff --git a/content/browser/web_contents_receiver_set_browsertest.cc b/content/browser/web_contents_receiver_set_browsertest.cc
index 456a9700..c40eeaec 100644
--- a/content/browser/web_contents_receiver_set_browsertest.cc
+++ b/content/browser/web_contents_receiver_set_browsertest.cc
@@ -7,6 +7,7 @@
 #include "base/run_loop.h"
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "content/public/browser/web_contents_receiver_set.h"
+#include "content/public/test/back_forward_cache_util.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/content_browser_test.h"
@@ -127,6 +128,12 @@
   // Now navigate the WebContents elsewhere, eventually tearing down the old
   // main frame.
   RenderFrameDeletedObserver deleted_observer(web_contents->GetMainFrame());
+
+  // Test expects the old frame to be deleted on navigation, but it doesn't
+  // happen as it is stored in bfcache.
+  DisableBackForwardCacheForTesting(shell()->web_contents(),
+                                    BackForwardCache::TEST_ASSUMES_NO_CACHING);
+
   EXPECT_TRUE(NavigateToURL(
       shell(), embedded_test_server()->GetURL(kTestHost2, "/title2.html")));
   deleted_observer.WaitUntilDeleted();
diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc
index e64fa18..09859b3 100644
--- a/content/child/runtime_features.cc
+++ b/content/child/runtime_features.cc
@@ -341,7 +341,8 @@
     {wf::EnableMediaFeeds, media::kMediaFeeds, kUseFeatureState},
     {wf::EnableRestrictGamepadAccess, features::kRestrictGamepadAccess,
      kEnableOnly},
-
+    {wf::EnableCompositingOptimizations,
+     blink::features::kCompositingOptimizations, kUseFeatureState},
   };
   for (const auto& mapping : blinkFeatureToBaseFeatureMapping) {
     SetRuntimeFeatureFromChromiumFeature(
diff --git a/content/common/navigation_params.mojom b/content/common/navigation_params.mojom
index 82150c6..ce2367bf 100644
--- a/content/common/navigation_params.mojom
+++ b/content/common/navigation_params.mojom
@@ -23,6 +23,7 @@
 import "third_party/blink/public/mojom/loader/referrer.mojom";
 import "url/mojom/origin.mojom";
 import "url/mojom/url.mojom";
+import "third_party/blink/public/mojom/page/page.mojom";
 
 [Native]
 struct Impression;
@@ -281,6 +282,23 @@
   mojo_base.mojom.TimeTicks fetch_start;
 };
 
+// Sent with CommitNavigationParams and should only be set for main-frame
+// same-site navigations where we did a proactive BrowsingInstance swap and
+// we're reusing the old page's process. Needed to ensure that the previous
+// page's pagehide and visibilitychange handlers are run before the new page
+// runs (which is what happens on other same-site main frame navigations).
+struct OldPageInfo {
+  // |routing_id_for_old_main_frame| contains the routing ID of the old page's
+  // main RenderFrameHost.
+  int32 routing_id_for_old_main_frame = -1;
+  // |new_lifecycle_state_for_old_page| contains the latest PageLifecycleState
+  // of the old page to ensure the PageVisibilityState gets properly updated,
+  // the "persisted" property of the pagehide event is set correctly, and
+  // pagehide and visibilitychange events won't get dispatched again when we
+  // unload/freeze the page later on.
+  blink.mojom.PageLifecycleState new_lifecycle_state_for_old_page;
+};
+
 // Used by commit IPC messages. Holds the parameters needed by the renderer to
 // commit a navigation besides those in CommonNavigationParams.
 struct CommitNavigationParams {
@@ -451,4 +469,9 @@
   // Enforcement for enforcing on frames Content Security Policies required by
   // their embedders.
   array<string> forced_content_security_policies;
+
+  // Should only be set to a valid value for main-frame same-site navigations
+  // where we did a proactive BrowsingInstance swap and we're reusing the old
+  // page's process.
+  OldPageInfo? old_page_info;
 };
diff --git a/content/common/widget_messages.h b/content/common/widget_messages.h
index a542ee4..e8a1e83 100644
--- a/content/common/widget_messages.h
+++ b/content/common/widget_messages.h
@@ -86,15 +86,6 @@
 // APIs, and the browser may ignore this message.
 IPC_MESSAGE_ROUTED1(WidgetHostMsg_RequestSetBounds, gfx::Rect /* bounds */)
 
-// Sends a set of queued messages that were being held until the next
-// CompositorFrame is being submitted from the renderer. These messages are
-// sent before the OnRenderFrameMetadataChanged message is sent (via mojo) and
-// before the CompositorFrame is sent to the viz service. The |frame_token|
-// will match the token in the about-to-be-submitted CompositorFrame.
-IPC_MESSAGE_ROUTED2(WidgetHostMsg_FrameSwapMessages,
-                    uint32_t /* frame_token */,
-                    std::vector<IPC::Message> /* messages */)
-
 // Indicates that the render widget has been closed in response to a
 // Close message.
 IPC_MESSAGE_CONTROL1(WidgetHostMsg_Close_ACK, int /* old_route_id */)
diff --git a/content/public/browser/accessibility_tree_formatter.h b/content/public/browser/accessibility_tree_formatter.h
index 59f08a2..70c71c2 100644
--- a/content/public/browser/accessibility_tree_formatter.h
+++ b/content/public/browser/accessibility_tree_formatter.h
@@ -74,12 +74,12 @@
   struct CONTENT_EXPORT PropertyFilter {
     enum Type { ALLOW, ALLOW_EMPTY, DENY };
 
-    base::string16 match_str;
-    base::string16 property_str;
-    base::string16 filter_str;
+    std::string match_str;
+    std::string property_str;
+    std::string filter_str;
     Type type;
 
-    PropertyFilter(const base::string16& str, Type type);
+    PropertyFilter(const std::string& str, Type type);
     PropertyFilter(const PropertyFilter&);
   };
 
@@ -120,7 +120,7 @@
 
   static bool MatchesPropertyFilters(
       const std::vector<PropertyFilter>& property_filters,
-      const base::string16& text,
+      const std::string& text,
       bool default_result);
 
   // Check if the given dictionary matches any of the supplied NodeFilter(s).
diff --git a/content/public/test/back_forward_cache_util.cc b/content/public/test/back_forward_cache_util.cc
index 4916450..d7604f5a 100644
--- a/content/public/test/back_forward_cache_util.cc
+++ b/content/public/test/back_forward_cache_util.cc
@@ -10,6 +10,7 @@
 #include "content/browser/frame_host/back_forward_cache_impl.h"
 #include "content/browser/frame_host/render_frame_host_impl.h"
 #include "content/public/browser/global_routing_id.h"
+#include "content/public/browser/web_contents.h"
 
 namespace content {
 
@@ -43,4 +44,11 @@
       GlobalFrameRoutingId{process_id, frame_routing_id}, reason);
 }
 
+void DisableBackForwardCacheForTesting(
+    WebContents* web_contents,
+    BackForwardCache::DisableForTestingReason reason) {
+  // Used by tests. Disables BackForwardCache for a given WebContents.
+  web_contents->GetController().GetBackForwardCache().DisableForTesting(reason);
+}
+
 }  // namespace content
diff --git a/content/public/test/back_forward_cache_util.h b/content/public/test/back_forward_cache_util.h
index 54ec0e26..5ad5008 100644
--- a/content/public/test/back_forward_cache_util.h
+++ b/content/public/test/back_forward_cache_util.h
@@ -8,9 +8,10 @@
 #include <memory>
 
 #include "base/strings/string_piece.h"
+#include "content/public/browser/back_forward_cache.h"
 
 namespace content {
-class BackForwardCacheImpl;
+class WebContents;
 
 // This is a helper class to check in the tests that back-forward cache
 // was disabled for a particular reason.
@@ -43,6 +44,19 @@
   std::unique_ptr<Impl> impl_;
 };
 
+// Helper function to be used when the tests are interested in covering the
+// scenarios when back-forward cache is not used. This is similar to method
+// BackForwardCache::DisableForTesting(), but it takes a WebContents instead of
+// a BackForwardCache. This method disables BackForwardCache for a given
+// WebContents with the reason specified.
+//
+// Note that it is preferred to make the test work with BackForwardCache when
+// feasible, or have a standalone test with BackForwardCache enabled to test
+// the functionality when necessary.
+void DisableBackForwardCacheForTesting(
+    WebContents* web_contents,
+    BackForwardCache::DisableForTestingReason reason);
+
 }  // namespace content
 
 #endif  // CONTENT_PUBLIC_TEST_BACK_FORWARD_CACHE_UTIL_H_
diff --git a/content/renderer/BUILD.gn b/content/renderer/BUILD.gn
index 0dae59a..8ccdb89 100644
--- a/content/renderer/BUILD.gn
+++ b/content/renderer/BUILD.gn
@@ -73,8 +73,6 @@
     "frame_blame_context.h",
     "frame_owner_properties_converter.cc",
     "frame_owner_properties_converter.h",
-    "frame_swap_message_queue.cc",
-    "frame_swap_message_queue.h",
     "gpu_benchmarking_extension.cc",
     "gpu_benchmarking_extension.h",
     "history_entry.cc",
@@ -188,8 +186,6 @@
     "net_info_helper.h",
     "peripheral_content_heuristic.cc",
     "peripheral_content_heuristic.h",
-    "queue_message_swap_promise.cc",
-    "queue_message_swap_promise.h",
     "render_frame_impl.cc",
     "render_frame_impl.h",
     "render_frame_metadata_observer_impl.cc",
diff --git a/content/renderer/android/synchronous_layer_tree_frame_sink_impl.cc b/content/renderer/android/synchronous_layer_tree_frame_sink_impl.cc
index 2883b6e..e84687d4 100644
--- a/content/renderer/android/synchronous_layer_tree_frame_sink_impl.cc
+++ b/content/renderer/android/synchronous_layer_tree_frame_sink_impl.cc
@@ -34,7 +34,6 @@
 #include "content/common/android/sync_compositor_statics.h"
 #include "content/common/view_messages.h"
 #include "content/public/common/content_switches.h"
-#include "content/renderer/frame_swap_message_queue.h"
 #include "content/renderer/render_thread_impl.h"
 #include "gpu/command_buffer/client/context_support.h"
 #include "gpu/command_buffer/client/gles2_interface.h"
@@ -154,7 +153,6 @@
     uint32_t layer_tree_frame_sink_id,
     std::unique_ptr<viz::BeginFrameSource> synthetic_begin_frame_source,
     blink::SynchronousCompositorRegistry* registry,
-    scoped_refptr<FrameSwapMessageQueue> frame_swap_message_queue,
     mojo::PendingRemote<viz::mojom::CompositorFrameSink>
         compositor_frame_sink_remote,
     mojo::PendingReceiver<viz::mojom::CompositorFrameSinkClient>
@@ -167,7 +165,6 @@
       registry_(registry),
       sender_(sender),
       memory_policy_(0u),
-      frame_swap_message_queue_(frame_swap_message_queue),
       unbound_compositor_frame_sink_(std::move(compositor_frame_sink_remote)),
       unbound_client_(std::move(client_receiver)),
       synthetic_begin_frame_source_(std::move(synthetic_begin_frame_source)),
@@ -555,17 +552,6 @@
   DCHECK(CalledOnValidThread());
   if (sync_client_)
     sync_client_->DidActivatePendingTree();
-  DeliverMessages();
-}
-
-void SynchronousLayerTreeFrameSinkImpl::DeliverMessages() {
-  std::vector<std::unique_ptr<IPC::Message>> messages;
-  std::unique_ptr<FrameSwapMessageQueue::SendMessageScope> send_message_scope =
-      frame_swap_message_queue_->AcquireSendMessageScope();
-  frame_swap_message_queue_->DrainMessages(&messages);
-  for (auto& msg : messages) {
-    Send(msg.release());
-  }
 }
 
 bool SynchronousLayerTreeFrameSinkImpl::Send(IPC::Message* message) {
diff --git a/content/renderer/android/synchronous_layer_tree_frame_sink_impl.h b/content/renderer/android/synchronous_layer_tree_frame_sink_impl.h
index f640c139..cae9af0e 100644
--- a/content/renderer/android/synchronous_layer_tree_frame_sink_impl.h
+++ b/content/renderer/android/synchronous_layer_tree_frame_sink_impl.h
@@ -54,8 +54,6 @@
 
 namespace content {
 
-class FrameSwapMessageQueue;
-
 // Specialization of the output surface that adapts it to implement the
 // content::SynchronousCompositor public API. This class effects an "inversion
 // of control" - enabling drawing to be  orchestrated by the embedding
@@ -78,7 +76,6 @@
       uint32_t layer_tree_frame_sink_id,
       std::unique_ptr<viz::BeginFrameSource> begin_frame_source,
       blink::SynchronousCompositorRegistry* registry,
-      scoped_refptr<FrameSwapMessageQueue> frame_swap_message_queue,
       mojo::PendingRemote<viz::mojom::CompositorFrameSink>
           compositor_frame_sink_remote,
       mojo::PendingReceiver<viz::mojom::CompositorFrameSinkClient>
@@ -157,7 +154,6 @@
   cc::ManagedMemoryPolicy memory_policy_;
   bool in_software_draw_ = false;
   bool did_submit_frame_ = false;
-  scoped_refptr<FrameSwapMessageQueue> frame_swap_message_queue_;
 
   mojo::PendingRemote<viz::mojom::CompositorFrameSink>
       unbound_compositor_frame_sink_;
diff --git a/content/renderer/compositor/compositor_dependencies.h b/content/renderer/compositor/compositor_dependencies.h
index c820413..766f795 100644
--- a/content/renderer/compositor/compositor_dependencies.h
+++ b/content/renderer/compositor/compositor_dependencies.h
@@ -35,7 +35,6 @@
 }  // namespace blink
 
 namespace content {
-class FrameSwapMessageQueue;
 class RenderWidget;
 
 class CONTENT_EXPORT CompositorDependencies {
@@ -57,7 +56,6 @@
       std::unique_ptr<cc::RenderFrameMetadataObserver>)>;
   virtual void RequestNewLayerTreeFrameSink(
       RenderWidget* render_widget,
-      scoped_refptr<FrameSwapMessageQueue> frame_swap_message_queue,
       const GURL& url,
       LayerTreeFrameSinkCallback callback,
       const char* client_name) = 0;
diff --git a/content/renderer/frame_swap_message_queue.cc b/content/renderer/frame_swap_message_queue.cc
deleted file mode 100644
index d85fe70..0000000
--- a/content/renderer/frame_swap_message_queue.cc
+++ /dev/null
@@ -1,157 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "content/renderer/frame_swap_message_queue.h"
-
-#include <algorithm>
-#include <limits>
-#include <memory>
-#include <utility>
-
-#include "base/check.h"
-#include "base/macros.h"
-#include "base/stl_util.h"
-#include "ipc/ipc_message.h"
-
-namespace content {
-
-class FrameSwapMessageSubQueue {
- public:
-  FrameSwapMessageSubQueue() {}
-  virtual ~FrameSwapMessageSubQueue() {}
-  virtual bool Empty() const = 0;
-  virtual void QueueMessage(int source_frame_number,
-                            std::unique_ptr<IPC::Message> msg,
-                            bool* is_first) = 0;
-  virtual void DrainMessages(
-      int source_frame_number,
-      std::vector<std::unique_ptr<IPC::Message>>* messages) = 0;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(FrameSwapMessageSubQueue);
-};
-
-namespace {
-
-class SendMessageScopeImpl : public FrameSwapMessageQueue::SendMessageScope {
- public:
-  SendMessageScopeImpl(base::Lock* lock) : auto_lock_(*lock) {}
-  ~SendMessageScopeImpl() override {}
-
- private:
-  base::AutoLock auto_lock_;
-};
-
-class VisualStateQueue : public FrameSwapMessageSubQueue {
- public:
-  VisualStateQueue() = default;
-
-  ~VisualStateQueue() override = default;
-
-  bool Empty() const override { return queue_.empty(); }
-
-  void QueueMessage(int source_frame_number,
-                    std::unique_ptr<IPC::Message> msg,
-                    bool* is_first) override {
-    if (is_first)
-      *is_first = (queue_.count(source_frame_number) == 0);
-
-    queue_[source_frame_number].push_back(std::move(msg));
-  }
-
-  void DrainMessages(
-      int source_frame_number,
-      std::vector<std::unique_ptr<IPC::Message>>* messages) override {
-    auto end = queue_.upper_bound(source_frame_number);
-    for (auto i = queue_.begin(); i != end; i++) {
-      DCHECK(i->first <= source_frame_number);
-      std::move(i->second.begin(), i->second.end(),
-                std::back_inserter(*messages));
-    }
-    queue_.erase(queue_.begin(), end);
-  }
-
- private:
-  std::map<int, std::vector<std::unique_ptr<IPC::Message>>> queue_;
-
-  DISALLOW_COPY_AND_ASSIGN(VisualStateQueue);
-};
-
-}  // namespace
-
-FrameSwapMessageQueue::FrameSwapMessageQueue(int32_t routing_id)
-    : visual_state_queue_(new VisualStateQueue()), routing_id_(routing_id) {
-  DETACH_FROM_THREAD(impl_thread_checker_);
-}
-
-FrameSwapMessageQueue::~FrameSwapMessageQueue() {}
-
-bool FrameSwapMessageQueue::Empty() const {
-  base::AutoLock lock(lock_);
-  return next_drain_messages_.empty() && visual_state_queue_->Empty();
-}
-
-void FrameSwapMessageQueue::QueueMessageForFrame(
-    int source_frame_number,
-    std::unique_ptr<IPC::Message> msg,
-    bool* is_first) {
-  base::AutoLock lock(lock_);
-  visual_state_queue_->QueueMessage(source_frame_number, std::move(msg),
-                                    is_first);
-}
-
-void FrameSwapMessageQueue::DidActivate(int source_frame_number) {
-  base::AutoLock lock(lock_);
-  visual_state_queue_->DrainMessages(source_frame_number,
-                                     &next_drain_messages_);
-}
-
-void FrameSwapMessageQueue::DidSwap(int source_frame_number) {}
-
-cc::SwapPromise::DidNotSwapAction FrameSwapMessageQueue::DidNotSwap(
-    int source_frame_number,
-    cc::SwapPromise::DidNotSwapReason reason,
-    std::vector<std::unique_ptr<IPC::Message>>* messages) {
-  base::AutoLock lock(lock_);
-  switch (reason) {
-    case cc::SwapPromise::SWAP_FAILS:
-    case cc::SwapPromise::COMMIT_NO_UPDATE:
-      DrainMessages(messages);
-      visual_state_queue_->DrainMessages(source_frame_number, messages);
-      return cc::SwapPromise::DidNotSwapAction::BREAK_PROMISE;
-    case cc::SwapPromise::COMMIT_FAILS:
-      return cc::SwapPromise::DidNotSwapAction::KEEP_ACTIVE;
-    case cc::SwapPromise::ACTIVATION_FAILS:
-      // Do not queue any responses or return KEEP_ALIVE here. If
-      // ACTIVATION_FAILS the renderer is shutting down, which will result
-      // in the RenderFrameHostImpl destructor firing the remaining
-      // response callbacks itself.
-      return cc::SwapPromise::DidNotSwapAction::BREAK_PROMISE;
-  }
-}
-
-void FrameSwapMessageQueue::DrainMessages(
-    std::vector<std::unique_ptr<IPC::Message>>* messages) {
-  lock_.AssertAcquired();
-  std::move(next_drain_messages_.begin(), next_drain_messages_.end(),
-            std::back_inserter(*messages));
-  next_drain_messages_.clear();
-}
-
-std::unique_ptr<FrameSwapMessageQueue::SendMessageScope>
-FrameSwapMessageQueue::AcquireSendMessageScope() {
-  return std::make_unique<SendMessageScopeImpl>(&lock_);
-}
-
-// static
-void FrameSwapMessageQueue::TransferMessages(
-    std::vector<std::unique_ptr<IPC::Message>>* source,
-    std::vector<IPC::Message>* dest) {
-  for (const auto& msg : *source) {
-    dest->push_back(*msg.get());
-  }
-  source->clear();
-}
-
-}  // namespace content
diff --git a/content/renderer/frame_swap_message_queue.h b/content/renderer/frame_swap_message_queue.h
deleted file mode 100644
index 3220f22..0000000
--- a/content/renderer/frame_swap_message_queue.h
+++ /dev/null
@@ -1,116 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CONTENT_RENDERER_FRAME_SWAP_MESSAGE_QUEUE_H_
-#define CONTENT_RENDERER_FRAME_SWAP_MESSAGE_QUEUE_H_
-
-#include <map>
-#include <memory>
-#include <vector>
-
-#include "base/auto_reset.h"
-#include "base/macros.h"
-#include "base/memory/ref_counted.h"
-#include "base/synchronization/lock.h"
-#include "base/threading/thread_checker.h"
-#include "cc/trees/swap_promise.h"
-#include "content/common/content_export.h"
-
-namespace IPC {
-class Message;
-}
-
-namespace content {
-
-class FrameSwapMessageSubQueue;
-
-// Queue used to keep track of which IPC::Messages should be sent after a
-// particular compositor frame swap. The messages are guaranteed to be processed
-// after the frame is processed, but there is no guarantee that nothing else
-// happens between processing the frame and processing the messages.
-class CONTENT_EXPORT FrameSwapMessageQueue
-    : public base::RefCountedThreadSafe<FrameSwapMessageQueue> {
- public:
-  class CONTENT_EXPORT SendMessageScope {
-   public:
-    virtual ~SendMessageScope() {}
-  };
-
-  explicit FrameSwapMessageQueue(int32_t routing_id);
-
-  // Queues message to be returned on a matching DrainMessages call.
-  //
-  // |source_frame_number| frame number to queue |msg| for.
-  // |msg| - message to queue. The method takes ownership of |msg|.
-  // |is_first| - output parameter. Set to true if this was the first message
-  //              enqueued for the given source_frame_number.
-  void QueueMessageForFrame(int source_frame_number,
-                            std::unique_ptr<IPC::Message> msg,
-                            bool* is_first);
-
-  // Returns true if there are no messages in the queue.
-  bool Empty() const;
-
-  // Should be called when a successful activation occurs. The messages for
-  // that activation can be obtained by calling DrainMessages.
-  //
-  // |source_frame_number| frame number for which the activate occurred.
-  void DidActivate(int source_frame_number);
-
-  // Should be called when a successful swap occurs. The messages for that
-  // swap can be obtained by calling DrainMessages.
-  //
-  // |source_frame_number| frame number for which the swap occurred.
-  void DidSwap(int source_frame_number);
-
-  // Should be called when we know a swap will not occur.
-  //
-  // |source_frame_number| frame number for which the swap will not occur.
-  // |reason| reason for the which the swap will not occur.
-  // |messages| depending on |reason| it may make sense to deliver certain
-  //            messages asynchronously. This vector will contain those
-  //            messages.
-  cc::SwapPromise::DidNotSwapAction DidNotSwap(
-      int source_frame_number,
-      cc::SwapPromise::DidNotSwapReason reason,
-      std::vector<std::unique_ptr<IPC::Message>>* messages);
-
-  // A SendMessageScope object must be held by the caller when this method is
-  // called.
-  //
-  // |messages| vector to store messages, it's not cleared, only appended to.
-  //            The method will append messages queued for frame numbers lower
-  //            or equal to |source_frame_number|
-  void DrainMessages(std::vector<std::unique_ptr<IPC::Message>>* messages);
-
-  // SendMessageScope is used to make sure that messages sent from different
-  // threads (impl/main) are scheduled in the right order on the IO threads.
-  //
-  // Returns an object that must be kept in scope till an IPC message containing
-  // |messages| is sent.
-  std::unique_ptr<SendMessageScope> AcquireSendMessageScope();
-
-  static void TransferMessages(
-      std::vector<std::unique_ptr<IPC::Message>>* source,
-      std::vector<IPC::Message>* dest);
-
-  int32_t routing_id() const { return routing_id_; }
-
- private:
-  friend class base::RefCountedThreadSafe<FrameSwapMessageQueue>;
-
-  ~FrameSwapMessageQueue();
-
-  mutable base::Lock lock_;
-  std::unique_ptr<FrameSwapMessageSubQueue> visual_state_queue_;
-  std::vector<std::unique_ptr<IPC::Message>> next_drain_messages_;
-  int32_t routing_id_ = 0;
-  THREAD_CHECKER(impl_thread_checker_);
-
-  DISALLOW_COPY_AND_ASSIGN(FrameSwapMessageQueue);
-};
-
-}  // namespace content
-
-#endif  // CONTENT_RENDERER_FRAME_SWAP_MESSAGE_QUEUE_H_
diff --git a/content/renderer/frame_swap_message_queue_unittest.cc b/content/renderer/frame_swap_message_queue_unittest.cc
deleted file mode 100644
index 0282e46..0000000
--- a/content/renderer/frame_swap_message_queue_unittest.cc
+++ /dev/null
@@ -1,302 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "content/renderer/frame_swap_message_queue.h"
-
-#include <utility>
-
-#include "ipc/ipc_message.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace content {
-
-class FrameSwapMessageQueueTest : public testing::Test {
- public:
-  FrameSwapMessageQueueTest()
-      : first_message_(41, 1, IPC::Message::PRIORITY_NORMAL),
-        second_message_(42, 2, IPC::Message::PRIORITY_NORMAL),
-        third_message_(43, 3, IPC::Message::PRIORITY_NORMAL),
-        queue_(new FrameSwapMessageQueue(0)) {}
-
- protected:
-  void QueueNextSwapMessage(std::unique_ptr<IPC::Message> msg) {
-    queue_->QueueMessageForFrame(0, std::move(msg), nullptr);
-  }
-
-  void QueueNextSwapMessage(std::unique_ptr<IPC::Message> msg, bool* first) {
-    queue_->QueueMessageForFrame(0, std::move(msg), first);
-  }
-
-  void QueueVisualStateMessage(int source_frame_number,
-                               std::unique_ptr<IPC::Message> msg) {
-    queue_->QueueMessageForFrame(source_frame_number, std::move(msg), nullptr);
-  }
-
-  void QueueVisualStateMessage(int source_frame_number,
-                               std::unique_ptr<IPC::Message> msg,
-                               bool* first) {
-    queue_->QueueMessageForFrame(source_frame_number, std::move(msg), first);
-  }
-
-  void DrainMessages(int source_frame_number,
-                     std::vector<std::unique_ptr<IPC::Message>>* messages) {
-    messages->clear();
-    queue_->DidActivate(source_frame_number);
-    queue_->DidSwap(source_frame_number);
-    std::unique_ptr<FrameSwapMessageQueue::SendMessageScope>
-        send_message_scope = queue_->AcquireSendMessageScope();
-    queue_->DrainMessages(messages);
-  }
-
-  bool HasMessageForId(
-      const std::vector<std::unique_ptr<IPC::Message>>& messages,
-      int routing_id) {
-    for (const auto& msg : messages) {
-      if (msg->routing_id() == routing_id)
-        return true;
-    }
-    return false;
-  }
-
-  std::unique_ptr<IPC::Message> CloneMessage(const IPC::Message& other) {
-    return std::make_unique<IPC::Message>(other);
-  }
-
-  void TestDidNotSwap(cc::SwapPromise::DidNotSwapReason reason);
-
-  IPC::Message first_message_;
-  IPC::Message second_message_;
-  IPC::Message third_message_;
-  scoped_refptr<FrameSwapMessageQueue> queue_;
-};
-
-TEST_F(FrameSwapMessageQueueTest, TestEmptyQueueDrain) {
-  std::vector<std::unique_ptr<IPC::Message>> messages;
-
-  DrainMessages(0, &messages);
-  ASSERT_TRUE(messages.empty());
-}
-
-TEST_F(FrameSwapMessageQueueTest, TestEmpty) {
-  std::vector<std::unique_ptr<IPC::Message>> messages;
-  ASSERT_TRUE(queue_->Empty());
-  QueueNextSwapMessage(CloneMessage(first_message_));
-  ASSERT_FALSE(queue_->Empty());
-  DrainMessages(0, &messages);
-  ASSERT_TRUE(queue_->Empty());
-  QueueVisualStateMessage(1, CloneMessage(first_message_));
-  ASSERT_FALSE(queue_->Empty());
-  queue_->DidActivate(1);
-  queue_->DidSwap(1);
-  ASSERT_FALSE(queue_->Empty());
-}
-
-TEST_F(FrameSwapMessageQueueTest, TestQueueMessageFirst) {
-  std::vector<std::unique_ptr<IPC::Message>> messages;
-  bool visual_state_first = false;
-  bool next_swap_first = false;
-
-  // Queuing the first time should result in true.
-  QueueVisualStateMessage(1, CloneMessage(first_message_), &visual_state_first);
-  ASSERT_TRUE(visual_state_first);
-  // Queuing the second time should result in true.
-  QueueVisualStateMessage(1, CloneMessage(second_message_),
-                          &visual_state_first);
-  ASSERT_FALSE(visual_state_first);
-  // Queuing for a different frame should result in true.
-  QueueVisualStateMessage(2, CloneMessage(first_message_), &visual_state_first);
-  ASSERT_TRUE(visual_state_first);
-
-  // Queuing for a different policy should result in true.
-  QueueNextSwapMessage(CloneMessage(first_message_), &next_swap_first);
-  ASSERT_TRUE(next_swap_first);
-  // Second time for the same policy is still false.
-  QueueNextSwapMessage(CloneMessage(first_message_), &next_swap_first);
-  ASSERT_FALSE(next_swap_first);
-
-  DrainMessages(4, &messages);
-  // Queuing after all messages are drained is a true again.
-  QueueVisualStateMessage(4, CloneMessage(first_message_), &visual_state_first);
-  ASSERT_TRUE(visual_state_first);
-}
-
-TEST_F(FrameSwapMessageQueueTest, TestNextSwapMessageSentWithNextFrame) {
-  std::vector<std::unique_ptr<IPC::Message>> messages;
-
-  DrainMessages(1, &messages);
-  QueueNextSwapMessage(CloneMessage(first_message_));
-  DrainMessages(2, &messages);
-  ASSERT_EQ(1u, messages.size());
-  ASSERT_EQ(first_message_.routing_id(), messages.front()->routing_id());
-  messages.clear();
-
-  DrainMessages(2, &messages);
-  ASSERT_TRUE(messages.empty());
-}
-
-TEST_F(FrameSwapMessageQueueTest, TestNextSwapMessageSentWithCurrentFrame) {
-  std::vector<std::unique_ptr<IPC::Message>> messages;
-
-  DrainMessages(1, &messages);
-  QueueNextSwapMessage(CloneMessage(first_message_));
-  DrainMessages(1, &messages);
-  ASSERT_EQ(1u, messages.size());
-  ASSERT_EQ(first_message_.routing_id(), messages.front()->routing_id());
-  messages.clear();
-
-  DrainMessages(1, &messages);
-  ASSERT_TRUE(messages.empty());
-}
-
-TEST_F(FrameSwapMessageQueueTest,
-       TestDrainsVisualStateMessagesForCorrespondingFrames) {
-  std::vector<std::unique_ptr<IPC::Message>> messages;
-
-  QueueVisualStateMessage(1, CloneMessage(first_message_));
-  QueueVisualStateMessage(2, CloneMessage(second_message_));
-  QueueVisualStateMessage(3, CloneMessage(third_message_));
-  DrainMessages(0, &messages);
-  ASSERT_TRUE(messages.empty());
-
-  DrainMessages(2, &messages);
-  ASSERT_EQ(2u, messages.size());
-  ASSERT_TRUE(HasMessageForId(messages, first_message_.routing_id()));
-  ASSERT_TRUE(HasMessageForId(messages, second_message_.routing_id()));
-  messages.clear();
-
-  DrainMessages(2, &messages);
-  ASSERT_TRUE(messages.empty());
-
-  DrainMessages(5, &messages);
-  ASSERT_EQ(1u, messages.size());
-  ASSERT_EQ(third_message_.routing_id(), messages.front()->routing_id());
-}
-
-TEST_F(FrameSwapMessageQueueTest,
-       TestQueueNextSwapMessagePreservesFifoOrdering) {
-  std::vector<std::unique_ptr<IPC::Message>> messages;
-
-  QueueNextSwapMessage(CloneMessage(first_message_));
-  QueueNextSwapMessage(CloneMessage(second_message_));
-  DrainMessages(1, &messages);
-  ASSERT_EQ(2u, messages.size());
-  ASSERT_EQ(first_message_.routing_id(), messages[0]->routing_id());
-  ASSERT_EQ(second_message_.routing_id(), messages[1]->routing_id());
-}
-
-TEST_F(FrameSwapMessageQueueTest,
-       TestQueueVisualStateMessagePreservesFifoOrdering) {
-  std::vector<std::unique_ptr<IPC::Message>> messages;
-
-  QueueVisualStateMessage(1, CloneMessage(first_message_));
-  QueueVisualStateMessage(1, CloneMessage(second_message_));
-  DrainMessages(1, &messages);
-  ASSERT_EQ(2u, messages.size());
-  ASSERT_EQ(first_message_.routing_id(), messages[0]->routing_id());
-  ASSERT_EQ(second_message_.routing_id(), messages[1]->routing_id());
-}
-
-void FrameSwapMessageQueueTest::TestDidNotSwap(
-    cc::SwapPromise::DidNotSwapReason reason) {
-  std::vector<std::unique_ptr<IPC::Message>> messages;
-
-  QueueNextSwapMessage(CloneMessage(first_message_));
-  QueueVisualStateMessage(2, CloneMessage(second_message_));
-  QueueVisualStateMessage(3, CloneMessage(third_message_));
-  const int rid[] = {first_message_.routing_id(), second_message_.routing_id(),
-                     third_message_.routing_id()};
-
-  bool msg_delivered = reason != cc::SwapPromise::COMMIT_FAILS &&
-                       reason != cc::SwapPromise::ACTIVATION_FAILS;
-
-  queue_->DidNotSwap(2, reason, &messages);
-  ASSERT_TRUE(msg_delivered == HasMessageForId(messages, rid[0]));
-  ASSERT_TRUE(msg_delivered == HasMessageForId(messages, rid[1]));
-  ASSERT_FALSE(HasMessageForId(messages, rid[2]));
-  messages.clear();
-
-  queue_->DidNotSwap(3, reason, &messages);
-  ASSERT_FALSE(HasMessageForId(messages, rid[0]));
-  ASSERT_FALSE(HasMessageForId(messages, rid[1]));
-  ASSERT_TRUE(msg_delivered == HasMessageForId(messages, rid[2]));
-  messages.clear();
-
-  // all undelivered messages should still be available for RenderFrameHostImpl
-  // to deliver.
-  DrainMessages(3, &messages);
-  ASSERT_TRUE(msg_delivered != HasMessageForId(messages, rid[0]));
-  ASSERT_TRUE(msg_delivered != HasMessageForId(messages, rid[1]));
-  ASSERT_TRUE(msg_delivered != HasMessageForId(messages, rid[2]));
-}
-
-TEST_F(FrameSwapMessageQueueTest, TestDidNotSwapNoUpdate) {
-  TestDidNotSwap(cc::SwapPromise::COMMIT_NO_UPDATE);
-}
-
-TEST_F(FrameSwapMessageQueueTest, TestDidNotSwapSwapFails) {
-  TestDidNotSwap(cc::SwapPromise::SWAP_FAILS);
-}
-
-TEST_F(FrameSwapMessageQueueTest, TestDidNotSwapCommitFails) {
-  TestDidNotSwap(cc::SwapPromise::COMMIT_FAILS);
-}
-
-TEST_F(FrameSwapMessageQueueTest, TestDidNotSwapActivationFails) {
-  TestDidNotSwap(cc::SwapPromise::ACTIVATION_FAILS);
-}
-
-class NotifiesDeletionMessage : public IPC::Message {
- public:
-  NotifiesDeletionMessage(bool* deleted, const IPC::Message& other)
-      : IPC::Message(other), deleted_(deleted) {}
-  ~NotifiesDeletionMessage() override { *deleted_ = true; }
-
- private:
-  bool* deleted_;
-};
-
-TEST_F(FrameSwapMessageQueueTest, TestDeletesNextSwapMessage) {
-  bool message_deleted = false;
-  QueueNextSwapMessage(std::make_unique<NotifiesDeletionMessage>(
-      &message_deleted, first_message_));
-  queue_ = nullptr;
-  ASSERT_TRUE(message_deleted);
-}
-
-TEST_F(FrameSwapMessageQueueTest, TestDeletesVisualStateMessage) {
-  bool message_deleted = false;
-  QueueVisualStateMessage(1, std::make_unique<NotifiesDeletionMessage>(
-                                 &message_deleted, first_message_));
-  queue_ = nullptr;
-  ASSERT_TRUE(message_deleted);
-}
-
-TEST_F(FrameSwapMessageQueueTest, TestDeletesQueuedVisualStateMessage) {
-  bool message_deleted = false;
-  QueueVisualStateMessage(1, std::make_unique<NotifiesDeletionMessage>(
-                                 &message_deleted, first_message_));
-  queue_->DidActivate(1);
-  queue_->DidSwap(1);
-  queue_ = nullptr;
-  ASSERT_TRUE(message_deleted);
-}
-
-TEST_F(FrameSwapMessageQueueTest, TestDrainsMessageOnActivationThanDidNotSwap) {
-  const int frame = 6;
-  std::unique_ptr<IPC::Message> msg = CloneMessage(first_message_);
-  IPC::Message* msgSent = msg.get();
-  QueueVisualStateMessage(frame, std::move(msg));
-  queue_->DidActivate(frame);
-  EXPECT_TRUE(!queue_->Empty());
-
-  std::vector<std::unique_ptr<IPC::Message>> messages;
-  queue_->DidNotSwap(frame, cc::SwapPromise::SWAP_FAILS, &messages);
-  CHECK_EQ(1UL, messages.size());
-  EXPECT_EQ(messages[0].get(), msgSent);
-  msgSent = nullptr;
-
-  queue_ = nullptr;
-}
-
-}  // namespace content
diff --git a/content/renderer/queue_message_swap_promise.cc b/content/renderer/queue_message_swap_promise.cc
deleted file mode 100644
index ccf8d41..0000000
--- a/content/renderer/queue_message_swap_promise.cc
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "content/renderer/queue_message_swap_promise.h"
-
-#include <memory>
-
-#include "base/command_line.h"
-#include "content/common/widget_messages.h"
-#include "content/public/common/content_switches.h"
-#include "content/public/renderer/render_thread.h"
-#include "content/renderer/frame_swap_message_queue.h"
-#include "ipc/ipc_sync_message_filter.h"
-
-namespace content {
-
-QueueMessageSwapPromise::QueueMessageSwapPromise(
-    scoped_refptr<IPC::SyncMessageFilter> message_sender,
-    scoped_refptr<content::FrameSwapMessageQueue> message_queue,
-    int source_frame_number)
-    : message_sender_(message_sender),
-      message_queue_(message_queue),
-      source_frame_number_(source_frame_number)
-{
-  DCHECK(message_sender_.get());
-  DCHECK(message_queue_.get());
-}
-
-QueueMessageSwapPromise::~QueueMessageSwapPromise() {
-}
-
-void QueueMessageSwapPromise::DidActivate() {
-  message_queue_->DidActivate(source_frame_number_);
-  // The OutputSurface will take care of the Drain+Send.
-}
-
-void QueueMessageSwapPromise::WillSwap(viz::CompositorFrameMetadata* metadata) {
-  message_queue_->DidSwap(source_frame_number_);
-
-  std::unique_ptr<FrameSwapMessageQueue::SendMessageScope> send_message_scope =
-      message_queue_->AcquireSendMessageScope();
-  std::vector<std::unique_ptr<IPC::Message>> messages;
-  message_queue_->DrainMessages(&messages);
-
-  std::vector<IPC::Message> messages_to_send;
-  FrameSwapMessageQueue::TransferMessages(&messages, &messages_to_send);
-  if (!messages_to_send.empty()) {
-    metadata->send_frame_token_to_embedder = true;
-    message_sender_->Send(new WidgetHostMsg_FrameSwapMessages(
-        message_queue_->routing_id(), metadata->frame_token, messages_to_send));
-  }
-}
-
-void QueueMessageSwapPromise::DidSwap() {}
-
-cc::SwapPromise::DidNotSwapAction QueueMessageSwapPromise::DidNotSwap(
-    DidNotSwapReason reason) {
-  // TODO(eseckler): Deliver messages with the next swap instead of sending
-  // them here directly.
-  std::vector<std::unique_ptr<IPC::Message>> messages;
-  cc::SwapPromise::DidNotSwapAction action =
-      message_queue_->DidNotSwap(source_frame_number_, reason, &messages);
-  for (auto& msg : messages) {
-    message_sender_->Send(msg.release());
-  }
-  return action;
-}
-
-int64_t QueueMessageSwapPromise::TraceId() const {
-  return 0;
-}
-
-}  // namespace content
diff --git a/content/renderer/queue_message_swap_promise.h b/content/renderer/queue_message_swap_promise.h
deleted file mode 100644
index 2c31b90..0000000
--- a/content/renderer/queue_message_swap_promise.h
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CONTENT_RENDERER_QUEUE_MESSAGE_SWAP_PROMISE_H_
-#define CONTENT_RENDERER_QUEUE_MESSAGE_SWAP_PROMISE_H_
-
-#include <stdint.h>
-
-#include "base/memory/ref_counted.h"
-#include "cc/trees/swap_promise.h"
-
-namespace IPC {
-class SyncMessageFilter;
-}
-
-namespace content {
-
-class FrameSwapMessageQueue;
-
-class QueueMessageSwapPromise : public cc::SwapPromise {
- public:
-  QueueMessageSwapPromise(scoped_refptr<IPC::SyncMessageFilter> message_sender,
-                          scoped_refptr<FrameSwapMessageQueue> message_queue,
-                          int source_frame_number);
-
-  ~QueueMessageSwapPromise() override;
-
-  void DidActivate() override;
-  void WillSwap(viz::CompositorFrameMetadata* metadata) override;
-  void DidSwap() override;
-  DidNotSwapAction DidNotSwap(DidNotSwapReason reason) override;
-
-  int64_t TraceId() const override;
-
- private:
-  scoped_refptr<IPC::SyncMessageFilter> message_sender_;
-  scoped_refptr<content::FrameSwapMessageQueue> message_queue_;
-  int source_frame_number_;
-};
-
-}  // namespace content
-
-#endif  // CONTENT_RENDERER_QUEUE_MESSAGE_SWAP_PROMISE_H_
diff --git a/content/renderer/queue_message_swap_promise_unittest.cc b/content/renderer/queue_message_swap_promise_unittest.cc
deleted file mode 100644
index f9cb7f1..0000000
--- a/content/renderer/queue_message_swap_promise_unittest.cc
+++ /dev/null
@@ -1,308 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "content/renderer/queue_message_swap_promise.h"
-
-#include <stddef.h>
-
-#include <memory>
-#include <vector>
-
-#include "base/macros.h"
-#include "base/memory/ptr_util.h"
-#include "base/test/task_environment.h"
-#include "cc/trees/swap_promise.h"
-#include "content/common/render_frame_metadata.mojom.h"
-#include "content/common/widget_messages.h"
-#include "content/renderer/frame_swap_message_queue.h"
-#include "content/renderer/render_widget.h"
-#include "ipc/ipc_message.h"
-#include "ipc/ipc_sync_message_filter.h"
-#include "ipc/ipc_test_sink.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace content {
-
-class TestSyncMessageFilter : public IPC::SyncMessageFilter {
- public:
-  TestSyncMessageFilter() : IPC::SyncMessageFilter(nullptr) {}
-
-  bool Send(IPC::Message* message) override {
-    if (message->type() == WidgetHostMsg_FrameSwapMessages::ID) {
-      WidgetHostMsg_FrameSwapMessages::Param param;
-      WidgetHostMsg_FrameSwapMessages::Read(message, &param);
-      std::vector<IPC::Message> messages = std::get<1>(param);
-      last_swap_messages_.clear();
-      for (const IPC::Message& message : messages) {
-        last_swap_messages_.push_back(std::make_unique<IPC::Message>(message));
-      }
-      delete message;
-    } else {
-      direct_send_messages_.push_back(base::WrapUnique(message));
-    }
-    return true;
-  }
-
-  std::vector<std::unique_ptr<IPC::Message>>& last_swap_messages() {
-    return last_swap_messages_;
-  }
-
-  const std::vector<std::unique_ptr<IPC::Message>>& direct_send_messages() {
-    return direct_send_messages_;
-  }
-
- private:
-  ~TestSyncMessageFilter() override {}
-
-  std::vector<std::unique_ptr<IPC::Message>> direct_send_messages_;
-  std::vector<std::unique_ptr<IPC::Message>> last_swap_messages_;
-
-  DISALLOW_COPY_AND_ASSIGN(TestSyncMessageFilter);
-};
-
-class QueueMessageSwapPromiseTest : public testing::Test {
- public:
-  QueueMessageSwapPromiseTest()
-      : frame_swap_message_queue_(new FrameSwapMessageQueue(0)),
-        sync_message_filter_(new TestSyncMessageFilter()) {}
-
-  ~QueueMessageSwapPromiseTest() override {}
-
-  std::unique_ptr<cc::SwapPromise> QueueMessageImpl(
-      std::unique_ptr<IPC::Message> msg,
-      int source_frame_number) {
-    return RenderWidget::QueueMessageImpl(
-        std::move(msg), frame_swap_message_queue_.get(), sync_message_filter_,
-        source_frame_number);
-  }
-
-  const std::vector<std::unique_ptr<IPC::Message>>& DirectSendMessages() {
-    return sync_message_filter_->direct_send_messages();
-  }
-
-  std::vector<std::unique_ptr<IPC::Message>>& LastSwapMessages() {
-    return sync_message_filter_->last_swap_messages();
-  }
-
-  std::vector<std::unique_ptr<IPC::Message>>& NextSwapMessages() {
-    next_swap_messages_.clear();
-    std::unique_ptr<FrameSwapMessageQueue::SendMessageScope>
-        send_message_scope =
-            frame_swap_message_queue_->AcquireSendMessageScope();
-    frame_swap_message_queue_->DrainMessages(&next_swap_messages_);
-    return next_swap_messages_;
-  }
-
-  bool ContainsMessage(
-      const std::vector<std::unique_ptr<IPC::Message>>& messages,
-      const IPC::Message& message) {
-    if (messages.empty())
-      return false;
-    for (const auto& msg : messages) {
-      if (msg->type() == message.type())
-        return true;
-    }
-    return false;
-  }
-
-  bool LastSwapHasMessage(const IPC::Message& message) {
-    return ContainsMessage(LastSwapMessages(), message);
-  }
-
-  bool NextSwapHasMessage(const IPC::Message& message) {
-    return ContainsMessage(NextSwapMessages(), message);
-  }
-
-  void QueueMessages(int source_frame_numbers[], size_t count) {
-    for (size_t i = 0; i < count; ++i) {
-      messages_.push_back(
-          IPC::Message(0, i + 1, IPC::Message::PRIORITY_NORMAL));
-      promises_.push_back(
-          QueueMessageImpl(std::make_unique<IPC::Message>(messages_[i]),
-                           source_frame_numbers[i]));
-    }
-  }
-
-  void CleanupPromises() {
-    for (const auto& promise : promises_) {
-      if (promise.get()) {
-        promise->DidActivate();
-        promise->WillSwap(&dummy_metadata_);
-        promise->DidSwap();
-      }
-    }
-  }
-
- protected:
-  void VisualStateSwapPromiseDidNotSwap(
-      cc::SwapPromise::DidNotSwapReason reason);
-
-  base::test::TaskEnvironment task_environment_;
-  scoped_refptr<FrameSwapMessageQueue> frame_swap_message_queue_;
-  scoped_refptr<TestSyncMessageFilter> sync_message_filter_;
-  std::vector<IPC::Message> messages_;
-  std::vector<std::unique_ptr<cc::SwapPromise>> promises_;
-  viz::CompositorFrameMetadata dummy_metadata_;
-  cc::RenderFrameMetadata dummy_render_frame_metadata_;
-
- private:
-  std::vector<std::unique_ptr<IPC::Message>> next_swap_messages_;
-
-  DISALLOW_COPY_AND_ASSIGN(QueueMessageSwapPromiseTest);
-};
-
-TEST_F(QueueMessageSwapPromiseTest, NextSwapPolicySchedulesMessageForNextSwap) {
-  int source_frame_numbers[] = {1};
-  QueueMessages(source_frame_numbers, base::size(source_frame_numbers));
-
-  ASSERT_TRUE(promises_[0].get());
-  promises_[0]->DidActivate();
-  promises_[0]->WillSwap(&dummy_metadata_);
-  promises_[0]->DidSwap();
-
-  EXPECT_TRUE(DirectSendMessages().empty());
-  EXPECT_TRUE(frame_swap_message_queue_->Empty());
-  EXPECT_TRUE(LastSwapHasMessage(messages_[0]));
-}
-
-TEST_F(QueueMessageSwapPromiseTest, NextSwapPolicyNeedsAtMostOnePromise) {
-  int source_frame_numbers[] = {1, 1};
-  QueueMessages(source_frame_numbers, base::size(source_frame_numbers));
-
-  ASSERT_TRUE(promises_[0].get());
-  ASSERT_FALSE(promises_[1].get());
-
-  CleanupPromises();
-}
-
-TEST_F(QueueMessageSwapPromiseTest, NextSwapPolicySendsMessageOnNoUpdate) {
-  int source_frame_numbers[] = {1};
-  QueueMessages(source_frame_numbers, base::size(source_frame_numbers));
-
-  promises_[0]->DidNotSwap(cc::SwapPromise::COMMIT_NO_UPDATE);
-  EXPECT_TRUE(ContainsMessage(DirectSendMessages(), messages_[0]));
-  EXPECT_TRUE(LastSwapMessages().empty());
-  EXPECT_TRUE(frame_swap_message_queue_->Empty());
-}
-
-TEST_F(QueueMessageSwapPromiseTest, NextSwapPolicySendsMessageOnSwapFails) {
-  int source_frame_numbers[] = {1};
-  QueueMessages(source_frame_numbers, base::size(source_frame_numbers));
-
-  promises_[0]->DidNotSwap(cc::SwapPromise::SWAP_FAILS);
-  EXPECT_TRUE(ContainsMessage(DirectSendMessages(), messages_[0]));
-  EXPECT_TRUE(LastSwapMessages().empty());
-  EXPECT_TRUE(frame_swap_message_queue_->Empty());
-}
-
-TEST_F(QueueMessageSwapPromiseTest,
-       NextActivatePolicyRetainsMessageOnCommitFails) {
-  int source_frame_numbers[] = {1};
-  QueueMessages(source_frame_numbers, base::size(source_frame_numbers));
-
-  promises_[0]->DidNotSwap(cc::SwapPromise::COMMIT_FAILS);
-  EXPECT_TRUE(DirectSendMessages().empty());
-  EXPECT_TRUE(LastSwapMessages().empty());
-  EXPECT_FALSE(frame_swap_message_queue_->Empty());
-  frame_swap_message_queue_->DidActivate(2);
-  EXPECT_TRUE(NextSwapHasMessage(messages_[0]));
-}
-
-TEST_F(QueueMessageSwapPromiseTest,
-       VisualStateQueuesMessageWhenCommitRequested) {
-  int source_frame_numbers[] = {1};
-  QueueMessages(source_frame_numbers, base::size(source_frame_numbers));
-
-  ASSERT_TRUE(promises_[0].get());
-  EXPECT_TRUE(DirectSendMessages().empty());
-  EXPECT_FALSE(frame_swap_message_queue_->Empty());
-  EXPECT_TRUE(NextSwapMessages().empty());
-
-  CleanupPromises();
-}
-
-TEST_F(QueueMessageSwapPromiseTest,
-       VisualStateQueuesMessageWhenOtherMessageAlreadyQueued) {
-  int source_frame_numbers[] = {1, 1};
-  QueueMessages(source_frame_numbers, base::size(source_frame_numbers));
-
-  EXPECT_TRUE(DirectSendMessages().empty());
-  EXPECT_FALSE(frame_swap_message_queue_->Empty());
-  EXPECT_FALSE(NextSwapHasMessage(messages_[1]));
-
-  CleanupPromises();
-}
-
-TEST_F(QueueMessageSwapPromiseTest, VisualStateSwapPromiseDidActivate) {
-  int source_frame_numbers[] = {1, 1, 2};
-  QueueMessages(source_frame_numbers, base::size(source_frame_numbers));
-
-  promises_[0]->DidActivate();
-  promises_[0]->WillSwap(&dummy_metadata_);
-  promises_[0]->DidSwap();
-  ASSERT_FALSE(promises_[1].get());
-  std::vector<std::unique_ptr<IPC::Message>> messages;
-  messages.swap(LastSwapMessages());
-  EXPECT_EQ(2u, messages.size());
-  EXPECT_TRUE(ContainsMessage(messages, messages_[0]));
-  EXPECT_TRUE(ContainsMessage(messages, messages_[1]));
-  EXPECT_FALSE(ContainsMessage(messages, messages_[2]));
-
-  promises_[2]->DidActivate();
-  promises_[2]->DidNotSwap(cc::SwapPromise::SWAP_FAILS);
-  messages.swap(NextSwapMessages());
-  EXPECT_TRUE(messages.empty());
-
-  EXPECT_EQ(1u, DirectSendMessages().size());
-  EXPECT_TRUE(ContainsMessage(DirectSendMessages(), messages_[2]));
-
-  EXPECT_TRUE(NextSwapMessages().empty());
-  EXPECT_TRUE(frame_swap_message_queue_->Empty());
-}
-
-void QueueMessageSwapPromiseTest::VisualStateSwapPromiseDidNotSwap(
-    cc::SwapPromise::DidNotSwapReason reason) {
-  int source_frame_numbers[] = {1, 1, 2};
-  QueueMessages(source_frame_numbers, base::size(source_frame_numbers));
-
-  // If we fail to swap with COMMIT_FAILS or ACTIVATE_FAILS, then
-  // messages are delivered by the RenderFrameHostImpl destructor,
-  // rather than directly by the swap promise.
-  bool msg_delivered = reason != cc::SwapPromise::COMMIT_FAILS &&
-                       reason != cc::SwapPromise::ACTIVATION_FAILS;
-
-  promises_[0]->DidNotSwap(reason);
-  ASSERT_FALSE(promises_[1].get());
-  EXPECT_TRUE(NextSwapMessages().empty());
-  EXPECT_EQ(msg_delivered, ContainsMessage(DirectSendMessages(), messages_[0]));
-  EXPECT_EQ(msg_delivered, ContainsMessage(DirectSendMessages(), messages_[1]));
-  EXPECT_FALSE(ContainsMessage(DirectSendMessages(), messages_[2]));
-
-  promises_[2]->DidNotSwap(reason);
-  EXPECT_TRUE(NextSwapMessages().empty());
-  EXPECT_EQ(msg_delivered, ContainsMessage(DirectSendMessages(), messages_[2]));
-
-  EXPECT_TRUE(NextSwapMessages().empty());
-  EXPECT_EQ(msg_delivered, frame_swap_message_queue_->Empty());
-}
-
-TEST_F(QueueMessageSwapPromiseTest, VisualStateSwapPromiseDidNotSwapNoUpdate) {
-  VisualStateSwapPromiseDidNotSwap(cc::SwapPromise::COMMIT_NO_UPDATE);
-}
-
-TEST_F(QueueMessageSwapPromiseTest,
-       VisualStateSwapPromiseDidNotSwapCommitFails) {
-  VisualStateSwapPromiseDidNotSwap(cc::SwapPromise::COMMIT_FAILS);
-}
-
-TEST_F(QueueMessageSwapPromiseTest, VisualStateSwapPromiseDidNotSwapSwapFails) {
-  VisualStateSwapPromiseDidNotSwap(cc::SwapPromise::SWAP_FAILS);
-}
-
-TEST_F(QueueMessageSwapPromiseTest,
-       VisualStateSwapPromiseDidNotSwapActivationFails) {
-  VisualStateSwapPromiseDidNotSwap(cc::SwapPromise::ACTIVATION_FAILS);
-}
-
-}  // namespace content
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index f3fbb23..2996bf0 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -3121,6 +3121,30 @@
   WebUIExtensionData::Create(this, std::move(receiver), std::move(remote));
 }
 
+void RenderFrameImpl::SetOldPageLifecycleStateFromNewPageCommitIfNeeded(
+    const mojom::OldPageInfo* old_page_info) {
+  if (!old_page_info)
+    return;
+  RenderFrameImpl* old_main_render_frame = RenderFrameImpl::FromRoutingID(
+      old_page_info->routing_id_for_old_main_frame);
+  if (!old_main_render_frame) {
+    // Even if we sent a valid |routing_id_for_old_main_frame|, it might have
+    // already been destroyed by the time we try to get the RenderFrame, so
+    // we should check if it still exists.
+    return;
+  }
+  CHECK(IsMainFrame());
+  CHECK(old_main_render_frame->IsMainFrame());
+  DCHECK_EQ(old_page_info->new_lifecycle_state_for_old_page->visibility,
+            PageVisibilityState::kHidden);
+  DCHECK_NE(old_page_info->new_lifecycle_state_for_old_page->pagehide_dispatch,
+            blink::mojom::PagehideDispatch::kNotDispatched);
+  WebFrame* old_main_web_frame = old_main_render_frame->GetWebFrame();
+  old_main_web_frame->View()->SetPageLifecycleStateFromNewPageCommit(
+      old_page_info->new_lifecycle_state_for_old_page->visibility,
+      old_page_info->new_lifecycle_state_for_old_page->pagehide_dispatch);
+}
+
 void RenderFrameImpl::CommitNavigation(
     mojom::CommonNavigationParamsPtr common_params,
     mojom::CommitNavigationParamsPtr commit_params,
@@ -3156,6 +3180,9 @@
     return;
   }
 
+  SetOldPageLifecycleStateFromNewPageCommitIfNeeded(
+      commit_params->old_page_info.get());
+
   bool was_initiated_in_this_frame =
       navigation_client_impl_ &&
       navigation_client_impl_->was_initiated_in_this_frame();
diff --git a/content/renderer/render_frame_impl.h b/content/renderer/render_frame_impl.h
index 73d2d3e..846717d 100644
--- a/content/renderer/render_frame_impl.h
+++ b/content/renderer/render_frame_impl.h
@@ -1192,6 +1192,19 @@
   // should be used.
   bool ShouldUseUserAgentOverride() const;
 
+  // Sets the PageLifecycleState and runs pagehide and visibilitychange handlers
+  // of the old page before committing this RenderFrame. Should only be called
+  // for main-frame same-site navigations where we did a proactive
+  // BrowsingInstance swap and we're reusing the old page's process. This is
+  // needed to ensure consistency with other same-site main frame navigations.
+  // Note that we will set the page's visibility to hidden, but not run the
+  // unload handlers of the old page, nor actually unload/freeze the page here.
+  // That needs a more complicated support on the browser side which will be
+  // implemented later.
+  // TODO(crbug.com/1110744): Support unload-in-commit.
+  void SetOldPageLifecycleStateFromNewPageCommitIfNeeded(
+      const mojom::OldPageInfo* old_page_info);
+
   // Updates the state when asked to commit a history navigation.  Sets
   // |item_for_history_navigation| and |load_type| to the appropriate values for
   // commit.
diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc
index 5530052..8945de2 100644
--- a/content/renderer/render_thread_impl.cc
+++ b/content/renderer/render_thread_impl.cc
@@ -77,7 +77,6 @@
 #include "content/renderer/browser_exposed_renderer_interfaces.h"
 #include "content/renderer/categorized_worker_pool.h"
 #include "content/renderer/effective_connection_type_helper.h"
-#include "content/renderer/frame_swap_message_queue.h"
 #include "content/renderer/loader/resource_dispatcher.h"
 #include "content/renderer/media/audio/audio_renderer_mixer_manager.h"
 #include "content/renderer/media/gpu/gpu_video_accelerator_factories_impl.h"
@@ -1636,7 +1635,6 @@
 
 void RenderThreadImpl::RequestNewLayerTreeFrameSink(
     RenderWidget* render_widget,
-    scoped_refptr<FrameSwapMessageQueue> frame_swap_message_queue,
     const GURL& url,
     LayerTreeFrameSinkCallback callback,
     const char* client_name) {
@@ -1780,7 +1778,6 @@
             sync_message_filter(), g_next_layer_tree_frame_sink_id++,
             std::move(params.synthetic_begin_frame_source),
             render_widget->GetWebWidget()->GetSynchronousCompositorRegistry(),
-            std::move(frame_swap_message_queue),
             std::move(params.pipes.compositor_frame_sink_remote),
             std::move(params.pipes.client_receiver)),
         std::move(render_frame_metadata_observer));
diff --git a/content/renderer/render_thread_impl.h b/content/renderer/render_thread_impl.h
index f1b5dcc..6721d865 100644
--- a/content/renderer/render_thread_impl.h
+++ b/content/renderer/render_thread_impl.h
@@ -201,7 +201,6 @@
   std::unique_ptr<cc::UkmRecorderFactory> CreateUkmRecorderFactory() override;
   void RequestNewLayerTreeFrameSink(
       RenderWidget* render_widget,
-      scoped_refptr<FrameSwapMessageQueue> frame_swap_message_queue,
       const GURL& url,
       LayerTreeFrameSinkCallback callback,
       const char* client_name) override;
diff --git a/content/renderer/render_widget.cc b/content/renderer/render_widget.cc
index 4e4d8a1..c26b9f7a 100644
--- a/content/renderer/render_widget.cc
+++ b/content/renderer/render_widget.cc
@@ -55,9 +55,7 @@
 #include "content/public/renderer/content_renderer_client.h"
 #include "content/public/renderer/render_thread.h"
 #include "content/renderer/drop_data_builder.h"
-#include "content/renderer/frame_swap_message_queue.h"
 #include "content/renderer/pepper/pepper_plugin_instance_impl.h"
-#include "content/renderer/queue_message_swap_promise.h"
 #include "content/renderer/render_frame_impl.h"
 #include "content/renderer/render_frame_proxy.h"
 #include "content/renderer/render_process.h"
@@ -68,8 +66,6 @@
 #include "gpu/command_buffer/service/gpu_switches.h"
 #include "gpu/config/gpu_finch_features.h"
 #include "ipc/ipc_message_start.h"
-#include "ipc/ipc_sync_message.h"
-#include "ipc/ipc_sync_message_filter.h"
 #include "media/base/media_switches.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "ppapi/buildflags/buildflags.h"
@@ -326,8 +322,7 @@
     : routing_id_(widget_routing_id),
       compositor_deps_(compositor_deps),
       is_hidden_(hidden),
-      never_composited_(never_composited),
-      frame_swap_message_queue_(new FrameSwapMessageQueue(routing_id_)) {
+      never_composited_(never_composited) {
   DCHECK_NE(routing_id_, MSG_ROUTING_NONE);
   DCHECK(RenderThread::IsMainThread());
   DCHECK(compositor_deps_);
@@ -740,8 +735,7 @@
   // would also be used for other widgets such as popups.
   const char* client_name = for_child_local_root_frame_ ? kOOPIF : kRenderer;
   compositor_deps_->RequestNewLayerTreeFrameSink(
-      this, frame_swap_message_queue_, std::move(url), std::move(callback),
-      client_name);
+      this, std::move(url), std::move(callback), client_name);
 }
 
 void RenderWidget::DidCommitAndDrawCompositorFrame() {
@@ -956,48 +950,10 @@
     observer.DidMeaningfulLayout(layout_type);
 }
 
-// static
-std::unique_ptr<cc::SwapPromise> RenderWidget::QueueMessageImpl(
-    std::unique_ptr<IPC::Message> msg,
-    FrameSwapMessageQueue* frame_swap_message_queue,
-    scoped_refptr<IPC::SyncMessageFilter> sync_message_filter,
-    int source_frame_number) {
-  bool first_message_for_frame = false;
-  frame_swap_message_queue->QueueMessageForFrame(
-      source_frame_number, std::move(msg), &first_message_for_frame);
-  if (!first_message_for_frame)
-    return nullptr;
-  return std::make_unique<QueueMessageSwapPromise>(
-      sync_message_filter, frame_swap_message_queue, source_frame_number);
-}
-
 void RenderWidget::SetHandlingInputEvent(bool handling_input_event) {
   GetWebWidget()->SetHandlingInputEvent(handling_input_event);
 }
 
-void RenderWidget::QueueMessage(std::unique_ptr<IPC::Message> msg) {
-  // RenderThreadImpl::current() is NULL in some tests.
-  if (!RenderThreadImpl::current()) {
-    Send(msg.release());
-    return;
-  }
-
-  std::unique_ptr<cc::SwapPromise> swap_promise =
-      QueueMessageImpl(std::move(msg), frame_swap_message_queue_.get(),
-                       RenderThreadImpl::current()->sync_message_filter(),
-                       layer_tree_host_->SourceFrameNumber());
-  if (swap_promise) {
-    layer_tree_host_->QueueSwapPromise(std::move(swap_promise));
-
-    // Request a main frame if one is not already in progress. This might either
-    // A) request a commit ahead of time or B) request a commit which is not
-    // needed because there are not pending updates. If B) then the frame will
-    // be aborted early and the swap promises will be broken (see
-    // EarlyOut_NoUpdates).
-    layer_tree_host_->SetNeedsAnimateIfNotInsideMainFrame();
-  }
-}
-
 // We are supposed to get a single call to Show for a newly created RenderWidget
 // that was created via RenderWidget::CreateWebView.  So, we wait until this
 // point to dispatch the ShowWidget message.
diff --git a/content/renderer/render_widget.h b/content/renderer/render_widget.h
index 5e8bf6f..2cb0eee 100644
--- a/content/renderer/render_widget.h
+++ b/content/renderer/render_widget.h
@@ -65,10 +65,6 @@
 #include "ui/gfx/range/range.h"
 #include "ui/surface/transport_dib.h"
 
-namespace IPC {
-class SyncMessageFilter;
-}
-
 namespace blink {
 struct VisualProperties;
 struct DeviceEmulationParams;
@@ -80,10 +76,6 @@
 class WebPagePopup;
 }  // namespace blink
 
-namespace cc {
-class SwapPromise;
-}
-
 namespace gfx {
 class ColorSpace;
 struct PresentationFeedback;
@@ -92,7 +84,6 @@
 
 namespace content {
 class CompositorDependencies;
-class FrameSwapMessageQueue;
 class PepperPluginInstanceImpl;
 class RenderFrameImpl;
 class RenderFrameProxy;
@@ -333,17 +324,6 @@
   cc::LayerTreeHost* layer_tree_host() { return layer_tree_host_; }
   void SetHandlingInputEvent(bool handling_input_event);
 
-  // Queues the IPC |message| to be sent to the browser, delaying sending until
-  // the next compositor frame submission. At that time they will be sent before
-  // any message from the compositor as part of submitting its frame. This is
-  // used for messages that need synchronization with the compositor, but in
-  // general you should use Send().
-  //
-  // This mechanism is not a drop-in replacement for IPC: messages sent this way
-  // will not be automatically available to BrowserMessageFilter, for example.
-  // FIFO ordering is preserved between messages enqueued.
-  void QueueMessage(std::unique_ptr<IPC::Message> msg);
-
   // Checks if the selection bounds have been changed. If they are changed,
   // the new value will be sent to the browser process.
   void UpdateSelectionBounds();
@@ -405,7 +385,6 @@
   // For unit tests.
   friend class InteractiveRenderWidget;
   friend class PopupRenderWidget;
-  friend class QueueMessageSwapPromiseTest;
   friend class RenderWidgetTest;
   friend class RenderViewImplTest;
 
@@ -465,14 +444,6 @@
   // constrained to limit overdraw.
   gfx::Rect ViewportVisibleRect();
 
-  // QueueMessage implementation extracted into a static method for easy
-  // testing.
-  static std::unique_ptr<cc::SwapPromise> QueueMessageImpl(
-      std::unique_ptr<IPC::Message> msg,
-      FrameSwapMessageQueue* frame_swap_message_queue,
-      scoped_refptr<IPC::SyncMessageFilter> sync_message_filter,
-      int source_frame_number);
-
   // Set the pending window rect.
   // Because the real render_widget is hosted in another process, there is
   // a time period where we may have set a new window rect which has not yet
@@ -614,8 +585,6 @@
   // The time spent in input handlers this frame. Used to throttle input acks.
   base::TimeDelta total_input_handling_time_this_frame_;
 
-  scoped_refptr<FrameSwapMessageQueue> frame_swap_message_queue_;
-
   // Lists of RenderFrameProxy objects for which this RenderWidget is their
   // local root. Each of these represents a child local root RenderWidget in
   // another RenderView frame tree. For values that are propagated from
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index d08cafc..d14c114 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -2036,7 +2036,6 @@
     "../renderer/accessibility/ax_image_stopwords_unittest.cc",
     "../renderer/categorized_worker_pool_unittest.cc",
     "../renderer/child_frame_compositing_helper_unittest.cc",
-    "../renderer/frame_swap_message_queue_unittest.cc",
     "../renderer/loader/navigation_body_loader_unittest.cc",
     "../renderer/loader/resource_dispatcher_unittest.cc",
     "../renderer/loader/sync_load_context_unittest.cc",
@@ -2056,7 +2055,6 @@
     "../renderer/media/power_status_helper_impl_unittest.cc",
     "../renderer/media/renderer_webaudiodevice_impl_unittest.cc",
     "../renderer/peripheral_content_heuristic_unittest.cc",
-    "../renderer/queue_message_swap_promise_unittest.cc",
     "../renderer/render_frame_metadata_observer_impl_unittest.cc",
     "../renderer/render_thread_impl_unittest.cc",
     "../renderer/render_widget_unittest.cc",
diff --git a/content/test/data/fuzzer_corpus/code_cache_host_mojolpm_fuzzer/did_generate_cacheable_metadata_origin_a.textproto b/content/test/data/fuzzer_corpus/code_cache_host_mojolpm_fuzzer/did_generate_cacheable_metadata_origin_a.textproto
index 5182fdbf..f52ef8a 100644
--- a/content/test/data/fuzzer_corpus/code_cache_host_mojolpm_fuzzer/did_generate_cacheable_metadata_origin_a.textproto
+++ b/content/test/data/fuzzer_corpus/code_cache_host_mojolpm_fuzzer/did_generate_cacheable_metadata_origin_a.textproto
@@ -6,7 +6,7 @@
   }
 }
 actions {
-  code_cache_host_call {
+  code_cache_host_remote_action {
     id: 1
     m_did_generate_cacheable_metadata {
       m_cache_type: CodeCacheType_kJavascript
diff --git a/content/test/data/fuzzer_corpus/code_cache_host_mojolpm_fuzzer/did_generate_cacheable_metadata_origin_b.textproto b/content/test/data/fuzzer_corpus/code_cache_host_mojolpm_fuzzer/did_generate_cacheable_metadata_origin_b.textproto
index f1538a4..835e1f4e 100644
--- a/content/test/data/fuzzer_corpus/code_cache_host_mojolpm_fuzzer/did_generate_cacheable_metadata_origin_b.textproto
+++ b/content/test/data/fuzzer_corpus/code_cache_host_mojolpm_fuzzer/did_generate_cacheable_metadata_origin_b.textproto
@@ -6,7 +6,7 @@
   }
 }
 actions {
-  code_cache_host_call {
+  code_cache_host_remote_action {
     id: 1
     m_did_generate_cacheable_metadata {
       m_cache_type: CodeCacheType_kJavascript
diff --git a/content/test/data/local_storage.html b/content/test/data/local_storage.html
new file mode 100644
index 0000000..c2ad4821
--- /dev/null
+++ b/content/test/data/local_storage.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<body>
+  <script>
+    var pagehide_storage_at_load =
+      localStorage.getItem('pagehide_storage');
+    var visibilitychange_storage_at_load =
+      localStorage.getItem('visibilitychange_storage');
+    var unload_storage_at_load =
+      localStorage.getItem('unload_storage');
+  </script>
+  This page gets the value of the items "pagehide_storage",
+  "visibilitychange_storage" and "unload_storage" from
+  localStorage at first load.
+</body>
diff --git a/content/test/fake_compositor_dependencies.cc b/content/test/fake_compositor_dependencies.cc
index 143fe975..3d1abaf6 100644
--- a/content/test/fake_compositor_dependencies.cc
+++ b/content/test/fake_compositor_dependencies.cc
@@ -11,7 +11,6 @@
 #include "cc/test/fake_layer_tree_frame_sink.h"
 #include "cc/test/test_ukm_recorder_factory.h"
 #include "cc/trees/render_frame_metadata_observer.h"
-#include "content/renderer/frame_swap_message_queue.h"
 #include "third_party/khronos/GLES2/gl2.h"
 #include "ui/gfx/buffer_types.h"
 
@@ -65,7 +64,6 @@
 
 void FakeCompositorDependencies::RequestNewLayerTreeFrameSink(
     RenderWidget* render_widget,
-    scoped_refptr<FrameSwapMessageQueue> frame_swap_message_queue,
     const GURL& url,
     LayerTreeFrameSinkCallback callback,
     const char* client_name) {
diff --git a/content/test/fake_compositor_dependencies.h b/content/test/fake_compositor_dependencies.h
index 8507857..01dc408 100644
--- a/content/test/fake_compositor_dependencies.h
+++ b/content/test/fake_compositor_dependencies.h
@@ -36,7 +36,6 @@
   std::unique_ptr<cc::UkmRecorderFactory> CreateUkmRecorderFactory() override;
   void RequestNewLayerTreeFrameSink(
       RenderWidget* render_widget,
-      scoped_refptr<FrameSwapMessageQueue> frame_swap_message_queue,
       const GURL& url,
       LayerTreeFrameSinkCallback callback,
       const char* client_name) override;
diff --git a/content/test/fuzzer/code_cache_host_mojolpm_fuzzer.cc b/content/test/fuzzer/code_cache_host_mojolpm_fuzzer.cc
index 4d0c31f..d0ff296 100644
--- a/content/test/fuzzer/code_cache_host_mojolpm_fuzzer.cc
+++ b/content/test/fuzzer/code_cache_host_mojolpm_fuzzer.cc
@@ -54,59 +54,16 @@
     fuzzer_thread_.StartAndWaitForTesting();
   }
 
-  void RunThreadUntilIdle(
-      const scoped_refptr<base::SingleThreadTaskRunner>& task_runner) {
-    if (task_runner->RunsTasksInCurrentSequence()) {
-      base::RunLoop(base::RunLoop::Type::kNestableTasksAllowed).RunUntilIdle();
-    } else {
-      base::WaitableEvent thread_idle(
-          base::WaitableEvent::ResetPolicy::MANUAL,
-          base::WaitableEvent::InitialState::NOT_SIGNALED);
-      task_runner->PostTask(
-          FROM_HERE,
-          base::BindOnce(
-              [](base::WaitableEvent* thread_idle) {
-                base::RunLoop(base::RunLoop::Type::kNestableTasksAllowed)
-                    .RunUntilIdle();
-                thread_idle->Signal();
-              },
-              base::Unretained(&thread_idle)));
-      thread_idle.Wait();
-    }
-  }
-
-  void RunUntilIdle() { RunThreadUntilIdle(fuzzer_thread_.task_runner()); }
-
-  void RunUIThreadUntilIdle() { RunThreadUntilIdle(ui_task_runner()); }
-
-  void RunIOThreadUntilIdle() { RunThreadUntilIdle(io_task_runner()); }
-
-  scoped_refptr<base::SequencedTaskRunner> task_runner() {
+  scoped_refptr<base::SequencedTaskRunner> fuzzer_task_runner() {
     return fuzzer_thread_.task_runner();
   }
 
-  scoped_refptr<base::SingleThreadTaskRunner> io_task_runner() {
-    if (!io_task_runner_) {
-      io_task_runner_ = base::CreateSingleThreadTaskRunner({BrowserThread::IO});
-    }
-    return io_task_runner_;
-  }
-
-  scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner() {
-    if (!ui_task_runner_) {
-      ui_task_runner_ = base::CreateSingleThreadTaskRunner({BrowserThread::UI});
-    }
-    return ui_task_runner_;
-  }
-
  private:
   base::AtExitManager at_exit_manager_;
   std::unique_ptr<base::FieldTrialList> field_trial_list_;
   base::test::ScopedFeatureList scoped_feature_list_;
   base::Thread fuzzer_thread_;
   BrowserTaskEnvironment task_environment_;
-  scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
-  scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_;
   TestContentClientInitializer content_client_initializer_;
 };
 
@@ -116,28 +73,8 @@
   return g_environment;
 }
 
-scoped_refptr<base::SequencedTaskRunner> GetTaskRunner() {
-  return SingletonEnvironment().task_runner();
-}
-
-scoped_refptr<base::SingleThreadTaskRunner> GetIOTaskRunner() {
-  return SingletonEnvironment().io_task_runner();
-}
-
-scoped_refptr<base::SingleThreadTaskRunner> GetUITaskRunner() {
-  return SingletonEnvironment().ui_task_runner();
-}
-
-void RunUntilIdle() {
-  SingletonEnvironment().RunUntilIdle();
-}
-
-void RunIOThreadUntilIdle() {
-  SingletonEnvironment().RunIOThreadUntilIdle();
-}
-
-void RunUIThreadUntilIdle() {
-  SingletonEnvironment().RunUIThreadUntilIdle();
+scoped_refptr<base::SequencedTaskRunner> GetFuzzerTaskRunner() {
+  return SingletonEnvironment().fuzzer_task_runner();
 }
 
 class CodeCacheHostFuzzerContext : public mojolpm::Context {
@@ -152,20 +89,14 @@
         kOriginB(url::Origin::Create(GURL("http://bbb.com/"))),
         kOriginOpaque(url::Origin::Create(GURL("opaque"))),
         kOriginEmpty(url::Origin::Create(GURL("file://this_becomes_empty"))),
-        browser_context_() {}
-
-  void InitializeServices() {
-    if (!initialized_) {
-      base::PostTask(
-          FROM_HERE, {BrowserThread::UI},
-          base::BindOnce(&CodeCacheHostFuzzerContext::InitializeOnUIThread,
-                         base::Unretained(this)));
-      RunUIThreadUntilIdle();
-
-      RunUntilIdle();
-
-      initialized_ = true;
-    }
+        browser_context_() {
+    base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
+    base::PostTaskAndReply(
+        FROM_HERE, {BrowserThread::UI},
+        base::BindOnce(&CodeCacheHostFuzzerContext::InitializeOnUIThread,
+                       base::Unretained(this)),
+        run_loop.QuitClosure());
+    run_loop.Run();
   }
 
   void InitializeOnUIThread() {
@@ -180,41 +111,23 @@
                                               65536);
   }
 
-  void CleanupServices() {
-    base::PostTask(
-        FROM_HERE, {BrowserThread::UI},
-        base::BindOnce(&CodeCacheHostFuzzerContext::CleanupOnUIThread,
-                       base::Unretained(this)));
-    RunUIThreadUntilIdle();
-
-    RunUntilIdle();
-
-    initialized_ = false;
-  }
-
-  void CleanupOnUIThread() {}
-
   void AddCodeCacheHostImpl(
       uint32_t id,
       int renderer_id,
       const Origin& origin,
-      mojo::PendingReceiver<::blink::mojom::CodeCacheHost>&& receiver,
-      base::WaitableEvent* receiver_bound) {
+      mojo::PendingReceiver<::blink::mojom::CodeCacheHost>&& receiver) {
     code_cache_hosts_[renderer_id] = std::make_unique<CodeCacheHostImpl>(
         renderer_id, cache_storage_context_, generated_code_cache_context_,
         std::move(receiver));
-
-    receiver_bound->Signal();
   }
 
   void AddCodeCacheHost(
       uint32_t id,
       int renderer_id,
-      content::fuzzing::code_cache_host::proto::NewCodeCacheHost::OriginId
+      content::fuzzing::code_cache_host::proto::NewCodeCacheHostAction::OriginId
           origin_id) {
     mojo::Remote<::blink::mojom::CodeCacheHost> remote;
     auto receiver = remote.BindNewPipeAndPassReceiver();
-    base::WaitableEvent receiver_bound;
 
     const Origin* origin = &kOriginA;
     if (origin_id == 1) {
@@ -225,13 +138,14 @@
       origin = &kOriginEmpty;
     }
 
-    GetUITaskRunner()->PostTask(
-        FROM_HERE,
+    base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
+    base::PostTaskAndReply(
+        FROM_HERE, {BrowserThread::UI},
         base::BindOnce(&CodeCacheHostFuzzerContext::AddCodeCacheHostImpl,
                        base::Unretained(this), id, renderer_id, *origin,
-                       std::move(receiver), base::Unretained(&receiver_bound)));
-
-    receiver_bound.Wait();
+                       std::move(receiver)),
+        run_loop.QuitClosure());
+    run_loop.Run();
 
     AddInstance(id, std::move(remote));
   }
@@ -239,8 +153,6 @@
  private:
   TestBrowserContext browser_context_;
 
-  bool initialized_ = false;
-
   scoped_refptr<CacheStorageContextImpl> cache_storage_context_;
   scoped_refptr<GeneratedCodeCacheContext> generated_code_cache_context_;
 
@@ -295,17 +207,23 @@
               action.new_code_cache_host().origin_id());
         } break;
 
-        case content::fuzzing::code_cache_host::proto::Action::kRunUntilIdle: {
-          if (action.run_until_idle().id()) {
-            content::RunUIThreadUntilIdle();
+        case content::fuzzing::code_cache_host::proto::Action::kRunThread: {
+          if (action.run_thread().id()) {
+            base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
+            base::PostTask(FROM_HERE, {content::BrowserThread::UI},
+                           run_loop.QuitClosure());
+            run_loop.Run();
           } else {
-            content::RunIOThreadUntilIdle();
+            base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
+            base::PostTask(FROM_HERE, {content::BrowserThread::IO},
+                           run_loop.QuitClosure());
+            run_loop.Run();
           }
         } break;
 
         case content::fuzzing::code_cache_host::proto::Action::
-            kCodeCacheHostCall: {
-          mojolpm::HandleRemoteCall(action.code_cache_host_call());
+            kCodeCacheHostRemoteAction: {
+          mojolpm::HandleRemoteAction(action.code_cache_host_remote_action());
         } break;
 
         case content::fuzzing::code_cache_host::proto::Action::ACTION_NOT_SET:
@@ -315,28 +233,36 @@
   }
 }
 
-void run_testcase(
+void NextAction(content::CodeCacheHostFuzzerContext* context,
+                base::RepeatingClosure quit_closure) {
+  if (!context->IsFinished()) {
+    context->NextAction();
+    content::GetFuzzerTaskRunner()->PostTask(
+        FROM_HERE, base::BindOnce(NextAction, base::Unretained(context),
+                                  std::move(quit_closure)));
+  } else {
+    content::GetFuzzerTaskRunner()->PostTask(FROM_HERE,
+                                             std::move(quit_closure));
+  }
+}
+
+void RunTestcase(
     content::CodeCacheHostFuzzerContext* context,
-    const content::fuzzing::code_cache_host::proto::Testcase* testcase,
-    base::RepeatingClosure&& quit_closure) {
+    const content::fuzzing::code_cache_host::proto::Testcase* testcase) {
   mojo::Message message;
   auto dispatch_context =
       std::make_unique<mojo::internal::MessageDispatchContext>(&message);
 
   CodeCacheHostTestcase cch_testcase(*context, *testcase);
-  context->StartTestcase(&cch_testcase, content::GetTaskRunner());
+  context->StartTestcase(&cch_testcase, content::GetFuzzerTaskRunner());
 
-  while (!context->IsFinished()) {
-    context->NextAction();
-    content::RunUntilIdle();
-  }
-
-  content::RunIOThreadUntilIdle();
-  content::RunUIThreadUntilIdle();
+  base::RunLoop fuzzer_run_loop(base::RunLoop::Type::kNestableTasksAllowed);
+  content::GetFuzzerTaskRunner()->PostTask(
+      FROM_HERE, base::BindOnce(NextAction, base::Unretained(context),
+                                fuzzer_run_loop.QuitClosure()));
+  fuzzer_run_loop.Run();
 
   context->EndTestcase();
-
-  content::GetTaskRunner()->PostTask(FROM_HERE, std::move(quit_closure));
 }
 
 DEFINE_BINARY_PROTO_FUZZER(
@@ -347,18 +273,14 @@
   }
 
   content::CodeCacheHostFuzzerContext context;
-  context.InitializeServices();
   mojolpm::SetContext(&context);
 
-  base::RunLoop ui_nested_runloop{base::RunLoop::Type::kNestableTasksAllowed};
-  auto ui_nested_quit = ui_nested_runloop.QuitClosure();
-
-  content::GetTaskRunner()->PostTask(
+  base::RunLoop ui_run_loop(base::RunLoop::Type::kNestableTasksAllowed);
+  content::GetFuzzerTaskRunner()->PostTaskAndReply(
       FROM_HERE,
-      base::BindOnce(run_testcase, base::Unretained(&context),
-                     base::Unretained(&testcase), std::move(ui_nested_quit)));
+      base::BindOnce(RunTestcase, base::Unretained(&context),
+                     base::Unretained(&testcase)),
+      ui_run_loop.QuitClosure());
 
-  ui_nested_runloop.Run();
-
-  context.CleanupServices();
+  ui_run_loop.Run();
 }
diff --git a/content/test/fuzzer/code_cache_host_mojolpm_fuzzer.proto b/content/test/fuzzer/code_cache_host_mojolpm_fuzzer.proto
index f788d08..f9ec3638 100644
--- a/content/test/fuzzer/code_cache_host_mojolpm_fuzzer.proto
+++ b/content/test/fuzzer/code_cache_host_mojolpm_fuzzer.proto
@@ -1,10 +1,17 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Message format for the MojoLPM fuzzer for the CodeCacheHost interface.
+
 syntax = "proto2";
 
 package content.fuzzing.code_cache_host.proto;
 
 import "third_party/blink/public/mojom/loader/code_cache.mojom.mojolpm.proto";
 
-message NewCodeCacheHost {
+// Bind a new CodeCacheHost remote
+message NewCodeCacheHostAction {
   enum OriginId {
     ORIGIN_A = 0;
     ORIGIN_B = 1;
@@ -17,7 +24,10 @@
   required OriginId origin_id = 3;
 }
 
-message RunUntilIdle {
+// Run the specific sequence for (an indeterminate) period. This is not
+// intended to create a specific ordering, but to allow the fuzzer to delay a
+// later task until previous tasks have completed.
+message RunThreadAction {
   enum ThreadId {
     IO = 0;
     UI = 1;
@@ -26,18 +36,23 @@
   required ThreadId id = 1;
 }
 
+// Actions that can be performed by the fuzzer.
 message Action {
   oneof action {
-    NewCodeCacheHost new_code_cache_host = 1;
-    RunUntilIdle run_until_idle = 2;
-    mojolpm.blink.mojom.CodeCacheHost.RemoteCall code_cache_host_call = 3;
+    NewCodeCacheHostAction new_code_cache_host = 1;
+    RunThreadAction run_thread = 2;
+    mojolpm.blink.mojom.CodeCacheHost.RemoteAction
+        code_cache_host_remote_action = 3;
   }
 }
 
+// Sequence provides a level of indirection which allows Testcase to compactly
+// express repeated sequences of actions.
 message Sequence {
   repeated uint32 action_indexes = 1 [packed = true];
 }
 
+// Testcase is the top-level message type interpreted by the fuzzer.
 message Testcase {
   repeated Action actions = 1;
   repeated Sequence sequences = 2;
diff --git a/extensions/common/api/_api_features.json b/extensions/common/api/_api_features.json
index 778d3609..f0760bc 100644
--- a/extensions/common/api/_api_features.json
+++ b/extensions/common/api/_api_features.json
@@ -368,7 +368,7 @@
     "dependencies": ["manifest:mime_types_handler"],
     "contexts": ["blessed_extension"]
   },
-  "mojoPrivate": {
+  "mojoPrivate": [{
     "contexts": ["blessed_extension"],
     "channel": "stable",
     "extension_types": ["platform_app", "extension"],
@@ -378,7 +378,13 @@
       "B41E7F08E1179CC03CBD1F49E57CF353A40ADE07",   // Chrome Camera App Dev
       "A3E3DE9E9F16B41D4A2FAD106BD6CA76B94A0C94"    // Chrome Camera App Stable
     ]
-  },
+  }, {
+    "contexts": ["blessed_extension"],
+    "dependencies": ["behavior:imprivata_in_session_extension"],
+    "extension_types": ["extension"],
+    "location": "policy",
+    "platforms": ["chromeos"]
+  }],
   "networking.config": {
     "dependencies": ["permission:networking.config"],
     "contexts": ["blessed_extension"]
diff --git a/extensions/renderer/dispatcher.cc b/extensions/renderer/dispatcher.cc
index 4c828e8..c7ca3b8 100644
--- a/extensions/renderer/dispatcher.cc
+++ b/extensions/renderer/dispatcher.cc
@@ -752,6 +752,7 @@
 
       {"keep_alive", IDR_KEEP_ALIVE_JS},
       {"mojo_bindings", IDR_MOJO_MOJO_BINDINGS_JS},
+      {"mojo_bindings_lite", IDR_MOJO_MOJO_BINDINGS_LITE_JS},
       {"extensions/common/mojom/keep_alive.mojom", IDR_KEEP_ALIVE_MOJOM_JS},
 
       // Custom bindings.
diff --git a/google_apis/gaia/fake_gaia.cc b/google_apis/gaia/fake_gaia.cc
index 92176c8..5fc0308 100644
--- a/google_apis/gaia/fake_gaia.cc
+++ b/google_apis/gaia/fake_gaia.cc
@@ -230,6 +230,14 @@
   REGISTER_RESPONSE_HANDLER(gaia_urls->embedded_setup_chromeos_url(2),
                             HandleEmbeddedSetupChromeos);
 
+  // Handles /embedded/setup/kidsignup/chromeos GAIA call.
+  REGISTER_RESPONSE_HANDLER(gaia_urls->embedded_setup_chromeos_kid_signup_url(),
+                            HandleEmbeddedSetupChromeos);
+
+  // Handles /embedded/setup/kidsignin/chromeos GAIA call.
+  REGISTER_RESPONSE_HANDLER(gaia_urls->embedded_setup_chromeos_kid_signin_url(),
+                            HandleEmbeddedSetupChromeos);
+
   // Handles /OAuthLogin GAIA call.
   REGISTER_RESPONSE_HANDLER(
       gaia_urls->oauth1_login_url(), HandleOAuthLogin);
diff --git a/google_apis/gaia/gaia_urls.cc b/google_apis/gaia/gaia_urls.cc
index 666c1327..868c8b1 100644
--- a/google_apis/gaia/gaia_urls.cc
+++ b/google_apis/gaia/gaia_urls.cc
@@ -32,6 +32,10 @@
 const char kClientLoginUrlSuffix[] = "ClientLogin";
 const char kServiceLoginUrlSuffix[] = "ServiceLogin";
 const char kEmbeddedSetupChromeOsUrlSuffixV2[] = "embedded/setup/v2/chromeos";
+const char kEmbeddedSetupChromeOsKidSignupUrlSuffix[] =
+    "embedded/setup/kidsignup/chromeos";
+const char kEmbeddedSetupChromeOsKidSigninUrlSuffix[] =
+    "embedded/setup/kidsignin/chromeos";
 const char kEmbeddedSetupWindowsUrlSuffix[] = "embedded/setup/windows";
 // Parameter "ssp=1" is used to skip showing the password bubble when a user
 // signs in to Chrome. Note that Gaia will pass this client specified parameter
@@ -164,6 +168,14 @@
   return embedded_setup_chromeos_url_v2_;
 }
 
+const GURL& GaiaUrls::embedded_setup_chromeos_kid_signup_url() const {
+  return embedded_setup_chromeos_kid_signup_url_;
+}
+
+const GURL& GaiaUrls::embedded_setup_chromeos_kid_signin_url() const {
+  return embedded_setup_chromeos_kid_signin_url_;
+}
+
 const GURL& GaiaUrls::embedded_setup_windows_url() const {
   return embedded_setup_windows_url_;
 }
@@ -329,6 +341,10 @@
   ResolveURLIfInvalid(&service_login_url_, gaia_url_, kServiceLoginUrlSuffix);
   ResolveURLIfInvalid(&embedded_setup_chromeos_url_v2_, gaia_url_,
                       kEmbeddedSetupChromeOsUrlSuffixV2);
+  ResolveURLIfInvalid(&embedded_setup_chromeos_kid_signup_url_, gaia_url_,
+                      kEmbeddedSetupChromeOsKidSignupUrlSuffix);
+  ResolveURLIfInvalid(&embedded_setup_chromeos_kid_signin_url_, gaia_url_,
+                      kEmbeddedSetupChromeOsKidSigninUrlSuffix);
   ResolveURLIfInvalid(&embedded_setup_windows_url_, gaia_url_,
                       kEmbeddedSetupWindowsUrlSuffix);
   ResolveURLIfInvalid(&signin_chrome_sync_dice_, gaia_url_,
@@ -400,6 +416,10 @@
   config->GetURLIfExists(URL_KEY_AND_PTR(client_login_url));
   config->GetURLIfExists(URL_KEY_AND_PTR(service_login_url));
   config->GetURLIfExists(URL_KEY_AND_PTR(embedded_setup_chromeos_url_v2));
+  config->GetURLIfExists(
+      URL_KEY_AND_PTR(embedded_setup_chromeos_kid_signup_url));
+  config->GetURLIfExists(
+      URL_KEY_AND_PTR(embedded_setup_chromeos_kid_signin_url));
   config->GetURLIfExists(URL_KEY_AND_PTR(embedded_setup_windows_url));
   config->GetURLIfExists(URL_KEY_AND_PTR(signin_chrome_sync_dice));
   config->GetURLIfExists(URL_KEY_AND_PTR(signin_chrome_sync_keys_url));
diff --git a/google_apis/gaia/gaia_urls.h b/google_apis/gaia/gaia_urls.h
index 20f9acf..5799280 100644
--- a/google_apis/gaia/gaia_urls.h
+++ b/google_apis/gaia/gaia_urls.h
@@ -26,6 +26,8 @@
   const GURL& client_login_url() const;
   const GURL& service_login_url() const;
   const GURL& embedded_setup_chromeos_url(unsigned version) const;
+  const GURL& embedded_setup_chromeos_kid_signup_url() const;
+  const GURL& embedded_setup_chromeos_kid_signin_url() const;
   const GURL& embedded_setup_windows_url() const;
   const GURL& signin_chrome_sync_dice() const;
   const GURL& signin_chrome_sync_keys_url() const;
@@ -82,6 +84,8 @@
   GURL client_login_url_;
   GURL service_login_url_;
   GURL embedded_setup_chromeos_url_v2_;
+  GURL embedded_setup_chromeos_kid_signup_url_;
+  GURL embedded_setup_chromeos_kid_signin_url_;
   GURL embedded_setup_windows_url_;
   GURL signin_chrome_sync_dice_;
   GURL signin_chrome_sync_keys_url_;
diff --git a/google_apis/gaia/gaia_urls_unittest.cc b/google_apis/gaia/gaia_urls_unittest.cc
index 542ade4d..ea83433 100644
--- a/google_apis/gaia/gaia_urls_unittest.cc
+++ b/google_apis/gaia/gaia_urls_unittest.cc
@@ -71,6 +71,10 @@
             "https://accounts.google.com/ServiceLogin");
   EXPECT_EQ(gaia_urls()->embedded_setup_chromeos_url(2U).spec(),
             "https://accounts.google.com/embedded/setup/v2/chromeos");
+  EXPECT_EQ(gaia_urls()->embedded_setup_chromeos_kid_signup_url().spec(),
+            "https://accounts.google.com/embedded/setup/kidsignup/chromeos");
+  EXPECT_EQ(gaia_urls()->embedded_setup_chromeos_kid_signin_url().spec(),
+            "https://accounts.google.com/embedded/setup/kidsignin/chromeos");
   EXPECT_EQ(gaia_urls()->embedded_setup_windows_url().spec(),
             "https://accounts.google.com/embedded/setup/windows");
   EXPECT_EQ(gaia_urls()->signin_chrome_sync_dice().spec(),
@@ -155,6 +159,10 @@
             "https://test-gaia.com/ServiceLogin");
   EXPECT_EQ(gaia_urls()->embedded_setup_chromeos_url(2U).spec(),
             "https://test-gaia.com/embedded/setup/v2/chromeos");
+  EXPECT_EQ(gaia_urls()->embedded_setup_chromeos_kid_signup_url().spec(),
+            "https://test-gaia.com/embedded/setup/kidsignup/chromeos");
+  EXPECT_EQ(gaia_urls()->embedded_setup_chromeos_kid_signin_url().spec(),
+            "https://test-gaia.com/embedded/setup/kidsignin/chromeos");
   EXPECT_EQ(gaia_urls()->embedded_setup_windows_url().spec(),
             "https://test-gaia.com/embedded/setup/windows");
   EXPECT_EQ(gaia_urls()->signin_chrome_sync_dice().spec(),
@@ -277,6 +285,10 @@
             "https://accounts.example.com/ServiceLogin");
   EXPECT_EQ(gaia_urls()->embedded_setup_chromeos_url(2U).spec(),
             "https://accounts.example.com/embedded/setup/v2/chromeos");
+  EXPECT_EQ(gaia_urls()->embedded_setup_chromeos_kid_signup_url().spec(),
+            "https://accounts.example.com/embedded/setup/kidsignup/chromeos");
+  EXPECT_EQ(gaia_urls()->embedded_setup_chromeos_kid_signin_url().spec(),
+            "https://accounts.example.com/embedded/setup/kidsignin/chromeos");
   EXPECT_EQ(gaia_urls()->embedded_setup_windows_url().spec(),
             "https://accounts.example.com/embedded/setup/windows");
   EXPECT_EQ(gaia_urls()->signin_chrome_sync_dice().spec(),
diff --git a/google_apis/test/data/gaia/all_urls.json b/google_apis/test/data/gaia/all_urls.json
index 5a5b1b5..7909ac3 100644
--- a/google_apis/test/data/gaia/all_urls.json
+++ b/google_apis/test/data/gaia/all_urls.json
@@ -30,6 +30,12 @@
     "embedded_setup_chromeos_url_v2": {
       "url": "https://accounts.example.com/embedded/setup/v2/chromeos"
     },
+    "embedded_setup_chromeos_kid_signup_url": {
+      "url": "https://accounts.example.com/embedded/setup/kidsignup/chromeos"
+    },
+    "embedded_setup_chromeos_kid_signin_url": {
+      "url": "https://accounts.example.com/embedded/setup/kidsignin/chromeos"
+    },
     "embedded_setup_windows_url": {
       "url": "https://accounts.example.com/embedded/setup/windows"
     },
diff --git a/gpu/config/gpu_driver_bug_list.json b/gpu/config/gpu_driver_bug_list.json
index ec91ab2..596e83e5 100644
--- a/gpu/config/gpu_driver_bug_list.json
+++ b/gpu/config/gpu_driver_bug_list.json
@@ -3473,6 +3473,20 @@
       "features": [
         "disable_decode_swap_chain"
       ]
+    },
+    {
+      "id": 346,
+      "cr_bugs": [1062184],
+      "description": "Enable HDR video playing through overlay on Intel",
+      "os": {
+        "type": "win"
+      },
+      "intel_gpu_series": [
+        "icelake"
+      ],
+      "features": [
+        "force_rgb10a2_overlay_support_flags"
+      ]
     }
   ]
 }
diff --git a/gpu/config/gpu_workaround_list.txt b/gpu/config/gpu_workaround_list.txt
index 645897d..71a0eb2d 100644
--- a/gpu/config/gpu_workaround_list.txt
+++ b/gpu/config/gpu_workaround_list.txt
@@ -70,6 +70,7 @@
 force_high_performance_gpu
 force_int_or_srgb_cube_texture_complete
 force_low_power_gpu
+force_rgb10a2_overlay_support_flags
 force_update_scissor_state_when_binding_fbo0
 get_frag_data_info_bug
 gl_clear_broken
diff --git a/ios/chrome/browser/ui/authentication/signin/signin_coordinator_egtest.mm b/ios/chrome/browser/ui/authentication/signin/signin_coordinator_egtest.mm
index d904ebd..dddc548 100644
--- a/ios/chrome/browser/ui/authentication/signin/signin_coordinator_egtest.mm
+++ b/ios/chrome/browser/ui/authentication/signin/signin_coordinator_egtest.mm
@@ -89,7 +89,7 @@
   [[EarlGrey selectElementWithMatcher:SecondarySignInButton()]
       performAction:grey_tap()];
   [SigninEarlGreyUI selectIdentityWithEmail:fakeIdentity2.userEmail];
-  [SigninEarlGreyUI confirmSigninConfirmationDialog];
+  [SigninEarlGreyUI tapSigninConfirmationDialog];
 
   // Switch Sync account to |fakeIdentity2| should ask whether date should be
   // imported or kept separate. Choose to keep data separate.
@@ -250,7 +250,7 @@
   // The authentication flow is only created when the confirm button is
   // selected. Note that authentication flow actually blocks as the
   // "Clear Browsing Before Syncing" dialog is presented.
-  [SigninEarlGreyUI confirmSigninConfirmationDialog];
+  [SigninEarlGreyUI tapSigninConfirmationDialog];
   // Waits until the merge/delete data panel is shown.
   [[EarlGrey selectElementWithMatcher:SettingsImportDataKeepSeparateButton()]
       assertWithMatcher:grey_interactable()];
diff --git a/ios/chrome/browser/ui/authentication/signin_earl_grey_ui.h b/ios/chrome/browser/ui/authentication/signin_earl_grey_ui.h
index 1e9890c..4a341a4 100644
--- a/ios/chrome/browser/ui/authentication/signin_earl_grey_ui.h
+++ b/ios/chrome/browser/ui/authentication/signin_earl_grey_ui.h
@@ -11,12 +11,6 @@
 
 @class FakeChromeIdentity;
 
-typedef NS_ENUM(NSInteger, SignOutConfirmation) {
-  SignOutConfirmationManagedUser,
-  SignOutConfirmationNonManagedUser,
-  SignOutConfirmationNonManagedUserWithClearedData,
-};
-
 // Test methods that perform sign in actions on Chrome UI.
 @interface SigninEarlGreyUI : NSObject
 
@@ -37,45 +31,44 @@
 // method or if the account is not successfully signed out.
 + (void)signOutAndClearDataFromDevice;
 
-// Taps on the settings link in the sign-in view. The sign-in view has to be
-// opened before calling this method.
-+ (void)tapSettingsLink;
-
 // Selects an identity when the identity chooser dialog is presented. The dialog
 // is confirmed, but it doesn't validated the user consent page.
 + (void)selectIdentityWithEmail:(NSString*)userEmail;
 
-// Confirms the sign in confirmation page, scrolls first to make the OK button
-// visible on short devices (e.g. iPhone 5s).
+// Taps on the settings link in the sign-in view. The sign-in view has to be
+// opened before calling this method.
++ (void)tapSettingsLink;
+
+// Deprecated. Use |tapSigninConfirmationDialog|.
 + (void)confirmSigninConfirmationDialog;
 
+// Taps the sign in confirmation page, scrolls first to make the OK button
+// visible on short devices (e.g. iPhone 5s).
++ (void)tapSigninConfirmationDialog;
+
 // Taps on the "ADD ACCOUNT" button in the unified consent, to display the
 // SSO dialog.
 // This method should only be used with UnifiedConsent flag.
 + (void)tapAddAccountButton;
 
-// Checks that the sign-in promo view (with a close button) is visible using the
-// right mode.
-+ (void)checkSigninPromoVisibleWithMode:(SigninPromoViewMode)mode;
-
-// Checks that the sign-in promo view is visible using the right mode. If
-// |closeButton| is set to YES, the close button in the sign-in promo has to be
-// visible.
-+ (void)checkSigninPromoVisibleWithMode:(SigninPromoViewMode)mode
-                            closeButton:(BOOL)closeButton;
-
-// Checks that the sign-in promo view is not visible.
-+ (void)checkSigninPromoNotVisible;
-
-// Taps the appropriate action label on the sign-out dialog for the given
-// |signOutConfirmation| profile and signs out from the current identity.
-+ (void)signOutWithSignOutConfirmation:(SignOutConfirmation)signOutConfirmation;
-
 // Taps "Remove account from this device" button and follow-up confirmation.
 // Assumes the user is on the Settings screen.
 + (void)tapRemoveAccountFromDeviceWithFakeIdentity:
     (FakeChromeIdentity*)fakeIdentity;
 
+// Checks that the sign-in promo view (with a close button) is visible using the
+// right mode.
++ (void)verifySigninPromoVisibleWithMode:(SigninPromoViewMode)mode;
+
+// Checks that the sign-in promo view is visible using the right mode. If
+// |closeButton| is set to YES, the close button in the sign-in promo has to be
+// visible.
++ (void)verifySigninPromoVisibleWithMode:(SigninPromoViewMode)mode
+                             closeButton:(BOOL)closeButton;
+
+// Checks that the sign-in promo view is not visible.
++ (void)verifySigninPromoNotVisible;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_AUTHENTICATION_SIGNIN_EARL_GREY_UI_H_
diff --git a/ios/chrome/browser/ui/authentication/signin_earl_grey_ui.mm b/ios/chrome/browser/ui/authentication/signin_earl_grey_ui.mm
index 0d32a63..5f521c1 100644
--- a/ios/chrome/browser/ui/authentication/signin_earl_grey_ui.mm
+++ b/ios/chrome/browser/ui/authentication/signin_earl_grey_ui.mm
@@ -40,9 +40,16 @@
   [ChromeEarlGreyUI
       tapSettingsMenuButton:chrome_test_util::SecondarySignInButton()];
   [self selectIdentityWithEmail:fakeIdentity.userEmail];
-  [self confirmSigninConfirmationDialog];
+  [self tapSigninConfirmationDialog];
   if ([fakeIdentity.userEmail hasSuffix:ios::kManagedIdentityEmailSuffix]) {
-    [self confirmSigninWithManagedAccount];
+    // Synchronization off due to an infinite spinner, in the user consent view,
+    // under the managed consent dialog. This spinner is started by the sign-in
+    // process.
+    ScopedSynchronizationDisabler disabler;
+    id<GREYMatcher> acceptButton = [ChromeMatchersAppInterface
+        buttonWithAccessibilityLabelID:IDS_IOS_MANAGED_SIGNIN_ACCEPT_BUTTON];
+    [ChromeEarlGrey waitForMatcher:acceptButton];
+    [[EarlGrey selectElementWithMatcher:acceptButton] performAction:grey_tap()];
   }
   [[EarlGrey selectElementWithMatcher:SettingsDoneButton()]
       performAction:grey_tap()];
@@ -80,18 +87,11 @@
       performAction:grey_tap()];
 }
 
-+ (void)confirmSigninWithManagedAccount {
-  // Synchronization off due to an infinite spinner, in the user consent view,
-  // under the managed consent dialog. This spinner is started by the sign-in
-  // process.
-  ScopedSynchronizationDisabler disabler;
-  id<GREYMatcher> acceptButton = [ChromeMatchersAppInterface
-      buttonWithAccessibilityLabelID:IDS_IOS_MANAGED_SIGNIN_ACCEPT_BUTTON];
-  [ChromeEarlGrey waitForMatcher:acceptButton];
-  [[EarlGrey selectElementWithMatcher:acceptButton] performAction:grey_tap()];
++ (void)confirmSigninConfirmationDialog {
+  [self tapSigninConfirmationDialog];
 }
 
-+ (void)confirmSigninConfirmationDialog {
++ (void)tapSigninConfirmationDialog {
   // To confirm the dialog, the scroll view content has to be scrolled to the
   // bottom to transform "MORE" button into the validation button.
   // EarlGrey fails to scroll to the bottom, using grey_scrollToContentEdge(),
@@ -149,12 +149,12 @@
   [[EarlGrey selectElementWithMatcher:buttonMatcher] performAction:grey_tap()];
 }
 
-+ (void)checkSigninPromoVisibleWithMode:(SigninPromoViewMode)mode {
-  [self checkSigninPromoVisibleWithMode:mode closeButton:YES];
++ (void)verifySigninPromoVisibleWithMode:(SigninPromoViewMode)mode {
+  [self verifySigninPromoVisibleWithMode:mode closeButton:YES];
 }
 
-+ (void)checkSigninPromoVisibleWithMode:(SigninPromoViewMode)mode
-                            closeButton:(BOOL)closeButton {
++ (void)verifySigninPromoVisibleWithMode:(SigninPromoViewMode)mode
+                             closeButton:(BOOL)closeButton {
   [ChromeEarlGreyUI waitForAppToIdle];
   [[EarlGrey
       selectElementWithMatcher:grey_allOf(
@@ -188,7 +188,7 @@
   }
 }
 
-+ (void)checkSigninPromoNotVisible {
++ (void)verifySigninPromoNotVisible {
   [[EarlGrey
       selectElementWithMatcher:grey_allOf(
                                    grey_accessibilityID(kSigninPromoViewId),
@@ -204,29 +204,6 @@
       assertWithMatcher:grey_nil()];
 }
 
-+ (void)signOutWithSignOutConfirmation:
-    (SignOutConfirmation)signOutConfirmation {
-  [ChromeEarlGreyUI openSettingsMenu];
-  [ChromeEarlGreyUI tapSettingsMenuButton:SettingsAccountButton()];
-
-  id<GREYMatcher> signOutButtonMatcher;
-  int confirmationLabelID = 0;
-  switch (signOutConfirmation) {
-    case SignOutConfirmationNonManagedUser: {
-      signOutButtonMatcher = SignOutAccountsButton();
-      confirmationLabelID = IDS_IOS_DISCONNECT_DIALOG_CONTINUE_BUTTON_MOBILE;
-      break;
-    }
-    case SignOutConfirmationManagedUser:
-    case SignOutConfirmationNonManagedUserWithClearedData: {
-      signOutButtonMatcher = grey_accessibilityID(
-          kSettingsAccountsTableViewSignoutAndClearDataCellId);
-      confirmationLabelID = IDS_IOS_DISCONNECT_DIALOG_CONTINUE_AND_CLEAR_MOBILE;
-      break;
-    }
-  }
-}
-
 + (void)tapRemoveAccountFromDeviceWithFakeIdentity:
     (FakeChromeIdentity*)fakeIdentity {
   [[EarlGrey selectElementWithMatcher:ButtonWithAccessibilityLabel(
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/address_view_controller_egtest.mm b/ios/chrome/browser/ui/autofill/manual_fill/address_view_controller_egtest.mm
index f64db3f..634437d 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/address_view_controller_egtest.mm
+++ b/ios/chrome/browser/ui/autofill/manual_fill/address_view_controller_egtest.mm
@@ -44,9 +44,14 @@
   return [waitForKeyboard waitWithTimeout:kWaitForActionTimeout];
 }
 
+// Returns a matcher for a web view.
+id<GREYMatcher> WebViewMatcher() {
+  return grey_kindOfClass(NSClassFromString(@"WKWebView"));
+}
+
 }  // namespace
 
-// Integration Tests for Mannual Fallback Addresses View Controller.
+// Integration Tests for Manual Fallback Addresses View Controller.
 @interface AddressViewControllerTestCase : ChromeTestCase
 @end
 
@@ -69,17 +74,9 @@
 }
 
 // Tests that the addresses view controller appears on screen.
-// TODO(crbug.com/1116274): Flaky on ios simulator.
-#if TARGET_IPHONE_SIMULATOR
-#define MAYBE_testAddressesViewControllerIsPresented \
-  DISABLED_testAddressesViewControllerIsPresented
-#else
-#define MAYBE_testAddressesViewControllerIsPresented \
-  testAddressesViewControllerIsPresented
-#endif
-- (void)MAYBE_testAddressesViewControllerIsPresented {
+- (void)testAddressesViewControllerIsPresented {
   // Bring up the keyboard.
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+  [[EarlGrey selectElementWithMatcher:WebViewMatcher()]
       performAction:chrome_test_util::TapWebElementWithId(kFormElementName)];
 
   // Tap on the addresses icon.
@@ -95,17 +92,9 @@
 
 // Tests that the addresses view controller contains the "Manage Addresses..."
 // action.
-// TODO(crbug.com/1116043): Flaky on ios simulator.
-#if TARGET_IPHONE_SIMULATOR
-#define MAYBE_testAddressesViewControllerContainsManageAddressesAction \
-  DISABLED_testAddressesViewControllerContainsManageAddressesAction
-#else
-#define MAYBE_testAddressesViewControllerContainsManageAddressesAction \
-  testAddressesViewControllerContainsManageAddressesAction
-#endif
-- (void)MAYBE_testAddressesViewControllerContainsManageAddressesAction {
+- (void)testAddressesViewControllerContainsManageAddressesAction {
   // Bring up the keyboard.
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+  [[EarlGrey selectElementWithMatcher:WebViewMatcher()]
       performAction:chrome_test_util::TapWebElementWithId(kFormElementName)];
 
   // Tap on the addresses icon.
@@ -124,7 +113,7 @@
 // Tests that the "Manage Addresses..." action works.
 - (void)testManageAddressesActionOpensAddressSettings {
   // Bring up the keyboard.
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+  [[EarlGrey selectElementWithMatcher:WebViewMatcher()]
       performAction:chrome_test_util::TapWebElementWithId(kFormElementName)];
 
   // Tap on the addresses icon.
@@ -146,17 +135,9 @@
 
 // Tests that returning from "Manage Addresses..." leaves the icons and keyboard
 // in the right state.
-// TODO(crbug.com/1111076): Flaky on iOS simulator.
-#if TARGET_IPHONE_SIMULATOR
-#define MAYBE_testAddressesStateAfterPresentingManageAddresses \
-  DISABLED_testAddressesStateAfterPresentingManageAddresses
-#else
-#define MAYBE_testAddressesStateAfterPresentingManageAddresses \
-  testAddressesStateAfterPresentingManageAddresses
-#endif
-- (void)MAYBE_testAddressesStateAfterPresentingManageAddresses {
+- (void)testAddressesStateAfterPresentingManageAddresses {
   // Bring up the keyboard.
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+  [[EarlGrey selectElementWithMatcher:WebViewMatcher()]
       performAction:chrome_test_util::TapWebElementWithId(kFormElementName)];
 
   // Tap on the addresses icon.
@@ -211,7 +192,7 @@
     ;
   }
   // Bring up the keyboard.
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+  [[EarlGrey selectElementWithMatcher:WebViewMatcher()]
       performAction:chrome_test_util::TapWebElementWithId(kFormElementName)];
 
   // Tap on the addresses icon.
@@ -238,20 +219,12 @@
 
 // Tests that the Address View Controller is dismissed when tapping the outside
 // the popover on iPad.
-// TODO(crbug.com/1116887) Flaky on iOS simulator
-#if TARGET_IPHONE_SIMULATOR
-#define MAYBE_testIPadTappingOutsidePopOverDismissAddressController \
-  DISABLED_testIPadTappingOutsidePopOverDismissAddressController
-#else
-#define MAYBE_testIPadTappingOutsidePopOverDismissAddressController \
-  testIPadTappingOutsidePopOverDismissAddressController
-#endif
-- (void)MAYBE_testIPadTappingOutsidePopOverDismissAddressController {
+- (void)testIPadTappingOutsidePopOverDismissAddressController {
   if (![ChromeEarlGrey isIPadIdiom]) {
     EARL_GREY_TEST_SKIPPED(@"Test is not applicable for iPhone");
   }
   // Bring up the keyboard.
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+  [[EarlGrey selectElementWithMatcher:WebViewMatcher()]
       performAction:chrome_test_util::TapWebElementWithId(kFormElementName)];
 
   // Tap on the addresses icon.
@@ -280,20 +253,12 @@
 }
 
 // Tests that the address icon is hidden when no addresses are available.
-// TODO(crbug.com/1116043): Flaky on ios simulator.
-#if TARGET_IPHONE_SIMULATOR
-#define MAYBE_testAddressIconIsNotVisibleWhenAddressStoreEmpty \
-  DISABLED_testAddressIconIsNotVisibleWhenAddressStoreEmpty
-#else
-#define MAYBE_testAddressIconIsNotVisibleWhenAddressStoreEmpty \
-  testAddressIconIsNotVisibleWhenAddressStoreEmpty
-#endif
-- (void)MAYBE_testAddressIconIsNotVisibleWhenAddressStoreEmpty {
+- (void)testAddressIconIsNotVisibleWhenAddressStoreEmpty {
   // Delete the profile that is added on |-setUp|.
   [AutofillAppInterface clearProfilesStore];
 
   // Bring up the keyboard.
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+  [[EarlGrey selectElementWithMatcher:WebViewMatcher()]
       performAction:chrome_test_util::TapWebElementWithId(kFormElementName)];
 
   // Wait for the keyboard to appear.
@@ -307,7 +272,7 @@
   [AutofillAppInterface saveExampleProfile];
 
   // Tap another field to trigger form activity.
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+  [[EarlGrey selectElementWithMatcher:WebViewMatcher()]
       performAction:chrome_test_util::TapWebElementWithId(kFormElementCity)];
 
   // Assert the address icon is visible now.
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/fallback_coordinator_egtest.mm b/ios/chrome/browser/ui/autofill/manual_fill/fallback_coordinator_egtest.mm
index 886523b..3b31cef 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/fallback_coordinator_egtest.mm
+++ b/ios/chrome/browser/ui/autofill/manual_fill/fallback_coordinator_egtest.mm
@@ -48,6 +48,11 @@
   return grey_buttonTitle(@"Underworld");
 }
 
+// Returns a matcher for a web view.
+id<GREYMatcher> WebViewMatcher() {
+  return grey_kindOfClass(NSClassFromString(@"WKWebView"));
+}
+
 // Polls the JavaScript query |java_script_condition| until the returned
 // |boolValue| is YES with a kWaitForActionTimeout timeout.
 BOOL WaitForJavaScriptCondition(NSString* java_script_condition) {
@@ -163,7 +168,7 @@
   // will dismiss any.
   if ([ChromeEarlGrey isIPadIdiom]) {
     // Tap in the web view so the popover dismisses.
-    [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+    [[EarlGrey selectElementWithMatcher:WebViewMatcher()]
         performAction:grey_tapAtPoint(CGPointMake(0, 0))];
 
     // Verify the table view is not visible.
@@ -187,7 +192,7 @@
   [AutofillAppInterface saveExampleProfile];
 
   // Bring up the keyboard.
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+  [[EarlGrey selectElementWithMatcher:WebViewMatcher()]
       performAction:chrome_test_util::TapWebElementWithId(kFormElementName)];
 
   // Tap on the profiles icon.
@@ -230,7 +235,7 @@
   [AutofillAppInterface saveExampleProfile];
 
   // Bring up the keyboard.
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+  [[EarlGrey selectElementWithMatcher:WebViewMatcher()]
       performAction:chrome_test_util::TapWebElementWithId(kFormElementCity)];
 
   // Tap on the profiles icon.
@@ -276,7 +281,7 @@
 
   // Bring up the keyboard by tapping the city, which is the element before the
   // picker.
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+  [[EarlGrey selectElementWithMatcher:WebViewMatcher()]
       performAction:chrome_test_util::TapWebElementWithId(kFormElementCity)];
 
   // Tap on the profiles icon.
@@ -301,7 +306,7 @@
   // first.
   if ([ChromeEarlGrey isIPadIdiom]) {
     // Tap in the web view so the popover dismisses.
-    [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+    [[EarlGrey selectElementWithMatcher:WebViewMatcher()]
         performAction:grey_tapAtPoint(CGPointMake(0, 0))];
 
     // Verify the table view is not visible.
@@ -313,7 +318,7 @@
   }
 
   // Bring up the regular keyboard again.
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+  [[EarlGrey selectElementWithMatcher:WebViewMatcher()]
       performAction:chrome_test_util::TapWebElementWithId(kFormElementCity)];
 
   // Wait for the accessory icon to appear.
@@ -344,7 +349,7 @@
 
   // Bring up the keyboard by tapping the city, which is the element before the
   // picker.
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+  [[EarlGrey selectElementWithMatcher:WebViewMatcher()]
       performAction:chrome_test_util::TapWebElementWithId(kFormElementCity)];
 
   if (!UndockAndSplitKeyboard()) {
@@ -389,7 +394,7 @@
       assertWithMatcher:grey_nil()];
 
   // Bring up the regular keyboard again.
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+  [[EarlGrey selectElementWithMatcher:WebViewMatcher()]
       performAction:chrome_test_util::TapWebElementWithId(kFormElementName)];
 
   // Wait for the accessory icon to appear.
@@ -420,7 +425,7 @@
 
   // Bring up the keyboard by tapping the city, which is the element before the
   // picker.
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+  [[EarlGrey selectElementWithMatcher:WebViewMatcher()]
       performAction:chrome_test_util::TapWebElementWithId(kFormElementCity)];
 
   if (!UndockAndSplitKeyboard()) {
@@ -449,14 +454,13 @@
 }
 
 // Tests that the manual fallback view is present in incognito.
-// Disabled due to flakiness. See crbug.com/1115321.
-- (void)DISABLED_testIncognitoManualFallbackMenu {
+- (void)testIncognitoManualFallbackMenu {
   // Add the profile to use for verification.
   [AutofillAppInterface saveExampleProfile];
 
   // Bring up the keyboard by tapping the city, which is the element before the
   // picker.
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+  [[EarlGrey selectElementWithMatcher:WebViewMatcher()]
       performAction:chrome_test_util::TapWebElementWithId(kFormElementCity)];
 
   // Verify the profiles icon is visible.
@@ -473,7 +477,7 @@
 
   // Bring up the keyboard by tapping the city, which is the element before the
   // picker.
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+  [[EarlGrey selectElementWithMatcher:WebViewMatcher()]
       performAction:chrome_test_util::TapWebElementWithId(kFormElementCity)];
 
   // Verify the profiles icon is visible.
@@ -486,13 +490,12 @@
 // Tests the mediator stops observing objects when the incognito BVC is
 // destroyed. Waiting for dealloc was causing a race condition with the
 // autorelease pool, and some times a DCHECK will be hit.
-// TODO(crbug.com/1111258): Investigate cause of flakiness and re-enable.
-- (void)DISABLED_testOpeningIncognitoTabsDoNotLeak {
+- (void)testOpeningIncognitoTabsDoNotLeak {
   const GURL URL = self.testServer->GetURL(kFormHTMLFile);
   std::string webViewText("Profile form");
   [AutofillAppInterface saveExampleProfile];
 
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+  [[EarlGrey selectElementWithMatcher:WebViewMatcher()]
       performAction:chrome_test_util::TapWebElementWithId(kFormElementCity)];
 
   // Verify the profiles icon is visible.
@@ -506,7 +509,7 @@
   [ChromeEarlGrey loadURL:URL];
   [ChromeEarlGrey waitForWebStateContainingText:webViewText];
 
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+  [[EarlGrey selectElementWithMatcher:WebViewMatcher()]
       performAction:chrome_test_util::TapWebElementWithId(kFormElementCity)];
 
   // Verify the profiles icon is visible.
@@ -522,7 +525,7 @@
 
   // Bring up the keyboard by tapping the city, which is the element before the
   // picker.
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+  [[EarlGrey selectElementWithMatcher:WebViewMatcher()]
       performAction:chrome_test_util::TapWebElementWithId(kFormElementCity)];
 
   // Verify the profiles icon is visible.
@@ -538,7 +541,7 @@
 
   // Bring up the keyboard by tapping the city, which is the element before the
   // picker.
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+  [[EarlGrey selectElementWithMatcher:WebViewMatcher()]
       performAction:chrome_test_util::TapWebElementWithId(kFormElementCity)];
 
   // Open a  regular tab.
@@ -548,7 +551,7 @@
 
   // Bring up the keyboard by tapping the city, which is the element before the
   // picker.
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+  [[EarlGrey selectElementWithMatcher:WebViewMatcher()]
       performAction:chrome_test_util::TapWebElementWithId(kFormElementCity)];
 
   // This will fail if there is more than one profiles icon in the hierarchy.
@@ -559,14 +562,13 @@
 }
 
 // Tests that the manual fallback view is not duplicated after incognito.
-// Disabled due to flakiness. See crbug.com/1115282.
-- (void)DISABLED_testReturningFromIncognitoDoesNotDuplicatesManualFallbackMenu {
+- (void)testReturningFromIncognitoDoesNotDuplicatesManualFallbackMenu {
   // Add the profile to use for verification.
   [AutofillAppInterface saveExampleProfile];
 
   // Bring up the keyboard by tapping the city, which is the element before the
   // picker.
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+  [[EarlGrey selectElementWithMatcher:WebViewMatcher()]
       performAction:chrome_test_util::TapWebElementWithId(kFormElementCity)];
 
   // Verify the profiles icon is visible.
@@ -583,7 +585,7 @@
 
   // Bring up the keyboard by tapping the city, which is the element before the
   // picker.
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+  [[EarlGrey selectElementWithMatcher:WebViewMatcher()]
       performAction:chrome_test_util::TapWebElementWithId(kFormElementCity)];
 
   // Open a  regular tab.
@@ -593,7 +595,7 @@
 
   // Bring up the keyboard by tapping the city, which is the element before the
   // picker.
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+  [[EarlGrey selectElementWithMatcher:WebViewMatcher()]
       performAction:chrome_test_util::TapWebElementWithId(kFormElementCity)];
 
   // This will fail if there is more than one profiles icon in the hierarchy.
diff --git a/ios/chrome/browser/ui/bookmarks/bookmarks_promo_egtest.mm b/ios/chrome/browser/ui/bookmarks/bookmarks_promo_egtest.mm
index 5c5d22b..3e345b6 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmarks_promo_egtest.mm
+++ b/ios/chrome/browser/ui/bookmarks/bookmarks_promo_egtest.mm
@@ -66,13 +66,13 @@
   // Check that sign-in promo view is visible.
   [BookmarkEarlGrey verifyPromoAlreadySeen:NO];
   [SigninEarlGreyUI
-      checkSigninPromoVisibleWithMode:SigninPromoViewModeColdState];
+      verifySigninPromoVisibleWithMode:SigninPromoViewModeColdState];
 
   // Go to child node.
   [BookmarkEarlGreyUI openMobileBookmarks];
 
   // Wait until promo is gone.
-  [SigninEarlGreyUI checkSigninPromoNotVisible];
+  [SigninEarlGreyUI verifySigninPromoNotVisible];
 
   // Check that the promo already seen state is not updated.
   [BookmarkEarlGrey verifyPromoAlreadySeen:NO];
@@ -99,7 +99,7 @@
   // Check that sign-in promo view is visible.
   [BookmarkEarlGrey verifyPromoAlreadySeen:NO];
   [SigninEarlGreyUI
-      checkSigninPromoVisibleWithMode:SigninPromoViewModeColdState];
+      verifySigninPromoVisibleWithMode:SigninPromoViewModeColdState];
 
   // Tap the dismiss button.
   [[EarlGrey
@@ -109,7 +109,7 @@
       performAction:grey_tap()];
 
   // Wait until promo is gone.
-  [SigninEarlGreyUI checkSigninPromoNotVisible];
+  [SigninEarlGreyUI verifySigninPromoNotVisible];
 
   // Check that the promo already seen state is updated.
   [BookmarkEarlGrey verifyPromoAlreadySeen:YES];
@@ -124,7 +124,7 @@
   // Check that sign-in promo view are visible.
   [BookmarkEarlGrey verifyPromoAlreadySeen:NO];
   [SigninEarlGreyUI
-      checkSigninPromoVisibleWithMode:SigninPromoViewModeColdState];
+      verifySigninPromoVisibleWithMode:SigninPromoViewModeColdState];
 
   // Tap the primary button.
   [[EarlGrey
@@ -139,7 +139,7 @@
   // Check that the bookmarks UI reappeared and the cell is still here.
   [BookmarkEarlGrey verifyPromoAlreadySeen:NO];
   [SigninEarlGreyUI
-      checkSigninPromoVisibleWithMode:SigninPromoViewModeColdState];
+      verifySigninPromoVisibleWithMode:SigninPromoViewModeColdState];
 }
 
 // Tests the tapping on the primary button of sign-in promo view in a warm
@@ -155,7 +155,7 @@
   // Check that promo is visible.
   [BookmarkEarlGrey verifyPromoAlreadySeen:NO];
   [SigninEarlGreyUI
-      checkSigninPromoVisibleWithMode:SigninPromoViewModeWarmState];
+      verifySigninPromoVisibleWithMode:SigninPromoViewModeWarmState];
 
   // Tap the primary button.
   [[EarlGrey
@@ -170,7 +170,7 @@
 
   // Check that the bookmarks UI reappeared and the cell is still here.
   [SigninEarlGreyUI
-      checkSigninPromoVisibleWithMode:SigninPromoViewModeWarmState];
+      verifySigninPromoVisibleWithMode:SigninPromoViewModeWarmState];
 
   [BookmarkEarlGrey verifyPromoAlreadySeen:NO];
 }
@@ -188,7 +188,7 @@
   // Check that sign-in promo view are visible.
   [BookmarkEarlGrey verifyPromoAlreadySeen:NO];
   [SigninEarlGreyUI
-      checkSigninPromoVisibleWithMode:SigninPromoViewModeWarmState];
+      verifySigninPromoVisibleWithMode:SigninPromoViewModeWarmState];
 
   // Tap the secondary button.
   [[EarlGrey
@@ -207,7 +207,7 @@
   // Check that the bookmarks UI reappeared and the cell is still here.
   [BookmarkEarlGrey verifyPromoAlreadySeen:NO];
   [SigninEarlGreyUI
-      checkSigninPromoVisibleWithMode:SigninPromoViewModeWarmState];
+      verifySigninPromoVisibleWithMode:SigninPromoViewModeWarmState];
 }
 
 // Tests that the sign-in promo should not be shown after been shown 19 times.
@@ -216,7 +216,7 @@
   [BookmarkEarlGreyUI openBookmarks];
   // Check the sign-in promo view is visible.
   [SigninEarlGreyUI
-      checkSigninPromoVisibleWithMode:SigninPromoViewModeColdState];
+      verifySigninPromoVisibleWithMode:SigninPromoViewModeColdState];
   // Check the sign-in promo already-seen state didn't change.
   [BookmarkEarlGrey verifyPromoAlreadySeen:NO];
   GREYAssertEqual(20, [BookmarkEarlGrey numberOfTimesPromoAlreadySeen],
@@ -228,7 +228,7 @@
   [BookmarkEarlGreyUI openBookmarks];
   [ChromeEarlGreyUI waitForAppToIdle];
   // Check that the sign-in promo is not visible anymore.
-  [SigninEarlGreyUI checkSigninPromoNotVisible];
+  [SigninEarlGreyUI verifySigninPromoNotVisible];
 }
 
 @end
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_mediator.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_mediator.mm
index 50b0ab8..743ed01 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_mediator.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_mediator.mm
@@ -418,13 +418,13 @@
 
 - (void)toggleArticlesVisibility {
   [self.contentArticlesExpanded setValue:![self.contentArticlesExpanded value]];
-  [self reloadArticleSection];
+  [self reloadArticleSectionOrAllData:NO];
 }
 
 #pragma mark - BooleanObserver
 
 - (void)booleanDidChange:(id<ObservableBoolean>)observableBoolean {
-  [self reloadArticleSection];
+  [self reloadArticleSectionOrAllData:YES];
 }
 
 #pragma mark - ContentSuggestionsServiceObserver
@@ -606,7 +606,10 @@
 
 #pragma mark - Private
 
-- (void)reloadArticleSection {
+// Reloads article section (for the page that trigers the request) or all
+// for other tabs/windows observing the pref. It avoids issues with scroll
+// changes, in hidden tabs/windows which leads to crashes.
+- (void)reloadArticleSectionOrAllData:(BOOL)allData {
   // Update the section information for new collapsed state.
   ntp_snippets::Category category = ntp_snippets::Category::FromKnownCategory(
       ntp_snippets::KnownCategories::ARTICLES);
@@ -616,12 +619,16 @@
       self.sectionInformationByCategory[wrapper];
   sectionInfo.expanded = [self.contentArticlesExpanded value];
 
-  // Reloading the section with animations looks bad because the section
-  // border with the new collapsed height draws before the elements collapse.
-  BOOL animationsWereEnabled = [UIView areAnimationsEnabled];
-  [UIView setAnimationsEnabled:NO];
-  [self.dataSink reloadSection:sectionInfo];
-  [UIView setAnimationsEnabled:animationsWereEnabled];
+  if (allData) {
+    [self reloadAllData];
+  } else {
+    // Reloading the section with animations looks bad because the section
+    // border with the new collapsed height draws before the elements collapse.
+    BOOL animationsWereEnabled = [UIView areAnimationsEnabled];
+    [UIView setAnimationsEnabled:NO];
+    [self.dataSink reloadSection:sectionInfo];
+    [UIView setAnimationsEnabled:animationsWereEnabled];
+  }
 }
 
 // Converts the |suggestions| from |category| to CSCollectionViewItem and adds
diff --git a/ios/chrome/browser/ui/recent_tabs/recent_tabs_egtest.mm b/ios/chrome/browser/ui/recent_tabs/recent_tabs_egtest.mm
index 62d03a81..10221f3 100644
--- a/ios/chrome/browser/ui/recent_tabs/recent_tabs_egtest.mm
+++ b/ios/chrome/browser/ui/recent_tabs/recent_tabs_egtest.mm
@@ -160,13 +160,15 @@
 - (void)testRecentTabSigninPromoReloaded {
   OpenRecentTabsPanel();
   // Sign-in promo should be visible with cold state.
-  [SigninEarlGreyUI checkSigninPromoVisibleWithMode:SigninPromoViewModeColdState
-                                        closeButton:NO];
+  [SigninEarlGreyUI
+      verifySigninPromoVisibleWithMode:SigninPromoViewModeColdState
+                           closeButton:NO];
   FakeChromeIdentity* fakeIdentity = [SigninEarlGrey fakeIdentity1];
   [SigninEarlGrey addFakeIdentity:fakeIdentity];
   // Sign-in promo should be visible with warm state.
-  [SigninEarlGreyUI checkSigninPromoVisibleWithMode:SigninPromoViewModeWarmState
-                                        closeButton:NO];
+  [SigninEarlGreyUI
+      verifySigninPromoVisibleWithMode:SigninPromoViewModeWarmState
+                           closeButton:NO];
   [self closeRecentTabs];
   [SigninEarlGrey forgetFakeIdentity:fakeIdentity];
 }
@@ -175,8 +177,9 @@
 // crbug.com/776939
 - (void)testRecentTabSigninPromoReloadedWhileHidden {
   OpenRecentTabsPanel();
-  [SigninEarlGreyUI checkSigninPromoVisibleWithMode:SigninPromoViewModeColdState
-                                        closeButton:NO];
+  [SigninEarlGreyUI
+      verifySigninPromoVisibleWithMode:SigninPromoViewModeColdState
+                           closeButton:NO];
 
   // Tap on "Other Devices", to hide the sign-in promo.
   NSString* otherDevicesLabel =
@@ -186,7 +189,7 @@
       grey_sufficientlyVisible(), nil);
   [[EarlGrey selectElementWithMatcher:otherDevicesMatcher]
       performAction:grey_tap()];
-  [SigninEarlGreyUI checkSigninPromoNotVisible];
+  [SigninEarlGreyUI verifySigninPromoNotVisible];
 
   // Add an account.
   FakeChromeIdentity* fakeIdentity = [SigninEarlGrey fakeIdentity1];
@@ -195,8 +198,9 @@
   // Tap on "Other Devices", to show the sign-in promo.
   [[EarlGrey selectElementWithMatcher:otherDevicesMatcher]
       performAction:grey_tap()];
-  [SigninEarlGreyUI checkSigninPromoVisibleWithMode:SigninPromoViewModeWarmState
-                                        closeButton:NO];
+  [SigninEarlGreyUI
+      verifySigninPromoVisibleWithMode:SigninPromoViewModeWarmState
+                           closeButton:NO];
   [self closeRecentTabs];
   [SigninEarlGrey forgetFakeIdentity:fakeIdentity];
 }
@@ -274,8 +278,9 @@
     [illustratedCell assertWithMatcher:grey_nil()];
   }
 
-  [SigninEarlGreyUI checkSigninPromoVisibleWithMode:SigninPromoViewModeColdState
-                                        closeButton:NO];
+  [SigninEarlGreyUI
+      verifySigninPromoVisibleWithMode:SigninPromoViewModeColdState
+                           closeButton:NO];
 }
 
 @end
diff --git a/ios/chrome/browser/ui/settings/signin_settings_egtest.mm b/ios/chrome/browser/ui/settings/signin_settings_egtest.mm
index d3fea67..835c8d3 100644
--- a/ios/chrome/browser/ui/settings/signin_settings_egtest.mm
+++ b/ios/chrome/browser/ui/settings/signin_settings_egtest.mm
@@ -42,7 +42,7 @@
 - (void)testSignInPromoWithColdStateUsingPrimaryButton {
   [ChromeEarlGreyUI openSettingsMenu];
   [SigninEarlGreyUI
-      checkSigninPromoVisibleWithMode:SigninPromoViewModeColdState];
+      verifySigninPromoVisibleWithMode:SigninPromoViewModeColdState];
   [ChromeEarlGreyUI tapSettingsMenuButton:PrimarySignInButton()];
 
   // Cancel the sign-in operation.
@@ -50,7 +50,7 @@
                                           kSkipSigninAccessibilityIdentifier)]
       performAction:grey_tap()];
   [SigninEarlGreyUI
-      checkSigninPromoVisibleWithMode:SigninPromoViewModeColdState];
+      verifySigninPromoVisibleWithMode:SigninPromoViewModeColdState];
 }
 
 // Tests signing in, using the primary button with a warm state.
@@ -60,13 +60,13 @@
 
   [ChromeEarlGreyUI openSettingsMenu];
   [SigninEarlGreyUI
-      checkSigninPromoVisibleWithMode:SigninPromoViewModeWarmState];
+      verifySigninPromoVisibleWithMode:SigninPromoViewModeWarmState];
   [ChromeEarlGreyUI tapSettingsMenuButton:PrimarySignInButton()];
-  [SigninEarlGreyUI confirmSigninConfirmationDialog];
+  [SigninEarlGreyUI tapSigninConfirmationDialog];
 
   // User signed in.
   [SigninEarlGrey verifySignedInWithFakeIdentity:fakeIdentity];
-  [SigninEarlGreyUI checkSigninPromoNotVisible];
+  [SigninEarlGreyUI verifySigninPromoNotVisible];
   [[EarlGrey selectElementWithMatcher:SettingsAccountButton()]
       assertWithMatcher:grey_interactable()];
 }
@@ -78,14 +78,14 @@
 
   [ChromeEarlGreyUI openSettingsMenu];
   [SigninEarlGreyUI
-      checkSigninPromoVisibleWithMode:SigninPromoViewModeWarmState];
+      verifySigninPromoVisibleWithMode:SigninPromoViewModeWarmState];
   [ChromeEarlGreyUI tapSettingsMenuButton:SecondarySignInButton()];
   [SigninEarlGreyUI selectIdentityWithEmail:fakeIdentity.userEmail];
-  [SigninEarlGreyUI confirmSigninConfirmationDialog];
+  [SigninEarlGreyUI tapSigninConfirmationDialog];
 
   // User signed in.
   [SigninEarlGrey verifySignedInWithFakeIdentity:fakeIdentity];
-  [SigninEarlGreyUI checkSigninPromoNotVisible];
+  [SigninEarlGreyUI verifySigninPromoNotVisible];
   [[EarlGrey selectElementWithMatcher:SettingsAccountButton()]
       assertWithMatcher:grey_interactable()];
 }
@@ -98,7 +98,7 @@
   [ChromeEarlGreyUI openSettingsMenu];
   // Check the sign-in promo view is visible.
   [SigninEarlGreyUI
-      checkSigninPromoVisibleWithMode:SigninPromoViewModeColdState];
+      verifySigninPromoVisibleWithMode:SigninPromoViewModeColdState];
   // Check the sign-in promo will not be shown anymore.
   int newDisplayedCount =
       [SigninSettingsAppInterface settingsSigninPromoDisplayedCount];
@@ -109,7 +109,7 @@
       performAction:grey_tap()];
   [ChromeEarlGreyUI openSettingsMenu];
   // Check that the sign-in promo is not visible anymore.
-  [SigninEarlGreyUI checkSigninPromoNotVisible];
+  [SigninEarlGreyUI verifySigninPromoNotVisible];
   [[EarlGrey
       selectElementWithMatcher:grey_allOf(
                                    grey_accessibilityID(kSettingsSignInCellId),
@@ -121,7 +121,7 @@
   [ChromeEarlGreyUI openSettingsMenu];
   // Check the sign-in promo view is visible.
   [SigninEarlGreyUI
-      checkSigninPromoVisibleWithMode:SigninPromoViewModeColdState];
+      verifySigninPromoVisibleWithMode:SigninPromoViewModeColdState];
   // Tap on dismiss button.
   [[EarlGrey
       selectElementWithMatcher:grey_allOf(grey_accessibilityID(
@@ -129,7 +129,7 @@
                                           grey_sufficientlyVisible(), nil)]
       performAction:grey_tap()];
   // Check that the sign-in promo is not visible anymore.
-  [SigninEarlGreyUI checkSigninPromoNotVisible];
+  [SigninEarlGreyUI verifySigninPromoNotVisible];
   [[EarlGrey
       selectElementWithMatcher:grey_allOf(
                                    grey_accessibilityID(kSettingsSignInCellId),
diff --git a/media/gpu/chromeos/platform_video_frame_pool.cc b/media/gpu/chromeos/platform_video_frame_pool.cc
index eebdcb4..f4edd8d 100644
--- a/media/gpu/chromeos/platform_video_frame_pool.cc
+++ b/media/gpu/chromeos/platform_video_frame_pool.cc
@@ -9,6 +9,7 @@
 #include "base/logging.h"
 #include "base/optional.h"
 #include "base/task/post_task.h"
+#include "media/base/video_util.h"
 #include "media/gpu/chromeos/gpu_buffer_layout.h"
 #include "media/gpu/chromeos/platform_video_frame_utils.h"
 #include "media/gpu/macros.h"
@@ -76,12 +77,19 @@
     if (GetTotalNumFrames_Locked() >= max_num_frames_)
       return nullptr;
 
-    // VideoFrame::WrapVideoFrame() will check whether the updated visible_rect
-    // is sub rect of the original visible_rect. Therefore we set visible_rect
-    // as large as coded_size to guarantee this condition.
-    scoped_refptr<VideoFrame> new_frame = create_frame_cb_.Run(
-        gpu_memory_buffer_factory_, format, coded_size, gfx::Rect(coded_size),
-        coded_size, base::TimeDelta());
+    // We want to be able to re-use |new_frame| if possible even when the pool
+    // is re-initialized with a different |visible_rect_|. DRM framebuffers can
+    // be re-used if the visible-rect-from-the-origin (a.k.a "usable area") is
+    // the same. For example, say we have a |visible_rect_| of (10, 20), 640x360
+    // (DRM framebuffer of size 650x380); if we re-initialize the pool with
+    // (5, 2), 645x378, we can re-use the resources, but if we re-initialize
+    // with (10, 20), 100x100 we cannot (even though it's contained in the
+    // former). Hence the use of GetRectSizeFromOrigin() to calculate the
+    // visible rect for |new_frame|.
+    scoped_refptr<VideoFrame> new_frame =
+        create_frame_cb_.Run(gpu_memory_buffer_factory_, format, coded_size,
+                             gfx::Rect(GetRectSizeFromOrigin(visible_rect_)),
+                             coded_size, base::TimeDelta());
     if (!new_frame)
       return nullptr;
 
@@ -118,10 +126,6 @@
   DVLOGF(4);
   base::AutoLock auto_lock(lock_);
 
-  visible_rect_ = visible_rect;
-  natural_size_ = natural_size;
-  max_num_frames_ = max_num_frames;
-
   // Only support the Fourcc that could map to VideoPixelFormat.
   VideoPixelFormat format = fourcc.ToVideoPixelFormat();
   if (format == PIXEL_FORMAT_UNKNOWN) {
@@ -130,11 +134,18 @@
   }
 
   // If the frame layout changed we need to allocate new frames so we will clear
-  // the pool here. If only the visible or natural size changed we don't need to
-  // allocate new frames, but will just update the properties of wrapped frames
-  // returned by GetFrame().
-  // NOTE: It is assumed layout is determined by |format| and |coded_size|.
-  if (!IsSameFormat_Locked(format, coded_size)) {
+  // the pool here. If only the visible rect or natural size changed, we don't
+  // need to allocate new frames (unless the change in the visible rect causes a
+  // change in the size of the DRM framebuffer, see note below): we'll just
+  // update the properties of wrapped frames returned by GetFrame().
+  //
+  // NOTE: It is assumed layout is determined by |format|, |coded_size|, and
+  // possibly |visible_rect|. The reason for the "possibly" is that
+  // |visible_rect| is used to compute the size of the DRM framebuffer for
+  // hardware overlay purposes. The caveat is that different visible rectangles
+  // can map to the same framebuffer size, i.e., all the visible rectangles with
+  // the same bottom-right corner map to the same framebuffer size.
+  if (!IsSameFormat_Locked(format, coded_size, visible_rect)) {
     DVLOGF(4) << "The video frame format is changed. Clearing the pool.";
     free_frames_.clear();
 
@@ -142,7 +153,7 @@
     // VideoFrame that will be allocated in GetFrame() has.
     auto frame =
         create_frame_cb_.Run(gpu_memory_buffer_factory_, format, coded_size,
-                             visible_rect_, natural_size_, base::TimeDelta());
+                             visible_rect, natural_size, base::TimeDelta());
     if (!frame) {
       VLOGF(1) << "Failed to create video frame " << format << " (fourcc "
                << fourcc.ToString() << ")";
@@ -152,6 +163,10 @@
                                             frame->layout().planes());
   }
 
+  visible_rect_ = visible_rect;
+  natural_size_ = natural_size;
+  max_num_frames_ = max_num_frames;
+
   // The pool might become available because of |max_num_frames_| increased.
   // Notify the client if so.
   if (frame_available_cb_ && !IsExhausted_Locked())
@@ -219,8 +234,10 @@
   DCHECK(it != frames_in_use_.end());
   frames_in_use_.erase(it);
 
-  if (IsSameFormat_Locked(origin_frame->format(), origin_frame->coded_size()))
+  if (IsSameFormat_Locked(origin_frame->format(), origin_frame->coded_size(),
+                          origin_frame->visible_rect())) {
     InsertFreeFrame_Locked(std::move(origin_frame));
+  }
 
   if (frame_available_cb_ && !IsExhausted_Locked())
     std::move(frame_available_cb_).Run();
@@ -245,13 +262,16 @@
 
 bool PlatformVideoFramePool::IsSameFormat_Locked(
     VideoPixelFormat format,
-    const gfx::Size& coded_size) const {
+    const gfx::Size& coded_size,
+    const gfx::Rect& visible_rect) const {
   DVLOGF(4);
   lock_.AssertAcquired();
 
   return frame_layout_ &&
          frame_layout_->fourcc().ToVideoPixelFormat() == format &&
-         frame_layout_->size() == coded_size;
+         frame_layout_->size() == coded_size &&
+         GetRectSizeFromOrigin(visible_rect_) ==
+             GetRectSizeFromOrigin(visible_rect);
 }
 
 size_t PlatformVideoFramePool::GetPoolSizeForTesting() {
diff --git a/media/gpu/chromeos/platform_video_frame_pool.h b/media/gpu/chromeos/platform_video_frame_pool.h
index b594d10..4406b8f 100644
--- a/media/gpu/chromeos/platform_video_frame_pool.h
+++ b/media/gpu/chromeos/platform_video_frame_pool.h
@@ -86,7 +86,8 @@
       EXCLUSIVE_LOCKS_REQUIRED(lock_);
   size_t GetTotalNumFrames_Locked() const EXCLUSIVE_LOCKS_REQUIRED(lock_);
   bool IsSameFormat_Locked(VideoPixelFormat format,
-                           const gfx::Size& coded_size) const
+                           const gfx::Size& coded_size,
+                           const gfx::Rect& visible_rect) const
       EXCLUSIVE_LOCKS_REQUIRED(lock_);
   bool IsExhausted_Locked() EXCLUSIVE_LOCKS_REQUIRED(lock_);
 
diff --git a/media/gpu/chromeos/platform_video_frame_pool_unittest.cc b/media/gpu/chromeos/platform_video_frame_pool_unittest.cc
index ac7bb4a..94edfade 100644
--- a/media/gpu/chromeos/platform_video_frame_pool_unittest.cc
+++ b/media/gpu/chromeos/platform_video_frame_pool_unittest.cc
@@ -14,6 +14,7 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "gpu/command_buffer/common/mailbox_holder.h"
 #include "media/base/format_utils.h"
+#include "media/base/video_util.h"
 #include "media/gpu/chromeos/fourcc.h"
 #include "media/video/fake_gpu_memory_buffer.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -53,12 +54,17 @@
 
   bool Initialize(const Fourcc& fourcc) {
     constexpr gfx::Size kCodedSize(320, 240);
+    return Initialize(kCodedSize, /*visible_rect=*/gfx::Rect(kCodedSize),
+                      fourcc);
+  }
+
+  bool Initialize(const gfx::Size& coded_size,
+                  const gfx::Rect& visible_rect,
+                  const Fourcc& fourcc) {
     constexpr size_t kNumFrames = 10;
-
-    visible_rect_.set_size(kCodedSize);
-    natural_size_ = kCodedSize;
-
-    layout_ = pool_->Initialize(fourcc, kCodedSize, visible_rect_,
+    visible_rect_ = visible_rect;
+    natural_size_ = visible_rect.size();
+    layout_ = pool_->Initialize(fourcc, coded_size, visible_rect_,
                                 natural_size_, kNumFrames);
     return !!layout_;
   }
@@ -164,6 +170,64 @@
   EXPECT_EQ(0u, pool_->GetPoolSizeForTesting());
 }
 
+TEST_P(PlatformVideoFramePoolTest, InitializeWithDifferentUsableArea) {
+  const auto fourcc = Fourcc::FromVideoPixelFormat(GetParam());
+  ASSERT_TRUE(fourcc.has_value());
+
+  constexpr gfx::Size kCodedSize(640, 368);
+  constexpr gfx::Rect kInitialVisibleRect(10, 20, 300, 200);
+  ASSERT_TRUE(Initialize(kCodedSize, kInitialVisibleRect, fourcc.value()));
+  scoped_refptr<VideoFrame> frame_a = GetFrame(10);
+  scoped_refptr<VideoFrame> frame_b = GetFrame(10);
+
+  // Clear frame references to return the frames to the pool.
+  frame_a = nullptr;
+  frame_b = nullptr;
+  task_environment_.RunUntilIdle();
+
+  // Verify that both frames are in the pool.
+  EXPECT_EQ(2u, pool_->GetPoolSizeForTesting());
+
+  // Verify that requesting a frame with a different "usable area" causes the
+  // pool to get drained. The usable area is the area of the buffer starting at
+  // (0, 0) and extending to the bottom-right corner of the visible rectangle.
+  // It corresponds to the area used to import the video frame into a graphics
+  // API or to create the DRM framebuffer for hardware overlay purposes.
+  constexpr gfx::Rect kDifferentVisibleRect(5, 15, 300, 200);
+  ASSERT_EQ(kDifferentVisibleRect.size(), natural_size_);
+  ASSERT_NE(GetRectSizeFromOrigin(kInitialVisibleRect),
+            GetRectSizeFromOrigin(kDifferentVisibleRect));
+  ASSERT_TRUE(Initialize(kCodedSize, kDifferentVisibleRect, fourcc.value()));
+  scoped_refptr<VideoFrame> new_frame = GetFrame(10);
+  EXPECT_EQ(0u, pool_->GetPoolSizeForTesting());
+}
+
+TEST_P(PlatformVideoFramePoolTest, InitializeWithDifferentCodedSize) {
+  const auto fourcc = Fourcc::FromVideoPixelFormat(GetParam());
+  ASSERT_TRUE(fourcc.has_value());
+
+  constexpr gfx::Size kInitialCodedSize(640, 368);
+  constexpr gfx::Rect kVisibleRect(10, 20, 300, 200);
+  ASSERT_TRUE(Initialize(kInitialCodedSize, kVisibleRect, fourcc.value()));
+  scoped_refptr<VideoFrame> frame_a = GetFrame(10);
+  scoped_refptr<VideoFrame> frame_b = GetFrame(10);
+
+  // Clear frame references to return the frames to the pool.
+  frame_a = nullptr;
+  frame_b = nullptr;
+  task_environment_.RunUntilIdle();
+
+  // Verify that both frames are in the pool.
+  EXPECT_EQ(2u, pool_->GetPoolSizeForTesting());
+
+  // Verify that requesting a frame with a different coded size causes the pool
+  // to get drained.
+  constexpr gfx::Size kDifferentCodedSize(624, 368);
+  ASSERT_TRUE(Initialize(kDifferentCodedSize, kVisibleRect, fourcc.value()));
+  scoped_refptr<VideoFrame> new_frame = GetFrame(10);
+  EXPECT_EQ(0u, pool_->GetPoolSizeForTesting());
+}
+
 TEST_P(PlatformVideoFramePoolTest, UnwrapVideoFrame) {
   const auto fourcc = Fourcc::FromVideoPixelFormat(GetParam());
   ASSERT_TRUE(fourcc.has_value());
@@ -180,10 +244,14 @@
   EXPECT_NE(frame_1->GetGpuMemoryBuffer(), frame_3->GetGpuMemoryBuffer());
 }
 
-TEST_P(PlatformVideoFramePoolTest, InitializeWithSameFourcc) {
+TEST_P(PlatformVideoFramePoolTest,
+       InitializeWithSameFourccUsableAreaAndCodedSize) {
   const auto fourcc = Fourcc::FromVideoPixelFormat(GetParam());
   ASSERT_TRUE(fourcc.has_value());
-  ASSERT_TRUE(Initialize(fourcc.value()));
+
+  constexpr gfx::Size kCodedSize(640, 368);
+  constexpr gfx::Rect kInitialVisibleRect(kCodedSize);
+  ASSERT_TRUE(Initialize(kCodedSize, kInitialVisibleRect, fourcc.value()));
   scoped_refptr<VideoFrame> frame1 = GetFrame(10);
   gfx::GpuMemoryBufferId id1 =
       PlatformVideoFramePool::GetGpuMemoryBufferId(*frame1);
@@ -192,8 +260,17 @@
   frame1 = nullptr;
   task_environment_.RunUntilIdle();
 
-  // Request frame with the same format. The pool should not request new frames.
-  ASSERT_TRUE(Initialize(fourcc.value()));
+  // Request frame with the same format, coded size, and "usable area." To make
+  // things interesting, we change the visible rect while keeping the
+  // "usable area" constant. The usable area is the area of the buffer starting
+  // at (0, 0) and extending to the bottom-right corner of the visible
+  // rectangle. It corresponds to the area used to import the video frame into a
+  // graphics API or to create the DRM framebuffer for hardware overlay
+  // purposes. The pool should not request new frames.
+  constexpr gfx::Rect kDifferentVisibleRect(10, 20, 630, 348);
+  ASSERT_EQ(GetRectSizeFromOrigin(kInitialVisibleRect),
+            GetRectSizeFromOrigin(kDifferentVisibleRect));
+  ASSERT_TRUE(Initialize(kCodedSize, kDifferentVisibleRect, fourcc.value()));
 
   scoped_refptr<VideoFrame> frame2 = GetFrame(20);
   gfx::GpuMemoryBufferId id2 =
diff --git a/media/gpu/chromeos/platform_video_frame_utils.cc b/media/gpu/chromeos/platform_video_frame_utils.cc
index 7688d64a..b44eb15 100644
--- a/media/gpu/chromeos/platform_video_frame_utils.cc
+++ b/media/gpu/chromeos/platform_video_frame_utils.cc
@@ -20,6 +20,7 @@
 #include "media/base/format_utils.h"
 #include "media/base/scopedfd_helper.h"
 #include "media/base/video_frame_layout.h"
+#include "media/base/video_util.h"
 #include "media/gpu/buffer_validation.h"
 #include "media/gpu/macros.h"
 #include "ui/gfx/gpu_memory_buffer.h"
@@ -33,6 +34,7 @@
     gpu::GpuMemoryBufferFactory* factory,
     VideoPixelFormat pixel_format,
     const gfx::Size& coded_size,
+    const gfx::Rect& visible_rect,
     gfx::BufferUsage buffer_usage) {
   DCHECK(factory);
   gfx::GpuMemoryBufferHandle gmb_handle;
@@ -52,8 +54,9 @@
   // TODO(hiroh): Rename the client id to more generic one.
   gmb_handle = factory->CreateGpuMemoryBuffer(
       gfx::GpuMemoryBufferId(gpu_memory_buffer_id), coded_size,
-      /*framebuffer_size=*/coded_size, *buffer_format, buffer_usage,
-      gpu::kPlatformVideoFramePoolClientId, gfx::kNullAcceleratedWidget);
+      /*framebuffer_size=*/GetRectSizeFromOrigin(visible_rect), *buffer_format,
+      buffer_usage, gpu::kPlatformVideoFramePoolClientId,
+      gfx::kNullAcceleratedWidget);
   DCHECK(gmb_handle.is_null() || gmb_handle.type != gfx::NATIVE_PIXMAP ||
          VideoFrame::NumPlanes(pixel_format) ==
              gmb_handle.native_pixmap_handle.planes.size());
@@ -71,8 +74,8 @@
     base::TimeDelta timestamp,
     gfx::BufferUsage buffer_usage) {
   DCHECK(factory);
-  auto gmb_handle = AllocateGpuMemoryBufferHandle(factory, pixel_format,
-                                                  coded_size, buffer_usage);
+  auto gmb_handle = AllocateGpuMemoryBufferHandle(
+      factory, pixel_format, coded_size, visible_rect, buffer_usage);
   if (gmb_handle.is_null())
     return nullptr;
 
@@ -113,8 +116,8 @@
     base::TimeDelta timestamp,
     gfx::BufferUsage buffer_usage) {
   DCHECK(factory);
-  auto gmb_handle = AllocateGpuMemoryBufferHandle(factory, pixel_format,
-                                                  coded_size, buffer_usage);
+  auto gmb_handle = AllocateGpuMemoryBufferHandle(
+      factory, pixel_format, coded_size, visible_rect, buffer_usage);
   if (gmb_handle.is_null())
     return nullptr;
 
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 6dc4aab..2baa9b1 100644
--- a/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm.cc.tmpl
+++ b/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm.cc.tmpl
@@ -93,7 +93,6 @@
 {%-       if method.response_parameters != None %}
     mojolpm::GetContext()->AddInstance<{{mojom_type}}::{{method.name}}Callback>(std::move(callback));
 {%-       endif %}
-    mojolpm::GetContext()->NextAction();
   }
 {%-   endfor %}
 };
@@ -251,16 +250,16 @@
 {%-   set mojom_type = interface|get_qualified_name_for_kind(flatten_nested_kind=True) %}
 {%-   set proto_type = "::mojolpm" ~ (interface|get_qualified_name_for_kind(flatten_nested_kind=True)) %}
 {%-   if interface.methods %}
-bool HandleRemoteCall(const {{proto_type}}::RemoteCall& input) {
+bool HandleRemoteAction(const {{proto_type}}::RemoteAction& input) {
   bool result = true;
 
   switch (input.method_case()) {
 {%-     for method in interface.methods %}
-    case {{proto_type}}::RemoteCall::k{{("m_" ~ method.name)|under_to_camel(digits_split=True)}}: {
+    case {{proto_type}}::RemoteAction::k{{("m_" ~ method.name)|under_to_camel(digits_split=True)}}: {
       result = HandleRemoteCall(input.id(), input.{{("m" ~ method.name)|camel_to_under}}());
     } break;
 {%-     endfor %}
-    case {{proto_type}}::RemoteCall::kReset: {
+    case {{proto_type}}::RemoteAction::kReset: {
       mojolpm::GetContext()->GetAndRemoveInstance<::mojo::Remote<{{mojom_type}}>>(input.id());
     } break;
 
@@ -272,16 +271,16 @@
   return result;
 }
 
-bool HandleAssociatedRemoteCall(const {{proto_type}}::AssociatedRemoteCall& input) {
+bool HandleAssociatedRemoteAction(const {{proto_type}}::AssociatedRemoteAction& input) {
   bool result = true;
 
   switch (input.method_case()) {
 {%-     for method in interface.methods %}
-    case {{proto_type}}::AssociatedRemoteCall::k{{("m_" ~ method.name)|under_to_camel(digits_split=True)}}: {
+    case {{proto_type}}::AssociatedRemoteAction::k{{("m_" ~ method.name)|under_to_camel(digits_split=True)}}: {
       result = HandleAssociatedRemoteCall(input.id(), input.{{("m" ~ method.name)|camel_to_under}}());
     } break;
 {%-     endfor %}
-    case {{proto_type}}::AssociatedRemoteCall::kReset: {
+    case {{proto_type}}::AssociatedRemoteAction::kReset: {
       mojolpm::GetContext()->GetAndRemoveInstance<::mojo::AssociatedRemote<{{mojom_type}}>>(input.id());
     } break;
 
@@ -293,12 +292,12 @@
   return result;
 }
 
-bool HandleResponse(
-    const {{proto_type}}::ReceiverResponse& input) {
+bool HandleReceiverAction(
+    const {{proto_type}}::ReceiverAction& input) {
   bool result = true;
   switch (input.response_case()) {
 {%-     for method in interface.methods %}
-    case {{proto_type}}::ReceiverResponse::k{{("m_" ~ method.name ~ "_response")|under_to_camel(digits_split=True)}}: {
+    case {{proto_type}}::ReceiverAction::k{{("m_" ~ method.name ~ "_response")|under_to_camel(digits_split=True)}}: {
       result = HandleResponse(input.id(), input.{{("m" ~ method.name ~ "_response")|camel_to_under}}());
     } break;
 {%-     endfor %}
@@ -326,7 +325,6 @@
 {{ util.add_instance(kind, 'param_' ~ name, False) }}
 {%-         endfor %}
   mojolpmdbg("{{interface.name}}.{{method.name}}Callback\n");
-  mojolpm::GetContext()->NextAction();
 }{{"\n"-}}
 {%-       endif %}
 template <typename T>
diff --git a/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm.h.tmpl b/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm.h.tmpl
index dc18519e..b21d0ac6 100644
--- a/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm.h.tmpl
+++ b/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm.h.tmpl
@@ -177,14 +177,14 @@
 {%-   set mojom_type = interface|get_qualified_name_for_kind(flatten_nested_kind=True) %}
 {%-   set proto_type = "::mojolpm" ~ (interface|get_qualified_name_for_kind(flatten_nested_kind=True)) %}
 {%-   if interface.methods %}
-bool HandleRemoteCall(
-  const {{proto_type}}::RemoteCall& input);
+bool HandleRemoteAction(
+  const {{proto_type}}::RemoteAction& input);
 
-bool HandleAssociatedRemoteCall(
-  const {{proto_type}}::AssociatedRemoteCall& input);
+bool HandleAssociatedRemoteAction(
+  const {{proto_type}}::AssociatedRemoteAction& input);
 
-bool HandleResponse(
-  const {{proto_type}}::ReceiverResponse& response);{{"\n"-}}
+bool HandleReceiverAction(
+  const {{proto_type}}::ReceiverAction& input);{{"\n"-}}
 {%-     for method in interface.methods %}
 bool HandleRemoteCall(
   uint32_t instance_id,
diff --git a/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm.proto.tmpl b/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm.proto.tmpl
index 9da6341f..df0ccb2e4 100644
--- a/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm.proto.tmpl
+++ b/mojo/public/tools/bindings/generators/mojolpm_templates/mojolpm.proto.tmpl
@@ -316,7 +316,7 @@
 {%-   endfor%}
 
 {%-   if interface.methods|length %}
-  message RemoteCall {
+  message RemoteAction {
     required uint32 id = 1;
 
     oneof method {
@@ -327,7 +327,7 @@
     }
   }
 
-  message AssociatedRemoteCall {
+  message AssociatedRemoteAction {
     required uint32 id = 1;
 
     oneof method {
@@ -338,7 +338,7 @@
     }
   }
 
-  message ReceiverResponse {
+  message ReceiverAction {
     required uint32 id = 1;
 
     oneof response {
diff --git a/remoting/ios/app/client_connection_view_controller.mm b/remoting/ios/app/client_connection_view_controller.mm
index d6dbbddc..17294287 100644
--- a/remoting/ios/app/client_connection_view_controller.mm
+++ b/remoting/ios/app/client_connection_view_controller.mm
@@ -626,7 +626,7 @@
       // If the session is closed by the host, just go back to the host list and
       // show a toast.
       state = ClientViewClosed;
-      [MDCSnackbarManager
+      [MDCSnackbarManager.defaultManager
           showMessage:[MDCSnackbarMessage
                           messageWithText:l10n_util::GetNSString(
                                               IDS_MESSAGE_SESSION_FINISHED)]];
diff --git a/remoting/ios/app/remoting_view_controller.mm b/remoting/ios/app/remoting_view_controller.mm
index 9655a7c..2b9bf19f 100644
--- a/remoting/ios/app/remoting_view_controller.mm
+++ b/remoting/ios/app/remoting_view_controller.mm
@@ -276,19 +276,20 @@
   if (![cell.hostInfo isOnline]) {
     MDCSnackbarMessage* message = [[MDCSnackbarMessage alloc] init];
     message.text = l10n_util::GetNSString(IDS_HOST_OFFLINE_TOOLTIP);
-    [MDCSnackbarManager showMessage:message];
+    [MDCSnackbarManager.defaultManager showMessage:message];
     return;
   }
 
   if (GetConnectionType() == ConnectionType::NONE) {
-    [MDCSnackbarManager
+    [MDCSnackbarManager.defaultManager
         showMessage:[MDCSnackbarMessage
                         messageWithText:l10n_util::GetNSString(
                                             IDS_ERROR_NETWORK_ERROR)]];
     return;
   }
 
-  [MDCSnackbarManager dismissAndCallCompletionBlocksWithCategory:nil];
+  [MDCSnackbarManager.defaultManager
+      dismissAndCallCompletionBlocksWithCategory:nil];
   ClientConnectionViewController* clientConnectionViewController =
       [[ClientConnectionViewController alloc] initWithHostInfo:cell.hostInfo];
   [self.navigationController pushViewController:clientConnectionViewController
diff --git a/remoting/ios/app/side_menu_items.mm b/remoting/ios/app/side_menu_items.mm
index d1ca39a..bf6a385 100644
--- a/remoting/ios/app/side_menu_items.mm
+++ b/remoting/ios/app/side_menu_items.mm
@@ -61,7 +61,7 @@
                      NSString* message =
                          [NSString stringWithFormat:@"Using WebRTC: %s",
                                                     newValue ? "Yes" : "No"];
-                     [MDCSnackbarManager
+                     [MDCSnackbarManager.defaultManager
                          showMessage:[MDCSnackbarMessage
                                          messageWithText:message]];
                    }],
diff --git a/remoting/ios/app/user_status_presenter.mm b/remoting/ios/app/user_status_presenter.mm
index 41fa53cf..1b09141f 100644
--- a/remoting/ios/app/user_status_presenter.mm
+++ b/remoting/ios/app/user_status_presenter.mm
@@ -80,7 +80,7 @@
   MDCSnackbarMessage* message = [[MDCSnackbarMessage alloc] init];
   message.text = l10n_util::GetNSStringF(
       IDS_LOG_IN_ACCOUNT_DESCRIPTION, base::SysNSStringToUTF16(user.userEmail));
-  [MDCSnackbarManager showMessage:message];
+  [MDCSnackbarManager.defaultManager showMessage:message];
 }
 
 @end
diff --git a/remoting/ios/facade/remoting_oauth_authentication.mm b/remoting/ios/facade/remoting_oauth_authentication.mm
index 4cc1e798..3ea82e52 100644
--- a/remoting/ios/facade/remoting_oauth_authentication.mm
+++ b/remoting/ios/facade/remoting_oauth_authentication.mm
@@ -145,7 +145,7 @@
     } else {
       LOG(ERROR) << "Failed to fetch access token from authorization code. ("
                  << status << ")";
-      [MDCSnackbarManager
+      [MDCSnackbarManager.defaultManager
           showMessage:
               [MDCSnackbarMessage
                   messageWithText:@"Authentication Failed. Please try again."]];
diff --git a/services/network/cors/preflight_controller.cc b/services/network/cors/preflight_controller.cc
index 1ded5bb..11e1a9fa 100644
--- a/services/network/cors/preflight_controller.cc
+++ b/services/network/cors/preflight_controller.cc
@@ -89,6 +89,7 @@
   preflight_request->destination = request.destination;
   preflight_request->referrer = request.referrer;
   preflight_request->referrer_policy = request.referrer_policy;
+  preflight_request->mode = mojom::RequestMode::kCors;
 
   preflight_request->credentials_mode = mojom::CredentialsMode::kOmit;
   preflight_request->load_flags = RetrieveCacheFlags(request.load_flags);
diff --git a/services/network/cors/preflight_controller_unittest.cc b/services/network/cors/preflight_controller_unittest.cc
index b937483..effbbabe 100644
--- a/services/network/cors/preflight_controller_unittest.cc
+++ b/services/network/cors/preflight_controller_unittest.cc
@@ -363,6 +363,10 @@
     network::mojom::URLLoaderFactoryParamsPtr params =
         network::mojom::URLLoaderFactoryParams::New();
     params->process_id = mojom::kBrowserProcessId;
+    // We use network::CorsURLLoaderFactory for "internal" URLLoaderFactory
+    // used by the PreflightController. Hence here we disable CORS as otherwise
+    // the URLLoader would create a CORS-preflight for the preflight request.
+    params->disable_web_security = true;
     params->is_corb_enabled = false;
     network_context_remote_->CreateURLLoaderFactory(
         url_loader_factory_remote_.BindNewPipeAndPassReceiver(),
diff --git a/services/tracing/public/cpp/perfetto/trace_event_data_source.cc b/services/tracing/public/cpp/perfetto/trace_event_data_source.cc
index d0b9098a..ea32fad 100644
--- a/services/tracing/public/cpp/perfetto/trace_event_data_source.cc
+++ b/services/tracing/public/cpp/perfetto/trace_event_data_source.cc
@@ -1023,11 +1023,11 @@
     const char* histogram_name,
     uint64_t name_hash,
     base::HistogramBase::Sample sample) {
-  bool privacy_filtering_enabled =
-      TraceEventDataSource::GetInstance()->IsPrivacyFilteringEnabled();
   TRACE_EVENT_INSTANT(
       TRACE_DISABLED_BY_DEFAULT("histogram_samples"), "HistogramSample",
       TRACE_EVENT_SCOPE_THREAD, [&](perfetto::EventContext ctx) {
+        bool privacy_filtering_enabled =
+            TraceEventDataSource::GetInstance()->IsPrivacyFilteringEnabled();
         perfetto::protos::pbzero::ChromeHistogramSample* new_sample =
             ctx.event()->set_chrome_histogram_sample();
         new_sample->set_name_hash(name_hash);
@@ -1041,11 +1041,11 @@
 void TraceEventDataSource::OnUserActionSampleCallback(
     const std::string& action,
     base::TimeTicks action_time) {
-  bool privacy_filtering_enabled =
-      TraceEventDataSource::GetInstance()->IsPrivacyFilteringEnabled();
   TRACE_EVENT_INSTANT(
       TRACE_DISABLED_BY_DEFAULT("user_action_samples"), "UserAction",
       TRACE_EVENT_SCOPE_GLOBAL, [&](perfetto::EventContext ctx) {
+        bool privacy_filtering_enabled =
+            TraceEventDataSource::GetInstance()->IsPrivacyFilteringEnabled();
         perfetto::protos::pbzero::ChromeUserEvent* new_sample =
             ctx.event()->set_chrome_user_event();
         if (!privacy_filtering_enabled) {
@@ -1090,6 +1090,10 @@
 }
 
 void TraceEventDataSource::EmitTrackDescriptor() {
+  // Prevent reentrancy into tracing code (flushing the trace writer sends a
+  // mojo message which can result in additional trace events).
+  AutoThreadLocalBoolean thread_is_in_trace_event(GetThreadIsInTraceEventTLS());
+
   AutoLockWithDeferredTaskPosting lock(lock_);
 
   int process_id = TraceLog::GetInstance()->process_id();
diff --git a/third_party/android_deps/BUILD.gn b/third_party/android_deps/BUILD.gn
index 69feec3..a513b84 100644
--- a/third_party/android_deps/BUILD.gn
+++ b/third_party/android_deps/BUILD.gn
@@ -1139,14 +1139,6 @@
 }
 
 # This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
-java_prebuilt("com_android_tools_desugar_jdk_libs_configuration_java") {
-  jar_path = "libs/com_android_tools_desugar_jdk_libs_configuration/desugar_jdk_libs_configuration-1.0.10.jar"
-  output_name = "com_android_tools_desugar_jdk_libs_configuration"
-  supports_android = true
-  enable_bytecode_checks = false
-}
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
 android_aar_prebuilt("google_play_services_auth_java") {
   aar_path = "libs/com_google_android_gms_play_services_auth/play-services-auth-17.0.0.aar"
   info_path = "libs/com_google_android_gms_play_services_auth/com_google_android_gms_play_services_auth.info"
diff --git a/third_party/android_deps/buildSrc/src/main/groovy/ChromiumDepGraph.groovy b/third_party/android_deps/buildSrc/src/main/groovy/ChromiumDepGraph.groovy
index cb815bbf..2ca5ee5c 100644
--- a/third_party/android_deps/buildSrc/src/main/groovy/ChromiumDepGraph.groovy
+++ b/third_party/android_deps/buildSrc/src/main/groovy/ChromiumDepGraph.groovy
@@ -36,7 +36,8 @@
             generateTarget: false),
         'com_android_tools_desugar_jdk_libs_configuration': new PropertyOverride(
             licensePath: "licenses/desugar_jdk_libs_configuration.txt",
-            licenseName: "BSD 3-Clause"),
+            licenseName: "BSD 3-Clause",
+            generateTarget: false),
         'backport_util_concurrent_backport_util_concurrent': new PropertyOverride(
             licensePath: "licenses/CC01.0.txt",
             licenseName: "CC0 1.0"),
diff --git a/third_party/blink/public/common/loader/loading_behavior_flag.h b/third_party/blink/public/common/loader/loading_behavior_flag.h
index e49df091..7b35e73a 100644
--- a/third_party/blink/public/common/loader/loading_behavior_flag.h
+++ b/third_party/blink/public/common/loader/loading_behavior_flag.h
@@ -37,16 +37,12 @@
   kLoadingBehaviorSubresourceFilterMatch = 1 << 6,
   // Indicates that the page is an AMP document, with <html amp> tag.
   kLoadingBehaviorAmpDocumentLoaded = 1 << 7,
-  // Indicates that font preloading (via <link rel=preload> or Font JS API) has
-  // occurred before the first rendering cycle begins. Used to study the
-  // effects of delaying the first rendering cycle for web font loading.
-  kLoadingBehaviorFontPreloadStartedBeforeRendering = 1 << 8,
   // Indicates that the page uses the Next.js JavaScript framework (via a
   // window variable)
-  kLoadingBehaviorNextJSFrameworkUsed = 1 << 9,
+  kLoadingBehaviorNextJSFrameworkUsed = 1 << 8,
   // Indicates that an async script was ready to execute before the script
   // element's node document has finished parsing.
-  kLoadingBehaviorAsyncScriptReadyBeforeDocumentFinishedParsing = 1 << 10,
+  kLoadingBehaviorAsyncScriptReadyBeforeDocumentFinishedParsing = 1 << 9,
 };
 
 }  // namespace blink
diff --git a/third_party/blink/public/mojom/page/page.mojom b/third_party/blink/public/mojom/page/page.mojom
index 2a67beb1..184e6a5 100644
--- a/third_party/blink/public/mojom/page/page.mojom
+++ b/third_party/blink/public/mojom/page/page.mojom
@@ -6,6 +6,15 @@
 import "mojo/public/mojom/base/time.mojom";
 import "third_party/blink/public/mojom/page/page_visibility_state.mojom";
 
+enum PagehideDispatch {
+  // We haven't dispatched pagehide and should do so when appropriate.
+  kNotDispatched,
+  // We've dispatched pagehide with persisted == false.
+  kDispatchedNotPersisted,
+  // We've dispatched pagehide with persisted == true.
+  kDispatchedPersisted
+};
+
 // We need this structure to be able to atomically update the state of the page
 // to avoid it being in an inconsistent state (e.g. frozen but visible).
 // TODO(yuzus): Replace
@@ -15,6 +24,7 @@
   bool is_frozen;
   PageVisibilityState visibility;
   bool is_in_back_forward_cache;
+  PagehideDispatch pagehide_dispatch;
 };
 
 // Used for broadcast messages from browser to renderer for messages that need
diff --git a/third_party/blink/public/platform/web_runtime_features.h b/third_party/blink/public/platform/web_runtime_features.h
index e04c16a..3804680 100644
--- a/third_party/blink/public/platform/web_runtime_features.h
+++ b/third_party/blink/public/platform/web_runtime_features.h
@@ -251,6 +251,8 @@
   BLINK_PLATFORM_EXPORT static void EnableContentIndex(bool);
   BLINK_PLATFORM_EXPORT static void EnableRestrictGamepadAccess(bool);
 
+  BLINK_PLATFORM_EXPORT static void EnableCompositingOptimizations(bool);
+
  private:
   WebRuntimeFeatures();
 };
diff --git a/third_party/blink/public/web/web_frame.h b/third_party/blink/public/web/web_frame.h
index b0e8b7c..521f5f6 100644
--- a/third_party/blink/public/web/web_frame.h
+++ b/third_party/blink/public/web/web_frame.h
@@ -33,6 +33,7 @@
 
 #include <memory>
 #include "cc/paint/paint_canvas.h"
+#include "third_party/blink/public/common/tokens/tokens.h"
 #include "third_party/blink/public/mojom/frame/tree_scope_type.mojom-shared.h"
 #include "third_party/blink/public/mojom/security_context/insecure_request_policy.mojom-shared.h"
 #include "third_party/blink/public/platform/web_common.h"
@@ -72,7 +73,9 @@
   // Returns the number of live WebFrame objects, used for leak checking.
   static int InstanceCount();
 
+  // TODO(crbug.com/1096617): Remove the UnguessableToken version of this.
   static WebFrame* FromFrameToken(const base::UnguessableToken&);
+  static WebFrame* FromFrameToken(const FrameToken&);
 
   virtual bool IsWebLocalFrame() const = 0;
   virtual WebLocalFrame* ToWebLocalFrame() = 0;
@@ -155,6 +158,7 @@
   // This identifier represents the stable identifier between a
   // LocalFrame  <--> RenderFrameHostImpl or a
   // RemoteFrame <--> RenderFrameProxyHost in the browser process.
+  // TODO(crbug.com/1096617): Make this return a FrameToken instead.
   const base::UnguessableToken& GetFrameToken() const { return frame_token_; }
 
 #if INSIDE_BLINK
diff --git a/third_party/blink/public/web/web_local_frame.h b/third_party/blink/public/web/web_local_frame.h
index 9bda8d2f..1f67a12 100644
--- a/third_party/blink/public/web/web_local_frame.h
+++ b/third_party/blink/public/web/web_local_frame.h
@@ -172,6 +172,10 @@
 
   // Basic properties ---------------------------------------------------
 
+  LocalFrameToken GetLocalFrameToken() const {
+    return LocalFrameToken(GetFrameToken());
+  }
+
   virtual WebDocument GetDocument() const = 0;
 
   // The name of this frame. If no name is given, empty string is returned.
diff --git a/third_party/blink/public/web/web_remote_frame.h b/third_party/blink/public/web/web_remote_frame.h
index bf1ea8d..e9f0910 100644
--- a/third_party/blink/public/web/web_remote_frame.h
+++ b/third_party/blink/public/web/web_remote_frame.h
@@ -7,6 +7,7 @@
 
 #include "services/network/public/mojom/content_security_policy.mojom-shared.h"
 #include "third_party/blink/public/common/feature_policy/feature_policy.h"
+#include "third_party/blink/public/common/tokens/tokens.h"
 #include "third_party/blink/public/mojom/ad_tagging/ad_frame.mojom-shared.h"
 #include "third_party/blink/public/mojom/frame/frame_owner_element_type.mojom-shared.h"
 #include "third_party/blink/public/mojom/frame/user_activation_notification_type.mojom-shared.h"
@@ -161,6 +162,10 @@
   // session restore state for this frame.
   virtual WebString UniqueName() const = 0;
 
+  RemoteFrameToken GetRemoteFrameToken() const {
+    return RemoteFrameToken(GetFrameToken());
+  }
+
  protected:
   explicit WebRemoteFrame(mojom::TreeScopeType scope,
                           const base::UnguessableToken& frame_token)
diff --git a/third_party/blink/public/web/web_view.h b/third_party/blink/public/web/web_view.h
index 9e8f2404a..f855029 100644
--- a/third_party/blink/public/web/web_view.h
+++ b/third_party/blink/public/web/web_view.h
@@ -403,6 +403,21 @@
                                   bool is_initial_state) = 0;
   virtual mojom::PageVisibilityState GetVisibilityState() = 0;
 
+  // PageLifecycleState ----------------------------------------------------
+
+  // Sets the |visibility| and |pagehide_dispatch| properties for the
+  // PageLifecycleState of this page from a new page's commit. Should only be
+  // called from a main-frame same-site navigation where we did a proactive
+  // BrowsingInstance swap and we're reusing the old page's process.
+  // Note that unlike SetPageLifecycleState in PageBroadcast/WebViewImpl, we
+  // don't need to pass a callback here to notify the browser site that the
+  // PageLifecycleState has been successfully updated.
+  // TODO(rakina): When it's possible to pass PageLifecycleState here, pass
+  // PageLifecycleState instead.
+  virtual void SetPageLifecycleStateFromNewPageCommit(
+      mojom::PageVisibilityState visibility,
+      mojom::PagehideDispatch pagehide_dispatch) = 0;
+
   // Page Importance Signals ----------------------------------------------
 
   virtual WebPageImportanceSignals* PageImportanceSignals() { return nullptr; }
diff --git a/third_party/blink/renderer/bindings/scripts/bind_gen/codegen_expr.py b/third_party/blink/renderer/bindings/scripts/bind_gen/codegen_expr.py
index 1b2db92..a229a6c 100644
--- a/third_party/blink/renderer/bindings/scripts/bind_gen/codegen_expr.py
+++ b/third_party/blink/renderer/bindings/scripts/bind_gen/codegen_expr.py
@@ -185,7 +185,8 @@
     #        (or
     #         (and feature_selector.IsAll()  # 1st phase; all enabled
     #              cond_exposed_term
-    #              feature_enabled_term)
+    #              (or feature_enabled_term
+    #                  context_enabled_term))
     #         (or exposed_selector_term      # 2nd phase; any selected
     #             feature_selector_term)))
     # where
@@ -193,11 +194,13 @@
     #   uncond_exposed_term represents [Exposed=(G1, G2)]
     #   cond_exposed_term represents [Exposed(G1 F1, G2 F2)]
     #   feature_enabled_term represents [RuntimeEnabled=(F1, F2)]
+    #   context_enabled_term represents [ContextEnabled=F1]
     #   exposed_selector_term represents [Exposed(G1 F1, G2 F2)]
     #   feature_selector_term represents [RuntimeEnabled=(F1, F2)]
     uncond_exposed_terms = []
     cond_exposed_terms = []
     feature_enabled_terms = []
+    context_enabled_terms = []
     exposed_selector_terms = []
     feature_selector_names = []  # Will turn into feature_selector.IsAnyOf(...)
 
@@ -276,7 +279,7 @@
             lambda feature: _Expr(
                 "${{context_feature_settings}}->is{}Enabled()".format(
                     feature)), exposure.context_enabled_features)
-        feature_enabled_terms.append(
+        context_enabled_terms.append(
             expr_and([_Expr("${context_feature_settings}"),
                       expr_or(terms)]))
 
@@ -297,8 +300,13 @@
     all_enabled_terms = [_Expr("${feature_selector}.IsAll()")]
     if cond_exposed_terms:
         all_enabled_terms.append(expr_or(cond_exposed_terms))
-    if feature_enabled_terms:
-        all_enabled_terms.append(expr_or(feature_enabled_terms))
+    if feature_enabled_terms or context_enabled_terms:
+        terms = []
+        if feature_enabled_terms:
+            terms.append(expr_and(feature_enabled_terms))
+        if context_enabled_terms:
+            terms.append(expr_or(context_enabled_terms))
+        all_enabled_terms.append(expr_or(terms))
 
     selector_terms = []
     if exposed_selector_terms:
diff --git a/third_party/blink/renderer/bindings/scripts/web_idl/exposure.py b/third_party/blink/renderer/bindings/scripts/web_idl/exposure.py
index 46e2e45..abaeef3 100644
--- a/third_party/blink/renderer/bindings/scripts/web_idl/exposure.py
+++ b/third_party/blink/renderer/bindings/scripts/web_idl/exposure.py
@@ -76,7 +76,7 @@
     def runtime_enabled_features(self):
         """
         Returns a list of runtime enabled features.  This construct is exposed
-        only when one of these features is enabled.
+        only when all these features are enabled.
         """
         return self._runtime_enabled_features
 
diff --git a/third_party/blink/renderer/core/css/cssom/computed_style_property_map.h b/third_party/blink/renderer/core/css/cssom/computed_style_property_map.h
index eb0cd77..0769412 100644
--- a/third_party/blink/renderer/core/css/cssom/computed_style_property_map.h
+++ b/third_party/blink/renderer/core/css/cssom/computed_style_property_map.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_COMPUTED_STYLE_PROPERTY_MAP_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_COMPUTED_STYLE_PROPERTY_MAP_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/css/css_computed_style_declaration.h"
 #include "third_party/blink/renderer/core/css/css_selector.h"
 #include "third_party/blink/renderer/core/css/cssom/style_property_map_read_only_main_thread.h"
@@ -28,6 +27,8 @@
   ComputedStylePropertyMap(Node* node, const String& pseudo_element = String())
       : pseudo_id_(CSSSelector::ParsePseudoId(pseudo_element, node)),
         node_(node) {}
+  ComputedStylePropertyMap(const ComputedStylePropertyMap&) = delete;
+  ComputedStylePropertyMap& operator=(const ComputedStylePropertyMap&) = delete;
 
   void Trace(Visitor* visitor) const override {
     visitor->Trace(node_);
@@ -58,7 +59,6 @@
 
   Node* StyledNode() const;
   const ComputedStyle* UpdateStyle() const;
-  DISALLOW_COPY_AND_ASSIGN(ComputedStylePropertyMap);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/cross_thread_color_value.h b/third_party/blink/renderer/core/css/cssom/cross_thread_color_value.h
index 95a64a4..de23b7b 100644
--- a/third_party/blink/renderer/core/css/cssom/cross_thread_color_value.h
+++ b/third_party/blink/renderer/core/css/cssom/cross_thread_color_value.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CROSS_THREAD_COLOR_VALUE_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CROSS_THREAD_COLOR_VALUE_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/css/css_primitive_value.h"
 #include "third_party/blink/renderer/core/css/cssom/cross_thread_style_value.h"
@@ -19,6 +18,8 @@
 class CORE_EXPORT CrossThreadColorValue final : public CrossThreadStyleValue {
  public:
   explicit CrossThreadColorValue(Color value) : value_(value) {}
+  CrossThreadColorValue(const CrossThreadColorValue&) = delete;
+  CrossThreadColorValue& operator=(const CrossThreadColorValue&) = delete;
   ~CrossThreadColorValue() override = default;
 
   StyleValueType GetType() const override { return StyleValueType::kColorType; }
@@ -33,7 +34,6 @@
   friend class CrossThreadStyleValueTest;
 
   Color value_;
-  DISALLOW_COPY_AND_ASSIGN(CrossThreadColorValue);
 };
 
 template <>
diff --git a/third_party/blink/renderer/core/css/cssom/cross_thread_keyword_value.h b/third_party/blink/renderer/core/css/cssom/cross_thread_keyword_value.h
index a423ca825..1bec757 100644
--- a/third_party/blink/renderer/core/css/cssom/cross_thread_keyword_value.h
+++ b/third_party/blink/renderer/core/css/cssom/cross_thread_keyword_value.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CROSS_THREAD_KEYWORD_VALUE_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CROSS_THREAD_KEYWORD_VALUE_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/css/cssom/cross_thread_style_value.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
@@ -19,6 +18,8 @@
  public:
   explicit CrossThreadKeywordValue(const String& keyword)
       : keyword_value_(keyword) {}
+  CrossThreadKeywordValue(const CrossThreadKeywordValue&) = delete;
+  CrossThreadKeywordValue& operator=(const CrossThreadKeywordValue&) = delete;
   ~CrossThreadKeywordValue() override = default;
 
   StyleValueType GetType() const override {
@@ -34,7 +35,6 @@
   friend class CrossThreadStyleValueTest;
 
   String keyword_value_;
-  DISALLOW_COPY_AND_ASSIGN(CrossThreadKeywordValue);
 };
 
 template <>
diff --git a/third_party/blink/renderer/core/css/cssom/cross_thread_style_value.h b/third_party/blink/renderer/core/css/cssom/cross_thread_style_value.h
index 4b213fc0..65cfb7e 100644
--- a/third_party/blink/renderer/core/css/cssom/cross_thread_style_value.h
+++ b/third_party/blink/renderer/core/css/cssom/cross_thread_style_value.h
@@ -7,7 +7,6 @@
 
 #include <memory>
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/core_export.h"
 
 namespace blink {
@@ -26,6 +25,8 @@
     kColorType,
   };
 
+  CrossThreadStyleValue(const CrossThreadStyleValue&) = delete;
+  CrossThreadStyleValue& operator=(const CrossThreadStyleValue&) = delete;
   virtual ~CrossThreadStyleValue() = default;
 
   virtual StyleValueType GetType() const = 0;
@@ -36,9 +37,6 @@
 
  protected:
   CrossThreadStyleValue() = default;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(CrossThreadStyleValue);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/cross_thread_unit_value.h b/third_party/blink/renderer/core/css/cssom/cross_thread_unit_value.h
index ab279b5..852512af 100644
--- a/third_party/blink/renderer/core/css/cssom/cross_thread_unit_value.h
+++ b/third_party/blink/renderer/core/css/cssom/cross_thread_unit_value.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CROSS_THREAD_UNIT_VALUE_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CROSS_THREAD_UNIT_VALUE_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/css/css_primitive_value.h"
 #include "third_party/blink/renderer/core/css/cssom/cross_thread_style_value.h"
@@ -19,6 +18,8 @@
  public:
   explicit CrossThreadUnitValue(double value, CSSPrimitiveValue::UnitType unit)
       : value_(value), unit_(unit) {}
+  CrossThreadUnitValue(const CrossThreadUnitValue&) = delete;
+  CrossThreadUnitValue& operator=(const CrossThreadUnitValue&) = delete;
   ~CrossThreadUnitValue() override = default;
 
   StyleValueType GetType() const override { return StyleValueType::kUnitType; }
@@ -34,7 +35,6 @@
 
   double value_;
   CSSPrimitiveValue::UnitType unit_;
-  DISALLOW_COPY_AND_ASSIGN(CrossThreadUnitValue);
 };
 
 template <>
diff --git a/third_party/blink/renderer/core/css/cssom/cross_thread_unparsed_value.h b/third_party/blink/renderer/core/css/cssom/cross_thread_unparsed_value.h
index 2a8bd697..6f030cd 100644
--- a/third_party/blink/renderer/core/css/cssom/cross_thread_unparsed_value.h
+++ b/third_party/blink/renderer/core/css/cssom/cross_thread_unparsed_value.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CROSS_THREAD_UNPARSED_VALUE_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CROSS_THREAD_UNPARSED_VALUE_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/css/cssom/cross_thread_style_value.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
@@ -19,6 +18,8 @@
     : public CrossThreadStyleValue {
  public:
   explicit CrossThreadUnparsedValue(const String& value) : value_(value) {}
+  CrossThreadUnparsedValue(const CrossThreadUnparsedValue&) = delete;
+  CrossThreadUnparsedValue& operator=(const CrossThreadUnparsedValue&) = delete;
   ~CrossThreadUnparsedValue() override = default;
 
   StyleValueType GetType() const override {
@@ -34,7 +35,6 @@
   friend class CrossThreadStyleValueTest;
 
   String value_;
-  DISALLOW_COPY_AND_ASSIGN(CrossThreadUnparsedValue);
 };
 
 template <>
diff --git a/third_party/blink/renderer/core/css/cssom/cross_thread_unsupported_value.h b/third_party/blink/renderer/core/css/cssom/cross_thread_unsupported_value.h
index fa7eb2c..67cb1042 100644
--- a/third_party/blink/renderer/core/css/cssom/cross_thread_unsupported_value.h
+++ b/third_party/blink/renderer/core/css/cssom/cross_thread_unsupported_value.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CROSS_THREAD_UNSUPPORTED_VALUE_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CROSS_THREAD_UNSUPPORTED_VALUE_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/css/cssom/cross_thread_style_value.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
@@ -19,6 +18,9 @@
     : public CrossThreadStyleValue {
  public:
   explicit CrossThreadUnsupportedValue(const String& value) : value_(value) {}
+  CrossThreadUnsupportedValue(const CrossThreadUnsupportedValue&) = delete;
+  CrossThreadUnsupportedValue& operator=(const CrossThreadUnsupportedValue&) =
+      delete;
   ~CrossThreadUnsupportedValue() override = default;
 
   StyleValueType GetType() const override {
@@ -33,7 +35,6 @@
   friend class CrossThreadStyleValueTest;
 
   String value_;
-  DISALLOW_COPY_AND_ASSIGN(CrossThreadUnsupportedValue);
 };
 
 template <>
diff --git a/third_party/blink/renderer/core/css/cssom/css_keyword_value.h b/third_party/blink/renderer/core/css/cssom/css_keyword_value.h
index 64f8d78..843fc388 100644
--- a/third_party/blink/renderer/core/css/cssom/css_keyword_value.h
+++ b/third_party/blink/renderer/core/css/cssom/css_keyword_value.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_KEYWORD_VALUE_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_KEYWORD_VALUE_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/css/cssom/css_style_value.h"
 #include "third_party/blink/renderer/core/css_value_keywords.h"
@@ -24,6 +23,8 @@
   static CSSKeywordValue* FromCSSValue(const CSSValue&);
 
   explicit CSSKeywordValue(const String& keyword) : keyword_value_(keyword) {}
+  CSSKeywordValue(const CSSKeywordValue&) = delete;
+  CSSKeywordValue& operator=(const CSSKeywordValue&) = delete;
 
   StyleValueType GetType() const override { return kKeywordType; }
 
@@ -35,7 +36,6 @@
 
  private:
   String keyword_value_;
-  DISALLOW_COPY_AND_ASSIGN(CSSKeywordValue);
 };
 
 template <>
diff --git a/third_party/blink/renderer/core/css/cssom/css_math_invert.h b/third_party/blink/renderer/core/css/cssom/css_math_invert.h
index d9b53e05a..7781bd2 100644
--- a/third_party/blink/renderer/core/css/cssom/css_math_invert.h
+++ b/third_party/blink/renderer/core/css/cssom/css_math_invert.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_MATH_INVERT_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_MATH_INVERT_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/css/cssom/css_math_value.h"
 
 namespace blink {
@@ -28,6 +27,8 @@
 
   CSSMathInvert(CSSNumericValue* value, const CSSNumericValueType& type)
       : CSSMathValue(type), value_(value) {}
+  CSSMathInvert(const CSSMathInvert&) = delete;
+  CSSMathInvert& operator=(const CSSMathInvert&) = delete;
 
   String getOperator() const final { return "invert"; }
 
@@ -66,7 +67,6 @@
   void BuildCSSText(Nested, ParenLess, StringBuilder&) const final;
 
   Member<CSSNumericValue> value_;
-  DISALLOW_COPY_AND_ASSIGN(CSSMathInvert);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/css_math_max.h b/third_party/blink/renderer/core/css/cssom/css_math_max.h
index 9e2ddd8..b663518 100644
--- a/third_party/blink/renderer/core/css/cssom/css_math_max.h
+++ b/third_party/blink/renderer/core/css/cssom/css_math_max.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_MATH_MAX_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_MATH_MAX_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/css/cssom/css_math_variadic.h"
 
 namespace blink {
@@ -24,6 +23,8 @@
 
   CSSMathMax(CSSNumericArray* values, const CSSNumericValueType& type)
       : CSSMathVariadic(values, type) {}
+  CSSMathMax(const CSSMathMax&) = delete;
+  CSSMathMax& operator=(const CSSMathMax&) = delete;
 
   String getOperator() const final { return "max"; }
 
@@ -36,7 +37,6 @@
   void BuildCSSText(Nested, ParenLess, StringBuilder&) const final;
 
   base::Optional<CSSNumericSumValue> SumValue() const final;
-  DISALLOW_COPY_AND_ASSIGN(CSSMathMax);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/css_math_min.h b/third_party/blink/renderer/core/css/cssom/css_math_min.h
index 5952a3d4..6a57b3ff 100644
--- a/third_party/blink/renderer/core/css/cssom/css_math_min.h
+++ b/third_party/blink/renderer/core/css/cssom/css_math_min.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_MATH_MIN_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_MATH_MIN_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/css/cssom/css_math_variadic.h"
 
 namespace blink {
@@ -26,6 +25,8 @@
 
   CSSMathMin(CSSNumericArray* values, const CSSNumericValueType& type)
       : CSSMathVariadic(values, type) {}
+  CSSMathMin(const CSSMathMin&) = delete;
+  CSSMathMin& operator=(const CSSMathMin&) = delete;
 
   String getOperator() const final { return "min"; }
 
@@ -38,7 +39,6 @@
   void BuildCSSText(Nested, ParenLess, StringBuilder&) const final;
 
   base::Optional<CSSNumericSumValue> SumValue() const final;
-  DISALLOW_COPY_AND_ASSIGN(CSSMathMin);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/css_math_negate.h b/third_party/blink/renderer/core/css/cssom/css_math_negate.h
index 7e4ff5a..df90fcc1 100644
--- a/third_party/blink/renderer/core/css/cssom/css_math_negate.h
+++ b/third_party/blink/renderer/core/css/cssom/css_math_negate.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_MATH_NEGATE_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_MATH_NEGATE_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/css/cssom/css_math_value.h"
 
 namespace blink {
@@ -27,6 +26,8 @@
 
   CSSMathNegate(CSSNumericValue* value, const CSSNumericValueType& type)
       : CSSMathValue(type), value_(value) {}
+  CSSMathNegate(const CSSMathNegate&) = delete;
+  CSSMathNegate& operator=(const CSSMathNegate&) = delete;
 
   String getOperator() const final { return "negate"; }
 
@@ -65,7 +66,6 @@
   void BuildCSSText(Nested, ParenLess, StringBuilder&) const final;
 
   Member<CSSNumericValue> value_;
-  DISALLOW_COPY_AND_ASSIGN(CSSMathNegate);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/css_math_product.h b/third_party/blink/renderer/core/css/cssom/css_math_product.h
index e8f8f19..efff524 100644
--- a/third_party/blink/renderer/core/css/cssom/css_math_product.h
+++ b/third_party/blink/renderer/core/css/cssom/css_math_product.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_MATH_PRODUCT_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_MATH_PRODUCT_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/css/cssom/css_math_variadic.h"
 
 namespace blink {
@@ -24,6 +23,8 @@
 
   CSSMathProduct(CSSNumericArray* values, const CSSNumericValueType& type)
       : CSSMathVariadic(values, type) {}
+  CSSMathProduct(const CSSMathProduct&) = delete;
+  CSSMathProduct& operator=(const CSSMathProduct&) = delete;
 
   String getOperator() const final { return "product"; }
 
@@ -36,7 +37,6 @@
   void BuildCSSText(Nested, ParenLess, StringBuilder&) const final;
 
   base::Optional<CSSNumericSumValue> SumValue() const final;
-  DISALLOW_COPY_AND_ASSIGN(CSSMathProduct);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/css_math_sum.h b/third_party/blink/renderer/core/css/cssom/css_math_sum.h
index 8bdb4a3f..8556736d 100644
--- a/third_party/blink/renderer/core/css/cssom/css_math_sum.h
+++ b/third_party/blink/renderer/core/css/cssom/css_math_sum.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_MATH_SUM_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_MATH_SUM_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/css/cssom/css_math_variadic.h"
 
 namespace blink {
@@ -24,6 +23,8 @@
 
   CSSMathSum(CSSNumericArray* values, const CSSNumericValueType& type)
       : CSSMathVariadic(values, type) {}
+  CSSMathSum(const CSSMathSum&) = delete;
+  CSSMathSum& operator=(const CSSMathSum&) = delete;
 
   String getOperator() const final { return "sum"; }
 
@@ -36,7 +37,6 @@
   void BuildCSSText(Nested, ParenLess, StringBuilder&) const final;
 
   base::Optional<CSSNumericSumValue> SumValue() const final;
-  DISALLOW_COPY_AND_ASSIGN(CSSMathSum);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/css_math_value.h b/third_party/blink/renderer/core/css/cssom/css_math_value.h
index 576495c..c65f673 100644
--- a/third_party/blink/renderer/core/css/cssom/css_math_value.h
+++ b/third_party/blink/renderer/core/css/cssom/css_math_value.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_MATH_VALUE_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_MATH_VALUE_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/css/cssom/css_numeric_value.h"
 
 namespace blink {
@@ -16,6 +15,9 @@
   DEFINE_WRAPPERTYPEINFO();
 
  public:
+  CSSMathValue(const CSSMathValue&) = delete;
+  CSSMathValue& operator=(const CSSMathValue&) = delete;
+
   virtual String getOperator() const = 0;
 
   // From CSSNumericValue.
@@ -26,9 +28,6 @@
 
  protected:
   CSSMathValue(const CSSNumericValueType& type) : CSSNumericValue(type) {}
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(CSSMathValue);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/css_math_variadic.h b/third_party/blink/renderer/core/css/cssom/css_math_variadic.h
index 8d5938e..ee57f721 100644
--- a/third_party/blink/renderer/core/css/cssom/css_math_variadic.h
+++ b/third_party/blink/renderer/core/css/cssom/css_math_variadic.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_MATH_VARIADIC_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_MATH_VARIADIC_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/css/cssom/css_math_value.h"
 #include "third_party/blink/renderer/core/css/cssom/css_numeric_array.h"
 
@@ -15,6 +14,9 @@
 class CORE_EXPORT CSSMathVariadic : public CSSMathValue {
 
  public:
+  CSSMathVariadic(const CSSMathVariadic&) = delete;
+  CSSMathVariadic& operator=(const CSSMathVariadic&) = delete;
+
   CSSNumericArray* values() { return values_.Get(); }
 
   const CSSNumericValueVector& NumericValues() const {
@@ -61,7 +63,6 @@
 
  private:
   Member<CSSNumericArray> values_;
-  DISALLOW_COPY_AND_ASSIGN(CSSMathVariadic);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/css_matrix_component.h b/third_party/blink/renderer/core/css/cssom/css_matrix_component.h
index 00b1d218..04ddf1d 100644
--- a/third_party/blink/renderer/core/css/cssom/css_matrix_component.h
+++ b/third_party/blink/renderer/core/css/cssom/css_matrix_component.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_MATRIX_COMPONENT_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_MATRIX_COMPONENT_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/css/cssom/css_transform_component.h"
 #include "third_party/blink/renderer/core/geometry/dom_matrix.h"
 #include "third_party/blink/renderer/core/geometry/dom_matrix_read_only.h"
@@ -30,6 +29,8 @@
 
   CSSMatrixComponent(DOMMatrixReadOnly* matrix, bool is2D)
       : CSSTransformComponent(is2D), matrix_(DOMMatrix::Create(matrix)) {}
+  CSSMatrixComponent(const CSSMatrixComponent&) = delete;
+  CSSMatrixComponent& operator=(const CSSMatrixComponent&) = delete;
 
   // Getters and setters for attributes defined in the IDL.
   DOMMatrix* matrix() { return matrix_.Get(); }
@@ -48,7 +49,6 @@
 
  private:
   Member<DOMMatrix> matrix_;
-  DISALLOW_COPY_AND_ASSIGN(CSSMatrixComponent);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/css_numeric_array.h b/third_party/blink/renderer/core/css/cssom/css_numeric_array.h
index ab67aba..1cc19526 100644
--- a/third_party/blink/renderer/core/css/cssom/css_numeric_array.h
+++ b/third_party/blink/renderer/core/css/cssom/css_numeric_array.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_NUMERIC_ARRAY_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_NUMERIC_ARRAY_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/css/cssom/css_numeric_value.h"
 
 namespace blink {
@@ -23,6 +22,8 @@
 
   explicit CSSNumericArray(CSSNumericValueVector values)
       : values_(std::move(values)) {}
+  CSSNumericArray(const CSSNumericArray&) = delete;
+  CSSNumericArray& operator=(const CSSNumericArray&) = delete;
 
   void Trace(Visitor* visitor) const override {
     visitor->Trace(values_);
@@ -40,7 +41,6 @@
 
  private:
   CSSNumericValueVector values_;
-  DISALLOW_COPY_AND_ASSIGN(CSSNumericArray);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/css_numeric_value.h b/third_party/blink/renderer/core/css/cssom/css_numeric_value.h
index 15d9723..b5d0af1 100644
--- a/third_party/blink/renderer/core/css/cssom/css_numeric_value.h
+++ b/third_party/blink/renderer/core/css/cssom/css_numeric_value.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_NUMERIC_VALUE_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_NUMERIC_VALUE_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/bindings/core/v8/double_or_css_numeric_value.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/css/css_primitive_value.h"
@@ -31,6 +30,9 @@
   DEFINE_WRAPPERTYPEINFO();
 
  public:
+  CSSNumericValue(const CSSNumericValue&) = delete;
+  CSSNumericValue& operator=(const CSSNumericValue&) = delete;
+
   static CSSNumericValue* parse(const String& css_text, ExceptionState&);
   // Blink-internal ways of creating CSSNumericValues.
   static CSSNumericValue* FromCSSValue(const CSSPrimitiveValue&);
@@ -81,7 +83,6 @@
 
  private:
   CSSNumericValueType type_;
-  DISALLOW_COPY_AND_ASSIGN(CSSNumericValue);
 };
 
 CSSNumericValueVector CSSNumberishesToNumericValues(
diff --git a/third_party/blink/renderer/core/css/cssom/css_perspective.h b/third_party/blink/renderer/core/css/cssom/css_perspective.h
index 62b527f0..e3bc57b 100644
--- a/third_party/blink/renderer/core/css/cssom/css_perspective.h
+++ b/third_party/blink/renderer/core/css/cssom/css_perspective.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_PERSPECTIVE_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_PERSPECTIVE_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/css/cssom/css_numeric_value.h"
 #include "third_party/blink/renderer/core/css/cssom/css_transform_component.h"
@@ -29,6 +28,8 @@
   static CSSPerspective* FromCSSValue(const CSSFunctionValue&);
 
   CSSPerspective(CSSNumericValue* length);
+  CSSPerspective(const CSSPerspective&) = delete;
+  CSSPerspective& operator=(const CSSPerspective&) = delete;
 
   // Getters and setters for attributes defined in the IDL.
   CSSNumericValue* length() { return length_.Get(); }
@@ -52,7 +53,6 @@
 
  private:
   Member<CSSNumericValue> length_;
-  DISALLOW_COPY_AND_ASSIGN(CSSPerspective);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/css_position_value.h b/third_party/blink/renderer/core/css/cssom/css_position_value.h
index 182427e..94ef49f 100644
--- a/third_party/blink/renderer/core/css/cssom/css_position_value.h
+++ b/third_party/blink/renderer/core/css/cssom/css_position_value.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_POSITION_VALUE_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_POSITION_VALUE_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/css/cssom/css_style_value.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
@@ -30,6 +29,8 @@
   static CSSPositionValue* FromCSSValue(const CSSValue&);
 
   CSSPositionValue(CSSNumericValue* x, CSSNumericValue* y) : x_(x), y_(y) {}
+  CSSPositionValue(const CSSPositionValue&) = delete;
+  CSSPositionValue& operator=(const CSSPositionValue&) = delete;
 
   // Getters and setters defined in the IDL.
   CSSNumericValue* x() { return x_.Get(); }
@@ -54,7 +55,6 @@
  protected:
   Member<CSSNumericValue> x_;
   Member<CSSNumericValue> y_;
-  DISALLOW_COPY_AND_ASSIGN(CSSPositionValue);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/css_resource_value.h b/third_party/blink/renderer/core/css/cssom/css_resource_value.h
index 0bb34d6d..d8eb1d5a 100644
--- a/third_party/blink/renderer/core/css/cssom/css_resource_value.h
+++ b/third_party/blink/renderer/core/css/cssom/css_resource_value.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_RESOURCE_VALUE_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_RESOURCE_VALUE_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/css/cssom/css_style_value.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource.h"
 
@@ -13,6 +12,8 @@
 
 class CORE_EXPORT CSSResourceValue : public CSSStyleValue {
  public:
+  CSSResourceValue(const CSSResourceValue&) = delete;
+  CSSResourceValue& operator=(const CSSResourceValue&) = delete;
   ~CSSResourceValue() override = default;
 
   const String state() const {
@@ -38,9 +39,6 @@
   CSSResourceValue() = default;
 
   virtual ResourceStatus Status() const = 0;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(CSSResourceValue);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/css_rotate.h b/third_party/blink/renderer/core/css/cssom/css_rotate.h
index f6366232..dc7b876 100644
--- a/third_party/blink/renderer/core/css/cssom/css_rotate.h
+++ b/third_party/blink/renderer/core/css/cssom/css_rotate.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_ROTATE_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_ROTATE_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/css/cssom/css_numeric_value.h"
 #include "third_party/blink/renderer/core/css/cssom/css_transform_component.h"
 
@@ -43,6 +42,8 @@
             CSSNumericValue* z,
             CSSNumericValue* angle,
             bool is2D);
+  CSSRotate(const CSSRotate&) = delete;
+  CSSRotate& operator=(const CSSRotate&) = delete;
 
   // Getters and setters for attributes defined in the IDL.
   CSSNumericValue* angle() { return angle_.Get(); }
@@ -73,7 +74,6 @@
   Member<CSSNumericValue> x_;
   Member<CSSNumericValue> y_;
   Member<CSSNumericValue> z_;
-  DISALLOW_COPY_AND_ASSIGN(CSSRotate);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/css_scale.h b/third_party/blink/renderer/core/css/cssom/css_scale.h
index 27e411c..bcbcfcd 100644
--- a/third_party/blink/renderer/core/css/cssom/css_scale.h
+++ b/third_party/blink/renderer/core/css/cssom/css_scale.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_SCALE_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_SCALE_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/css/cssom/css_transform_component.h"
 #include "third_party/blink/renderer/core/css/cssom/css_unit_value.h"
 #include "third_party/blink/renderer/core/geometry/dom_matrix.h"
@@ -47,6 +46,8 @@
            CSSNumericValue* y,
            CSSNumericValue* z,
            bool is2D);
+  CSSScale(const CSSScale&) = delete;
+  CSSScale& operator=(const CSSScale&) = delete;
 
   // Getters and setters for attributes defined in the IDL.
   void x(CSSNumberish& x) { x.SetCSSNumericValue(x_); }
@@ -73,8 +74,6 @@
   Member<CSSNumericValue> x_;
   Member<CSSNumericValue> y_;
   Member<CSSNumericValue> z_;
-
-  DISALLOW_COPY_AND_ASSIGN(CSSScale);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/css_skew.h b/third_party/blink/renderer/core/css/cssom/css_skew.h
index 7e18a96..722b6a1 100644
--- a/third_party/blink/renderer/core/css/cssom/css_skew.h
+++ b/third_party/blink/renderer/core/css/cssom/css_skew.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_SKEW_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_SKEW_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/css/cssom/css_numeric_value.h"
 #include "third_party/blink/renderer/core/css/cssom/css_transform_component.h"
 
@@ -31,6 +30,8 @@
   static CSSSkew* FromCSSValue(const CSSFunctionValue&);
 
   CSSSkew(CSSNumericValue* ax, CSSNumericValue* ay);
+  CSSSkew(const CSSSkew&) = delete;
+  CSSSkew& operator=(const CSSSkew&) = delete;
 
   // Getters and setters for the ax and ay attributes defined in the IDL.
   CSSNumericValue* ax() { return ax_.Get(); }
@@ -58,7 +59,6 @@
  private:
   Member<CSSNumericValue> ax_;
   Member<CSSNumericValue> ay_;
-  DISALLOW_COPY_AND_ASSIGN(CSSSkew);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/css_skew_x.h b/third_party/blink/renderer/core/css/cssom/css_skew_x.h
index 70089dc..fa1e9b0 100644
--- a/third_party/blink/renderer/core/css/cssom/css_skew_x.h
+++ b/third_party/blink/renderer/core/css/cssom/css_skew_x.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_SKEW_X_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_SKEW_X_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/css/cssom/css_numeric_value.h"
 #include "third_party/blink/renderer/core/css/cssom/css_transform_component.h"
 
@@ -31,6 +30,8 @@
   static CSSSkewX* FromCSSValue(const CSSFunctionValue&);
 
   CSSSkewX(CSSNumericValue* ax);
+  CSSSkewX(const CSSSkewX&) = delete;
+  CSSSkewX& operator=(const CSSSkewX&) = delete;
 
   // Getters and setters for the ax attributes defined in the IDL.
   CSSNumericValue* ax() { return ax_.Get(); }
@@ -54,7 +55,6 @@
 
  private:
   Member<CSSNumericValue> ax_;
-  DISALLOW_COPY_AND_ASSIGN(CSSSkewX);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/css_skew_y.h b/third_party/blink/renderer/core/css/cssom/css_skew_y.h
index 9d215862..02d44df 100644
--- a/third_party/blink/renderer/core/css/cssom/css_skew_y.h
+++ b/third_party/blink/renderer/core/css/cssom/css_skew_y.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_SKEW_Y_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_SKEW_Y_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/css/cssom/css_numeric_value.h"
 #include "third_party/blink/renderer/core/css/cssom/css_transform_component.h"
 
@@ -31,6 +30,8 @@
   static CSSSkewY* FromCSSValue(const CSSFunctionValue&);
 
   CSSSkewY(CSSNumericValue* ay);
+  CSSSkewY(const CSSSkewY&) = delete;
+  CSSSkewY& operator=(const CSSSkewY&) = delete;
 
   // Getters and setters for the ay attributes defined in the IDL.
   CSSNumericValue* ay() { return ay_.Get(); }
@@ -54,7 +55,6 @@
 
  private:
   Member<CSSNumericValue> ay_;
-  DISALLOW_COPY_AND_ASSIGN(CSSSkewY);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/css_style_image_value.h b/third_party/blink/renderer/core/css/cssom/css_style_image_value.h
index 1731aa9..5a7c8b3 100644
--- a/third_party/blink/renderer/core/css/cssom/css_style_image_value.h
+++ b/third_party/blink/renderer/core/css/cssom/css_style_image_value.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_STYLE_IMAGE_VALUE_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_STYLE_IMAGE_VALUE_H_
 
-#include "base/macros.h"
 #include "base/optional.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/css/cssom/css_resource_value.h"
@@ -20,6 +19,8 @@
   DEFINE_WRAPPERTYPEINFO();
 
  public:
+  CSSStyleImageValue(const CSSStyleImageValue&) = delete;
+  CSSStyleImageValue& operator=(const CSSStyleImageValue&) = delete;
   ~CSSStyleImageValue() override = default;
 
   // IDL
@@ -37,9 +38,6 @@
   CSSStyleImageValue() = default;
 
   virtual base::Optional<IntSize> IntrinsicSize() const = 0;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(CSSStyleImageValue);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/css_style_value.h b/third_party/blink/renderer/core/css/cssom/css_style_value.h
index 7931dec..065b3a11 100644
--- a/third_party/blink/renderer/core/css/cssom/css_style_value.h
+++ b/third_party/blink/renderer/core/css/cssom/css_style_value.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_STYLE_VALUE_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_STYLE_VALUE_H_
 
-#include "base/macros.h"
 #include "base/optional.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/css/css_property_names.h"
@@ -57,6 +56,8 @@
                                       const String& value,
                                       ExceptionState&);
 
+  CSSStyleValue(const CSSStyleValue&) = delete;
+  CSSStyleValue& operator=(const CSSStyleValue&) = delete;
   ~CSSStyleValue() override = default;
 
   virtual StyleValueType GetType() const = 0;
@@ -82,7 +83,6 @@
 
  private:
   String css_text_;
-  DISALLOW_COPY_AND_ASSIGN(CSSStyleValue);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/css_style_variable_reference_value.h b/third_party/blink/renderer/core/css/cssom/css_style_variable_reference_value.h
index 606d25e..c087e975 100644
--- a/third_party/blink/renderer/core/css/cssom/css_style_variable_reference_value.h
+++ b/third_party/blink/renderer/core/css/cssom/css_style_variable_reference_value.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_STYLE_VARIABLE_REFERENCE_VALUE_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_STYLE_VARIABLE_REFERENCE_VALUE_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/css/cssom/css_unparsed_value.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
@@ -35,6 +34,10 @@
   CSSStyleVariableReferenceValue(const String& variable,
                                  CSSUnparsedValue* fallback)
       : variable_(variable), fallback_(fallback) {}
+  CSSStyleVariableReferenceValue(const CSSStyleVariableReferenceValue&) =
+      delete;
+  CSSStyleVariableReferenceValue& operator=(
+      const CSSStyleVariableReferenceValue&) = delete;
 
   const String& variable() const { return variable_; }
   void setVariable(const String&, ExceptionState&);
@@ -50,9 +53,6 @@
  protected:
   String variable_;
   Member<CSSUnparsedValue> fallback_;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(CSSStyleVariableReferenceValue);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/css_transform_component.h b/third_party/blink/renderer/core/css/cssom/css_transform_component.h
index 39c0640..4010fbb 100644
--- a/third_party/blink/renderer/core/css/cssom/css_transform_component.h
+++ b/third_party/blink/renderer/core/css/cssom/css_transform_component.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_TRANSFORM_COMPONENT_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_TRANSFORM_COMPONENT_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/css/css_function_value.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
@@ -35,6 +34,8 @@
     kTranslationType,
   };
 
+  CSSTransformComponent(const CSSTransformComponent&) = delete;
+  CSSTransformComponent& operator=(const CSSTransformComponent&) = delete;
   ~CSSTransformComponent() override = default;
 
   // Blink-internal ways of creating CSSTransformComponents.
@@ -56,7 +57,6 @@
 
  private:
   bool is2D_;
-  DISALLOW_COPY_AND_ASSIGN(CSSTransformComponent);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/css_transform_value.h b/third_party/blink/renderer/core/css/cssom/css_transform_value.h
index cfe7614..2e0d968 100644
--- a/third_party/blink/renderer/core/css/cssom/css_transform_value.h
+++ b/third_party/blink/renderer/core/css/cssom/css_transform_value.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_TRANSFORM_VALUE_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_TRANSFORM_VALUE_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/css/cssom/css_style_value.h"
 #include "third_party/blink/renderer/core/css/cssom/css_transform_component.h"
@@ -34,6 +33,8 @@
   CSSTransformValue(
       const HeapVector<Member<CSSTransformComponent>>& transform_components)
       : CSSStyleValue(), transform_components_(transform_components) {}
+  CSSTransformValue(const CSSTransformValue&) = delete;
+  CSSTransformValue& operator=(const CSSTransformValue&) = delete;
 
   bool is2D() const;
 
@@ -60,7 +61,6 @@
 
  private:
   HeapVector<Member<CSSTransformComponent>> transform_components_;
-  DISALLOW_COPY_AND_ASSIGN(CSSTransformValue);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/css_translate.h b/third_party/blink/renderer/core/css/cssom/css_translate.h
index d139df5..9140584 100644
--- a/third_party/blink/renderer/core/css/cssom/css_translate.h
+++ b/third_party/blink/renderer/core/css/cssom/css_translate.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_TRANSLATE_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_TRANSLATE_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/css/css_primitive_value.h"
 #include "third_party/blink/renderer/core/css/cssom/css_style_value.h"
 #include "third_party/blink/renderer/core/css/cssom/css_transform_component.h"
@@ -44,6 +43,8 @@
                CSSNumericValue* y,
                CSSNumericValue* z,
                bool is2D);
+  CSSTranslate(const CSSTranslate&) = delete;
+  CSSTranslate& operator=(const CSSTranslate&) = delete;
 
   // Getters and setters for attributes defined in the IDL.
   CSSNumericValue* x() { return x_; }
@@ -70,7 +71,6 @@
   Member<CSSNumericValue> x_;
   Member<CSSNumericValue> y_;
   Member<CSSNumericValue> z_;
-  DISALLOW_COPY_AND_ASSIGN(CSSTranslate);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/css_unit_value.h b/third_party/blink/renderer/core/css/cssom/css_unit_value.h
index 268f79d1..10ca84e 100644
--- a/third_party/blink/renderer/core/css/cssom/css_unit_value.h
+++ b/third_party/blink/renderer/core/css/cssom/css_unit_value.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_UNIT_VALUE_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_UNIT_VALUE_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/css/css_numeric_literal_value.h"
 #include "third_party/blink/renderer/core/css/cssom/css_numeric_value.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
@@ -33,6 +32,8 @@
       : CSSNumericValue(CSSNumericValueType(unit)),
         value_(value),
         unit_(unit) {}
+  CSSUnitValue(const CSSUnitValue&) = delete;
+  CSSUnitValue& operator=(const CSSUnitValue&) = delete;
 
   // Setters and getters for attributes defined in the IDL.
   void setValue(double new_value) { value_ = new_value; }
@@ -67,7 +68,6 @@
 
   double value_;
   CSSPrimitiveValue::UnitType unit_;
-  DISALLOW_COPY_AND_ASSIGN(CSSUnitValue);
 };
 
 template <>
diff --git a/third_party/blink/renderer/core/css/cssom/css_unparsed_value.h b/third_party/blink/renderer/core/css/cssom/css_unparsed_value.h
index 2ae0cea2..7fc3d728 100644
--- a/third_party/blink/renderer/core/css/cssom/css_unparsed_value.h
+++ b/third_party/blink/renderer/core/css/cssom/css_unparsed_value.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_UNPARSED_VALUE_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_UNPARSED_VALUE_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/bindings/core/v8/string_or_css_variable_reference_value.h"
 #include "third_party/blink/renderer/core/css/cssom/css_style_value.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
@@ -42,6 +41,8 @@
 
   CSSUnparsedValue(const HeapVector<CSSUnparsedSegment>& tokens)
       : CSSStyleValue(), tokens_(tokens) {}
+  CSSUnparsedValue(const CSSUnparsedValue&) = delete;
+  CSSUnparsedValue& operator=(const CSSUnparsedValue&) = delete;
 
   const CSSValue* ToCSSValue() const override;
 
@@ -74,7 +75,6 @@
   FRIEND_TEST_ALL_PREFIXES(CSSVariableReferenceValueTest, MixedList);
 
   HeapVector<CSSUnparsedSegment> tokens_;
-  DISALLOW_COPY_AND_ASSIGN(CSSUnparsedValue);
 };
 
 template <>
diff --git a/third_party/blink/renderer/core/css/cssom/css_unsupported_color_value.h b/third_party/blink/renderer/core/css/cssom/css_unsupported_color_value.h
index 4e23e45..c11a3565 100644
--- a/third_party/blink/renderer/core/css/cssom/css_unsupported_color_value.h
+++ b/third_party/blink/renderer/core/css/cssom/css_unsupported_color_value.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_UNSUPPORTED_COLOR_VALUE_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_UNSUPPORTED_COLOR_VALUE_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/css/css_color_value.h"
 #include "third_party/blink/renderer/core/css/cssom/css_unsupported_style_value.h"
@@ -32,6 +31,8 @@
         color_value_(color) {}
   explicit CSSUnsupportedColorValue(const cssvalue::CSSColorValue& color_value)
       : CSSUnsupportedColorValue(color_value.Value()) {}
+  CSSUnsupportedColorValue(const CSSUnsupportedColorValue&) = delete;
+  CSSUnsupportedColorValue& operator=(const CSSUnsupportedColorValue&) = delete;
 
   StyleValueType GetType() const override { return kUnsupportedColorType; }
 
@@ -41,7 +42,6 @@
 
  private:
   Color color_value_;
-  DISALLOW_COPY_AND_ASSIGN(CSSUnsupportedColorValue);
 };
 
 template <>
diff --git a/third_party/blink/renderer/core/css/cssom/css_unsupported_style_value.h b/third_party/blink/renderer/core/css/cssom/css_unsupported_style_value.h
index 751ddda..c72f275 100644
--- a/third_party/blink/renderer/core/css/cssom/css_unsupported_style_value.h
+++ b/third_party/blink/renderer/core/css/cssom/css_unsupported_style_value.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_UNSUPPORTED_STYLE_VALUE_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_UNSUPPORTED_STYLE_VALUE_H_
 
-#include "base/macros.h"
 #include "base/optional.h"
 #include "third_party/blink/renderer/core/css/css_property_name.h"
 #include "third_party/blink/renderer/core/css/cssom/css_style_value.h"
@@ -35,6 +34,8 @@
       : name_(name) {
     SetCSSText(value.CssText());
   }
+  CSSUnsupportedStyleValue(const CSSUnsupportedStyleValue&) = delete;
+  CSSUnsupportedStyleValue& operator=(const CSSUnsupportedStyleValue&) = delete;
 
   StyleValueType GetType() const override {
     return StyleValueType::kUnknownType;
@@ -52,7 +53,6 @@
 
  private:
   base::Optional<CSSPropertyName> name_;
-  DISALLOW_COPY_AND_ASSIGN(CSSUnsupportedStyleValue);
 };
 
 template <>
diff --git a/third_party/blink/renderer/core/css/cssom/css_url_image_value.h b/third_party/blink/renderer/core/css/cssom/css_url_image_value.h
index f15f2fb..2aa3354 100644
--- a/third_party/blink/renderer/core/css/cssom/css_url_image_value.h
+++ b/third_party/blink/renderer/core/css/cssom/css_url_image_value.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_URL_IMAGE_VALUE_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_CSS_URL_IMAGE_VALUE_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/css/cssom/css_style_image_value.h"
 
 namespace blink {
@@ -15,6 +14,8 @@
 class CORE_EXPORT CSSURLImageValue final : public CSSStyleImageValue {
  public:
   explicit CSSURLImageValue(const CSSImageValue& value) : value_(value) {}
+  CSSURLImageValue(const CSSURLImageValue&) = delete;
+  CSSURLImageValue& operator=(const CSSURLImageValue&) = delete;
 
   const String& url() const;
 
@@ -37,7 +38,6 @@
   scoped_refptr<Image> GetImage() const;
 
   Member<const CSSImageValue> value_;
-  DISALLOW_COPY_AND_ASSIGN(CSSURLImageValue);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/declared_style_property_map.h b/third_party/blink/renderer/core/css/cssom/declared_style_property_map.h
index 195cd305..b4dda3e 100644
--- a/third_party/blink/renderer/core/css/cssom/declared_style_property_map.h
+++ b/third_party/blink/renderer/core/css/cssom/declared_style_property_map.h
@@ -23,6 +23,8 @@
 class CORE_EXPORT DeclaredStylePropertyMap final : public StylePropertyMap {
  public:
   explicit DeclaredStylePropertyMap(CSSStyleRule* owner_rule);
+  DeclaredStylePropertyMap(const DeclaredStylePropertyMap&) = delete;
+  DeclaredStylePropertyMap& operator=(const DeclaredStylePropertyMap&) = delete;
 
   void Trace(Visitor* visitor) const override {
     visitor->Trace(owner_rule_);
@@ -50,8 +52,6 @@
   StyleRule* GetStyleRule() const;
 
   WeakMember<CSSStyleRule> owner_rule_;
-
-  DISALLOW_COPY_AND_ASSIGN(DeclaredStylePropertyMap);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/inline_style_property_map.h b/third_party/blink/renderer/core/css/cssom/inline_style_property_map.h
index a15f466..43de99e 100644
--- a/third_party/blink/renderer/core/css/cssom/inline_style_property_map.h
+++ b/third_party/blink/renderer/core/css/cssom/inline_style_property_map.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_INLINE_STYLE_PROPERTY_MAP_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_INLINE_STYLE_PROPERTY_MAP_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/css/cssom/style_property_map.h"
 #include "third_party/blink/renderer/core/dom/element.h"
 
@@ -15,6 +14,8 @@
  public:
   explicit InlineStylePropertyMap(Element* owner_element)
       : owner_element_(owner_element) {}
+  InlineStylePropertyMap(const InlineStylePropertyMap&) = delete;
+  InlineStylePropertyMap& operator=(const InlineStylePropertyMap&) = delete;
 
   void Trace(Visitor* visitor) const override {
     visitor->Trace(owner_element_);
@@ -40,7 +41,6 @@
 
  private:
   Member<Element> owner_element_;
-  DISALLOW_COPY_AND_ASSIGN(InlineStylePropertyMap);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/paint_worklet_style_property_map.h b/third_party/blink/renderer/core/css/cssom/paint_worklet_style_property_map.h
index 92953610..c3d9a7a 100644
--- a/third_party/blink/renderer/core/css/cssom/paint_worklet_style_property_map.h
+++ b/third_party/blink/renderer/core/css/cssom/paint_worklet_style_property_map.h
@@ -7,7 +7,6 @@
 
 #include <memory>
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/css/cssom/cross_thread_style_value.h"
 #include "third_party/blink/renderer/core/css/cssom/style_property_map_read_only.h"
 #include "third_party/blink/renderer/platform/graphics/compositor_element_id.h"
@@ -50,6 +49,9 @@
 
   // This constructor should be called on the worklet-thread only.
   explicit PaintWorkletStylePropertyMap(CrossThreadData data);
+  PaintWorkletStylePropertyMap(const PaintWorkletStylePropertyMap&) = delete;
+  PaintWorkletStylePropertyMap& operator=(const PaintWorkletStylePropertyMap&) =
+      delete;
 
   CSSStyleValue* get(const ExecutionContext*,
                      const String& property_name,
@@ -75,8 +77,6 @@
   IterationSource* StartIteration(ScriptState*, ExceptionState&) override;
 
   CrossThreadData data_;
-
-  DISALLOW_COPY_AND_ASSIGN(PaintWorkletStylePropertyMap);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/prepopulated_computed_style_property_map.h b/third_party/blink/renderer/core/css/cssom/prepopulated_computed_style_property_map.h
index 73c34d53..27fbe42 100644
--- a/third_party/blink/renderer/core/css/cssom/prepopulated_computed_style_property_map.h
+++ b/third_party/blink/renderer/core/css/cssom/prepopulated_computed_style_property_map.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_PREPOPULATED_COMPUTED_STYLE_PROPERTY_MAP_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_PREPOPULATED_COMPUTED_STYLE_PROPERTY_MAP_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/core/css/css_property_id_templates.h"
 #include "third_party/blink/renderer/core/css/cssom/style_property_map_read_only_main_thread.h"
 #include "third_party/blink/renderer/platform/wtf/text/atomic_string_hash.h"
@@ -34,6 +33,10 @@
       const ComputedStyle&,
       const Vector<CSSPropertyID>& native_properties,
       const Vector<AtomicString>& custom_properties);
+  PrepopulatedComputedStylePropertyMap(
+      const PrepopulatedComputedStylePropertyMap&) = delete;
+  PrepopulatedComputedStylePropertyMap& operator=(
+      const PrepopulatedComputedStylePropertyMap&) = delete;
 
   // Updates the values of the properties based on the new computed style.
   void UpdateStyle(const Document&, const ComputedStyle&);
@@ -56,8 +59,6 @@
 
   HeapHashMap<CSSPropertyID, Member<const CSSValue>> native_values_;
   HeapHashMap<AtomicString, Member<const CSSValue>> custom_values_;
-
-  DISALLOW_COPY_AND_ASSIGN(PrepopulatedComputedStylePropertyMap);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/style_property_map.h b/third_party/blink/renderer/core/css/cssom/style_property_map.h
index 6b9e16c..39dd572 100644
--- a/third_party/blink/renderer/core/css/cssom/style_property_map.h
+++ b/third_party/blink/renderer/core/css/cssom/style_property_map.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_STYLE_PROPERTY_MAP_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSSOM_STYLE_PROPERTY_MAP_H_
 
-#include "base/macros.h"
 #include "third_party/blink/renderer/bindings/core/v8/css_style_value_or_string.h"
 #include "third_party/blink/renderer/core/css/cssom/style_property_map_read_only_main_thread.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
@@ -19,6 +18,9 @@
   DEFINE_WRAPPERTYPEINFO();
 
  public:
+  StylePropertyMap(const StylePropertyMap&) = delete;
+  StylePropertyMap& operator=(const StylePropertyMap&) = delete;
+
   void set(const ExecutionContext*,
            const String& property_name,
            const HeapVector<CSSStyleValueOrString>& values,
@@ -43,9 +45,6 @@
   virtual void RemoveAllProperties() = 0;
 
   StylePropertyMap() = default;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(StylePropertyMap);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/cssom/style_property_map_read_only_main_thread.h b/third_party/blink/renderer/core/css/cssom/style_property_map_read_only_main_thread.h
index 1c259a9..9c3c44e 100644
--- a/third_party/blink/renderer/core/css/cssom/style_property_map_read_only_main_thread.h
+++ b/third_party/blink/renderer/core/css/cssom/style_property_map_read_only_main_thread.h
@@ -17,6 +17,10 @@
  public:
   using StylePropertyMapEntry = std::pair<String, CSSStyleValueVector>;
 
+  StylePropertyMapReadOnlyMainThread(
+      const StylePropertyMapReadOnlyMainThread&) = delete;
+  StylePropertyMapReadOnlyMainThread& operator=(
+      const StylePropertyMapReadOnlyMainThread&) = delete;
   ~StylePropertyMapReadOnlyMainThread() override = default;
 
   CSSStyleValue* get(const ExecutionContext*,
@@ -47,9 +51,6 @@
   IterationSource* StartIteration(ScriptState*, ExceptionState&) override;
 
   CSSStyleValue* GetShorthandProperty(const CSSProperty&) const;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(StylePropertyMapReadOnlyMainThread);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index bf288e600..92c10f0 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -4082,7 +4082,8 @@
       input->EndEditing();
     if (load_event_progress_ < kPageHideInProgress) {
       load_event_progress_ = kPageHideInProgress;
-      if (LocalDOMWindow* window = domWindow()) {
+      LocalDOMWindow* window = domWindow();
+      if (window && !GetPage()->DispatchedPagehideAndStillHidden()) {
         const base::TimeTicks pagehide_event_start = base::TimeTicks::Now();
         window->DispatchEvent(
             *PageTransitionEvent::Create(event_type_names::kPagehide, false),
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.cc b/third_party/blink/renderer/core/exported/web_view_impl.cc
index 69ba56a..bd885b25 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_view_impl.cc
@@ -325,7 +325,6 @@
       maximum_zoom_level_(PageZoomFactorToZoomLevel(kMaximumPageZoomFactor)),
       does_composite_(does_composite),
       fullscreen_controller_(std::make_unique<FullscreenController>(this)),
-      lifecycle_state_(mojom::blink::PageLifecycleState::New()),
       receiver_(this, std::move(page_handle)) {
   if (!AsView().client) {
     DCHECK(!does_composite_);
@@ -339,7 +338,6 @@
                                                       AsView().client);
 
   SetVisibilityState(visibility, /*is_initial_state=*/true);
-  lifecycle_state_->visibility = visibility;
 
   // We pass this state to Page, but it's only used by the main frame in the
   // page.
@@ -2419,51 +2417,86 @@
   SetZoomLevel(zoom_level_);
 }
 
+void WebViewImpl::SetPageLifecycleStateFromNewPageCommit(
+    mojom::blink::PageVisibilityState visibility,
+    mojom::blink::PagehideDispatch pagehide_dispatch) {
+  mojom::blink::PageLifecycleStatePtr state =
+      GetPage()->GetPageLifecycleState().Clone();
+  state->visibility = visibility;
+  state->pagehide_dispatch = pagehide_dispatch;
+  SetPageLifecycleStateInternal(std::move(state), base::nullopt);
+}
+
 void WebViewImpl::SetPageLifecycleState(
     mojom::blink::PageLifecycleStatePtr state,
     base::Optional<base::TimeTicks> navigation_start,
     SetPageLifecycleStateCallback callback) {
+  SetPageLifecycleStateInternal(std::move(state), navigation_start);
+  // Tell the browser that the lifecycle update was successful.
+  std::move(callback).Run();
+}
+
+void WebViewImpl::SetPageLifecycleStateInternal(
+    mojom::blink::PageLifecycleStatePtr new_state,
+    base::Optional<base::TimeTicks> navigation_start) {
   Page* page = GetPage();
   if (!page)
     return;
-
-  bool storing_in_bfcache = state->is_in_back_forward_cache &&
-                            !lifecycle_state_->is_in_back_forward_cache;
-  bool restoring_from_bfcache = !state->is_in_back_forward_cache &&
-                                lifecycle_state_->is_in_back_forward_cache;
+  auto& old_state = page->GetPageLifecycleState();
+  bool storing_in_bfcache = new_state->is_in_back_forward_cache &&
+                            !old_state->is_in_back_forward_cache;
+  bool restoring_from_bfcache = !new_state->is_in_back_forward_cache &&
+                                old_state->is_in_back_forward_cache;
   bool hiding_page =
-      (state->visibility != mojom::blink::PageVisibilityState::kVisible) &&
-      (lifecycle_state_->visibility ==
-       mojom::blink::PageVisibilityState::kVisible);
+      (new_state->visibility != mojom::blink::PageVisibilityState::kVisible) &&
+      (old_state->visibility == mojom::blink::PageVisibilityState::kVisible);
   bool showing_page =
-      (state->visibility == mojom::blink::PageVisibilityState::kVisible) &&
-      (lifecycle_state_->visibility !=
-       mojom::blink::PageVisibilityState::kVisible);
-  bool freezing_page = state->is_frozen && !lifecycle_state_->is_frozen;
-  bool resuming_page = !state->is_frozen && lifecycle_state_->is_frozen;
+      (new_state->visibility == mojom::blink::PageVisibilityState::kVisible) &&
+      (old_state->visibility != mojom::blink::PageVisibilityState::kVisible);
+  bool freezing_page = new_state->is_frozen && !old_state->is_frozen;
+  bool resuming_page = !new_state->is_frozen && old_state->is_frozen;
+  bool dispatching_pagehide =
+      (new_state->pagehide_dispatch !=
+       mojom::blink::PagehideDispatch::kNotDispatched) &&
+      !GetPage()->DispatchedPagehideAndStillHidden();
+  bool dispatching_pageshow =
+      (new_state->pagehide_dispatch ==
+       mojom::blink::PagehideDispatch::kNotDispatched) &&
+      GetPage()->DispatchedPagehideAndStillHidden();
 
   if (hiding_page) {
-    SetVisibilityState(state->visibility, /*is_initial_state=*/false);
+    SetVisibilityState(new_state->visibility, /*is_initial_state=*/false);
+  }
+  if (dispatching_pagehide) {
+    // Note that |dispatching_pagehide| is different than |hiding_page|.
+    // |dispatching_pagehide| will only be true when we're navigating away from
+    // a page, while |hiding_page| might be true in other cases too such as when
+    // the tab containing a page is backgrounded, and might be false even when
+    // we're navigating away from a page, if the page is already hidden.
+    DispatchPagehide(new_state->pagehide_dispatch);
   }
   if (storing_in_bfcache) {
-    DispatchPagehide();
-    Scheduler()->SetPageBackForwardCached(state->is_in_back_forward_cache);
+    Scheduler()->SetPageBackForwardCached(new_state->is_in_back_forward_cache);
   }
   if (freezing_page)
     SetPageFrozen(true);
   if (storing_in_bfcache)
     HookBackForwardCacheEviction(true);
-  if (restoring_from_bfcache)
+  if (restoring_from_bfcache) {
     HookBackForwardCacheEviction(false);
+  }
   if (resuming_page)
     SetPageFrozen(false);
-  if (restoring_from_bfcache) {
+  if (dispatching_pageshow) {
+    DCHECK(restoring_from_bfcache);
     DispatchPageshow(navigation_start.value());
-    Scheduler()->SetPageBackForwardCached(state->is_in_back_forward_cache);
+  }
+  if (restoring_from_bfcache) {
+    DCHECK(dispatching_pageshow);
+    Scheduler()->SetPageBackForwardCached(new_state->is_in_back_forward_cache);
   }
   if (showing_page) {
-    SetVisibilityState(mojom::blink::PageVisibilityState::kVisible,
-                       /*is_initial_state=*/false);
+    SetVisibilityState(new_state->visibility, /*is_initial_state=*/false);
   }
 
   // Make sure no TrackedFeaturesUpdate message is sent after the ACK
@@ -2472,9 +2505,7 @@
   // move SchedulerTrackedFeatures to core/ and remove the back and forth.
   ReportActiveSchedulerTrackedFeatures();
 
-  lifecycle_state_ = std::move(state);
-  // Tell the browser that the freezing or resuming was successful.
-  std::move(callback).Run();
+  GetPage()->SetPageLifecycleState(std::move(new_state));
 }
 
 void WebViewImpl::ReportActiveSchedulerTrackedFeatures() {
@@ -2495,12 +2526,20 @@
   GetPage()->GetPageScheduler()->AudioStateChanged(is_audio_playing);
 }
 
-void WebViewImpl::DispatchPagehide() {
+void WebViewImpl::DispatchPagehide(
+    mojom::blink::PagehideDispatch pagehide_dispatch) {
+  DCHECK_NE(pagehide_dispatch, mojom::blink::PagehideDispatch::kNotDispatched);
+  bool persisted = (pagehide_dispatch ==
+                    mojom::blink::PagehideDispatch::kDispatchedPersisted);
+  // Dispatch pagehide on all frames.
   for (Frame* frame = GetPage()->MainFrame(); frame;
        frame = frame->Tree().TraverseNext()) {
     if (frame->DomWindow() && frame->DomWindow()->IsLocalDOMWindow()) {
       frame->DomWindow()->ToLocalDOMWindow()->DispatchPagehideEvent(
-          PageTransitionEventPersistence::kPageTransitionEventPersisted);
+          persisted
+              ? PageTransitionEventPersistence::kPageTransitionEventPersisted
+              : PageTransitionEventPersistence::
+                    kPageTransitionEventNotPersisted);
     }
   }
 }
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.h b/third_party/blink/renderer/core/exported/web_view_impl.h
index dfa50fb..a28c621 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.h
+++ b/third_party/blink/renderer/core/exported/web_view_impl.h
@@ -231,8 +231,8 @@
   void AudioStateChanged(bool is_audio_playing) override;
   void SetInsidePortal(bool is_inside_portal) override;
 
-  void DispatchPagehide();
   void DispatchPageshow(base::TimeTicks navigation_start);
+  void DispatchPagehide(mojom::blink::PagehideDispatch pagehide_dispatch);
   void HookBackForwardCacheEviction(bool hook);
 
   float DefaultMinimumPageScaleFactor() const;
@@ -343,6 +343,10 @@
                           bool is_initial_state) override;
   mojom::blink::PageVisibilityState GetVisibilityState() override;
 
+  void SetPageLifecycleStateFromNewPageCommit(
+      mojom::blink::PageVisibilityState visibility,
+      mojom::blink::PagehideDispatch pagehide_dispatch) override;
+
   // Called by a full frame plugin inside this view to inform it that its
   // zoom level has been updated.  The plugin should only call this function
   // if the zoom change was triggered by the browser, it's only needed in case
@@ -492,6 +496,10 @@
                                      const FloatPoint&);
   void PropagateZoomFactorToLocalFrameRoots(Frame*, float);
 
+  void SetPageLifecycleStateInternal(
+      mojom::blink::PageLifecycleStatePtr new_state,
+      base::Optional<base::TimeTicks> navigation_start);
+
   float MaximumLegiblePageScale() const;
   void RefreshPageScaleFactor();
   IntSize ContentsSize() const;
@@ -742,7 +750,6 @@
   // Set when a measurement begins, reset when the measurement is taken.
   base::Optional<base::TimeTicks> update_layers_start_time_;
 
-  mojom::blink::PageLifecycleStatePtr lifecycle_state_;
   mojo::AssociatedReceiver<mojom::blink::PageBroadcast> receiver_;
 
   base::WeakPtrFactory<WebViewImpl> weak_ptr_factory_{this};
diff --git a/third_party/blink/renderer/core/frame/frame.cc b/third_party/blink/renderer/core/frame/frame.cc
index 9f89663..89a56f8 100644
--- a/third_party/blink/renderer/core/frame/frame.cc
+++ b/third_party/blink/renderer/core/frame/frame.cc
@@ -78,6 +78,14 @@
   return nullptr;
 }
 
+// static
+Frame* Frame::ResolveFrame(const FrameToken& frame_token) {
+  if (frame_token.Is<RemoteFrameToken>())
+    return RemoteFrame::FromFrameToken(frame_token.GetAs<RemoteFrameToken>());
+  DCHECK(frame_token.Is<LocalFrameToken>());
+  return LocalFrame::FromFrameToken(frame_token.GetAs<LocalFrameToken>());
+}
+
 Frame::~Frame() {
   InstanceCounters::DecrementCounter(InstanceCounters::kFrameCounter);
   DCHECK(!owner_);
diff --git a/third_party/blink/renderer/core/frame/frame.h b/third_party/blink/renderer/core/frame/frame.h
index fec98fb..7c0f953 100644
--- a/third_party/blink/renderer/core/frame/frame.h
+++ b/third_party/blink/renderer/core/frame/frame.h
@@ -36,6 +36,7 @@
 #include "third_party/blink/public/common/feature_policy/feature_policy_features.h"
 #include "third_party/blink/public/common/frame/user_activation_state.h"
 #include "third_party/blink/public/common/frame/user_activation_update_source.h"
+#include "third_party/blink/public/common/tokens/tokens.h"
 #include "third_party/blink/public/mojom/ad_tagging/ad_frame.mojom-blink-forward.h"
 #include "third_party/blink/public/mojom/frame/frame_owner_properties.mojom-blink-forward.h"
 #include "third_party/blink/public/mojom/frame/user_activation_notification_type.mojom-blink-forward.h"
@@ -87,7 +88,9 @@
  public:
   // Returns the Frame instance for the given |frame_token|.
   // Note that this Frame can be either a LocalFrame or Remote instance.
+  // TODO(crbug.com/1096617): Remove the UnguessableToken version of this.
   static Frame* ResolveFrame(const base::UnguessableToken& frame_token);
+  static Frame* ResolveFrame(const FrameToken& frame_token);
 
   virtual ~Frame();
 
@@ -287,6 +290,7 @@
   // This identifier represents the stable identifier between a
   // LocalFrame  <--> RenderFrameHostImpl or a
   // RemoteFrame <--> RenderFrameProxyHost in the browser process.
+  // TODO(crbug.com/1096617): Make this return a FrameToken instead.
   const base::UnguessableToken& GetFrameToken() const { return frame_token_; }
 
   bool GetVisibleToHitTesting() const { return visible_to_hit_testing_; }
diff --git a/third_party/blink/renderer/core/frame/local_dom_window.cc b/third_party/blink/renderer/core/frame/local_dom_window.cc
index 72d0daf..19c21fa 100644
--- a/third_party/blink/renderer/core/frame/local_dom_window.cc
+++ b/third_party/blink/renderer/core/frame/local_dom_window.cc
@@ -680,6 +680,21 @@
                 document_.Get());
 }
 
+void LocalDOMWindow::DispatchPagehideEvent(
+    PageTransitionEventPersistence persistence) {
+  if (document_->UnloadStarted()) {
+    // We've already dispatched pagehide (since it's the first thing we do when
+    // starting unload) and shouldn't dispatch it again. We might get here on
+    // a document that is already unloading/has unloaded but still part of the
+    // FrameTree.
+    // TODO(crbug.com/1119291): Investigate whether this is possible or not.
+    return;
+  }
+  DispatchEvent(
+      *PageTransitionEvent::Create(event_type_names::kPagehide, persistence),
+      document_.Get());
+}
+
 void LocalDOMWindow::EnqueueHashchangeEvent(const String& old_url,
                                             const String& new_url) {
   // https://html.spec.whatwg.org/C/#history-traversal
diff --git a/third_party/blink/renderer/core/frame/local_dom_window.h b/third_party/blink/renderer/core/frame/local_dom_window.h
index 5350dbc..34e6a51 100644
--- a/third_party/blink/renderer/core/frame/local_dom_window.h
+++ b/third_party/blink/renderer/core/frame/local_dom_window.h
@@ -395,11 +395,7 @@
 
   void DispatchPersistedPageshowEvent(base::TimeTicks navigation_start);
 
-  void DispatchPagehideEvent(PageTransitionEventPersistence persistence) {
-    DispatchEvent(
-        *PageTransitionEvent::Create(event_type_names::kPagehide, persistence),
-        document_.Get());
-  }
+  void DispatchPagehideEvent(PageTransitionEventPersistence persistence);
 
   InputMethodController& GetInputMethodController() const {
     return *input_method_controller_;
diff --git a/third_party/blink/renderer/core/frame/local_frame.cc b/third_party/blink/renderer/core/frame/local_frame.cc
index bc2e4c2..11b7b4a6 100644
--- a/third_party/blink/renderer/core/frame/local_frame.cc
+++ b/third_party/blink/renderer/core/frame/local_frame.cc
@@ -337,6 +337,11 @@
   return it == local_frames_map.end() ? nullptr : it->value.Get();
 }
 
+// static
+LocalFrame* LocalFrame::FromFrameToken(const LocalFrameToken& frame_token) {
+  return FromFrameToken(frame_token.value());
+}
+
 void LocalFrame::Init(Frame* opener) {
   CoreInitializer::GetInstance().InitLocalFrame(*this);
 
diff --git a/third_party/blink/renderer/core/frame/local_frame.h b/third_party/blink/renderer/core/frame/local_frame.h
index e3e7d188..f228606 100644
--- a/third_party/blink/renderer/core/frame/local_frame.h
+++ b/third_party/blink/renderer/core/frame/local_frame.h
@@ -148,7 +148,9 @@
       public mojom::blink::HighPriorityLocalFrame {
  public:
   // Returns the LocalFrame instance for the given |frame_token|.
+  // TODO(crbug.com/1096617): Remove the UnguessableToken version of this.
   static LocalFrame* FromFrameToken(const base::UnguessableToken& frame_token);
+  static LocalFrame* FromFrameToken(const LocalFrameToken& frame_token);
 
   // For a description of |inheriting_agent_factory| go see the comment on the
   // Frame constructor.
@@ -672,6 +674,10 @@
     return optimization_guide_hints_.get();
   }
 
+  LocalFrameToken GetLocalFrameToken() const {
+    return LocalFrameToken(GetFrameToken());
+  }
+
  private:
   friend class FrameNavigationDisabler;
   FRIEND_TEST_ALL_PREFIXES(LocalFrameTest, CharacterIndexAtPointWithPinchZoom);
diff --git a/third_party/blink/renderer/core/frame/remote_frame.cc b/third_party/blink/renderer/core/frame/remote_frame.cc
index 922908f..a2b3ae9 100644
--- a/third_party/blink/renderer/core/frame/remote_frame.cc
+++ b/third_party/blink/renderer/core/frame/remote_frame.cc
@@ -83,6 +83,11 @@
   return it == remote_frames_map.end() ? nullptr : it->value.Get();
 }
 
+// static
+RemoteFrame* RemoteFrame::FromFrameToken(const RemoteFrameToken& frame_token) {
+  return FromFrameToken(frame_token.value());
+}
+
 RemoteFrame::RemoteFrame(
     RemoteFrameClient* client,
     Page& page,
diff --git a/third_party/blink/renderer/core/frame/remote_frame.h b/third_party/blink/renderer/core/frame/remote_frame.h
index ed5b3c7..1baa5be 100644
--- a/third_party/blink/renderer/core/frame/remote_frame.h
+++ b/third_party/blink/renderer/core/frame/remote_frame.h
@@ -37,7 +37,9 @@
                                       public mojom::blink::RemoteFrame {
  public:
   // Returns the RemoteFrame for the given |frame_token|.
+  // TODO(crbug.com/1096617): Remove the UnguessableToken version of this.
   static RemoteFrame* FromFrameToken(const base::UnguessableToken& frame_token);
+  static RemoteFrame* FromFrameToken(const RemoteFrameToken& frame_token);
 
   // For a description of |inheriting_agent_factory| go see the comment on the
   // Frame constructor.
@@ -178,6 +180,10 @@
   // Indicate that this frame was attached as a MainFrame.
   void WasAttachedAsRemoteMainFrame();
 
+  RemoteFrameToken GetRemoteFrameToken() const {
+    return RemoteFrameToken(GetFrameToken());
+  }
+
  private:
   // Frame protected overrides:
   void DetachImpl(FrameDetachType) override;
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 584792c6..c9a7df1d 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
@@ -595,6 +595,12 @@
   return WebFrame::FromFrame(frame);
 }
 
+// static
+WebFrame* WebFrame::FromFrameToken(const FrameToken& frame_token) {
+  auto* frame = Frame::ResolveFrame(frame_token);
+  return WebFrame::FromFrame(frame);
+}
+
 WebLocalFrame* WebLocalFrame::FrameForCurrentContext() {
   v8::Local<v8::Context> context =
       v8::Isolate::GetCurrent()->GetCurrentContext();
diff --git a/third_party/blink/renderer/core/loader/font_preload_manager.cc b/third_party/blink/renderer/core/loader/font_preload_manager.cc
index 8a60287..82f44589 100644
--- a/third_party/blink/renderer/core/loader/font_preload_manager.cc
+++ b/third_party/blink/renderer/core/loader/font_preload_manager.cc
@@ -6,7 +6,6 @@
 
 #include "build/build_config.h"
 #include "third_party/blink/public/common/features.h"
-#include "third_party/blink/public/common/loader/loading_behavior_flag.h"
 #include "third_party/blink/renderer/core/css/font_face.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/loader/document_loader.h"
@@ -91,9 +90,6 @@
   if (state_ == State::kUnblocked)
     return;
 
-  document_->Loader()->DidObserveLoadingBehavior(
-      kLoadingBehaviorFontPreloadStartedBeforeRendering);
-
   if (!base::FeatureList::IsEnabled(features::kFontPreloadingDelaysRendering))
     return;
 
@@ -114,9 +110,6 @@
   if (state_ == State::kUnblocked)
     return;
 
-  document_->Loader()->DidObserveLoadingBehavior(
-      kLoadingBehaviorFontPreloadStartedBeforeRendering);
-
   if (!base::FeatureList::IsEnabled(features::kFontPreloadingDelaysRendering))
     return;
 
diff --git a/third_party/blink/renderer/core/loader/font_preload_manager_test.cc b/third_party/blink/renderer/core/loader/font_preload_manager_test.cc
index cbafcb4..76265c2 100644
--- a/third_party/blink/renderer/core/loader/font_preload_manager_test.cc
+++ b/third_party/blink/renderer/core/loader/font_preload_manager_test.cc
@@ -544,105 +544,4 @@
   EXPECT_FALSE(GetTargetFont().ShouldSkipDrawing());
 }
 
-class FontPreloadBehaviorObservationTest
-    : public testing::WithParamInterface<bool>,
-      public SimTest {
- public:
-  class LoadingBehaviorObserver
-      : public frame_test_helpers::TestWebFrameClient {
-   public:
-    void DidObserveLoadingBehavior(LoadingBehaviorFlag flag) override {
-      observed_behaviors_ =
-          static_cast<LoadingBehaviorFlag>(observed_behaviors_ | flag);
-    }
-
-    LoadingBehaviorFlag ObservedBehaviors() const {
-      return observed_behaviors_;
-    }
-
-   private:
-    LoadingBehaviorFlag observed_behaviors_ = kLoadingBehaviorNone;
-  };
-
-  void SetUp() override {
-    SimTest::SetUp();
-    original_web_local_frame_client_ = MainFrame().Client();
-    MainFrame().SetClient(&loading_behavior_observer_);
-  }
-
-  void TearDown() override {
-    MainFrame().SetClient(original_web_local_frame_client_);
-    SimTest::TearDown();
-  }
-
-  LoadingBehaviorFlag ObservedBehaviors() const {
-    return loading_behavior_observer_.ObservedBehaviors();
-  }
-
- private:
-  WebLocalFrameClient* original_web_local_frame_client_;
-  LoadingBehaviorObserver loading_behavior_observer_;
-};
-
-INSTANTIATE_TEST_SUITE_P(All,
-                         FontPreloadBehaviorObservationTest,
-                         testing::Bool());
-
-TEST_P(FontPreloadBehaviorObservationTest, ObserveBehaviorWithLinkPreload) {
-  // kLoadingBehaviorFontPreloadStartedBeforeRendering should be observed as
-  // long as there's font preloading, regardless of the enabled status of the
-  // feature FontPreloadingDelaysRendering.
-  base::test::ScopedFeatureList scoped_feature_list;
-  if (GetParam()) {
-    scoped_feature_list.InitAndEnableFeature(
-        features::kFontPreloadingDelaysRendering);
-  }
-
-  SimRequest main_resource("https://example.com", "text/html");
-  SimSubresourceRequest font_resource("https://example.com/font.woff",
-                                      "font/woff2");
-
-  LoadURL("https://example.com");
-  main_resource.Complete(R"HTML(
-    <!doctype html>
-    <link rel="preload" as="font" type="font/woff2"
-          href="https://example.com/font.woff" crossorigin>
-  )HTML");
-
-  EXPECT_TRUE(ObservedBehaviors() |
-              kLoadingBehaviorFontPreloadStartedBeforeRendering);
-
-  font_resource.Complete();
-  test::RunPendingTasks();
-}
-
-TEST_P(FontPreloadBehaviorObservationTest, ObserveBehaviorWithImperativeLoad) {
-  // kLoadingBehaviorFontPreloadStartedBeforeRendering should be observed as
-  // long as there's an imperative font load, regardless of the enabled status
-  // of the feature FontPreloadingDelaysRendering.
-  base::test::ScopedFeatureList scoped_feature_list;
-  if (GetParam()) {
-    scoped_feature_list.InitAndEnableFeature(
-        features::kFontPreloadingDelaysRendering);
-  }
-
-  SimRequest main_resource("https://example.com", "text/html");
-  SimSubresourceRequest font_resource("https://example.com/font.woff",
-                                      "font/woff2");
-
-  LoadURL("https://example.com");
-  main_resource.Complete(R"HTML(
-    <!doctype html>
-    <script>
-    new FontFace('custom-font', 'url(https://example.com/font.woff)').load();
-    </script>
-  )HTML");
-
-  EXPECT_TRUE(ObservedBehaviors() |
-              kLoadingBehaviorFontPreloadStartedBeforeRendering);
-
-  font_resource.Complete();
-  test::RunPendingTasks();
-}
-
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/page/page.cc b/third_party/blink/renderer/core/page/page.cc
index 4065dbe2..ed6e866 100644
--- a/third_party/blink/renderer/core/page/page.cc
+++ b/third_party/blink/renderer/core/page/page.cc
@@ -219,7 +219,7 @@
       opened_by_dom_(false),
       tab_key_cycles_through_elements_(true),
       device_scale_factor_(1),
-      visibility_state_(mojom::blink::PageVisibilityState::kVisible),
+      lifecycle_state_(mojom::blink::PageLifecycleState::New()),
       is_ordinary_(false),
       is_cursor_visible_(true),
       subframe_count_(0),
@@ -528,9 +528,9 @@
 void Page::SetVisibilityState(
     mojom::blink::PageVisibilityState visibility_state,
     bool is_initial_state) {
-  if (visibility_state_ == visibility_state)
+  if (lifecycle_state_->visibility == visibility_state)
     return;
-  visibility_state_ = visibility_state;
+  lifecycle_state_->visibility = visibility_state;
 
   if (is_initial_state)
     return;
@@ -541,18 +541,25 @@
       });
 
   if (main_frame_) {
-    if (visibility_state_ == mojom::blink::PageVisibilityState::kVisible)
+    if (lifecycle_state_->visibility ==
+        mojom::blink::PageVisibilityState::kVisible)
       RestoreSVGImageAnimations();
     main_frame_->DidChangeVisibilityState();
   }
 }
 
 mojom::blink::PageVisibilityState Page::GetVisibilityState() const {
-  return visibility_state_;
+  return lifecycle_state_->visibility;
 }
 
 bool Page::IsPageVisible() const {
-  return visibility_state_ == mojom::blink::PageVisibilityState::kVisible;
+  return lifecycle_state_->visibility ==
+         mojom::blink::PageVisibilityState::kVisible;
+}
+
+bool Page::DispatchedPagehideAndStillHidden() {
+  return lifecycle_state_->pagehide_dispatch !=
+         mojom::blink::PagehideDispatch::kNotDispatched;
 }
 
 void Page::OnSetPageFrozen(bool frozen) {
diff --git a/third_party/blink/renderer/core/page/page.h b/third_party/blink/renderer/core/page/page.h
index 13ea3cb..a43f860 100644
--- a/third_party/blink/renderer/core/page/page.h
+++ b/third_party/blink/renderer/core/page/page.h
@@ -28,6 +28,7 @@
 #include "base/macros.h"
 #include "third_party/blink/public/mojom/devtools/inspector_issue.mojom-blink-forward.h"
 #include "third_party/blink/public/mojom/frame/frame.mojom-blink.h"
+#include "third_party/blink/public/mojom/page/page.mojom-blink.h"
 #include "third_party/blink/public/mojom/page/page_visibility_state.mojom-blink.h"
 #include "third_party/blink/public/platform/scheduler/web_scoped_virtual_time_pauser.h"
 #include "third_party/blink/public/web/web_window_features.h"
@@ -355,6 +356,21 @@
     return page_visibility_observer_list_;
   }
 
+  void SetPageLifecycleState(
+      mojom::blink::PageLifecycleStatePtr lifecycle_state) {
+    lifecycle_state_ = std::move(lifecycle_state);
+  }
+
+  const mojom::blink::PageLifecycleStatePtr& GetPageLifecycleState() {
+    return lifecycle_state_;
+  }
+
+  // Whether we've dispatched "pagehide" on this page previously, and haven't
+  // dispatched the "pageshow" event after the last time we've dispatched
+  // "pagehide". This means that we've navigated away from the page and it's
+  // still hidden (possibly preserved in the back-forward cache, or unloaded).
+  bool DispatchedPagehideAndStillHidden();
+
   static void PrepareForLeakDetection();
 
   TextFragmentSelectorGenerator& GetTextFragmentSelectorGenerator() const {
@@ -434,7 +450,7 @@
 
   float device_scale_factor_;
 
-  mojom::blink::PageVisibilityState visibility_state_;
+  mojom::blink::PageLifecycleStatePtr lifecycle_state_;
 
   bool is_ordinary_;
 
diff --git a/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc b/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc
index 8d251f0..0f8f4074 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc
@@ -1450,9 +1450,16 @@
       !layout_object_->IsBox())
     return nullptr;
 
-  // Must be called with lifecycle >= compositing clean.
-  DCHECK_GE(GetDocument()->Lifecycle().GetState(),
-            DocumentLifecycle::kCompositingAssignmentsClean);
+    // Must be called with lifecycle >= compositing clean
+#if DCHECK_IS_ON()
+  if (RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) {
+    DCHECK_GE(GetDocument()->Lifecycle().GetState(),
+              DocumentLifecycle::kPrePaintClean);
+  } else {
+    DCHECK_GE(GetDocument()->Lifecycle().GetState(),
+              DocumentLifecycle::kCompositingAssignmentsClean);
+  }
+#endif
 
   PaintLayer* layer = ToLayoutBox(layout_object_)->Layer();
 
diff --git a/third_party/blink/renderer/modules/imagecapture/image_capture.cc b/third_party/blink/renderer/modules/imagecapture/image_capture.cc
index 6b0d71fe..a1fe377 100644
--- a/third_party/blink/renderer/modules/imagecapture/image_capture.cc
+++ b/third_party/blink/renderer/modules/imagecapture/image_capture.cc
@@ -13,6 +13,7 @@
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/renderer/bindings/core/v8/callback_promise_adapter.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_fill_light_mode.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_media_settings_range.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_media_track_capabilities.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_media_track_constraints.h"
@@ -85,16 +86,29 @@
   }
 }
 
-WebString ToString(FillLightMode value) {
+#ifdef USE_BLINK_V8_BINDING_NEW_IDL_DICTIONARY
+V8FillLightMode ToV8FillLightMode(FillLightMode value) {
   switch (value) {
     case FillLightMode::OFF:
-      return WebString::FromUTF8("off");
+      return V8FillLightMode(V8FillLightMode::Enum::kOff);
     case FillLightMode::AUTO:
-      return WebString::FromUTF8("auto");
+      return V8FillLightMode(V8FillLightMode::Enum::kAuto);
     case FillLightMode::FLASH:
-      return WebString::FromUTF8("flash");
+      return V8FillLightMode(V8FillLightMode::Enum::kFlash);
   }
 }
+#else
+String ToV8FillLightMode(FillLightMode value) {
+  switch (value) {
+    case FillLightMode::OFF:
+      return String::FromUTF8("off");
+    case FillLightMode::AUTO:
+      return String::FromUTF8("auto");
+    case FillLightMode::FLASH:
+      return String::FromUTF8("flash");
+  }
+}
+#endif
 
 WebString ToString(RedEyeReduction value) {
   switch (value) {
@@ -965,9 +979,14 @@
     photo_capabilities_->setImageWidth(
         ToMediaSettingsRange(*photo_state->width));
   }
+
+#ifdef USE_BLINK_V8_BINDING_NEW_IDL_DICTIONARY
+  WTF::Vector<V8FillLightMode> fill_light_mode;
+#else
   WTF::Vector<WTF::String> fill_light_mode;
+#endif
   for (const auto& mode : photo_state->fill_light_mode) {
-    fill_light_mode.push_back(ToString(mode));
+    fill_light_mode.push_back(ToV8FillLightMode(mode));
   }
   if (!fill_light_mode.IsEmpty())
     photo_capabilities_->setFillLightMode(fill_light_mode);
diff --git a/third_party/blink/renderer/modules/websockets/dom_websocket.cc b/third_party/blink/renderer/modules/websockets/dom_websocket.cc
index 0e9c874..006263c 100644
--- a/third_party/blink/renderer/modules/websockets/dom_websocket.cc
+++ b/third_party/blink/renderer/modules/websockets/dom_websocket.cc
@@ -498,9 +498,6 @@
   NETWORK_DVLOG(1) << "WebSocket " << this << " contextDestroyed()";
   event_queue_->ContextDestroyed();
   if (channel_) {
-    if (common_.GetState() == kOpen) {
-      channel_->Close(WebSocketChannel::kCloseEventCodeGoingAway, String());
-    }
     ReleaseChannel();
   }
   if (common_.GetState() != kClosed) {
diff --git a/third_party/blink/renderer/modules/websockets/websocket_channel_impl.cc b/third_party/blink/renderer/modules/websockets/websocket_channel_impl.cc
index cd00271..3c99d67 100644
--- a/third_party/blink/renderer/modules/websockets/websocket_channel_impl.cc
+++ b/third_party/blink/renderer/modules/websockets/websocket_channel_impl.cc
@@ -362,6 +362,7 @@
 
 void WebSocketChannelImpl::Close(int code, const String& reason) {
   DCHECK_EQ(GetState(), State::kOpen);
+  DCHECK(!execution_context_->IsContextDestroyed());
   NETWORK_DVLOG(1) << this << " Close(" << code << ", " << reason << ")";
   uint16_t code_to_send = static_cast<uint16_t>(
       code == kCloseEventCodeNotSpecified ? kCloseEventCodeNoStatusRcvd : code);
@@ -661,6 +662,7 @@
 void WebSocketChannelImpl::ProcessSendQueue() {
   // TODO(yhirano): This should be DCHECK_EQ(GetState(), State::kOpen).
   DCHECK(GetState() == State::kOpen || GetState() == State::kConnecting);
+  DCHECK(!execution_context_->IsContextDestroyed());
   while (!messages_.IsEmpty() && !blob_loader_ && !wait_for_writable_) {
     Message& message = messages_.front();
     network::mojom::blink::WebSocketMessageType message_type =
diff --git a/third_party/blink/renderer/modules/websockets/websocket_stream.cc b/third_party/blink/renderer/modules/websockets/websocket_stream.cc
index 78fb647..15c007e 100644
--- a/third_party/blink/renderer/modules/websockets/websocket_stream.cc
+++ b/third_party/blink/renderer/modules/websockets/websocket_stream.cc
@@ -573,9 +573,6 @@
 void WebSocketStream::ContextDestroyed() {
   DVLOG(1) << "WebSocketStream " << this << " ContextDestroyed()";
   if (channel_) {
-    if (common_.GetState() == WebSocketCommon::kOpen) {
-      channel_->Close(WebSocketChannel::kCloseEventCodeGoingAway, String());
-    }
     channel_ = nullptr;
   }
   if (common_.GetState() != WebSocketCommon::kClosed) {
diff --git a/third_party/blink/renderer/modules/websockets/websocket_stream_test.cc b/third_party/blink/renderer/modules/websockets/websocket_stream_test.cc
index b354da7..51b9aa89 100644
--- a/third_party/blink/renderer/modules/websockets/websocket_stream_test.cc
+++ b/third_party/blink/renderer/modules/websockets/websocket_stream_test.cc
@@ -199,7 +199,6 @@
 
 TEST_F(WebSocketStreamTest, ConnectWithSuccessfulHandshake) {
   V8TestingScope scope;
-  Checkpoint checkpoint;
 
   {
     InSequence s;
@@ -207,8 +206,6 @@
     EXPECT_CALL(Channel(),
                 Connect(KURL("ws://example.com/chat"), String("chat")))
         .WillOnce(Return(true));
-    EXPECT_CALL(checkpoint, Call(1));
-    EXPECT_CALL(Channel(), Close(1001, String()));
   }
 
   auto* options = WebSocketStreamOptions::Create();
@@ -238,9 +235,6 @@
   EXPECT_EQ(PropertyAsString(script_state, value, "protocol"), "chat");
   EXPECT_EQ(PropertyAsString(script_state, value, "extensions"),
             "permessage-deflate");
-
-  // Destruction of V8TestingScope causes Close() to be called.
-  checkpoint.Call(1);
 }
 
 TEST_F(WebSocketStreamTest, ConnectThenCloseCleanly) {
diff --git a/third_party/blink/renderer/platform/exported/web_runtime_features.cc b/third_party/blink/renderer/platform/exported/web_runtime_features.cc
index 7ab2daa..36480e6e5 100644
--- a/third_party/blink/renderer/platform/exported/web_runtime_features.cc
+++ b/third_party/blink/renderer/platform/exported/web_runtime_features.cc
@@ -716,4 +716,9 @@
 void WebRuntimeFeatures::EnableRestrictGamepadAccess(bool enable) {
   RuntimeEnabledFeatures::SetRestrictGamepadAccessEnabled(enable);
 }
+
+void WebRuntimeFeatures::EnableCompositingOptimizations(bool enable) {
+  RuntimeEnabledFeatures::SetCompositingOptimizationsEnabled(enable);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/fonts/shaping/shape_result.cc b/third_party/blink/renderer/platform/fonts/shaping/shape_result.cc
index 972fdc4a..35d1001 100644
--- a/third_party/blink/renderer/platform/fonts/shaping/shape_result.cc
+++ b/third_party/blink/renderer/platform/fonts/shaping/shape_result.cc
@@ -1259,6 +1259,7 @@
       std::min(end_offset, EndIndex()) - std::max(start_offset, StartIndex());
 
   unsigned target_run_size_before = target->runs_.size();
+  bool should_merge = !target->runs_.IsEmpty();
   for (; run_index < runs_.size(); run_index++) {
     const auto& run = runs_[run_index];
     unsigned run_start = run->start_index_;
@@ -1273,7 +1274,14 @@
       sub_run->start_index_ += index_diff;
       target->width_ += sub_run->width_;
       target->num_glyphs_ += sub_run->glyph_data_.size();
-      target->runs_.push_back(std::move(sub_run));
+      if (auto merged_run =
+              should_merge ? target->runs_.back()->MergeIfPossible(*sub_run)
+                           : scoped_refptr<RunInfo>()) {
+        target->runs_.back() = std::move(merged_run);
+      } else {
+        target->runs_.push_back(std::move(sub_run));
+      }
+      should_merge = false;
 
       // No need to process runs after the end of the range.
       if ((!Rtl() && end_offset <= run_end) ||
diff --git a/third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h b/third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h
index 32fa8f2..c13257b8 100644
--- a/third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h
+++ b/third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h
@@ -193,6 +193,44 @@
     return run;
   }
 
+  // Returns new |RunInfo| if |this| and |other| are merged. Otherwise returns
+  // null.
+  scoped_refptr<RunInfo> MergeIfPossible(const RunInfo& other) const {
+    if (!CanMerge(other))
+      return nullptr;
+    DCHECK_LT(start_index_, other.start_index_);
+    auto run =
+        Create(font_data_.get(), direction_, canvas_rotation_, script_,
+               start_index_, glyph_data_.size() + other.glyph_data_.size(),
+               num_characters_ + other.num_characters_);
+    // Note: We populate |graphemes_| on demand, e.g. hit testing.
+    const int index_adjust = other.start_index_ - start_index_;
+    if (UNLIKELY(Rtl())) {
+      run->glyph_data_.CopyFrom(other.glyph_data_, glyph_data_);
+      auto* const end = run->glyph_data_.begin() + other.glyph_data_.size();
+      for (auto* it = run->glyph_data_.begin(); it < end; ++it)
+        it->character_index += index_adjust;
+    } else {
+      run->glyph_data_.CopyFrom(glyph_data_, other.glyph_data_);
+      auto* const end = run->glyph_data_.end();
+      for (auto* it = run->glyph_data_.begin() + glyph_data_.size(); it < end;
+           ++it)
+        it->character_index += index_adjust;
+    }
+    run->width_ = width_ + other.width_;
+    return run;
+  }
+
+  // Returns true if |other| can be merged at end of |this|.
+  bool CanMerge(const RunInfo& other) const {
+    return start_index_ + num_characters_ == other.start_index_ &&
+           canvas_rotation_ == other.canvas_rotation_ &&
+           font_data_ == other.font_data_ && direction_ == other.direction_ &&
+           script_ == other.script_ &&
+           glyph_data_.size() + other.glyph_data_.size() <
+               HarfBuzzRunGlyphData::kMaxCharacterIndex + 1;
+  }
+
   void ExpandRangeToIncludePartialGlyphs(int offset, int* from, int* to) const {
     int start = !Rtl() ? offset : (offset + num_characters_);
     int end = offset + num_characters_;
@@ -263,11 +301,31 @@
     }
 
     unsigned size() const { return size_; }
+    bool IsEmpty() const { return size() == 0; }
 
     size_t ByteSize() const {
       return storage_ ? size() * sizeof(GlyphOffset) : 0;
     }
 
+    void CopyFrom(const GlyphOffsetArray& other1,
+                  const GlyphOffsetArray& other2) {
+      SECURITY_CHECK(size() == other1.size() + other2.size());
+      DCHECK(!other1.IsEmpty());
+      DCHECK(!other2.IsEmpty());
+      if (other1.storage_) {
+        if (!storage_)
+          AllocateStorage();
+        std::copy(other1.storage_.get(), other1.storage_.get() + other1.size(),
+                  storage_.get());
+      }
+      if (other2.storage_) {
+        if (!storage_)
+          AllocateStorage();
+        std::copy(other2.storage_.get(), other2.storage_.get() + other2.size(),
+                  storage_.get() + other1.size());
+      }
+    }
+
     void CopyFromRange(const GlyphDataRange& range) {
       DCHECK_EQ(range.size(), size());
       if (!range.offsets || range.size() == 0) {
@@ -379,6 +437,20 @@
 
     GlyphOffset* GetMayBeOffsets() const { return offsets_.GetStorage(); }
 
+    // Note: Caller should be adjust |HarfBuzzRunGlyphData.character_index|.
+    void CopyFrom(const GlyphDataCollection& other1,
+                  const GlyphDataCollection& other2) {
+      SECURITY_CHECK(size() == other1.size() + other2.size());
+      DCHECK(!other1.IsEmpty());
+      DCHECK(!other2.IsEmpty());
+      std::copy(other1.data_.get(), other1.data_.get() + other1.size(),
+                data_.get());
+      std::copy(other2.data_.get(), other2.data_.get() + other2.size(),
+                data_.get() + other1.size());
+      offsets_.CopyFrom(other1.offsets_, other2.offsets_);
+    }
+
+    // Note: Caller should be adjust |HarfBuzzRunGlyphData.character_index|.
     void CopyFromRange(const GlyphDataRange& range) {
       DCHECK_EQ(static_cast<size_t>(range.end - range.begin), size());
       static_assert(base::is_trivially_copyable<HarfBuzzRunGlyphData>::value,
diff --git a/third_party/blink/renderer/platform/heap/BUILD.gn b/third_party/blink/renderer/platform/heap/BUILD.gn
index 502c1a0..ae2e032 100644
--- a/third_party/blink/renderer/platform/heap/BUILD.gn
+++ b/third_party/blink/renderer/platform/heap/BUILD.gn
@@ -48,8 +48,6 @@
     "blink_gc.h",
     "blink_gc_memory_dump_provider.cc",
     "blink_gc_memory_dump_provider.h",
-    "cancelable_task_scheduler.cc",
-    "cancelable_task_scheduler.h",
     "collection_support/heap_hash_table_backing.h",
     "collection_support/heap_linked_stack.h",
     "collection_support/heap_vector_backing.h",
@@ -154,7 +152,6 @@
   sources = [
     "../testing/run_all_tests.cc",
     "test/blink_gc_memory_dump_provider_test.cc",
-    "test/cancelable_task_scheduler_test.cc",
     "test/card_table_test.cc",
     "test/concurrent_marking_test.cc",
     "test/gc_info_test.cc",
diff --git a/third_party/blink/renderer/platform/heap/cancelable_task_scheduler.cc b/third_party/blink/renderer/platform/heap/cancelable_task_scheduler.cc
deleted file mode 100644
index 8b65942..0000000
--- a/third_party/blink/renderer/platform/heap/cancelable_task_scheduler.cc
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "third_party/blink/renderer/platform/heap/cancelable_task_scheduler.h"
-
-#include "base/check.h"
-#include "base/macros.h"
-#include "base/task_runner.h"
-#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
-#include "third_party/blink/renderer/platform/wtf/vector.h"
-
-namespace blink {
-
-class CancelableTaskScheduler::TaskData {
-  USING_FAST_MALLOC(TaskData);
-  DISALLOW_COPY_AND_ASSIGN(TaskData);
-
- public:
-  TaskData(Task task, CancelableTaskScheduler* scheduler)
-      : task_(std::move(task)), scheduler_(scheduler), status_(kWaiting) {}
-
-  ~TaskData() {
-    // The task runner is responsible for unregistering the task in case the
-    // task hasn't been cancelled.
-    if (TryCancel()) {
-      scheduler_->UnregisterAndSignal(this);
-    }
-  }
-
-  void Run() {
-    if (TryRun()) {
-      std::move(task_).Run();
-      scheduler_->UnregisterAndSignal(this);
-    }
-  }
-
-  bool TryCancel() {
-    Status expected = kWaiting;
-    return status_.compare_exchange_strong(expected, kCancelled,
-                                           std::memory_order_acq_rel,
-                                           std::memory_order_acquire);
-  }
-
- private:
-  // Identifies the state a cancelable task is in:
-  // |kWaiting|: The task is scheduled and waiting to be executed. {TryRun} will
-  // succeed.
-  // |kCancelled|: The task has been cancelled. {TryRun} will fail.
-  // |kRunning|: The task is currently running and cannot be canceled anymore.
-  enum Status : uint8_t { kWaiting, kCancelled, kRunning };
-
-  bool TryRun() {
-    Status expected = kWaiting;
-    return status_.compare_exchange_strong(expected, kRunning,
-                                           std::memory_order_acq_rel,
-                                           std::memory_order_acquire);
-  }
-
-  Task task_;
-  CancelableTaskScheduler* const scheduler_;
-  std::atomic<Status> status_;
-};
-
-CancelableTaskScheduler::CancelableTaskScheduler(
-    scoped_refptr<base::TaskRunner> task_runner)
-    : cond_var_(&lock_), task_runner_(std::move(task_runner)) {}
-
-CancelableTaskScheduler::~CancelableTaskScheduler() {
-  base::AutoLock lock(lock_);
-  CHECK(tasks_.IsEmpty());
-}
-
-void CancelableTaskScheduler::ScheduleTask(Task task) {
-  std::unique_ptr<TaskData> task_data = Register(std::move(task));
-  task_runner_->PostTask(FROM_HERE,
-                         base::BindOnce(&TaskData::Run, std::move(task_data)));
-}
-
-size_t CancelableTaskScheduler::CancelAndWait() {
-  size_t result = 0;
-  base::AutoLock lock(lock_);
-  while (!tasks_.IsEmpty()) {
-    result += RemoveCancelledTasks();
-    if (!tasks_.IsEmpty()) {
-      cond_var_.Wait();
-    }
-  }
-  return result;
-}
-
-std::unique_ptr<CancelableTaskScheduler::TaskData>
-CancelableTaskScheduler::Register(Task task) {
-  auto task_data = std::make_unique<TaskData>(std::move(task), this);
-  base::AutoLock lock(lock_);
-  tasks_.insert(task_data.get());
-  return task_data;
-}
-
-void CancelableTaskScheduler::UnregisterAndSignal(TaskData* task_data) {
-  base::AutoLock lock(lock_);
-  CHECK(tasks_.Contains(task_data));
-  tasks_.erase(task_data);
-  cond_var_.Signal();
-}
-
-// This function is needed because WTF::HashSet::erase function invalidates
-// all iterators. Returns number of removed tasks.
-size_t CancelableTaskScheduler::RemoveCancelledTasks() {
-  WTF::Vector<TaskData*> to_be_removed;
-  // Assume worst case.
-  to_be_removed.ReserveCapacity(tasks_.size());
-  for (TaskData* task : tasks_) {
-    if (task->TryCancel()) {
-      to_be_removed.push_back(task);
-    }
-  }
-  tasks_.RemoveAll(to_be_removed);
-  return to_be_removed.size();
-}
-
-size_t CancelableTaskScheduler::NumberOfTasksForTesting() const {
-  base::AutoLock lock(lock_);
-  return tasks_.size();
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/platform/heap/cancelable_task_scheduler.h b/third_party/blink/renderer/platform/heap/cancelable_task_scheduler.h
deleted file mode 100644
index 914ab57..0000000
--- a/third_party/blink/renderer/platform/heap/cancelable_task_scheduler.h
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_HEAP_CANCELABLE_TASK_SCHEDULER_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_HEAP_CANCELABLE_TASK_SCHEDULER_H_
-
-#include <memory>
-
-#include "base/memory/scoped_refptr.h"
-#include "base/synchronization/condition_variable.h"
-#include "base/synchronization/lock.h"
-#include "third_party/blink/renderer/platform/platform_export.h"
-#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
-#include "third_party/blink/renderer/platform/wtf/functional.h"
-#include "third_party/blink/renderer/platform/wtf/hash_set.h"
-
-namespace base {
-class TaskRunner;
-}
-
-namespace blink {
-
-// CancelableTaskScheduler allows for scheduling tasks that can be cancelled
-// before they are invoked. User is responsible for synchronizing completion of
-// tasks and destruction of CancelableTaskScheduler.
-class PLATFORM_EXPORT CancelableTaskScheduler final {
-  USING_FAST_MALLOC(CancelableTaskScheduler);
-
- public:
-  using Task = WTF::CrossThreadOnceFunction<void()>;
-
-  explicit CancelableTaskScheduler(scoped_refptr<base::TaskRunner>);
-  ~CancelableTaskScheduler();
-
-  // Schedules task to run on TaskRunner.
-  void ScheduleTask(Task);
-  // Cancels all not yet started tasks and waits for running ones to complete.
-  // Returns number of cancelled (not executed) tasks.
-  size_t CancelAndWait();
-
- private:
-  class TaskData;
-  template <class T>
-  friend class CancelableTaskSchedulerTest;
-
-  std::unique_ptr<TaskData> Register(Task);
-  void UnregisterAndSignal(TaskData*);
-
-  size_t RemoveCancelledTasks();
-
-  size_t NumberOfTasksForTesting() const;
-
-  WTF::HashSet<TaskData*> tasks_;
-  mutable base::Lock lock_;
-  base::ConditionVariable cond_var_;
-  scoped_refptr<base::TaskRunner> task_runner_;
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_HEAP_CANCELABLE_TASK_SCHEDULER_H_
diff --git a/third_party/blink/renderer/platform/heap/heap.cc b/third_party/blink/renderer/platform/heap/heap.cc
index aba11a5c..6aee323 100644
--- a/third_party/blink/renderer/platform/heap/heap.cc
+++ b/third_party/blink/renderer/platform/heap/heap.cc
@@ -309,17 +309,18 @@
 
 template <size_t kDeadlineCheckInterval = kDefaultDeadlineCheckInterval,
           typename Worklist,
-          typename Callback>
-bool DrainWorklistWithDeadline(base::TimeTicks deadline,
-                               Worklist* worklist,
-                               Callback callback,
-                               int task_id) {
+          typename Callback,
+          typename YieldPredicate>
+bool DrainWorklist(Worklist* worklist,
+                   Callback callback,
+                   YieldPredicate should_yield,
+                   int task_id) {
   size_t processed_callback_count = 0;
   typename Worklist::EntryType item;
   while (worklist->Pop(task_id, &item)) {
     callback(item);
     if (processed_callback_count-- == 0) {
-      if (deadline <= base::TimeTicks::Now()) {
+      if (should_yield()) {
         return false;
       }
       processed_callback_count = kDeadlineCheckInterval;
@@ -328,6 +329,30 @@
   return true;
 }
 
+template <size_t kDeadlineCheckInterval = kDefaultDeadlineCheckInterval,
+          typename Worklist,
+          typename Callback>
+bool DrainWorklistWithDeadline(base::TimeTicks deadline,
+                               Worklist* worklist,
+                               Callback callback,
+                               int task_id) {
+  return DrainWorklist<kDeadlineCheckInterval>(
+      worklist, std::move(callback),
+      [deadline]() { return deadline <= base::TimeTicks::Now(); }, task_id);
+}
+
+template <size_t kDeadlineCheckInterval = kDefaultDeadlineCheckInterval,
+          typename Worklist,
+          typename Callback>
+bool DrainWorklistWithYielding(base::JobDelegate* delegate,
+                               Worklist* worklist,
+                               Callback callback,
+                               int task_id) {
+  return DrainWorklist<kDeadlineCheckInterval>(
+      worklist, std::move(callback),
+      [delegate]() { return delegate->ShouldYield(); }, task_id);
+}
+
 }  // namespace
 
 bool ThreadHeap::InvokeEphemeronCallbacks(
@@ -492,7 +517,15 @@
          !ephemeron_pairs_to_process_worklist_->IsGlobalPoolEmpty();
 }
 
+size_t ThreadHeap::ConcurrentMarkingGlobalWorkSize() const {
+  return marking_worklist_->GlobalPoolSize() +
+         write_barrier_worklist_->GlobalPoolSize() +
+         previously_not_fully_constructed_worklist_->GlobalPoolSize() +
+         ephemeron_pairs_to_process_worklist_->GlobalPoolSize();
+}
+
 bool ThreadHeap::AdvanceConcurrentMarking(ConcurrentMarkingVisitor* visitor,
+                                          base::JobDelegate* delegate,
                                           base::TimeTicks deadline) {
   bool finished;
   do {
@@ -500,8 +533,8 @@
     // |marking_worklist_|. This merely re-adds items with the proper
     // callbacks.
     finished =
-        DrainWorklistWithDeadline<kDefaultConcurrentDeadlineCheckInterval>(
-            deadline, previously_not_fully_constructed_worklist_.get(),
+        DrainWorklistWithYielding<kDefaultConcurrentDeadlineCheckInterval>(
+            delegate, previously_not_fully_constructed_worklist_.get(),
             [visitor](NotFullyConstructedItem& item) {
               visitor->DynamicallyMarkAddress(
                   reinterpret_cast<ConstAddress>(item));
@@ -512,9 +545,9 @@
 
     // Iteratively mark all objects that are reachable from the objects
     // currently pushed onto the marking worklist.
-    finished = DrainWorklistWithDeadline<
+    finished = DrainWorklistWithYielding<
         kDefaultConcurrentDeadlineCheckInterval>(
-        deadline, marking_worklist_.get(),
+        delegate, marking_worklist_.get(),
         [visitor](const MarkingItem& item) {
           HeapObjectHeader* header =
               HeapObjectHeader::FromPayload(item.base_object_payload);
@@ -529,9 +562,9 @@
     if (!finished)
       break;
 
-    finished = DrainWorklistWithDeadline<
+    finished = DrainWorklistWithYielding<
         kDefaultConcurrentDeadlineCheckInterval>(
-        deadline, write_barrier_worklist_.get(),
+        delegate, write_barrier_worklist_.get(),
         [visitor](HeapObjectHeader* header) {
           PageFromObject(header)->SynchronizedLoad();
           DCHECK(
@@ -554,8 +587,8 @@
       // by the mutator thread and then invoked either concurrently or by the
       // mutator thread (in the atomic pause at latest).
       finished =
-          DrainWorklistWithDeadline<kDefaultConcurrentDeadlineCheckInterval>(
-              deadline, ephemeron_pairs_to_process_worklist_.get(),
+          DrainWorklistWithYielding<kDefaultConcurrentDeadlineCheckInterval>(
+              delegate, ephemeron_pairs_to_process_worklist_.get(),
               [visitor](EphemeronPairItem& item) {
                 visitor->VisitEphemeron(item.key, item.value,
                                         item.value_trace_callback);
diff --git a/third_party/blink/renderer/platform/heap/heap.h b/third_party/blink/renderer/platform/heap/heap.h
index 0f7d696..af6aea8b 100644
--- a/third_party/blink/renderer/platform/heap/heap.h
+++ b/third_party/blink/renderer/platform/heap/heap.h
@@ -301,8 +301,13 @@
 
   // Returns true if concurrent markers will have work to steal
   bool HasWorkForConcurrentMarking() const;
+  // Returns the amount of work currently available for stealing (there could be
+  // work remaining even if this is 0).
+  size_t ConcurrentMarkingGlobalWorkSize() const;
   // Returns true if marker is done
-  bool AdvanceConcurrentMarking(ConcurrentMarkingVisitor*, base::TimeTicks);
+  bool AdvanceConcurrentMarking(ConcurrentMarkingVisitor*,
+                                base::JobDelegate*,
+                                base::TimeTicks);
 
   // Conservatively checks whether an address is a pointer in any of the
   // thread heaps.  If so marks the object pointed to as live.
diff --git a/third_party/blink/renderer/platform/heap/test/cancelable_task_scheduler_test.cc b/third_party/blink/renderer/platform/heap/test/cancelable_task_scheduler_test.cc
deleted file mode 100644
index 97f4c8b..0000000
--- a/third_party/blink/renderer/platform/heap/test/cancelable_task_scheduler_test.cc
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "third_party/blink/renderer/platform/heap/cancelable_task_scheduler.h"
-
-#include <atomic>
-
-#include "base/memory/scoped_refptr.h"
-#include "base/task_runner.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/blink/renderer/platform/heap/heap_test_utilities.h"
-#include "third_party/blink/renderer/platform/scheduler/public/worker_pool.h"
-#include "third_party/blink/renderer/platform/scheduler/test/fake_task_runner.h"
-#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
-
-namespace blink {
-
-class ParallelTaskRunner : public base::TaskRunner {
- public:
-  bool PostDelayedTask(const base::Location& location,
-                       base::OnceClosure task,
-                       base::TimeDelta) override {
-    worker_pool::PostTask(location, WTF::CrossThreadBindOnce(std::move(task)));
-    return true;
-  }
-
-  void RunUntilIdle() {}
-};
-
-template <class Runner>
-class CancelableTaskSchedulerTest : public TestSupportingGC {
- public:
-  using Task = CancelableTaskScheduler::Task;
-
-  void ScheduleTask(Task callback) {
-    scheduler_.ScheduleTask(std::move(callback));
-  }
-
-  void RunTaskRunner() { task_runner_->RunUntilIdle(); }
-  size_t CancelAndWait() { return scheduler_.CancelAndWait(); }
-
-  size_t NumberOfRegisteredTasks() const {
-    return scheduler_.NumberOfTasksForTesting();
-  }
-
- private:
-  scoped_refptr<Runner> task_runner_ = base::MakeRefCounted<Runner>();
-  CancelableTaskScheduler scheduler_{task_runner_};
-};
-
-using RunnerTypes =
-    ::testing::Types<scheduler::FakeTaskRunner, ParallelTaskRunner>;
-TYPED_TEST_SUITE(CancelableTaskSchedulerTest, RunnerTypes);
-
-TYPED_TEST(CancelableTaskSchedulerTest, EmptyCancelTasks) {
-  const size_t cancelled = this->CancelAndWait();
-  EXPECT_EQ(0u, cancelled);
-  EXPECT_EQ(0u, this->NumberOfRegisteredTasks());
-}
-
-TYPED_TEST(CancelableTaskSchedulerTest, RunAndCancelTasks) {
-  static constexpr size_t kNumberOfTasks = 10u;
-
-  const auto callback = [](std::atomic<int>* i) { ++(*i); };
-  std::atomic<int> var{0};
-
-  for (size_t i = 0; i < kNumberOfTasks; ++i) {
-    this->ScheduleTask(
-        WTF::CrossThreadBindOnce(callback, WTF::CrossThreadUnretained(&var)));
-    EXPECT_GE(i + 1, this->NumberOfRegisteredTasks());
-  }
-
-  this->RunTaskRunner();
-  // Tasks will remove themselves after running
-  EXPECT_LE(0u, this->NumberOfRegisteredTasks());
-
-  const size_t cancelled = this->CancelAndWait();
-  EXPECT_EQ(0u, this->NumberOfRegisteredTasks());
-  EXPECT_EQ(kNumberOfTasks, var + cancelled);
-}
-
-TEST(CancelableTaskSchedulerTest, RemoveTasksFromQueue) {
-  auto task_runner = base::MakeRefCounted<scheduler::FakeTaskRunner>();
-  CancelableTaskScheduler scheduler{task_runner};
-  int var = 0;
-  scheduler.ScheduleTask(WTF::CrossThreadBindOnce(
-      [](int* var) { ++(*var); }, WTF::CrossThreadUnretained(&var)));
-  auto tasks = task_runner->TakePendingTasksForTesting();
-  // Clearing the task queue should destroy all cancelable closures, which in
-  // turn will notify CancelableTaskScheduler to remove corresponding tasks.
-  tasks.clear();
-  EXPECT_EQ(0, var);
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/platform/heap/thread_state.cc b/third_party/blink/renderer/platform/heap/thread_state.cc
index e6859a8..2c322dc8 100644
--- a/third_party/blink/renderer/platform/heap/thread_state.cc
+++ b/third_party/blink/renderer/platform/heap/thread_state.cc
@@ -50,7 +50,6 @@
 #include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h"
 #include "third_party/blink/renderer/platform/heap/blink_gc.h"
 #include "third_party/blink/renderer/platform/heap/blink_gc_memory_dump_provider.h"
-#include "third_party/blink/renderer/platform/heap/cancelable_task_scheduler.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/heap/heap.h"
 #include "third_party/blink/renderer/platform/heap/heap_buildflags.h"
@@ -107,11 +106,6 @@
 // TODO(omerkatz): What is a good value to set here?
 constexpr base::TimeDelta kConcurrentMarkingStepDuration =
     base::TimeDelta::FromMilliseconds(2);
-// Number of concurrent marking tasks to use.
-//
-// TODO(omerkatz): kNumberOfMarkingTasks should be set heuristically
-// instead of a constant.
-constexpr uint8_t kNumberOfConcurrentMarkingTasks = 3u;
 
 constexpr size_t kMaxTerminationGCLoops = 20;
 
@@ -198,9 +192,7 @@
       asan_fake_stack_(__asan_get_current_fake_stack()),
 #endif
       incremental_marking_scheduler_(
-          std::make_unique<IncrementalMarkingScheduler>(this)),
-      marker_scheduler_(std::make_unique<CancelableTaskScheduler>(
-          base::MakeRefCounted<WorkerPoolTaskRunner>())) {
+          std::make_unique<IncrementalMarkingScheduler>(this)) {
   DCHECK(CheckThread());
   DCHECK(!**thread_specific_);
   **thread_specific_ = this;
@@ -697,10 +689,9 @@
     SetGCState(kNoGCScheduled);
     if (base::FeatureList::IsEnabled(
             blink::features::kBlinkHeapConcurrentMarking)) {
-      // Stop concurrent markers
-      marker_scheduler_->CancelAndWait();
-      active_markers_ = 0;
-      available_concurrent_marking_task_ids_.clear();
+      // Stop concurrent markers and wait synchronously until they have all
+      // returned.
+      marker_handle_.Cancel();
     }
 #if DCHECK_IS_ON()
     MarkingWorklist* marking_worklist = Heap().GetMarkingWorklist();
@@ -1132,23 +1123,10 @@
     if (base::FeatureList::IsEnabled(
             blink::features::kBlinkHeapConcurrentMarking)) {
       current_gc_data_.visitor->FlushMarkingWorklists();
-      // Check that the marking worklist has enough private segments for all
-      // concurrent marking tasks.
-      const uint8_t max_concurrent_task_id =
-          WorklistTaskId::ConcurrentThreadBase +
-          kNumberOfConcurrentMarkingTasks;
       static_assert(
           MarkingWorklist::kNumTasks == WriteBarrierWorklist::kNumTasks,
           "Marking worklist and write-barrier worklist should be the "
           "same size");
-      static_assert(max_concurrent_task_id <= MarkingWorklist::kNumTasks,
-                    "Number of concurrent marking tasks should not exceed "
-                    "number of tasks in worlkist");
-      // Initialize concurrent marking task ids.
-      for (uint8_t i = WorklistTaskId::ConcurrentThreadBase;
-           i < max_concurrent_task_id; ++i) {
-        available_concurrent_marking_task_ids_.push_back(i);
-      }
       ScheduleConcurrentMarking();
     }
     SetGCState(kIncrementalMarkingStepScheduled);
@@ -1208,11 +1186,12 @@
 bool ThreadState::ConcurrentMarkingStep() {
   current_gc_data_.visitor->FlushMarkingWorklists();
   if (Heap().HasWorkForConcurrentMarking()) {
-    ScheduleConcurrentMarking();
+    // Notifies the scheduler that max concurrency might have increased.
+    // This will adjust the number of markers if necessary.
+    marker_handle_.NotifyConcurrencyIncrease();
     return false;
   }
-  base::AutoLock lock(concurrent_marker_bootstrapping_lock_);
-  return active_markers_ == 0;
+  return marker_handle_.IsCompleted();
 }
 
 void ThreadState::IncrementalMarkingFinalize() {
@@ -1700,33 +1679,39 @@
 }
 
 void ThreadState::ScheduleConcurrentMarking() {
-  base::AutoLock lock(concurrent_marker_bootstrapping_lock_);
-
   DCHECK(base::FeatureList::IsEnabled(
       blink::features::kBlinkHeapConcurrentMarking));
 
-  for (uint8_t i = active_markers_; i < kNumberOfConcurrentMarkingTasks; ++i) {
-    marker_scheduler_->ScheduleTask(WTF::CrossThreadBindOnce(
-        &ThreadState::PerformConcurrentMark, WTF::CrossThreadUnretained(this)));
-  }
-
-  active_markers_ = kNumberOfConcurrentMarkingTasks;
+  // |USER_BLOCKING| is used to minimize marking on foreground thread.
+  marker_handle_ = base::PostJob(
+      FROM_HERE, {base::ThreadPool(), base::TaskPriority::USER_VISIBLE},
+      ConvertToBaseRepeatingCallback(
+          WTF::CrossThreadBindRepeating(&ThreadState::PerformConcurrentMark,
+                                        WTF::CrossThreadUnretained(this))),
+      ConvertToBaseRepeatingCallback(WTF::CrossThreadBindRepeating(
+          [](ThreadState* state, size_t active_worker_count) -> size_t {
+            // We need to account for local segments in addition to
+            // ConcurrentMarkingGlobalWorkSize().
+            return std::min<size_t>(
+                state->Heap().ConcurrentMarkingGlobalWorkSize() +
+                    active_worker_count,
+                MarkingWorklist::kNumTasks -
+                    WorklistTaskId::ConcurrentThreadBase);
+          },
+          WTF::CrossThreadUnretained(this))));
 }
 
-void ThreadState::PerformConcurrentMark() {
+void ThreadState::PerformConcurrentMark(base::JobDelegate* job) {
   VLOG(2) << "[state:" << this << "] [threadid:" << CurrentThread() << "] "
           << "ConcurrentMark";
   ThreadHeapStatsCollector::EnabledConcurrentScope stats_scope(
       Heap().stats_collector(),
       ThreadHeapStatsCollector::kConcurrentMarkingStep);
 
-  uint8_t task_id;
-  {
-    base::AutoLock lock(concurrent_marker_bootstrapping_lock_);
-    DCHECK(!available_concurrent_marking_task_ids_.IsEmpty());
-    task_id = available_concurrent_marking_task_ids_.back();
-    available_concurrent_marking_task_ids_.pop_back();
-  }
+  if (!Heap().HasWorkForConcurrentMarking())
+    return;
+
+  uint8_t task_id = job->GetTaskId() + 1;
 
   std::unique_ptr<ConcurrentMarkingVisitor> concurrent_visitor =
       IsUnifiedGCMarkingInProgress()
@@ -1737,28 +1722,14 @@
                 this, GetMarkingMode(Heap().Compaction()->IsCompacting()),
                 task_id);
 
-  const bool finished = Heap().AdvanceConcurrentMarking(
-      concurrent_visitor.get(),
+  Heap().AdvanceConcurrentMarking(
+      concurrent_visitor.get(), job,
       base::TimeTicks::Now() + kConcurrentMarkingStepDuration);
 
   marking_scheduling_->AddConcurrentlyMarkedBytes(
       concurrent_visitor->marked_bytes());
 
   concurrent_visitor->FlushWorklists();
-  {
-    base::AutoLock lock(concurrent_marker_bootstrapping_lock_);
-    // When marking is done, flush visitor worklists and decrement number of
-    // active markers so we know how many markers are left
-    available_concurrent_marking_task_ids_.push_back(task_id);
-    if (finished) {
-      --active_markers_;
-      return;
-    }
-  }
-
-  // Reschedule this marker
-  marker_scheduler_->ScheduleTask(WTF::CrossThreadBindOnce(
-      &ThreadState::PerformConcurrentMark, WTF::CrossThreadUnretained(this)));
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/heap/thread_state.h b/third_party/blink/renderer/platform/heap/thread_state.h
index 06a5c40..e7f1906 100644
--- a/third_party/blink/renderer/platform/heap/thread_state.h
+++ b/third_party/blink/renderer/platform/heap/thread_state.h
@@ -63,7 +63,6 @@
 class IncrementalMarkingScope;
 }  // namespace incremental_marking_test
 
-class CancelableTaskScheduler;
 class MarkingVisitor;
 class MarkingSchedulingOracle;
 class PersistentNode;
@@ -558,7 +557,7 @@
   // terminated and the worklist is empty)
   bool ConcurrentMarkingStep();
   void ScheduleConcurrentMarking();
-  void PerformConcurrentMark();
+  void PerformConcurrentMark(base::JobDelegate* job);
 
   // Schedule helpers.
   void ScheduleIdleLazySweep();
@@ -666,10 +665,7 @@
   std::unique_ptr<IncrementalMarkingScheduler> incremental_marking_scheduler_;
   std::unique_ptr<MarkingSchedulingOracle> marking_scheduling_;
 
-  std::unique_ptr<CancelableTaskScheduler> marker_scheduler_;
-  Vector<uint8_t> available_concurrent_marking_task_ids_;
-  uint8_t active_markers_ = 0;
-  base::Lock concurrent_marker_bootstrapping_lock_;
+  base::JobHandle marker_handle_;
 
   base::JobHandle sweeper_handle_;
   std::atomic_bool has_unswept_pages_{false};
diff --git a/third_party/blink/renderer/platform/widget/input/input_handler_proxy.cc b/third_party/blink/renderer/platform/widget/input/input_handler_proxy.cc
index 89bf49b..00f4d00 100644
--- a/third_party/blink/renderer/platform/widget/input/input_handler_proxy.cc
+++ b/third_party/blink/renderer/platform/widget/input/input_handler_proxy.cc
@@ -1399,6 +1399,11 @@
     elastic_overscroll_controller_->Animate(time);
 
   snap_fling_controller_->Animate(time);
+
+  // These animations can change the root scroll offset, so inform the
+  // synchronous input handler.
+  if (synchronous_input_handler_)
+    input_handler_->RequestUpdateForSynchronousInputHandler();
 }
 
 void InputHandlerProxy::ReconcileElasticOverscrollAndRootScroll() {
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index dd393d3..1224e42e 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -107,9 +107,12 @@
               "inspector-protocol/dom-snapshot",
               "media/stable",
               "webexposed",
-              "wpt_internal/origin-isolation"],
+              "wpt_internal/origin-isolation",
+              "compositing/filters"
+              ],
     "args": ["--stable-release-mode",
-             "--disable-auto-wpt-origin-isolation"]
+             "--disable-auto-wpt-origin-isolation",
+             "--enable-blink-features=CompositingOptimizations"]
   },
   {
     "prefix": "feature-policy-permissions",
diff --git a/third_party/blink/web_tests/WebGPUExpectations b/third_party/blink/web_tests/WebGPUExpectations
index ba3c86d..ea0a3f7c 100644
--- a/third_party/blink/web_tests/WebGPUExpectations
+++ b/third_party/blink/web_tests/WebGPUExpectations
@@ -19,10 +19,27 @@
 # ****************************************************************************
 
 #
+# Untriaged
+#
+
+wpt_internal/webgpu/cts.html?q=webgpu:api,operation,buffers,map_oom:mappedAtCreation,smaller_getMappedRange:* [ Failure ]
+wpt_internal/webgpu/cts.html?q=webgpu:api,operation,render_pass,storeOp:* [ Failure ]
+wpt_internal/webgpu/cts.html?q=webgpu:api,validation,createBindGroupLayout:bindingTypeSpecific_optional_members,* [ Failure ]
+wpt_internal/webgpu/cts.html?q=webgpu:api,validation,createBindGroupLayout:visibility,* [ Failure ]
+wpt_internal/webgpu/cts.html?q=webgpu:api,validation,render_pass,storeOp:* [ Failure ]
+wpt_internal/webgpu/cts.html?q=webgpu:api,validation,resource_usages,textureUsageInRender:* [ Failure ]
+
+# Timeouts
+wpt_internal/webgpu/cts.html?q=webgpu:api,validation,copy_between_linear_data_and_texture,copyBetweenLinearDataAndTexture_dataRelated:* [ Skip ]
+wpt_internal/webgpu/cts.html?q=webgpu:api,validation,copy_between_linear_data_and_texture,copyBetweenLinearDataAndTexture_textureRelated:* [ Skip ]
+wpt_internal/webgpu/cts.html?q=webgpu:shader,execution,robust_access_vertex:* [ Skip ]
+wpt_internal/webgpu/cts.html?q=webgpu:web-platform,copyImageBitmapToTexture:from_ImageData:* [ Skip ]
+
+#
 # Test bugs
 #
 
-# These tests aren't working on CQ, unclear whether the test or harness is broken.
+# These tests aren't working on CQ, unclear whether the test or harness (or Chrome) is broken.
 # Mac: mostly works
 # Linux: actual is white/blank
 crbug.com/1083478 [ Linux ] wpt_internal/webgpu/webgpu/web-platform/reftests/canvas_clear.html [ Failure ]
@@ -31,9 +48,6 @@
 crbug.com/1083478 [ Win ] wpt_internal/webgpu/webgpu/web-platform/reftests/canvas_clear.html [ Failure ]
 crbug.com/1083478 [ Win ] wpt_internal/webgpu/webgpu/web-platform/reftests/canvas_complex_bgra8unorm.html [ Failure ]
 
-# Tests have already been updated upstream
-wpt_internal/webgpu/cts.html?q=webgpu:idl,constants,flags:* [ Failure ]
-
 #
 # Platform-independent failures
 #
@@ -55,12 +69,11 @@
 crbug.com/dawn/375 wpt_internal/webgpu/cts.html?q=webgpu:api,validation,createBindGroup:buffer_offset_and_size_for_bind_groups_match:offset=0;size=0 [ Failure ]
 crbug.com/dawn/375 wpt_internal/webgpu/cts.html?q=webgpu:api,validation,createBindGroup:buffer_offset_and_size_for_bind_groups_match:offset=256;size=0 [ Failure ]
 crbug.com/dawn/375 wpt_internal/webgpu/cts.html?q=webgpu:api,validation,createBindGroup:buffer_offset_and_size_for_bind_groups_match:offset=1024;size=0 [ Failure ]
-crbug.com/dawn/375 wpt_internal/webgpu/cts.html?q=webgpu:api,validation,createBindGroup:buffer_offset_and_size_for_bind_groups_match:offset=1024;size=undefined [ Failure ]
+crbug.com/dawn/375 wpt_internal/webgpu/cts.html?q=webgpu:api,validation,createBindGroup:buffer_offset_and_size_for_bind_groups_match:offset=1024;size="_undef_" [ Failure ]
 
 # Test doesn't check for an OOM error. gpuweb/cts#199 crbug.com/1014740 crbug.com/1014740
 # Actually passes on Windows NVIDIA which may be a Dawn bug.
-wpt_internal/webgpu/cts.html?q=webgpu:api,operation,buffers,map_oom:mapReadAsync: [ Failure Crash ]
-wpt_internal/webgpu/cts.html?q=webgpu:api,operation,buffers,map_oom:mapWriteAsync: [ Failure Crash ]
+wpt_internal/webgpu/cts.html?q=webgpu:api,operation,buffers,map_oom:mapAsync:* [ Failure Crash ]
 
 # Timeout on Windows/NVIDIA with backend validation
 # Dawn doesn't implement these limits yet
@@ -71,10 +84,11 @@
 wpt_internal/webgpu/cts.html?q=webgpu:api,validation,createBindGroupLayout:number_of_dynamic_buffers_exceeds_the_maximum_value,* [ Failure ]
 wpt_internal/webgpu/cts.html?q=webgpu:api,validation,createPipelineLayout:number_of_dynamic_buffers_exceeds_the_maximum_value:* [ Failure ]
 
-crbug.com/1069953 wpt_internal/webgpu/cts.html?q=webgpu:web-platform,copyImageBitmapToTexture:from_ImageData:* [ Failure ]
-
 wpt_internal/webgpu/cts.html?q=webgpu:shader,execution,robust_access:* [ Skip ]
 
+# Failure in both D3D12 and Vulkan validation layers
+wpt_internal/webgpu/cts.html?q=webgpu:api,validation,encoding,cmds,index_access:* [ Skip ]
+
 #
 # Mac (Metal) specific
 #
diff --git a/third_party/blink/web_tests/compositing/filters/sw-nested-shadow-overlaps-hw-nested-shadow-expected.txt b/third_party/blink/web_tests/compositing/filters/sw-nested-shadow-overlaps-hw-nested-shadow-expected.txt
index ce951477..16ea11b 100644
--- a/third_party/blink/web_tests/compositing/filters/sw-nested-shadow-overlaps-hw-nested-shadow-expected.txt
+++ b/third_party/blink/web_tests/compositing/filters/sw-nested-shadow-overlaps-hw-nested-shadow-expected.txt
@@ -11,19 +11,6 @@
       "position": [-100, -100],
       "bounds": [200, 200],
       "transform": 1
-    },
-    {
-      "name": "LayoutNGBlockFlow (positioned) DIV id='software-parent'",
-      "bounds": [100, 100],
-      "contentsOpaque": true,
-      "backgroundColor": "#008000"
-    },
-    {
-      "name": "LayoutNGBlockFlow (positioned) DIV id='software-child'",
-      "position": [100, 100],
-      "bounds": [50, 50],
-      "contentsOpaque": true,
-      "backgroundColor": "#0000FF"
     }
   ],
   "transforms": [
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 0876f85..9a342b6 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
@@ -9984,6 +9984,21 @@
        {}
       ]
      ]
+    },
+    "css-values": {
+     "viewport-units-001-print.html": [
+      "3bff494e535b9101a11222a40a382384415eee3f",
+      [
+       null,
+       [
+        [
+         "/css/css-values/viewport-units-001-print-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ]
     }
    },
    "html": {
@@ -86164,6 +86179,58 @@
         {}
        ]
       ],
+      "flex-aspect-ratio-011.tentative.html": [
+       "2c111477e51758aad7e218385e8ee5aa2752ef0b",
+       [
+        null,
+        [
+         [
+          "/css/reference/ref-filled-green-100px-square.xht",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
+      "flex-aspect-ratio-012.tentative.html": [
+       "187da3b4262ed5958dd55ee7a4ae8cb2f7f773d8",
+       [
+        null,
+        [
+         [
+          "/css/reference/ref-filled-green-100px-square.xht",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
+      "flex-aspect-ratio-013.tentative.html": [
+       "04933e9b10155ae022aedc2e92b038dd97f23819",
+       [
+        null,
+        [
+         [
+          "/css/reference/ref-filled-green-100px-square.xht",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
+      "flex-aspect-ratio-014.tentative.html": [
+       "13d1719bf2e732b979725dcf6c31fcef91b75b2d",
+       [
+        null,
+        [
+         [
+          "/css/reference/ref-filled-green-100px-square.xht",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
       "intrinsic-size-001.tentative.html": [
        "5b2c6049bd935c928f29c50bf4ad7afae41c4576",
        [
@@ -189412,6 +189479,10 @@
        []
       ]
      },
+     "changing-while-transition-004-expected.txt": [
+      "fb9a59feae5051b296630e58f0a2bc1491ccb096",
+      []
+     ],
      "event-dispatch.tentative-expected.txt": [
       "294ff2555a187ba81afc27ffa35ca46c9e9050ea",
       []
@@ -191569,7 +191640,11 @@
         []
        ]
       }
-     }
+     },
+     "viewport-units-001-print-ref.html": [
+      "bc914522c7fcf556c69ba56d2d0ba792d7e92b89",
+      []
+     ]
     },
     "css-variables": {
      "META.yml": [
@@ -218292,7 +218367,7 @@
      []
     ],
     "FileAPI.idl": [
-     "a630226c3df3d3dd09cb5ef85435319fdd8ef632",
+     "98732ff0ad1c3acc8a6f4abf6545efa329675704",
      []
     ],
     "IndexedDB.idl": [
@@ -218364,7 +218439,7 @@
      []
     ],
     "clipboard-apis.idl": [
-     "c99113059ec77b266911fe0ce2c2bf520444fcd0",
+     "1fe9dd2657b8c3814d0704c2d9ec35e801c42b2e",
      []
     ],
     "compat.idl": [
@@ -218496,7 +218571,7 @@
      []
     ],
     "dom.idl": [
-     "102c23123819ba2e9b633755cfb453b1f2168961",
+     "682903da9e58919a0f4097067490190e0a8a7fc2",
      []
     ],
     "element-timing.idl": [
@@ -218524,7 +218599,7 @@
      []
     ],
     "fetch.idl": [
-     "1d8a97ef940187ae4ac60fe93bb05d4b7beabd91",
+     "e655b1c0f424a6f92e46da7b3074a08d3e294277",
      []
     ],
     "filter-effects.idl": [
@@ -218532,7 +218607,7 @@
      []
     ],
     "fullscreen.idl": [
-     "677b2e116f6dcf6ff8340761d473e3dcf0f83af5",
+     "5caf0dee1551e6e737dc9d3b575770e323b3ceda",
      []
     ],
     "gamepad-extensions.idl": [
@@ -222027,7 +222102,7 @@
       []
      ],
      "subframe-painting.html": [
-      "5fb5e28d8a4abf3e5e07d9b06b36694cac2402c0",
+      "00fd39bcb81b56d39fa226dd9cd5f01a18b4a7bf",
       []
      ],
      "subframe-sending-paint.html": [
@@ -222035,7 +222110,7 @@
       []
      ],
      "utils.js": [
-      "975e84dadd4693b8a484221b5ce52f28b554ff01",
+      "5766971dd0d0230c644b385a4d6c282b554b2fe6",
       []
      ]
     }
@@ -225194,6 +225269,10 @@
      []
     ],
     "css": {
+     "animation-shorthand-expected.txt": [
+      "6b1e4677fe7f4f4eb18f28216a3754400b7beea5",
+      []
+     ],
      "animation-timeline-computed-expected.txt": [
       "8ce545a7c55ea42b7946992eb38835499bdffb1a",
       []
@@ -227816,7 +227895,7 @@
        []
       ],
       "test-helpers.sub.js": [
-       "bbd16abce4b3703a2f7499e7c2c4c7590566d33e",
+       "38840b75303dc598cf66a8771e51bbc5877db8fa",
        []
       ],
       "test-request-headers-worker.js": [
@@ -235942,7 +236021,7 @@
       "css": {
        "bulma-0.7.5": {
         "bulma.css": [
-         "e793432ae31c34957cab74f791d298871d90fa26",
+         "150bbfd34a3abf54bbad6036c70d00a5f32eccc2",
          []
         ],
         "bulma.css.map": [
@@ -286252,6 +286331,13 @@
        {}
       ]
      ],
+     "changing-while-transition-004.html": [
+      "71038ac11f78b3581645587da205d74b9ee71610",
+      [
+       null,
+       {}
+      ]
+     ],
      "currentcolor-animation-001.html": [
       "bcc7991a828ada651764f740d6e2f05f24f8d332",
       [
@@ -336721,14 +336807,14 @@
       ]
      ],
      "none-sw-from-none.https.html": [
-      "deb046cdb37559025ea42b72914814eef163c20c",
+      "b539561effd93df24734d25f1f0fc61a50498adf",
       [
        null,
        {}
       ]
      ],
      "none-sw-from-require-corp.https.html": [
-      "88c0d2cf6fb06b34711d19621579f9d7faf087d8",
+      "36cf4a153bfa7b5db387dcb2cbff46eea7f94a25",
       [
        null,
        {}
@@ -336801,14 +336887,14 @@
       ]
      ],
      "require-corp-sw-from-none.https.html": [
-      "adb73bfba45bfee3eaaa3516482424189ac8c493",
+      "a60b8bd457ea0af21c882bf37f0f107c1065026e",
       [
        null,
        {}
       ]
      ],
      "require-corp-sw-from-require-corp.https.html": [
-      "0696b7a33cc2a6c22bc631d67c443ef72392f718",
+      "deefc92b804c60559fe1c3a4b43feecba17bb6a9",
       [
        null,
        {}
@@ -364953,7 +365039,7 @@
      ]
     ],
     "child-painting-first-image.html": [
-     "38cf499ccaf8a5da96424c17dba675cbd5eff6e8",
+     "3badae1849feaf5b2a0288ebe556af3ea1216586",
      [
       null,
       {}
@@ -364961,21 +365047,21 @@
     ],
     "fcp-only": {
      "fcp-background-size.html": [
-      "8cc83702c1fb8c411813ba23594c6ad39de09bd7",
+      "25fe986bdede921980b96c10f9d3d26a83558cbc",
       [
        null,
        {}
       ]
      ],
      "fcp-bg-image-set.html": [
-      "0cc52d0263e0239816ab075ae1ed90b2fd9a26c6",
+      "443cef630bd1b1ea77e477fdf06967cc075cce2d",
       [
        null,
        {}
       ]
      ],
      "fcp-bg-image-two-steps.html": [
-      "4dc9af9da84fdb0dea6d39cf8dd50611de87c704",
+      "89b161f8593041226be4d38d5a335abbfb3324f1",
       [
        null,
        {}
@@ -364995,6 +365081,13 @@
        {}
       ]
      ],
+     "fcp-iframe.html": [
+      "674bcd91214232891f27a6598ad52fb2b92f3c45",
+      [
+       null,
+       {}
+      ]
+     ],
      "fcp-invisible-3d-rotate-descendant.html": [
       "76d459d0f40fcaa46e9561e76c99b4d4a17f157b",
       [
@@ -365017,7 +365110,7 @@
       ]
      ],
      "fcp-invisible-scale.html": [
-      "5389e8a846bf6c2844dd61fe3a4e0cb9344b4ffe",
+      "4d3a060e85107bcfcdb2811f667992ff780c78f9",
       [
        null,
        {}
@@ -365038,7 +365131,7 @@
       ]
      ],
      "fcp-opacity.html": [
-      "83afdde195b73eb7a348255da05594b1a2bc45a1",
+      "3c6912c4e6eef50e0d9eb90d64314f9be6dd4f67",
       [
        null,
        {}
@@ -365052,7 +365145,7 @@
       ]
      ],
      "fcp-out-of-bounds.html": [
-      "3553772d4fc6b68c9769442129723a57e07aea16",
+      "91556e50e0a0532f6c37acc846851e27b50a5d6d",
       [
        null,
        {}
@@ -365066,7 +365159,7 @@
       ]
      ],
      "fcp-pseudo-element-image.html": [
-      "ba38edb68cbedf3157344cb9477c2e48fb1daea8",
+      "d67ae21cd964678544d8da831a3a0983843a6a38",
       [
        null,
        {}
@@ -365129,7 +365222,7 @@
       ]
      ],
      "fcp-whitespace.html": [
-      "6e1f425de80225931a517b6c839e7be23bf26f91",
+      "71c72b018a482872484121a0840f8071fcdfad01",
       [
        null,
        {}
@@ -365262,7 +365355,7 @@
      ]
     ],
     "sibling-painting-first-image.html": [
-     "266d5d7af3248d086bb4e903bd8edbb768ca29b2",
+     "096c49ac19ec6f04255430b87e8e3ab216a8416c",
      [
       null,
       {}
@@ -365910,7 +366003,7 @@
      ]
     ],
     "case-sensitivity.any.js": [
-     "588b59cc6a796b64aa4ad42137700430805a1d16",
+     "3a98505ae67f7df6f617d6b9fde4af367503278e",
      [
       "performance-timeline/case-sensitivity.any.html",
       {}
@@ -379887,7 +379980,7 @@
      ]
     ],
     "getScreens.values.https.html": [
-     "881396adbc3befefb39b55020d8f798379bfa648",
+     "93b27b465561c1d97703f87804d4ba91389ef753",
      [
       null,
       {}
@@ -379916,7 +380009,7 @@
      ]
     ],
     "isMultiScreen.values.https.html": [
-     "5146803ea9c20c5f8ae43cf385895a95fc361747",
+     "2a79db4e10ec6e9664807a85df56e9327bdb73eb",
      [
       null,
       {}
@@ -379975,6 +380068,13 @@
      ]
     ],
     "css": {
+     "animation-shorthand.html": [
+      "7b30ec611f50c8b9bee8b28187cd9d39612b6f8d",
+      [
+       null,
+       {}
+      ]
+     ],
      "animation-timeline-computed.html": [
       "02836d714b59081b3a14c2bd124d2ac254d23d58",
       [
@@ -403059,8 +403159,29 @@
     ]
    },
    "webcodecs": {
+    "audio-decoder.html": [
+     "78450c3c5a0ec06478810f83398078c1635f4fde",
+     [
+      null,
+      {}
+     ]
+    ],
+    "video-decoder.html": [
+     "6c64d92560a9c3a1327658f0a7dd8c1c12269032",
+     [
+      null,
+      {}
+     ]
+    ],
     "video-encoder.html": [
-     "ce03f058f78ec551afc8024203f633e64cb1a29f",
+     "05f7a1c49c261be2f6220ed115febe2ca424b1e2",
+     [
+      null,
+      {}
+     ]
+    ],
+    "video-frame-serialization.html": [
+     "142cec190323fcc09b9b3dd83e0b903237b33ae0",
      [
       null,
       {}
diff --git a/third_party/blink/web_tests/external/wpt/css/css-values/viewport-units-001-print-ref.html b/third_party/blink/web_tests/external/wpt/css/css-values/viewport-units-001-print-ref.html
new file mode 100644
index 0000000..bc914522
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-values/viewport-units-001-print-ref.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS test reference</title>
+<style>
+  body { margin: 0 }
+  :root {
+    box-sizing: border-box;
+    width: 100%;
+    height: 100%;
+    border: 1px solid black;
+  }
+</style>
+<div>I should not overflow to the next page.</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-values/viewport-units-001-print.html b/third_party/blink/web_tests/external/wpt/css/css-values/viewport-units-001-print.html
new file mode 100644
index 0000000..3bff494
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-values/viewport-units-001-print.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Viewport units in print account for margins</title>
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1414600">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/5437">
+<link rel="help" href="https://drafts.csswg.org/css-values-4/#viewport-relative-lengths">
+<link rel="match" href="viewport-units-001-print-ref.html">
+<style>
+  body { margin: 0 }
+  div {
+    box-sizing: border-box;
+    width: 100vw;
+    height: 100vh;
+    border: 1px solid black;
+  }
+</style>
+<div>I should not overflow to the next page.</div>
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/none-sw-from-none.https.html b/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/none-sw-from-none.https.html
index deb046cd..b539561 100644
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/none-sw-from-none.https.html
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/none-sw-from-none.https.html
@@ -77,9 +77,13 @@
 
 promise_test(async (t) => {
   const URL = remote(
-    '/common/blank.html?pipe=header(access-control-allow-origin,*');
+    '/common/blank.html?pipe=header(access-control-allow-origin,*)');
   await fetch(URL, {mode: 'cors'});
 }, 'making a cross-origin request with CORS');
 
+promise_test(async (t) => {
+  const URL = remote('/fetch/api/resources/preflight.py?allow_headers=hoge');
+  await fetch(URL, {mode: 'cors', headers: {'hoge': 'fuga'}});
+}, 'making a cross-origin request with CORS-preflight');
 </script>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/none-sw-from-require-corp.https.html b/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/none-sw-from-require-corp.https.html
index 88c0d2cf..36cf4a1 100644
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/none-sw-from-require-corp.https.html
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/none-sw-from-require-corp.https.html
@@ -80,9 +80,14 @@
 
 promise_test(async (t) => {
   const URL = remote(
-    '/common/blank.html?pipe=header(access-control-allow-origin,*');
+    '/common/blank.html?pipe=header(access-control-allow-origin,*)');
   await fetch(URL, {mode: 'cors'});
 }, 'making a cross-origin request with CORS');
 
+promise_test(async (t) => {
+  const URL = remote('/fetch/api/resources/preflight.py?allow_headers=hoge');
+  await fetch(URL, {mode: 'cors', headers: {'hoge': 'fuga'}});
+}, 'making a cross-origin request with CORS-preflight');
+
 </script>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/require-corp-sw-from-none.https.html b/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/require-corp-sw-from-none.https.html
index adb73bf..a60b8bd 100644
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/require-corp-sw-from-none.https.html
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/require-corp-sw-from-none.https.html
@@ -79,9 +79,14 @@
 
 promise_test(async (t) => {
   const URL = remote(
-    '/common/blank.html?pipe=header(access-control-allow-origin,*');
+    '/common/blank.html?pipe=header(access-control-allow-origin,*)');
   await fetch(URL, {mode: 'cors'});
 }, 'making a cross-origin request with CORS');
 
+promise_test(async (t) => {
+  const URL = remote('/fetch/api/resources/preflight.py?allow_headers=hoge');
+  await fetch(URL, {mode: 'cors', headers: {'hoge': 'fuga'}});
+}, 'making a cross-origin request with CORS-preflight');
+
 </script>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/require-corp-sw-from-require-corp.https.html b/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/require-corp-sw-from-require-corp.https.html
index 0696b7a..deefc92b 100644
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/require-corp-sw-from-require-corp.https.html
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-embedder-policy/require-corp-sw-from-require-corp.https.html
@@ -81,9 +81,13 @@
 
 promise_test(async (t) => {
   const URL = remote(
-    '/common/blank.html?pipe=header(access-control-allow-origin,*');
+    '/common/blank.html?pipe=header(access-control-allow-origin,*)');
   await fetch(URL, {mode: 'cors'});
 }, 'making a cross-origin request with CORS');
 
+promise_test(async (t) => {
+  const URL = remote('/fetch/api/resources/preflight.py?allow_headers=hoge');
+  await fetch(URL, {mode: 'cors', headers: {'hoge': 'fuga'}});
+}, 'making a cross-origin request with CORS-preflight');
 </script>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/FileAPI.idl b/third_party/blink/web_tests/external/wpt/interfaces/FileAPI.idl
index a630226c..98732ff 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/FileAPI.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/FileAPI.idl
@@ -54,12 +54,12 @@
 interface FileReader: EventTarget {
   constructor();
   // async read methods
-  void readAsArrayBuffer(Blob blob);
-  void readAsBinaryString(Blob blob);
-  void readAsText(Blob blob, optional DOMString encoding);
-  void readAsDataURL(Blob blob);
+  undefined readAsArrayBuffer(Blob blob);
+  undefined readAsBinaryString(Blob blob);
+  undefined readAsText(Blob blob, optional DOMString encoding);
+  undefined readAsDataURL(Blob blob);
 
-  void abort();
+  undefined abort();
 
   // states
   const unsigned short EMPTY = 0;
@@ -96,5 +96,5 @@
 [Exposed=(Window,DedicatedWorker,SharedWorker)]
 partial interface URL {
   static DOMString createObjectURL((Blob or MediaSource) obj);
-  static void revokeObjectURL(DOMString url);
+  static undefined revokeObjectURL(DOMString url);
 };
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/clipboard-apis.idl b/third_party/blink/web_tests/external/wpt/interfaces/clipboard-apis.idl
index c991130..1fe9dd2 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/clipboard-apis.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/clipboard-apis.idl
@@ -22,8 +22,8 @@
 [SecureContext, Exposed=Window] interface Clipboard : EventTarget {
   Promise<ClipboardItems> read();
   Promise<DOMString> readText();
-  Promise<void> write(ClipboardItems data);
-  Promise<void> writeText(DOMString data);
+  Promise<undefined> write(ClipboardItems data);
+  Promise<undefined> writeText(DOMString data);
 };
 
 typedef (DOMString or Blob) ClipboardItemDataType;
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/dom.idl b/third_party/blink/web_tests/external/wpt/interfaces/dom.idl
index 102c231..682903da 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/dom.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/dom.idl
@@ -19,21 +19,21 @@
   const unsigned short BUBBLING_PHASE = 3;
   readonly attribute unsigned short eventPhase;
 
-  void stopPropagation();
+  undefined stopPropagation();
            attribute boolean cancelBubble; // historical alias of .stopPropagation
-  void stopImmediatePropagation();
+  undefined stopImmediatePropagation();
 
   readonly attribute boolean bubbles;
   readonly attribute boolean cancelable;
            attribute boolean returnValue;  // historical
-  void preventDefault();
+  undefined preventDefault();
   readonly attribute boolean defaultPrevented;
   readonly attribute boolean composed;
 
   [LegacyUnforgeable] readonly attribute boolean isTrusted;
   readonly attribute DOMHighResTimeStamp timeStamp;
 
-  void initEvent(DOMString type, optional boolean bubbles = false, optional boolean cancelable = false); // historical
+  undefined initEvent(DOMString type, optional boolean bubbles = false, optional boolean cancelable = false); // historical
 };
 
 dictionary EventInit {
@@ -52,7 +52,7 @@
 
   readonly attribute any detail;
 
-  void initCustomEvent(DOMString type, optional boolean bubbles = false, optional boolean cancelable = false, optional any detail = null); // historical
+  undefined initCustomEvent(DOMString type, optional boolean bubbles = false, optional boolean cancelable = false, optional any detail = null); // historical
 };
 
 dictionary CustomEventInit : EventInit {
@@ -63,13 +63,13 @@
 interface EventTarget {
   constructor();
 
-  void addEventListener(DOMString type, EventListener? callback, optional (AddEventListenerOptions or boolean) options = {});
-  void removeEventListener(DOMString type, EventListener? callback, optional (EventListenerOptions or boolean) options = {});
+  undefined addEventListener(DOMString type, EventListener? callback, optional (AddEventListenerOptions or boolean) options = {});
+  undefined removeEventListener(DOMString type, EventListener? callback, optional (EventListenerOptions or boolean) options = {});
   boolean dispatchEvent(Event event);
 };
 
 callback interface EventListener {
-  void handleEvent(Event event);
+  undefined handleEvent(Event event);
 };
 
 dictionary EventListenerOptions {
@@ -87,7 +87,7 @@
 
   [SameObject] readonly attribute AbortSignal signal;
 
-  void abort();
+  undefined abort();
 };
 
 [Exposed=(Window,Worker)]
@@ -113,9 +113,9 @@
   readonly attribute Element? lastElementChild;
   readonly attribute unsigned long childElementCount;
 
-  [CEReactions, Unscopable] void prepend((Node or DOMString)... nodes);
-  [CEReactions, Unscopable] void append((Node or DOMString)... nodes);
-  [CEReactions, Unscopable] void replaceChildren((Node or DOMString)... nodes);
+  [CEReactions, Unscopable] undefined prepend((Node or DOMString)... nodes);
+  [CEReactions, Unscopable] undefined append((Node or DOMString)... nodes);
+  [CEReactions, Unscopable] undefined replaceChildren((Node or DOMString)... nodes);
 
   Element? querySelector(DOMString selectors);
   [NewObject] NodeList querySelectorAll(DOMString selectors);
@@ -132,10 +132,10 @@
 CharacterData includes NonDocumentTypeChildNode;
 
 interface mixin ChildNode {
-  [CEReactions, Unscopable] void before((Node or DOMString)... nodes);
-  [CEReactions, Unscopable] void after((Node or DOMString)... nodes);
-  [CEReactions, Unscopable] void replaceWith((Node or DOMString)... nodes);
-  [CEReactions, Unscopable] void remove();
+  [CEReactions, Unscopable] undefined before((Node or DOMString)... nodes);
+  [CEReactions, Unscopable] undefined after((Node or DOMString)... nodes);
+  [CEReactions, Unscopable] undefined replaceWith((Node or DOMString)... nodes);
+  [CEReactions, Unscopable] undefined remove();
 };
 DocumentType includes ChildNode;
 Element includes ChildNode;
@@ -165,12 +165,12 @@
 interface MutationObserver {
   constructor(MutationCallback callback);
 
-  void observe(Node target, optional MutationObserverInit options = {});
-  void disconnect();
+  undefined observe(Node target, optional MutationObserverInit options = {});
+  undefined disconnect();
   sequence<MutationRecord> takeRecords();
 };
 
-callback MutationCallback = void (sequence<MutationRecord> mutations, MutationObserver observer);
+callback MutationCallback = undefined (sequence<MutationRecord> mutations, MutationObserver observer);
 
 dictionary MutationObserverInit {
   boolean childList = false;
@@ -228,7 +228,7 @@
 
   [CEReactions] attribute DOMString? nodeValue;
   [CEReactions] attribute DOMString? textContent;
-  [CEReactions] void normalize();
+  [CEReactions] undefined normalize();
 
   [CEReactions, NewObject] Node cloneNode(optional boolean deep = false);
   boolean isEqualNode(Node? otherNode);
@@ -353,10 +353,10 @@
   sequence<DOMString> getAttributeNames();
   DOMString? getAttribute(DOMString qualifiedName);
   DOMString? getAttributeNS(DOMString? namespace, DOMString localName);
-  [CEReactions] void setAttribute(DOMString qualifiedName, DOMString value);
-  [CEReactions] void setAttributeNS(DOMString? namespace, DOMString qualifiedName, DOMString value);
-  [CEReactions] void removeAttribute(DOMString qualifiedName);
-  [CEReactions] void removeAttributeNS(DOMString? namespace, DOMString localName);
+  [CEReactions] undefined setAttribute(DOMString qualifiedName, DOMString value);
+  [CEReactions] undefined setAttributeNS(DOMString? namespace, DOMString qualifiedName, DOMString value);
+  [CEReactions] undefined removeAttribute(DOMString qualifiedName);
+  [CEReactions] undefined removeAttributeNS(DOMString? namespace, DOMString localName);
   [CEReactions] boolean toggleAttribute(DOMString qualifiedName, optional boolean force);
   boolean hasAttribute(DOMString qualifiedName);
   boolean hasAttributeNS(DOMString? namespace, DOMString localName);
@@ -379,7 +379,7 @@
   HTMLCollection getElementsByClassName(DOMString classNames);
 
   [CEReactions] Element? insertAdjacentElement(DOMString where, Element element); // historical
-  void insertAdjacentText(DOMString where, DOMString data); // historical
+  undefined insertAdjacentText(DOMString where, DOMString data); // historical
 };
 
 dictionary ShadowRootInit {
@@ -417,10 +417,10 @@
   attribute [LegacyNullToEmptyString] DOMString data;
   readonly attribute unsigned long length;
   DOMString substringData(unsigned long offset, unsigned long count);
-  void appendData(DOMString data);
-  void insertData(unsigned long offset, DOMString data);
-  void deleteData(unsigned long offset, unsigned long count);
-  void replaceData(unsigned long offset, unsigned long count, DOMString data);
+  undefined appendData(DOMString data);
+  undefined insertData(unsigned long offset, DOMString data);
+  undefined deleteData(unsigned long offset, unsigned long count);
+  undefined replaceData(unsigned long offset, unsigned long count, DOMString data);
 };
 
 [Exposed=Window]
@@ -470,15 +470,15 @@
 
   readonly attribute Node commonAncestorContainer;
 
-  void setStart(Node node, unsigned long offset);
-  void setEnd(Node node, unsigned long offset);
-  void setStartBefore(Node node);
-  void setStartAfter(Node node);
-  void setEndBefore(Node node);
-  void setEndAfter(Node node);
-  void collapse(optional boolean toStart = false);
-  void selectNode(Node node);
-  void selectNodeContents(Node node);
+  undefined setStart(Node node, unsigned long offset);
+  undefined setEnd(Node node, unsigned long offset);
+  undefined setStartBefore(Node node);
+  undefined setStartAfter(Node node);
+  undefined setEndBefore(Node node);
+  undefined setEndAfter(Node node);
+  undefined collapse(optional boolean toStart = false);
+  undefined selectNode(Node node);
+  undefined selectNodeContents(Node node);
 
   const unsigned short START_TO_START = 0;
   const unsigned short START_TO_END = 1;
@@ -486,14 +486,14 @@
   const unsigned short END_TO_START = 3;
   short compareBoundaryPoints(unsigned short how, Range sourceRange);
 
-  [CEReactions] void deleteContents();
+  [CEReactions] undefined deleteContents();
   [CEReactions, NewObject] DocumentFragment extractContents();
   [CEReactions, NewObject] DocumentFragment cloneContents();
-  [CEReactions] void insertNode(Node node);
-  [CEReactions] void surroundContents(Node newParent);
+  [CEReactions] undefined insertNode(Node node);
+  [CEReactions] undefined surroundContents(Node newParent);
 
   [NewObject] Range cloneRange();
-  void detach();
+  undefined detach();
 
   boolean isPointInRange(Node node, unsigned long offset);
   short comparePoint(Node node, unsigned long offset);
@@ -514,7 +514,7 @@
   Node? nextNode();
   Node? previousNode();
 
-  void detach();
+  undefined detach();
 };
 
 [Exposed=Window]
@@ -562,8 +562,8 @@
   readonly attribute unsigned long length;
   getter DOMString? item(unsigned long index);
   boolean contains(DOMString token);
-  [CEReactions] void add(DOMString... tokens);
-  [CEReactions] void remove(DOMString... tokens);
+  [CEReactions] undefined add(DOMString... tokens);
+  [CEReactions] undefined remove(DOMString... tokens);
   [CEReactions] boolean toggle(DOMString token, optional boolean force);
   [CEReactions] boolean replace(DOMString token, DOMString newToken);
   boolean supports(DOMString token);
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/fetch.idl b/third_party/blink/web_tests/external/wpt/interfaces/fetch.idl
index 1d8a97e..e655b1c0 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/fetch.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/fetch.idl
@@ -9,11 +9,11 @@
 interface Headers {
   constructor(optional HeadersInit init);
 
-  void append(ByteString name, ByteString value);
-  void delete(ByteString name);
+  undefined append(ByteString name, ByteString value);
+  undefined delete(ByteString name);
   ByteString? get(ByteString name);
   boolean has(ByteString name);
-  void set(ByteString name, ByteString value);
+  undefined set(ByteString name, ByteString value);
   iterable<ByteString, ByteString>;
 };
 
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/fullscreen.idl b/third_party/blink/web_tests/external/wpt/interfaces/fullscreen.idl
index 677b2e1..5caf0de 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/fullscreen.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/fullscreen.idl
@@ -14,7 +14,7 @@
 };
 
 partial interface Element {
-  Promise<void> requestFullscreen(optional FullscreenOptions options = {});
+  Promise<undefined> requestFullscreen(optional FullscreenOptions options = {});
 
   attribute EventHandler onfullscreenchange;
   attribute EventHandler onfullscreenerror;
@@ -24,7 +24,7 @@
   [LegacyLenientSetter] readonly attribute boolean fullscreenEnabled;
   [LegacyLenientSetter, Unscopable] readonly attribute boolean fullscreen; // historical
 
-  Promise<void> exitFullscreen();
+  Promise<undefined> exitFullscreen();
 
   attribute EventHandler onfullscreenchange;
   attribute EventHandler onfullscreenerror;
diff --git a/third_party/blink/web_tests/external/wpt/paint-timing/child-painting-first-image.html b/third_party/blink/web_tests/external/wpt/paint-timing/child-painting-first-image.html
index 38cf499c..3badae1 100644
--- a/third_party/blink/web_tests/external/wpt/paint-timing/child-painting-first-image.html
+++ b/third_party/blink/web_tests/external/wpt/paint-timing/child-painting-first-image.html
@@ -4,19 +4,37 @@
 <script src="/resources/testharnessreport.js"></script>
 
 <script>
+var entriesExpectToReceive = [
+    {
+        'entryType': 'paint',
+        'name': 'first-paint'
+    },
+    {
+        'entryType': 'paint',
+        'name': 'first-contentful-paint'
+    }
+];
+
 setup({"hide_test_state": true});
 async_test(function (t) {
     assert_implements(window.PerformancePaintTiming, "Paint Timing isn't supported.");
     window.addEventListener('message', t.step_func(e => {
-        assert_equals(e.data, '2 paint first-paint paint first-contentful-paint');
         // When only child frame paints, expect only first-paint.
-        t.step_timeout( function() {
+        for (let i = 0; i < entriesExpectToReceive.length; i++) {
+            if (entriesExpectToReceive[i].entryType == e.data.entryType &&
+                entriesExpectToReceive[i].name == e.data.name) {
+                entriesExpectToReceive.splice(i, 1);
+                break;
+            }
+        }
+
+        if (entriesExpectToReceive.length == 0) {
             const bufferedEntries = performance.getEntriesByType('paint');
             assert_equals(bufferedEntries.length, 1);
             assert_equals(bufferedEntries[0].entryType, 'paint');
             assert_equals(bufferedEntries[0].name, 'first-paint');
             t.done();
-        }, 50);
+        }
     }));
     const iframe = document.createElement('iframe');
     iframe.id = 'child-iframe';
diff --git a/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-background-size.html b/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-background-size.html
index 8cc8370..25fe986 100644
--- a/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-background-size.html
+++ b/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-background-size.html
@@ -22,7 +22,17 @@
 <script src="../resources/utils.js"></script>
 <div id="main"></div>
 <script>
-    test_fcp("First contentful paint fires due to background size.");
+    // Load the image into memory first to make sure it's decoded.
+    function load_image() {
+      const img = document.createElement("img");
+      img.src = "../resources/circles.png";
+
+      return new Promise(resolve => {
+        img.onload = async () => resolve();
+      });
+    }
+
+    test_fcp("First contentful paint fires due to background size.", load_image);
 </script>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-bg-image-set.html b/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-bg-image-set.html
index 0cc52d0..443cef6 100644
--- a/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-bg-image-set.html
+++ b/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-bg-image-set.html
@@ -20,7 +20,17 @@
 <script src="../resources/utils.js"></script>
 <div id="main"></div>
 <script>
-    test_fcp("First contentful paint fires due to background image in image-set.");
+    // Load the image into memory first to make sure it's decoded.
+    function load_image() {
+      const img = document.createElement("img");
+      img.src = "../resources/circles.png";
+
+      return new Promise(resolve => {
+        img.onload = async () => resolve();
+      });
+    }
+
+    test_fcp("First contentful paint fires due to background image in image-set.", load_image);
 </script>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-bg-image-two-steps.html b/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-bg-image-two-steps.html
index 4dc9af9..89b161f 100644
--- a/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-bg-image-two-steps.html
+++ b/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-bg-image-two-steps.html
@@ -27,7 +27,17 @@
 <script src="../resources/utils.js"></script>
 <div id="main"></div>
 <script>
-    test_fcp("First contentful paint fires for background image only when visible.");
+    // Load the image into memory first to make sure it's decoded.
+    function load_image() {
+      const img = document.createElement("img");
+      img.src = "../resources/circles.png";
+
+      return new Promise(resolve => {
+        img.onload = async () => resolve();
+      });
+    }
+
+    test_fcp("First contentful paint fires for background image only when visible.", load_image);
 </script>
 </body>
 
diff --git a/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-iframe.html b/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-iframe.html
new file mode 100644
index 0000000..674bcd91
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-iframe.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<head>
+    <title>
+        Performance Paint Timing Test: Not only the top level document, paints
+        in the iframe should also generate the entry
+    </title>
+</head>
+<body>
+<script src="../resources/utils.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup({"hide_test_state": true});
+async_test(function (t) {
+    assert_implements(window.PerformancePaintTiming, "Paint Timing isn't supported.");
+    window.addEventListener('message', t.step_func(e => {
+        if (e.data.entryType == "paint" && e.data.name == "first-contentful-paint") {
+            t.done();
+        }
+    }));
+    const iframe = document.createElement('iframe');
+    iframe.src = '../resources/subframe-painting.html';
+    document.body.appendChild(iframe);
+}, 'Parent frame ignores paint-timing events fired from child image rendering.');
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-invisible-scale.html b/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-invisible-scale.html
index 5389e8a84..4d3a060 100644
--- a/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-invisible-scale.html
+++ b/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-invisible-scale.html
@@ -24,7 +24,17 @@
 <script src="../resources/utils.js"></script>
 <div id="main"></div>
 <script>
-    test_fcp("First contentful paint fires due to scale becoming positive.")
+    // Load the image into memory first to make sure it's decoded.
+    function load_image() {
+      const img = document.createElement("img");
+      img.src = "../resources/circles.png";
+
+      return new Promise(resolve => {
+        img.onload = async () => resolve();
+      });
+    }
+
+    test_fcp("First contentful paint fires due to scale becoming positive.", load_image)
 </script>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-opacity.html b/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-opacity.html
index 83afdde19..3c6912c4 100644
--- a/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-opacity.html
+++ b/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-opacity.html
@@ -24,7 +24,17 @@
 <script src="../resources/utils.js"></script>
 <div id="main"></div>
 <script>
-    test_fcp("First contentful paint fires due to opacity-revealed element.");
+    // Load the image into memory first to make sure it's decoded.
+    function load_image() {
+      const img = document.createElement("img");
+      img.src = "../resources/circles.png";
+
+      return new Promise(resolve => {
+        img.onload = async () => resolve();
+      });
+    }
+
+    test_fcp("First contentful paint fires due to opacity-revealed element.", load_image);
 </script>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-out-of-bounds.html b/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-out-of-bounds.html
index 3553772d4..91556e5 100644
--- a/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-out-of-bounds.html
+++ b/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-out-of-bounds.html
@@ -23,7 +23,17 @@
 <script src="../resources/utils.js"></script>
 <div id="main"></div>
 <script>
-    test_fcp("First contentful paint fires due to intersection with document.")
+    // Load the image into memory first to make sure it's decoded.
+    function load_image() {
+      const img = document.createElement("img");
+      img.src = "../resources/circles.png";
+
+      return new Promise(resolve => {
+        img.onload = async () => resolve();
+      });
+    }
+
+    test_fcp("First contentful paint fires due to intersection with document.", load_image)
 </script>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-pseudo-element-image.html b/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-pseudo-element-image.html
index ba38edb..d67ae21c 100644
--- a/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-pseudo-element-image.html
+++ b/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-pseudo-element-image.html
@@ -21,7 +21,17 @@
 <div id="main">
 </div>
 <script>
-    test_fcp("First contentful paint fires due to pseudo-element image.")
+    // Load the image into memory first to make sure it's decoded.
+    function load_image() {
+      const img = document.createElement("img");
+      img.src = "../resources/circles.png";
+
+      return new Promise(resolve => {
+        img.onload = async () => resolve();
+      });
+    }
+
+    test_fcp("First contentful paint fires due to pseudo-element image.", load_image)
 </script>
 </body>
 
diff --git a/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-whitespace.html b/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-whitespace.html
index 6e1f425..71c72b0 100644
--- a/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-whitespace.html
+++ b/third_party/blink/web_tests/external/wpt/paint-timing/fcp-only/fcp-whitespace.html
@@ -29,7 +29,17 @@
     <div id="text">TEXT</div>
 </div>
 <script>
-    test_fcp("Whitespace should not count as contentful.")
+    // Load the image into memory first to make sure it's decoded.
+    function load_image() {
+      const img = document.createElement("img");
+      img.src = "../resources/circles.png";
+
+      return new Promise(resolve => {
+        img.onload = async () => resolve();
+      });
+    }
+
+    test_fcp("Whitespace should not count as contentful.", load_image)
 </script>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/paint-timing/resources/subframe-painting.html b/third_party/blink/web_tests/external/wpt/paint-timing/resources/subframe-painting.html
index 5fb5e28..00fd39b 100644
--- a/third_party/blink/web_tests/external/wpt/paint-timing/resources/subframe-painting.html
+++ b/third_party/blink/web_tests/external/wpt/paint-timing/resources/subframe-painting.html
@@ -4,22 +4,20 @@
 <script>
   const img = document.createElement('IMG');
   img.src = 'circles.png';
-  img.onload = function() {
-    function sendPaintEntries() {
-      const paintEntries = performance.getEntriesByType('paint');
-      if (paintEntries.length < 2) {
-        setTimeout(sendPaintEntries, 20);
-        return;
-      }
-      let entryContents = paintEntries.length + '';
+
+  var observer = new PerformanceObserver(function(list, obj) {
+    var paintEntries = list.getEntries();
       for (let i = 0; i < paintEntries.length; i++) {
-        const entry = paintEntries[i];
-        entryContents += ' ' + entry.entryType + ' '  + entry.name;
+          // postMessage doesn't allow sending the entry object over directly
+          var dataToSend = {
+              "entryType": paintEntries[i]["entryType"],
+              "name": paintEntries[i]["name"]
+          };
+          parent.postMessage(dataToSend, '*');
       }
-      parent.postMessage(entryContents, '*');
-    };
-    sendPaintEntries();
-  };
+  });
+
+  observer.observe({"type": "paint"});
   document.getElementById('image').appendChild(img);
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/paint-timing/resources/utils.js b/third_party/blink/web_tests/external/wpt/paint-timing/resources/utils.js
index 975e84d..5766971d 100644
--- a/third_party/blink/web_tests/external/wpt/paint-timing/resources/utils.js
+++ b/third_party/blink/web_tests/external/wpt/paint-timing/resources/utils.js
@@ -32,7 +32,7 @@
   });
 }
 
-async function test_fcp(label) {
+async function test_fcp(label, before_assert_fcp_func) {
   setup({"hide_test_state": true});
   const style = document.createElement('style');
   document.head.appendChild(style);
@@ -43,6 +43,9 @@
     await assertNoFirstContentfulPaint(t);
     main.className = 'preFCP';
     await assertNoFirstContentfulPaint(t);
+    if (before_assert_fcp_func) {
+      await before_assert_fcp_func();
+    }
     main.className = 'contentful';
     await assertFirstContentfulPaint(t);
   }, label);
diff --git a/third_party/blink/web_tests/external/wpt/paint-timing/sibling-painting-first-image.html b/third_party/blink/web_tests/external/wpt/paint-timing/sibling-painting-first-image.html
index 266d5d7..096c49a 100644
--- a/third_party/blink/web_tests/external/wpt/paint-timing/sibling-painting-first-image.html
+++ b/third_party/blink/web_tests/external/wpt/paint-timing/sibling-painting-first-image.html
@@ -6,17 +6,35 @@
 <iframe id="listening-iframe" src="resources/subframe-sending-paint.html"></iframe>
 <script>
 setup({"hide_test_state": true});
+var entriesExpectToReceive = [
+    {
+        'entryType': 'paint',
+        'name': 'first-paint'
+    },
+    {
+        'entryType': 'paint',
+        'name': 'first-contentful-paint'
+    }
+];
 async_test(function (t) {
     assert_implements(window.PerformancePaintTiming, "Paint Timing isn't supported.");
     let paintingIframeHasDispatchedEntries = false;
     window.addEventListener('message', t.step_func(e => {
         if (!paintingIframeHasDispatchedEntries) {
             // Check paint-timing entries from the painting iframe.
-            assert_equals(e.data, '2 paint first-paint paint first-contentful-paint');
-            paintingIframeHasDispatchedEntries = true;
-            // Ask the listening iframe to send its paint-timing entries.
-            document.getElementById('listening-iframe').
-                contentWindow.postMessage('', '*');
+            for (let i = 0; i < entriesExpectToReceive.length; i++) {
+                if (entriesExpectToReceive[i].entryType == e.data.entryType &&
+                    entriesExpectToReceive[i].name == e.data.name) {
+                    entriesExpectToReceive.splice(i, 1);
+                    break;
+                }
+            }
+            if (entriesExpectToReceive.length == 0) {
+                paintingIframeHasDispatchedEntries = true;
+                // Ask the listening iframe to send its paint-timing entries.
+                document.getElementById('listening-iframe').
+                    contentWindow.postMessage('', '*');
+            }
             return;
         }
         // Check the paint-timing entries from the listening iframe.
diff --git a/third_party/blink/web_tests/external/wpt/performance-timeline/case-sensitivity.any.js b/third_party/blink/web_tests/external/wpt/performance-timeline/case-sensitivity.any.js
index 588b59c..3a98505a 100644
--- a/third_party/blink/web_tests/external/wpt/performance-timeline/case-sensitivity.any.js
+++ b/third_party/blink/web_tests/external/wpt/performance-timeline/case-sensitivity.any.js
@@ -27,3 +27,38 @@
     assert_equals(mixedList.length, 0, "getEntriesByName('" + location2 + "').length");
 
   }, "getEntriesByName values are case sensitive");
+
+  async_test(function (t) {
+    // Test type/buffered case sensitivity.
+    observer = new PerformanceObserver(
+      t.step_func(function (entryList, obs) {
+        assert_unreached("Observer(type) should not be called.");
+      })
+    );
+    observer.observe({type: "Mark"});
+    observer.observe({type: "Measure"});
+    observer.observe({type: "MARK"});
+    observer.observe({type: "MEASURE"});
+    observer.observe({type: "Mark", buffered: true});
+    observer.observe({type: "Measure", buffered: true});
+    observer.observe({type: "MARK", buffered: true});
+    observer.observe({type: "MEASURE", buffered: true});
+    self.performance.mark("mark1");
+    self.performance.measure("measure1");
+
+    // Test entryTypes case sensitivity.
+    observer = new PerformanceObserver(
+      t.step_func(function (entryList, obs) {
+        assert_unreached("Observer(entryTypes) should not be called.");
+      })
+    );
+    observer.observe({entryTypes: ["Mark", "Measure"]});
+    observer.observe({entryTypes: ["MARK", "MEASURE"]});
+    self.performance.mark("mark1");
+    self.performance.measure("measure1");
+
+    t.step_timeout(function() {
+      t.done();
+    }, 1000);
+
+  }, "observe() and case sensitivity for types/entryTypes and buffered.");
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/test-helpers.sub.js b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/test-helpers.sub.js
index bbd16ab..38840b75 100644
--- a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/test-helpers.sub.js
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/test-helpers.sub.js
@@ -92,56 +92,66 @@
     }));
 }
 
-function wait_for_state(test, worker, state) {
-  if (!worker || worker.state == undefined) {
-    return Promise.reject(new Error(
-      'wait_for_state must be passed a ServiceWorker'));
-  }
-  if (worker.state === state)
-    return Promise.resolve(state);
-
-  if (state === 'installing') {
-    switch (worker.state) {
+// Return true if |state_a| is more advanced than |state_b|.
+function is_state_advanced(state_a, state_b) {
+  if (state_b === 'installing') {
+    switch (state_a) {
       case 'installed':
       case 'activating':
       case 'activated':
       case 'redundant':
-        return Promise.reject(new Error(
-          'worker is ' + worker.state + ' but waiting for ' + state));
+        return true;
     }
   }
 
-  if (state === 'installed') {
-    switch (worker.state) {
+  if (state_b === 'installed') {
+    switch (state_a) {
       case 'activating':
       case 'activated':
       case 'redundant':
-        return Promise.reject(new Error(
-          'worker is ' + worker.state + ' but waiting for ' + state));
+        return true;
     }
   }
 
-  if (state === 'activating') {
-    switch (worker.state) {
+  if (state_b === 'activating') {
+    switch (state_a) {
       case 'activated':
       case 'redundant':
-        return Promise.reject(new Error(
-          'worker is ' + worker.state + ' but waiting for ' + state));
+        return true;
     }
   }
 
-  if (state === 'activated') {
-    switch (worker.state) {
+  if (state_b === 'activated') {
+    switch (state_a) {
       case 'redundant':
-        return Promise.reject(new Error(
-          'worker is ' + worker.state + ' but waiting for ' + state));
+        return true;
     }
   }
+  return false;
+}
 
-  return new Promise(test.step_func(function(resolve) {
+function wait_for_state(test, worker, state) {
+  if (!worker || worker.state == undefined) {
+    return Promise.reject(new Error(
+      'wait_for_state needs a ServiceWorker object to be passed.'));
+  }
+  if (worker.state === state)
+    return Promise.resolve(state);
+
+  if (is_state_advanced(worker.state, state)) {
+    return Promise.reject(new Error(
+      `Waiting for ${state} but the worker is already ${worker.state}.`));
+  }
+  return new Promise(test.step_func(function(resolve, reject) {
       worker.addEventListener('statechange', test.step_func(function() {
           if (worker.state === state)
             resolve(state);
+
+          if (is_state_advanced(worker.state, state)) {
+            reject(new Error(
+              `The state of the worker becomes ${worker.state} while waiting` +
+                `for ${state}.`));
+          }
         }));
     }));
 }
diff --git a/third_party/blink/web_tests/external/wpt/tools/wave/www/css/bulma-0.7.5/bulma.css b/third_party/blink/web_tests/external/wpt/tools/wave/www/css/bulma-0.7.5/bulma.css
index e793432a..150bbfd 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wave/www/css/bulma-0.7.5/bulma.css
+++ b/third_party/blink/web_tests/external/wpt/tools/wave/www/css/bulma-0.7.5/bulma.css
@@ -26,7 +26,7 @@
 .pagination-ellipsis, .tabs {
   -webkit-touch-callout: none;
   -webkit-user-select: none;
-  -moz-user-select: none;
+  user-select: none;
   -ms-user-select: none;
   user-select: none;
 }
diff --git a/third_party/blink/web_tests/platform/linux/fast/multicol/vertical-rl/composited-relpos-overlapping-will-change-expected.png b/third_party/blink/web_tests/platform/linux/fast/multicol/vertical-rl/composited-relpos-overlapping-will-change-expected.png
index 35b9d86..64bbd30 100644
--- a/third_party/blink/web_tests/platform/linux/fast/multicol/vertical-rl/composited-relpos-overlapping-will-change-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/multicol/vertical-rl/composited-relpos-overlapping-will-change-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/multicol/vertical-rl/composited-relpos-overlapping-will-change-expected.png b/third_party/blink/web_tests/platform/mac/fast/multicol/vertical-rl/composited-relpos-overlapping-will-change-expected.png
index e22a4f65..f7dd9a0e 100644
--- a/third_party/blink/web_tests/platform/mac/fast/multicol/vertical-rl/composited-relpos-overlapping-will-change-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/multicol/vertical-rl/composited-relpos-overlapping-will-change-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/multicol/vertical-rl/composited-relpos-overlapping-will-change-expected.png b/third_party/blink/web_tests/platform/win/fast/multicol/vertical-rl/composited-relpos-overlapping-will-change-expected.png
index 9224c4c..a8c7750 100644
--- a/third_party/blink/web_tests/platform/win/fast/multicol/vertical-rl/composited-relpos-overlapping-will-change-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/multicol/vertical-rl/composited-relpos-overlapping-will-change-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/stable/compositing/filters/README.txt b/third_party/blink/web_tests/virtual/stable/compositing/filters/README.txt
new file mode 100644
index 0000000..12ccc866
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/stable/compositing/filters/README.txt
@@ -0,0 +1 @@
+Test CompositingOptimizations code paths.
diff --git a/third_party/blink/web_tests/virtual/stable/compositing/filters/sw-nested-shadow-overlaps-hw-nested-shadow-expected.txt b/third_party/blink/web_tests/virtual/stable/compositing/filters/sw-nested-shadow-overlaps-hw-nested-shadow-expected.txt
new file mode 100644
index 0000000..ce951477
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/stable/compositing/filters/sw-nested-shadow-overlaps-hw-nested-shadow-expected.txt
@@ -0,0 +1,41 @@
+{
+  "layers": [
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutNGBlockFlow (positioned) DIV id='composited-parent'",
+      "position": [-100, -100],
+      "bounds": [200, 200],
+      "transform": 1
+    },
+    {
+      "name": "LayoutNGBlockFlow (positioned) DIV id='software-parent'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000"
+    },
+    {
+      "name": "LayoutNGBlockFlow (positioned) DIV id='software-child'",
+      "position": [100, 100],
+      "bounds": [50, 50],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF"
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [330, 330, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
index e773d61..5731967 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -11941,7 +11941,6 @@
     method cancelAnimationFrame
     method cancelIdleCallback
     method captureEvents
-    method chooseFileSystemEntries
     method clearInterval
     method clearTimeout
     method close
diff --git a/third_party/blink/web_tests/webgpu/ctshtml-template.txt b/third_party/blink/web_tests/webgpu/ctshtml-template.txt
index bfc7fe3..26ec45a 100644
--- a/third_party/blink/web_tests/webgpu/ctshtml-template.txt
+++ b/third_party/blink/web_tests/webgpu/ctshtml-template.txt
@@ -20,7 +20,6 @@
 <script type=module src=/wpt_internal/webgpu/common/runtime/wpt.js></script>
 
 <!-- Manually-selected tests to run on worker: -->
-<meta name=variant content='?worker=1&q=webgpu:api,operation,buffers,create_mapped:*'>
 <meta name=variant content='?worker=1&q=webgpu:api,operation,buffers,map:*'>
 <meta name=variant content='?worker=1&q=webgpu:api,operation,buffers,map_detach:*'>
 <meta name=variant content='?worker=1&q=webgpu:api,operation,command_buffer,basic:*'>
diff --git a/third_party/blink/web_tests/webgpu/regenerate_internal_cts_html.sh b/third_party/blink/web_tests/webgpu/regenerate_internal_cts_html.sh
index 136794c..5b9f8ce9 100755
--- a/third_party/blink/web_tests/webgpu/regenerate_internal_cts_html.sh
+++ b/third_party/blink/web_tests/webgpu/regenerate_internal_cts_html.sh
@@ -33,9 +33,7 @@
 pushd third_party/webgpu-cts/src > /dev/null
 
   npm install --frozen-lockfile
-  npx grunt prebuild
-
-  # TODO(kainino): only run a few tests on worker
+  npx grunt run:generate-listings
 
   echo 'Regenerating...'
   npx ./tools/gen_wpt_cts_html \
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/LICENSE.txt b/third_party/blink/web_tests/wpt_internal/webgpu/LICENSE.txt
deleted file mode 100644
index c7a75d7d..0000000
--- a/third_party/blink/web_tests/wpt_internal/webgpu/LICENSE.txt
+++ /dev/null
@@ -1,26 +0,0 @@
-Copyright 2019 WebGPU CTS Contributors
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-   1. Redistributions of source code must retain the above copyright notice,
-      this list of conditions and the following disclaimer.
-
-   2. Redistributions in binary form must reproduce the above copyright notice,
-      this list of conditions and the following disclaimer in the documentation
-      and/or other materials provided with the distribution.
-
-   3. Neither the name of the copyright holder nor the names of its
-      contributors may be used to endorse or promote products derived from this
-      software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/common/constants.js b/third_party/blink/web_tests/wpt_internal/webgpu/common/constants.js
deleted file mode 100644
index 69496b1..0000000
--- a/third_party/blink/web_tests/wpt_internal/webgpu/common/constants.js
+++ /dev/null
@@ -1,302 +0,0 @@
-/**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-// https://github.com/gpuweb/gpuweb/blob/0a48816412b5d08a5fb8b89005e019165a1a2c63/spec/index.bs
-// tslint:disable:variable-name
-// String enums
-export let ExtensionName;
-
-(function (ExtensionName) {
-  ExtensionName["TextureCompressionBC"] = "texture-compression-bc";
-})(ExtensionName || (ExtensionName = {}));
-
-export let AddressMode;
-
-(function (AddressMode) {
-  AddressMode["ClampToEdge"] = "clamp-to-edge";
-  AddressMode["Repeat"] = "repeat";
-  AddressMode["MirrorRepeat"] = "mirror-repeat";
-})(AddressMode || (AddressMode = {}));
-
-export let BindingType;
-
-(function (BindingType) {
-  BindingType["UniformBuffer"] = "uniform-buffer";
-  BindingType["StorageBuffer"] = "storage-buffer";
-  BindingType["ReadonlyStorageBuffer"] = "readonly-storage-buffer";
-  BindingType["Sampler"] = "sampler";
-  BindingType["ComparisonSampler"] = "comparison-sampler";
-  BindingType["SampledTexture"] = "sampled-texture";
-  BindingType["ReadonlyStorageTexture"] = "readonly-storage-texture";
-  BindingType["WriteonlyStorageTexture"] = "writeonly-storage-texture";
-})(BindingType || (BindingType = {}));
-
-export let BlendFactor;
-
-(function (BlendFactor) {
-  BlendFactor["Zero"] = "zero";
-  BlendFactor["One"] = "one";
-  BlendFactor["SrcColor"] = "src-color";
-  BlendFactor["OneMinusSrcColor"] = "one-minus-src-color";
-  BlendFactor["SrcAlpha"] = "src-alpha";
-  BlendFactor["OneMinusSrcAlpha"] = "one-minus-src-alpha";
-  BlendFactor["DstColor"] = "dst-color";
-  BlendFactor["OneMinusDstColor"] = "one-minus-dst-color";
-  BlendFactor["DstAlpha"] = "dst-alpha";
-  BlendFactor["OneMinusDstAlpha"] = "one-minus-dst-alpha";
-  BlendFactor["SrcAlphaSaturated"] = "src-alpha-saturated";
-  BlendFactor["BlendColor"] = "blend-color";
-  BlendFactor["OneMinusBlendColor"] = "one-minus-blend-color";
-})(BlendFactor || (BlendFactor = {}));
-
-export let BlendOperation;
-
-(function (BlendOperation) {
-  BlendOperation["Add"] = "add";
-  BlendOperation["Subtract"] = "subtract";
-  BlendOperation["ReverseSubtract"] = "reverse-subtract";
-  BlendOperation["Min"] = "min";
-  BlendOperation["Max"] = "max";
-})(BlendOperation || (BlendOperation = {}));
-
-export let CompareFunction;
-
-(function (CompareFunction) {
-  CompareFunction["Never"] = "never";
-  CompareFunction["Less"] = "less";
-  CompareFunction["Equal"] = "equal";
-  CompareFunction["LessEqual"] = "less-equal";
-  CompareFunction["Greater"] = "greater";
-  CompareFunction["NotEqual"] = "not-equal";
-  CompareFunction["GreaterEqual"] = "greater-equal";
-  CompareFunction["Always"] = "always";
-})(CompareFunction || (CompareFunction = {}));
-
-export let CullMode;
-
-(function (CullMode) {
-  CullMode["None"] = "none";
-  CullMode["Front"] = "front";
-  CullMode["Back"] = "back";
-})(CullMode || (CullMode = {}));
-
-export let FilterMode;
-
-(function (FilterMode) {
-  FilterMode["Nearest"] = "nearest";
-  FilterMode["Linear"] = "linear";
-})(FilterMode || (FilterMode = {}));
-
-export let FrontFace;
-
-(function (FrontFace) {
-  FrontFace["CCW"] = "ccw";
-  FrontFace["CW"] = "cw";
-})(FrontFace || (FrontFace = {}));
-
-export let IndexFormat;
-
-(function (IndexFormat) {
-  IndexFormat["Uint16"] = "uint16";
-  IndexFormat["Uint32"] = "uint32";
-})(IndexFormat || (IndexFormat = {}));
-
-export let InputStepMode;
-
-(function (InputStepMode) {
-  InputStepMode["Vertex"] = "vertex";
-  InputStepMode["Instance"] = "instance";
-})(InputStepMode || (InputStepMode = {}));
-
-export let LoadOp;
-
-(function (LoadOp) {
-  LoadOp["Load"] = "load";
-})(LoadOp || (LoadOp = {}));
-
-export let PrimitiveTopology;
-
-(function (PrimitiveTopology) {
-  PrimitiveTopology["PointList"] = "point-list";
-  PrimitiveTopology["LineList"] = "line-list";
-  PrimitiveTopology["LineStrip"] = "line-strip";
-  PrimitiveTopology["TriangleList"] = "triangle-list";
-  PrimitiveTopology["TriangleStrip"] = "triangle-strip";
-})(PrimitiveTopology || (PrimitiveTopology = {}));
-
-export let StencilOperation;
-
-(function (StencilOperation) {
-  StencilOperation["Keep"] = "keep";
-  StencilOperation["Zero"] = "zero";
-  StencilOperation["Replace"] = "replace";
-  StencilOperation["Invert"] = "invert";
-  StencilOperation["IncrementClamp"] = "increment-clamp";
-  StencilOperation["DecrementClamp"] = "decrement-clamp";
-  StencilOperation["IncrementWrap"] = "increment-wrap";
-  StencilOperation["DecrementWrap"] = "decrement-wrap";
-})(StencilOperation || (StencilOperation = {}));
-
-export let StoreOp;
-
-(function (StoreOp) {
-  StoreOp["Store"] = "store";
-  StoreOp["Clear"] = "clear";
-})(StoreOp || (StoreOp = {}));
-
-export let TextureDimension;
-
-(function (TextureDimension) {
-  TextureDimension["E1d"] = "1d";
-  TextureDimension["E2d"] = "2d";
-  TextureDimension["E3d"] = "3d";
-})(TextureDimension || (TextureDimension = {}));
-
-export let TextureFormat;
-
-(function (TextureFormat) {
-  TextureFormat["R8Unorm"] = "r8unorm";
-  TextureFormat["R8Snorm"] = "r8snorm";
-  TextureFormat["R8Uint"] = "r8uint";
-  TextureFormat["R8Sint"] = "r8sint";
-  TextureFormat["R16Uint"] = "r16uint";
-  TextureFormat["R16Sint"] = "r16sint";
-  TextureFormat["R16Float"] = "r16float";
-  TextureFormat["RG8Unorm"] = "rg8unorm";
-  TextureFormat["RG8Snorm"] = "rg8snorm";
-  TextureFormat["RG8Uint"] = "rg8uint";
-  TextureFormat["RG8Sint"] = "rg8sint";
-  TextureFormat["R32Uint"] = "r32uint";
-  TextureFormat["R32Sint"] = "r32sint";
-  TextureFormat["R32Float"] = "r32float";
-  TextureFormat["RG16Uint"] = "rg16uint";
-  TextureFormat["RG16Sint"] = "rg16sint";
-  TextureFormat["RG16Float"] = "rg16float";
-  TextureFormat["RGBA8Unorm"] = "rgba8unorm";
-  TextureFormat["RGBA8UnormSRGB"] = "rgba8unorm-srgb";
-  TextureFormat["RGBA8Snorm"] = "rgba8snorm";
-  TextureFormat["RGBA8Uint"] = "rgba8uint";
-  TextureFormat["RGBA8Sint"] = "rgba8sint";
-  TextureFormat["BGRA8Unorm"] = "bgra8unorm";
-  TextureFormat["BGRA8UnormSRGB"] = "bgra8unorm-srgb";
-  TextureFormat["RGB10A2Unorm"] = "rgb10a2unorm";
-  TextureFormat["RG11B10Float"] = "rg11b10float";
-  TextureFormat["RG32Uint"] = "rg32uint";
-  TextureFormat["RG32Sint"] = "rg32sint";
-  TextureFormat["RG32Float"] = "rg32float";
-  TextureFormat["RGBA16Uint"] = "rgba16uint";
-  TextureFormat["RGBA16Sint"] = "rgba16sint";
-  TextureFormat["RGBA16Float"] = "rgba16float";
-  TextureFormat["RGBA32Uint"] = "rgba32uint";
-  TextureFormat["RGBA32Sint"] = "rgba32sint";
-  TextureFormat["RGBA32Float"] = "rgba32float";
-  TextureFormat["Depth32Float"] = "depth32float";
-  TextureFormat["Depth24Plus"] = "depth24plus";
-  TextureFormat["Depth24PlusStencil8"] = "depth24plus-stencil8";
-})(TextureFormat || (TextureFormat = {}));
-
-export let TextureComponentType;
-
-(function (TextureComponentType) {
-  TextureComponentType["Float"] = "float";
-  TextureComponentType["Sint"] = "sint";
-  TextureComponentType["Uint"] = "uint";
-})(TextureComponentType || (TextureComponentType = {}));
-
-export let TextureViewDimension;
-
-(function (TextureViewDimension) {
-  TextureViewDimension["E1d"] = "1d";
-  TextureViewDimension["E2d"] = "2d";
-  TextureViewDimension["E2dArray"] = "2d-array";
-  TextureViewDimension["Cube"] = "cube";
-  TextureViewDimension["CubeArray"] = "cube-array";
-  TextureViewDimension["E3d"] = "3d";
-})(TextureViewDimension || (TextureViewDimension = {}));
-
-export let VertexFormat;
-
-(function (VertexFormat) {
-  VertexFormat["Uchar2"] = "uchar2";
-  VertexFormat["Uchar4"] = "uchar4";
-  VertexFormat["Char2"] = "char2";
-  VertexFormat["Char4"] = "char4";
-  VertexFormat["Uchar2Norm"] = "uchar2norm";
-  VertexFormat["Uchar4Norm"] = "uchar4norm";
-  VertexFormat["Char2Norm"] = "char2norm";
-  VertexFormat["Char4Norm"] = "char4norm";
-  VertexFormat["Ushort2"] = "ushort2";
-  VertexFormat["Ushort4"] = "ushort4";
-  VertexFormat["Short2"] = "short2";
-  VertexFormat["Short4"] = "short4";
-  VertexFormat["Ushort2Norm"] = "ushort2norm";
-  VertexFormat["Ushort4Norm"] = "ushort4norm";
-  VertexFormat["Short2Norm"] = "short2norm";
-  VertexFormat["Short4Norm"] = "short4norm";
-  VertexFormat["Half2"] = "half2";
-  VertexFormat["Half4"] = "half4";
-  VertexFormat["Float"] = "float";
-  VertexFormat["Float2"] = "float2";
-  VertexFormat["Float3"] = "float3";
-  VertexFormat["Float4"] = "float4";
-  VertexFormat["Uint"] = "uint";
-  VertexFormat["Uint2"] = "uint2";
-  VertexFormat["Uint3"] = "uint3";
-  VertexFormat["Uint4"] = "uint4";
-  VertexFormat["Int"] = "int";
-  VertexFormat["Int2"] = "int2";
-  VertexFormat["Int3"] = "int3";
-  VertexFormat["Int4"] = "int4";
-})(VertexFormat || (VertexFormat = {}));
-
-export let TextureAspect; // Bit fields
-
-(function (TextureAspect) {
-  TextureAspect["All"] = "all";
-  TextureAspect["StencilOnly"] = "stencil-only";
-  TextureAspect["DepthOnly"] = "depth-only";
-})(TextureAspect || (TextureAspect = {}));
-
-export let BufferUsage;
-
-(function (BufferUsage) {
-  BufferUsage[BufferUsage["MapRead"] = 1] = "MapRead";
-  BufferUsage[BufferUsage["MapWrite"] = 2] = "MapWrite";
-  BufferUsage[BufferUsage["CopySrc"] = 4] = "CopySrc";
-  BufferUsage[BufferUsage["CopyDst"] = 8] = "CopyDst";
-  BufferUsage[BufferUsage["Index"] = 16] = "Index";
-  BufferUsage[BufferUsage["Vertex"] = 32] = "Vertex";
-  BufferUsage[BufferUsage["Uniform"] = 64] = "Uniform";
-  BufferUsage[BufferUsage["Storage"] = 128] = "Storage";
-  BufferUsage[BufferUsage["Indirect"] = 256] = "Indirect";
-})(BufferUsage || (BufferUsage = {}));
-
-export let ColorWrite;
-
-(function (ColorWrite) {
-  ColorWrite[ColorWrite["Red"] = 1] = "Red";
-  ColorWrite[ColorWrite["Green"] = 2] = "Green";
-  ColorWrite[ColorWrite["Blue"] = 4] = "Blue";
-  ColorWrite[ColorWrite["Alpha"] = 8] = "Alpha";
-  ColorWrite[ColorWrite["All"] = 15] = "All";
-})(ColorWrite || (ColorWrite = {}));
-
-export let ShaderStage;
-
-(function (ShaderStage) {
-  ShaderStage[ShaderStage["Vertex"] = 1] = "Vertex";
-  ShaderStage[ShaderStage["Fragment"] = 2] = "Fragment";
-  ShaderStage[ShaderStage["Compute"] = 4] = "Compute";
-})(ShaderStage || (ShaderStage = {}));
-
-export let TextureUsage;
-
-(function (TextureUsage) {
-  TextureUsage[TextureUsage["CopySrc"] = 1] = "CopySrc";
-  TextureUsage[TextureUsage["CopyDst"] = 2] = "CopyDst";
-  TextureUsage[TextureUsage["Sampled"] = 4] = "Sampled";
-  TextureUsage[TextureUsage["Storage"] = 8] = "Storage";
-  TextureUsage[TextureUsage["OutputAttachment"] = 16] = "OutputAttachment";
-})(TextureUsage || (TextureUsage = {}));
-//# sourceMappingURL=constants.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/file_loader.js b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/file_loader.js
index 0f25ca4..b6f3d50 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/file_loader.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/file_loader.js
@@ -1,9 +1,10 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ import { parseQuery } from './query/parseQuery.js';
 
-import { parseQuery } from './query/parseQuery.js';
-import { loadTreeForQuery } from './tree.js'; // A listing file, e.g. either of:
+import { loadTreeForQuery } from './tree.js';
+
+// A listing file, e.g. either of:
 // - `src/webgpu/listing.ts` (which is dynamically computed, has a Promise<TestSuiteListing>)
 // - `out/webgpu/listing.js` (which is pre-baked, has a TestSuiteListing)
 
@@ -14,15 +15,19 @@
   }
 
   async loadTree(query, subqueriesToExpand = []) {
-    return loadTreeForQuery(this, query, subqueriesToExpand.map(q => parseQuery(q)));
+    return loadTreeForQuery(
+      this,
+      query,
+      subqueriesToExpand.map(q => parseQuery(q))
+    );
   }
 
   async loadCases(query) {
     const tree = await this.loadTree(query);
     return tree.iterateLeaves();
   }
-
 }
+
 export class DefaultTestFileLoader extends TestFileLoader {
   async listing(suite) {
     return (await import(`../../${suite}/listing.js`)).listing;
@@ -31,6 +36,4 @@
   import(path) {
     return import(`../../${path}`);
   }
-
 }
-//# sourceMappingURL=file_loader.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/fixture.js b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/fixture.js
index ef23f06..fcff2a6 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/fixture.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/fixture.js
@@ -1,30 +1,37 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
-
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ function _defineProperty(obj, key, value) {
+  if (key in obj) {
+    Object.defineProperty(obj, key, {
+      value: value,
+      enumerable: true,
+      configurable: true,
+      writable: true,
+    });
+  } else {
+    obj[key] = value;
+  }
+  return obj;
+}
 import { assert } from './util/util.js';
-export class SkipTestCase extends Error {} // A Fixture is a class used to instantiate each test case at run time.
+
+export class SkipTestCase extends Error {}
+
+// A Fixture is a class used to instantiate each test case at run time.
 // A new instance of the Fixture is created for every single test case
 // (i.e. every time the test function is run).
-
 export class Fixture {
   constructor(rec, params) {
-    _defineProperty(this, "params", void 0);
-
-    _defineProperty(this, "rec", void 0);
-
-    _defineProperty(this, "eventualExpectations", []);
-
-    _defineProperty(this, "numOutstandingAsyncExpectations", 0);
-
+    _defineProperty(this, 'params', void 0);
+    _defineProperty(this, 'rec', void 0);
+    _defineProperty(this, 'eventualExpectations', []);
+    _defineProperty(this, 'numOutstandingAsyncExpectations', 0);
     this.rec = rec;
     this.params = params;
-  } // This has to be a member function instead of an async `createFixture` function, because
+  }
+
+  // This has to be a member function instead of an async `createFixture` function, because
   // we need to be able to ergonomically override it in subclasses.
-
-
   async init() {}
 
   debug(msg) {
@@ -36,7 +43,11 @@
   }
 
   async finalize() {
-    assert(this.numOutstandingAsyncExpectations === 0, 'there were outstanding asynchronous expectations (e.g. shouldReject) at the end of the test');
+    assert(
+      this.numOutstandingAsyncExpectations === 0,
+      'there were outstanding asynchronous expectations (e.g. shouldReject) at the end of the test'
+    );
+
     await Promise.all(this.eventualExpectations);
   }
 
@@ -67,9 +78,7 @@
       this.rec.expectationFailed(niceStack);
       return;
     }
-
     const actualName = ex.name;
-
     if (actualName !== expectedName) {
       niceStack.message = `THREW ${actualName}, instead of ${expectedName}: ${ex}`;
       this.rec.expectationFailed(niceStack);
@@ -79,16 +88,28 @@
     }
   }
 
+  shouldResolve(p, msg) {
+    this.eventualAsyncExpectation(async niceStack => {
+      const m = msg ? ': ' + msg : '';
+      try {
+        await p;
+        niceStack.message = 'resolved as expected' + m;
+      } catch (ex) {
+        niceStack.message = `REJECTED${m}\n${ex.message}`;
+        this.rec.expectationFailed(niceStack);
+      }
+    });
+  }
+
   shouldReject(expectedName, p, msg) {
     this.eventualAsyncExpectation(async niceStack => {
       const m = msg ? ': ' + msg : '';
-
       try {
         await p;
         niceStack.message = 'DID NOT REJECT' + m;
         this.rec.expectationFailed(niceStack);
       } catch (ex) {
-        niceStack.message = m;
+        niceStack.message = 'rejected as expected' + m;
         this.expectErrorValue(expectedName, ex, niceStack);
       }
     });
@@ -96,7 +117,6 @@
 
   shouldThrow(expectedName, fn, msg) {
     const m = msg ? ': ' + msg : '';
-
     try {
       fn();
       this.rec.expectationFailed(new Error('DID NOT THROW' + m));
@@ -112,9 +132,6 @@
     } else {
       this.rec.expectationFailed(new Error(msg));
     }
-
     return cond;
   }
-
 }
-//# sourceMappingURL=fixture.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/glsl.js b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/glsl.js
index 3ed3d921..7571416d 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/glsl.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/glsl.js
@@ -1,10 +1,10 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ import { assert, unreachable } from './util/util.js';
 
-import { assert, unreachable } from './util/util.js';
 let glslangAttempted = false;
 let glslangInstance;
+
 export async function initGLSL() {
   if (glslangAttempted) {
     assert(glslangInstance !== undefined, 'glslang is not available');
@@ -12,7 +12,6 @@
     glslangAttempted = true;
     const glslangPath = '../../third_party/glslang_js/lib/glslang.js';
     let glslangModule;
-
     try {
       glslangModule = (await import(glslangPath)).default;
     } catch (ex) {
@@ -23,8 +22,12 @@
     glslangInstance = glslang;
   }
 }
+
 export function compileGLSL(glsl, shaderType, genDebug, spirvVersion) {
-  assert(glslangInstance !== undefined, 'GLSL compiler is not instantiated. Run `await initGLSL()` first');
+  assert(
+    glslangInstance !== undefined,
+    'GLSL compiler is not instantiated. Run `await initGLSL()` first'
+  );
+
   return glslangInstance.compileGLSL(glsl.trimLeft(), shaderType, genDebug, spirvVersion);
 }
-//# sourceMappingURL=glsl.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/gpu/device_pool.js b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/gpu/device_pool.js
index 848e580..f72b6b73 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/gpu/device_pool.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/gpu/device_pool.js
@@ -1,24 +1,32 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
-
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ function _defineProperty(obj, key, value) {
+  if (key in obj) {
+    Object.defineProperty(obj, key, {
+      value: value,
+      enumerable: true,
+      configurable: true,
+      writable: true,
+    });
+  } else {
+    obj[key] = value;
+  }
+  return obj;
+}
 import { assert, raceWithRejectOnTimeout, unreachable, assertReject } from '../util/util.js';
 import { getGPU } from './implementation.js';
 
 class TestFailedButDeviceReusable extends Error {}
-
 export class TestOOMedShouldAttemptGC extends Error {}
+
 const kPopErrorScopeTimeoutMS = 5000;
+
 export class DevicePool {
   constructor() {
-    _defineProperty(this, "failed", false);
+    _defineProperty(this, 'failed', false);
+    _defineProperty(this, 'holder', undefined);
+  } // undefined if "uninitialized" (not yet initialized, or lost)
 
-    _defineProperty(this, "holder", undefined);
-  }
-
-  // undefined if "uninitialized" (not yet initialized, or lost)
   async acquire() {
     assert(!this.failed, 'WebGPU device previously failed to initialize; not retrying');
 
@@ -30,15 +38,15 @@
         throw ex;
       }
     }
-
     assert(!this.holder.acquired, 'Device was in use on DevicePool.acquire');
     this.holder.acquired = true;
+
     this.beginErrorScopes();
     return this.holder.device;
-  } // When a test is done using a device, it's released back into the pool.
+  }
+
+  // When a test is done using a device, it's released back into the pool.
   // This waits for error scopes, checks their results, and checks for various error conditions.
-
-
   async release(device) {
     const holder = this.holder;
     assert(holder !== undefined, 'trying to release a device while pool is uninitialized');
@@ -48,12 +56,16 @@
     try {
       // Time out if popErrorScope never completes. This could happen due to a browser bug - e.g.,
       // as of this writing, on Chrome GPU process crash, popErrorScope just hangs.
-      await raceWithRejectOnTimeout(this.endErrorScopes(), kPopErrorScopeTimeoutMS, 'finalization popErrorScope timed out'); // (Hopefully if the device was lost, it has been reported by the time endErrorScopes()
+      await raceWithRejectOnTimeout(
+        this.endErrorScopes(),
+        kPopErrorScopeTimeoutMS,
+        'finalization popErrorScope timed out'
+      );
+
+      // (Hopefully if the device was lost, it has been reported by the time endErrorScopes()
       // has finished (or timed out). If not, it could cause a finite number of extra test
       // failures following this one (but should recover eventually).)
-
       const lostReason = holder.lostReason;
-
       if (lostReason !== undefined) {
         // Fail the current test.
         unreachable(`Device was lost: ${lostReason}`);
@@ -64,41 +76,46 @@
       if (!(ex instanceof TestFailedButDeviceReusable)) {
         this.holder = undefined;
       }
-
       throw ex;
     } finally {
       // TODO: device.destroy()
+
       // Mark the holder as free. (This only has an effect if the pool still has the holder.)
       // This could be done at the top but is done here to guard against async-races during release.
       holder.acquired = false;
     }
-  } // Gets a device and creates a DeviceHolder.
+  }
+
+  // Gets a device and creates a DeviceHolder.
   // If the device is lost, DeviceHolder.lostReason gets set.
-
-
   static async makeHolder() {
     const gpu = getGPU();
     const adapter = await gpu.requestAdapter();
+    assert(adapter !== null);
+    const device = await adapter.requestDevice();
+    assert(device !== null);
+
     const holder = {
       acquired: false,
-      device: await adapter.requestDevice(),
-      lostReason: undefined
+      device,
+      lostReason: undefined,
     };
+
     holder.device.lost.then(ev => {
       holder.lostReason = ev.message;
     });
     return holder;
-  } // Create error scopes that wrap the entire test.
+  }
 
-
+  // Create error scopes that wrap the entire test.
   beginErrorScopes() {
     assert(this.holder !== undefined);
     this.holder.device.pushErrorScope('out-of-memory');
     this.holder.device.pushErrorScope('validation');
-  } // End the whole-test error scopes. Check that there are no extra error scopes, and that no
+  }
+
+  // End the whole-test error scopes. Check that there are no extra error scopes, and that no
   // otherwise-uncaptured errors occurred during the test.
-
-
   async endErrorScopes() {
     assert(this.holder !== undefined);
     let gpuValidationError;
@@ -109,24 +126,30 @@
       gpuValidationError = await this.holder.device.popErrorScope();
       gpuOutOfMemoryError = await this.holder.device.popErrorScope();
     } catch (ex) {
-      assert(this.holder.lostReason !== undefined, "popErrorScope failed, but device.lost hasn't fired (yet)");
+      assert(
+        this.holder.lostReason !== undefined,
+        "popErrorScope failed, but device.lost hasn't fired (yet)"
+      );
+
       throw ex;
     }
 
-    await assertReject(this.holder.device.popErrorScope(), 'There was an extra error scope on the stack after a test');
+    await assertReject(
+      this.holder.device.popErrorScope(),
+      'There was an extra error scope on the stack after a test'
+    );
 
     if (gpuValidationError !== null) {
-      assert(gpuValidationError instanceof GPUValidationError); // Allow the device to be reused.
-
-      throw new TestFailedButDeviceReusable(`Unexpected validation error occurred: ${gpuValidationError.message}`);
+      assert(gpuValidationError instanceof GPUValidationError);
+      // Allow the device to be reused.
+      throw new TestFailedButDeviceReusable(
+        `Unexpected validation error occurred: ${gpuValidationError.message}`
+      );
     }
-
     if (gpuOutOfMemoryError !== null) {
-      assert(gpuOutOfMemoryError instanceof GPUOutOfMemoryError); // Don't allow the device to be reused; unexpected OOM could break the device.
-
+      assert(gpuOutOfMemoryError instanceof GPUOutOfMemoryError);
+      // Don't allow the device to be reused; unexpected OOM could break the device.
       throw new TestOOMedShouldAttemptGC('Unexpected out-of-memory error occurred');
     }
   }
-
 }
-//# sourceMappingURL=device_pool.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/gpu/implementation.js b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/gpu/implementation.js
index 670872b5..ab118a4 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/gpu/implementation.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/gpu/implementation.js
@@ -1,17 +1,19 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-/// <reference types="@webgpu/types" />
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ /// <reference types="@webgpu/types" />
 import { assert } from '../util/util.js';
 let impl = undefined;
+
 export function getGPU() {
   if (impl) {
     return impl;
   }
 
-  assert(typeof navigator !== 'undefined' && navigator.gpu !== undefined, 'No WebGPU implementation found');
+  assert(
+    typeof navigator !== 'undefined' && navigator.gpu !== undefined,
+    'No WebGPU implementation found'
+  );
+
   impl = navigator.gpu;
   return impl;
 }
-//# sourceMappingURL=implementation.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/logging/log_message.js b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/logging/log_message.js
index 11f352f8..8c1007f 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/logging/log_message.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/logging/log_message.js
@@ -1,50 +1,48 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
-
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ function _defineProperty(obj, key, value) {
+  if (key in obj) {
+    Object.defineProperty(obj, key, {
+      value: value,
+      enumerable: true,
+      configurable: true,
+      writable: true,
+    });
+  } else {
+    obj[key] = value;
+  }
+  return obj;
+}
 import { extractImportantStackTrace } from '../util/stack.js';
 export class LogMessageWithStack extends Error {
   constructor(name, ex) {
     super(ex.message);
-
-    _defineProperty(this, "stackHidden", false);
-
-    _defineProperty(this, "timesSeen", 1);
+    _defineProperty(this, 'stackHidden', false);
+    _defineProperty(this, 'timesSeen', 1);
 
     this.name = name;
     this.stack = ex.stack;
   }
+
   /** Set a flag so the stack is not printed in toJSON(). */
-
-
   setStackHidden() {
     this.stackHidden = true;
   }
+
   /** Increment the "seen x times" counter. */
-
-
   incrementTimesSeen() {
     this.timesSeen++;
   }
 
   toJSON() {
-    let m = this.name + ': ';
-
+    let m = this.name;
+    if (this.message) m += ': ' + this.message;
     if (!this.stackHidden && this.stack) {
-      // this.message is already included in this.stack
-      m += extractImportantStackTrace(this);
-    } else {
-      m += this.message;
+      m += '\n' + extractImportantStackTrace(this);
     }
-
     if (this.timesSeen > 1) {
       m += `\n(seen ${this.timesSeen} times with identical stack)`;
     }
-
     return m;
   }
-
 }
-//# sourceMappingURL=log_message.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/logging/logger.js b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/logging/logger.js
index 8d66c55..c4e65c74 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/logging/logger.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/logging/logger.js
@@ -1,35 +1,35 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
-
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ function _defineProperty(obj, key, value) {
+  if (key in obj) {
+    Object.defineProperty(obj, key, {
+      value: value,
+      enumerable: true,
+      configurable: true,
+      writable: true,
+    });
+  } else {
+    obj[key] = value;
+  }
+  return obj;
+}
 import { version } from '../version.js';
 import { TestCaseRecorder } from './test_case_recorder.js';
+
 export class Logger {
   constructor(debug) {
-    _defineProperty(this, "debug", void 0);
-
-    _defineProperty(this, "results", new Map());
-
+    _defineProperty(this, 'debug', void 0);
+    _defineProperty(this, 'results', new Map());
     this.debug = debug;
   }
 
   record(name) {
-    const result = {
-      status: 'running',
-      timems: -1
-    };
+    const result = { status: 'running', timems: -1 };
     this.results.set(name, result);
     return [new TestCaseRecorder(result, this.debug), result];
   }
 
   asJSON(space) {
-    return JSON.stringify({
-      version,
-      results: Array.from(this.results)
-    }, undefined, space);
+    return JSON.stringify({ version, results: Array.from(this.results) }, undefined, space);
   }
-
 }
-//# sourceMappingURL=logger.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/logging/result.js b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/logging/result.js
index 813e781..d387dc3e 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/logging/result.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/logging/result.js
@@ -1,4 +1,3 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-//# sourceMappingURL=result.js.map
\ No newline at end of file
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/logging/test_case_recorder.js b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/logging/test_case_recorder.js
index d16af0f..37acb53 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/logging/test_case_recorder.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/logging/test_case_recorder.js
@@ -1,43 +1,45 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
-
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ function _defineProperty(obj, key, value) {
+  if (key in obj) {
+    Object.defineProperty(obj, key, {
+      value: value,
+      enumerable: true,
+      configurable: true,
+      writable: true,
+    });
+  } else {
+    obj[key] = value;
+  }
+  return obj;
+}
 import { SkipTestCase } from '../fixture.js';
 import { now, assert } from '../util/util.js';
 import { LogMessageWithStack } from './log_message.js';
 var LogSeverity;
-
 (function (LogSeverity) {
-  LogSeverity[LogSeverity["Pass"] = 0] = "Pass";
-  LogSeverity[LogSeverity["Skip"] = 1] = "Skip";
-  LogSeverity[LogSeverity["Warn"] = 2] = "Warn";
-  LogSeverity[LogSeverity["ExpectFailed"] = 3] = "ExpectFailed";
-  LogSeverity[LogSeverity["ValidationFailed"] = 4] = "ValidationFailed";
-  LogSeverity[LogSeverity["ThrewException"] = 5] = "ThrewException";
+  LogSeverity[(LogSeverity['Pass'] = 0)] = 'Pass';
+  LogSeverity[(LogSeverity['Skip'] = 1)] = 'Skip';
+  LogSeverity[(LogSeverity['Warn'] = 2)] = 'Warn';
+  LogSeverity[(LogSeverity['ExpectFailed'] = 3)] = 'ExpectFailed';
+  LogSeverity[(LogSeverity['ValidationFailed'] = 4)] = 'ValidationFailed';
+  LogSeverity[(LogSeverity['ThrewException'] = 5)] = 'ThrewException';
 })(LogSeverity || (LogSeverity = {}));
 
 const kMaxLogStacks = 2;
-/** Holds onto a LiveTestCaseResult owned by the Logger, and writes the results into it. */
 
+/** Holds onto a LiveTestCaseResult owned by the Logger, and writes the results into it. */
 export class TestCaseRecorder {
   /** Used to dedup log messages which have identical stacks. */
+
   constructor(result, debugging) {
-    _defineProperty(this, "result", void 0);
-
-    _defineProperty(this, "maxLogSeverity", LogSeverity.Pass);
-
-    _defineProperty(this, "startTime", -1);
-
-    _defineProperty(this, "logs", []);
-
-    _defineProperty(this, "logLinesAtCurrentSeverity", 0);
-
-    _defineProperty(this, "debugging", false);
-
-    _defineProperty(this, "messagesForPreviouslySeenStacks", new Map());
-
+    _defineProperty(this, 'result', void 0);
+    _defineProperty(this, 'maxLogSeverity', LogSeverity.Pass);
+    _defineProperty(this, 'startTime', -1);
+    _defineProperty(this, 'logs', []);
+    _defineProperty(this, 'logLinesAtCurrentSeverity', 0);
+    _defineProperty(this, 'debugging', false);
+    _defineProperty(this, 'messagesForPreviouslySeenStacks', new Map());
     this.result = result;
     this.debugging = debugging;
   }
@@ -49,11 +51,20 @@
 
   finish() {
     assert(this.startTime >= 0, 'finish() before start()');
-    const timeMilliseconds = now() - this.startTime; // Round to next microsecond to avoid storing useless .xxxx00000000000002 in results.
 
-    this.result.timems = Math.ceil(timeMilliseconds * 1000) / 1000; // Convert numeric enum back to string (but expose 'exception' as 'fail')
+    const timeMilliseconds = now() - this.startTime;
+    // Round to next microsecond to avoid storing useless .xxxx00000000000002 in results.
+    this.result.timems = Math.ceil(timeMilliseconds * 1000) / 1000;
 
-    this.result.status = this.maxLogSeverity === LogSeverity.Pass ? 'pass' : this.maxLogSeverity === LogSeverity.Skip ? 'skip' : this.maxLogSeverity === LogSeverity.Warn ? 'warn' : 'fail'; // Everything else is an error
+    // Convert numeric enum back to string (but expose 'exception' as 'fail')
+    this.result.status =
+      this.maxLogSeverity === LogSeverity.Pass
+        ? 'pass'
+        : this.maxLogSeverity === LogSeverity.Skip
+        ? 'skip'
+        : this.maxLogSeverity === LogSeverity.Warn
+        ? 'warn'
+        : 'fail'; // Everything else is an error
 
     this.result.logs = this.logs;
   }
@@ -66,7 +77,6 @@
     if (!this.debugging) {
       return;
     }
-
     const logMessage = new LogMessageWithStack('DEBUG', ex);
     logMessage.setStackHidden();
     this.logImpl(LogSeverity.Pass, logMessage);
@@ -93,7 +103,6 @@
       this.skipped(ex);
       return;
     }
-
     this.logImpl(LogSeverity.ThrewException, new LogMessageWithStack('EXCEPTION', ex));
   }
 
@@ -101,20 +110,17 @@
     // Deduplicate errors with the exact same stack
     if (logMessage.stack) {
       const seen = this.messagesForPreviouslySeenStacks.get(logMessage.stack);
-
       if (seen) {
         seen.incrementTimesSeen();
         return;
       }
-
       this.messagesForPreviouslySeenStacks.set(logMessage.stack, logMessage);
-    } // Mark printStack=false for all logs except 2 at the highest severity
+    }
 
-
+    // Mark printStack=false for all logs except 2 at the highest severity
     if (level > this.maxLogSeverity) {
       this.logLinesAtCurrentSeverity = 0;
       this.maxLogSeverity = level;
-
       if (!this.debugging) {
         // Go back and turn off printStack for everything of a lower log level
         for (const log of this.logs) {
@@ -122,16 +128,12 @@
         }
       }
     }
-
     if (level < this.maxLogSeverity || this.logLinesAtCurrentSeverity >= kMaxLogStacks) {
       if (!this.debugging) {
         logMessage.setStackHidden();
       }
     }
-
     this.logs.push(logMessage);
     this.logLinesAtCurrentSeverity++;
   }
-
 }
-//# sourceMappingURL=test_case_recorder.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/params_builder.js b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/params_builder.js
index 10dd239..f585e3a2 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/params_builder.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/params_builder.js
@@ -1,36 +1,68 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-let _Symbol$iterator;
-
-function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
-
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ let _Symbol$iterator;
+function _defineProperty(obj, key, value) {
+  if (key in obj) {
+    Object.defineProperty(obj, key, {
+      value: value,
+      enumerable: true,
+      configurable: true,
+      writable: true,
+    });
+  } else {
+    obj[key] = value;
+  }
+  return obj;
+}
 import { publicParamsEquals } from './params_utils.js';
-import { assert } from './util/util.js'; // https://stackoverflow.com/a/56375136
+import { assert } from './util/util.js';
+/** Forces a type to resolve its type definitions, to make it readable/debuggable. */
+
+function typeAssert() {}
+{
+  {
+    typeAssert();
+    typeAssert();
+    typeAssert();
+    typeAssert();
+    typeAssert();
+
+    typeAssert();
+
+    typeAssert();
+    typeAssert();
+    typeAssert();
+    typeAssert();
+    typeAssert();
+
+    // Unexpected test results - hopefully okay to ignore these
+    typeAssert();
+    typeAssert();
+  }
+}
 
 export function poptions(name, values) {
   const iter = makeReusableIterable(function* () {
     for (const value of values) {
-      yield {
-        [name]: value
-      };
+      yield { [name]: value };
     }
   });
+
   return iter;
 }
+
 export function pbool(name) {
   return poptions(name, [false, true]);
 }
+
 export function params() {
   return new ParamsBuilder();
 }
 _Symbol$iterator = Symbol.iterator;
 export class ParamsBuilder {
   constructor() {
-    _defineProperty(this, "paramSpecs", [{}]);
+    _defineProperty(this, 'paramSpecs', [{}]);
   }
-
   [_Symbol$iterator]() {
     const iter = this.paramSpecs[Symbol.iterator]();
     return iter;
@@ -45,6 +77,7 @@
         }
       }
     });
+
     return this;
   }
 
@@ -57,6 +90,7 @@
         }
       }
     });
+
     return this;
   }
 
@@ -88,24 +122,19 @@
     });
     return this;
   }
+}
 
-} // If you create an Iterable by calling a generator function (e.g. in IIFE), it is exhausted after
+// If you create an Iterable by calling a generator function (e.g. in IIFE), it is exhausted after
 // one use. This just wraps a generator function in an object so it be iterated multiple times.
-
 function makeReusableIterable(generatorFn) {
-  return {
-    [Symbol.iterator]: generatorFn
-  };
+  return { [Symbol.iterator]: generatorFn };
 }
 
 // (keyof A & keyof B) is not empty, so they overlapped
+
 function mergeParams(a, b) {
   for (const key of Object.keys(a)) {
     assert(!(key in b), 'Duplicate key: ' + key);
   }
-
-  return { ...a,
-    ...b
-  };
+  return { ...a, ...b };
 }
-//# sourceMappingURL=params_builder.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/params_utils.js b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/params_utils.js
index 161feb5..a95d01b 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/params_utils.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/params_utils.js
@@ -1,26 +1,27 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-import { comparePublicParamsPaths, Ordering } from './query/compare.js';
-import { kWildcard, kParamSeparator, kParamKVSeparator } from './query/separators.js'; // Consider adding more types here if needed
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ import { comparePublicParamsPaths, Ordering } from './query/compare.js';
+import { kWildcard, kParamSeparator, kParamKVSeparator } from './query/separators.js';
+// Consider adding more types here if needed
 
 export function paramKeyIsPublic(key) {
   return !key.startsWith('_');
 }
+
 export function extractPublicParams(params) {
   const publicParams = {};
-
   for (const k of Object.keys(params)) {
     if (paramKeyIsPublic(k)) {
       publicParams[k] = params[k];
     }
   }
-
   return publicParams;
 }
-export const badParamValueChars = new RegExp('[' + kParamKVSeparator + kParamSeparator + kWildcard + ']');
+
+export const badParamValueChars = new RegExp(
+  '[' + kParamKVSeparator + kParamSeparator + kWildcard + ']'
+);
+
 export function publicParamsEquals(x, y) {
   return comparePublicParamsPaths(x, y) === Ordering.Equal;
 }
-//# sourceMappingURL=params_utils.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/query/compare.js b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/query/compare.js
index 52396f35..7820887 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/query/compare.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/query/compare.js
@@ -1,50 +1,44 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-import { paramKeyIsPublic } from '../params_utils.js';
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ import { paramKeyIsPublic } from '../params_utils.js';
 import { assert, objectEquals } from '../util/util.js';
+
 export let Ordering;
+
 /**
  * Compares two queries for their ordering (which is used to build the tree).
  *
  * See src/unittests/query_compare.spec.ts for examples.
- */
-
-(function (Ordering) {
-  Ordering[Ordering["Unordered"] = 0] = "Unordered";
-  Ordering[Ordering["StrictSuperset"] = 1] = "StrictSuperset";
-  Ordering[Ordering["Equal"] = 2] = "Equal";
-  Ordering[Ordering["StrictSubset"] = 3] = "StrictSubset";
+ */ (function (Ordering) {
+  Ordering[(Ordering['Unordered'] = 0)] = 'Unordered';
+  Ordering[(Ordering['StrictSuperset'] = 1)] = 'StrictSuperset';
+  Ordering[(Ordering['Equal'] = 2)] = 'Equal';
+  Ordering[(Ordering['StrictSubset'] = 3)] = 'StrictSubset';
 })(Ordering || (Ordering = {}));
-
 export function compareQueries(a, b) {
   if (a.suite !== b.suite) {
     return Ordering.Unordered;
   }
 
   const filePathOrdering = comparePaths(a.filePathParts, b.filePathParts);
-
   if (filePathOrdering !== Ordering.Equal || a.isMultiFile || b.isMultiFile) {
     return compareOneLevel(filePathOrdering, a.isMultiFile, b.isMultiFile);
   }
-
   assert('testPathParts' in a && 'testPathParts' in b);
-  const testPathOrdering = comparePaths(a.testPathParts, b.testPathParts);
 
+  const testPathOrdering = comparePaths(a.testPathParts, b.testPathParts);
   if (testPathOrdering !== Ordering.Equal || a.isMultiTest || b.isMultiTest) {
     return compareOneLevel(testPathOrdering, a.isMultiTest, b.isMultiTest);
   }
-
   assert('params' in a && 'params' in b);
-  const paramsPathOrdering = comparePublicParamsPaths(a.params, b.params);
 
+  const paramsPathOrdering = comparePublicParamsPaths(a.params, b.params);
   if (paramsPathOrdering !== Ordering.Equal || a.isMultiCase || b.isMultiCase) {
     return compareOneLevel(paramsPathOrdering, a.isMultiCase, b.isMultiCase);
   }
-
   return Ordering.Equal;
 }
+
 /**
  * Compares a single level of a query.
  *
@@ -52,14 +46,12 @@
  *   - Anything >= `suite:a,*` is big
  *   - Anything <= `suite:a:*` is small
  */
-
 function compareOneLevel(ordering, aIsBig, bIsBig) {
   assert(ordering !== Ordering.Equal || aIsBig || bIsBig);
   if (ordering === Ordering.Unordered) return Ordering.Unordered;
   if (aIsBig && bIsBig) return ordering;
   if (!aIsBig && !bIsBig) return Ordering.Unordered; // Equal case is already handled
   // Exactly one of (a, b) is big.
-
   if (aIsBig && ordering !== Ordering.StrictSubset) return Ordering.StrictSuperset;
   if (bIsBig && ordering !== Ordering.StrictSuperset) return Ordering.StrictSubset;
   return Ordering.Unordered;
@@ -73,7 +65,6 @@
       return Ordering.Unordered;
     }
   }
-
   if (a.length === b.length) {
     return Ordering.Equal;
   } else if (a.length < b.length) {
@@ -92,7 +83,6 @@
       return Ordering.Unordered;
     }
   }
-
   const bKeys = Object.keys(b).filter(k => paramKeyIsPublic(k));
   const aRemainingKeys = aKeys.length - commonKeys.size;
   const bRemainingKeys = bKeys.length - commonKeys.size;
@@ -101,4 +91,3 @@
   if (bRemainingKeys === 0) return Ordering.StrictSubset;
   return Ordering.Unordered;
 }
-//# sourceMappingURL=compare.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/query/encode_selectively.js b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/query/encode_selectively.js
index e153cf8..62cb55e 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/query/encode_selectively.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/query/encode_selectively.js
@@ -1,8 +1,6 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-/**
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ /**
  * Encodes a stringified TestQuery so that it can be placed in a `?q=` parameter in a URL.
  *
  * `encodeURIComponent` encodes in accordance with `application/x-www-form-urlencoded`,
@@ -10,23 +8,15 @@
  * (we interpret this purely from JavaScript).
  * So we encode the component, then selectively convert some %-encoded escape codes
  * back to their original form for readability/copyability.
- */
-export function encodeURIComponentSelectively(s) {
+ */ export function encodeURIComponentSelectively(s) {
   let ret = encodeURIComponent(s);
   ret = ret.replace(/%22/g, '"'); // for JSON strings
-
   ret = ret.replace(/%2C/g, ','); // for path separator, and JSON arrays
-
   ret = ret.replace(/%3A/g, ':'); // for big separator
-
   ret = ret.replace(/%3B/g, ';'); // for param separator
-
   ret = ret.replace(/%3D/g, '='); // for params (k=v)
-
   ret = ret.replace(/%5B/g, '['); // for JSON arrays
-
   ret = ret.replace(/%5D/g, ']'); // for JSON arrays
-
+  ret = ret.replace(/%E2%9C%97/g, '✗'); // for jsUndefinedMagicValue
   return ret;
 }
-//# sourceMappingURL=encode_selectively.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/query/json_param_value.js b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/query/json_param_value.js
new file mode 100644
index 0000000..921b3ddc
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/query/json_param_value.js
@@ -0,0 +1,19 @@
+/**
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ import { assert } from '../util/util.js';
+// JSON can't represent `undefined` and by default stores it as `null`.
+// Instead, store `undefined` as this magic string value in JSON.
+const jsUndefinedMagicValue = '_undef_';
+
+export function stringifyParamValue(value) {
+  return JSON.stringify(value, (k, v) => {
+    // Make sure no one actually uses the magic value as a parameter.
+    assert(v !== jsUndefinedMagicValue);
+
+    return v === undefined ? jsUndefinedMagicValue : v;
+  });
+}
+
+export function parseParamValue(s) {
+  return JSON.parse(s, (k, v) => (v === jsUndefinedMagicValue ? undefined : v));
+}
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/query/parseQuery.js b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/query/parseQuery.js
index 758227c..6c10baab 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/query/parseQuery.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/query/parseQuery.js
@@ -1,12 +1,18 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-import { badParamValueChars, paramKeyIsPublic } from '../params_utils.js';
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ import { badParamValueChars, paramKeyIsPublic } from '../params_utils.js';
 import { assert } from '../util/util.js';
-import { TestQueryMultiFile, TestQueryMultiTest, TestQueryMultiCase, TestQuerySingleCase } from './query.js';
+
+import { parseParamValue } from './json_param_value.js';
+import {
+  TestQueryMultiFile,
+  TestQueryMultiTest,
+  TestQueryMultiCase,
+  TestQuerySingleCase,
+} from './query.js';
 import { kBigSeparator, kWildcard, kPathSeparator, kParamSeparator } from './separators.js';
 import { validQueryPart } from './validQueryPart.js';
+
 export function parseQuery(s) {
   try {
     return parseQueryImpl(s);
@@ -18,91 +24,89 @@
 
 function parseQueryImpl(s) {
   // Undo encodeURIComponentSelectively
-  s = decodeURIComponent(s); // bigParts are: suite, group, test, params (note kBigSeparator could appear in params)
+  s = decodeURIComponent(s);
 
+  // bigParts are: suite, group, test, params (note kBigSeparator could appear in params)
   const [suite, fileString, testString, paramsString] = s.split(kBigSeparator, 4);
   assert(fileString !== undefined, `filter string must have at least one ${kBigSeparator}`);
-  const {
-    parts: file,
-    wildcard: filePathHasWildcard
-  } = parseBigPart(fileString, kPathSeparator);
+
+  const { parts: file, wildcard: filePathHasWildcard } = parseBigPart(fileString, kPathSeparator);
 
   if (testString === undefined) {
     // Query is file-level
-    assert(filePathHasWildcard, `File-level query without wildcard ${kWildcard}. Did you want a file-level query \
-(append ${kPathSeparator}${kWildcard}) or test-level query (append ${kBigSeparator}${kWildcard})?`);
+    assert(
+      filePathHasWildcard,
+      `File-level query without wildcard ${kWildcard}. Did you want a file-level query \
+(append ${kPathSeparator}${kWildcard}) or test-level query (append ${kBigSeparator}${kWildcard})?`
+    );
+
     return new TestQueryMultiFile(suite, file);
   }
-
   assert(!filePathHasWildcard, `Wildcard ${kWildcard} must be at the end of the query string`);
-  const {
-    parts: test,
-    wildcard: testPathHasWildcard
-  } = parseBigPart(testString, kPathSeparator);
+
+  const { parts: test, wildcard: testPathHasWildcard } = parseBigPart(testString, kPathSeparator);
 
   if (paramsString === undefined) {
     // Query is test-level
-    assert(testPathHasWildcard, `Test-level query without wildcard ${kWildcard}; did you want a test-level query \
-(append ${kPathSeparator}${kWildcard}) or case-level query (append ${kBigSeparator}${kWildcard})?`);
+    assert(
+      testPathHasWildcard,
+      `Test-level query without wildcard ${kWildcard}; did you want a test-level query \
+(append ${kPathSeparator}${kWildcard}) or case-level query (append ${kBigSeparator}${kWildcard})?`
+    );
+
     assert(file.length > 0, 'File part of test-level query was empty (::)');
     return new TestQueryMultiTest(suite, file, test);
-  } // Query is case-level
+  }
 
-
+  // Query is case-level
   assert(!testPathHasWildcard, `Wildcard ${kWildcard} must be at the end of the query string`);
-  const {
-    parts: paramsParts,
-    wildcard: paramsHasWildcard
-  } = parseBigPart(paramsString, kParamSeparator);
-  assert(test.length > 0, 'Test part of case-level query was empty (::)');
-  const params = {};
 
+  const { parts: paramsParts, wildcard: paramsHasWildcard } = parseBigPart(
+    paramsString,
+    kParamSeparator
+  );
+
+  assert(test.length > 0, 'Test part of case-level query was empty (::)');
+
+  const params = {};
   for (const paramPart of paramsParts) {
     const [k, v] = parseSingleParam(paramPart);
     assert(validQueryPart.test(k), 'param key names must match ' + validQueryPart);
     params[k] = v;
   }
-
   if (paramsHasWildcard) {
     return new TestQueryMultiCase(suite, file, test, params);
   } else {
     return new TestQuerySingleCase(suite, file, test, params);
   }
-} // webgpu:a,b,* or webgpu:a,b,c:*
+}
 
-
+// webgpu:a,b,* or webgpu:a,b,c:*
 const kExampleQueries = `\
 webgpu${kBigSeparator}a${kPathSeparator}b${kPathSeparator}${kWildcard} or \
 webgpu${kBigSeparator}a${kPathSeparator}b${kPathSeparator}c${kBigSeparator}${kWildcard}`;
 
 function parseBigPart(s, separator) {
   if (s === '') {
-    return {
-      parts: [],
-      wildcard: false
-    };
+    return { parts: [], wildcard: false };
   }
-
   const parts = s.split(separator);
-  let endsWithWildcard = false;
 
+  let endsWithWildcard = false;
   for (const [i, part] of parts.entries()) {
     if (i === parts.length - 1) {
       endsWithWildcard = part === kWildcard;
     }
-
-    assert(part.indexOf(kWildcard) === -1 || endsWithWildcard, `Wildcard ${kWildcard} must be complete last part of a path (e.g. ${kExampleQueries})`);
+    assert(
+      part.indexOf(kWildcard) === -1 || endsWithWildcard,
+      `Wildcard ${kWildcard} must be complete last part of a path (e.g. ${kExampleQueries})`
+    );
   }
-
   if (endsWithWildcard) {
     // Remove the last element of the array (which is just the wildcard).
     parts.length = parts.length - 1;
   }
-
-  return {
-    parts,
-    wildcard: endsWithWildcard
-  };
+  return { parts, wildcard: endsWithWildcard };
 }
 
 function parseSingleParam(paramSubstring) {
@@ -116,7 +120,10 @@
 }
 
 function parseSingleParamValue(s) {
-  assert(!badParamValueChars.test(s), `param value must not match ${badParamValueChars} - was ${s}`);
-  return s === 'undefined' ? undefined : JSON.parse(s);
+  assert(
+    !badParamValueChars.test(s),
+    `param value must not match ${badParamValueChars} - was ${s}`
+  );
+
+  return parseParamValue(s);
 }
-//# sourceMappingURL=parseQuery.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/query/query.js b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/query/query.js
index 8b35224..0a97420 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/query/query.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/query/query.js
@@ -1,13 +1,23 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
-
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ function _defineProperty(obj, key, value) {
+  if (key in obj) {
+    Object.defineProperty(obj, key, {
+      value: value,
+      enumerable: true,
+      configurable: true,
+      writable: true,
+    });
+  } else {
+    obj[key] = value;
+  }
+  return obj;
+}
 import { assert } from '../util/util.js';
 import { encodeURIComponentSelectively } from './encode_selectively.js';
 import { kBigSeparator, kPathSeparator, kWildcard, kParamSeparator } from './separators.js';
 import { stringifyPublicParams } from './stringify_params.js';
+
 /**
  * Represents a test query of some level.
  *
@@ -23,14 +33,10 @@
  */
 export class TestQueryMultiFile {
   constructor(suite, file) {
-    _defineProperty(this, "level", 1);
-
-    _defineProperty(this, "isMultiFile", true);
-
-    _defineProperty(this, "suite", void 0);
-
-    _defineProperty(this, "filePathParts", void 0);
-
+    _defineProperty(this, 'level', 1);
+    _defineProperty(this, 'isMultiFile', true);
+    _defineProperty(this, 'suite', void 0);
+    _defineProperty(this, 'filePathParts', void 0);
     this.suite = suite;
     this.filePathParts = [...file];
   }
@@ -42,84 +48,80 @@
   toStringHelper() {
     return [this.suite, [...this.filePathParts, kWildcard].join(kPathSeparator)];
   }
-
 }
+
 /**
  * A multi-test test query, like `s:f:*` or `s:f:a,b,*`.
  *
  * Immutable (makes copies of constructor args).
  */
-
 export class TestQueryMultiTest extends TestQueryMultiFile {
   constructor(suite, file, test) {
     super(suite, file);
-
-    _defineProperty(this, "level", 2);
-
-    _defineProperty(this, "isMultiFile", false);
-
-    _defineProperty(this, "isMultiTest", true);
-
-    _defineProperty(this, "testPathParts", void 0);
-
+    _defineProperty(this, 'level', 2);
+    _defineProperty(this, 'isMultiFile', false);
+    _defineProperty(this, 'isMultiTest', true);
+    _defineProperty(this, 'testPathParts', void 0);
     assert(file.length > 0, 'multi-test (or finer) query must have file-path');
     this.testPathParts = [...test];
   }
 
   toStringHelper() {
-    return [this.suite, this.filePathParts.join(kPathSeparator), [...this.testPathParts, kWildcard].join(kPathSeparator)];
+    return [
+      this.suite,
+      this.filePathParts.join(kPathSeparator),
+      [...this.testPathParts, kWildcard].join(kPathSeparator),
+    ];
   }
-
 }
+
 /**
  * A multi-case test query, like `s:f:t:*` or `s:f:t:a,b,*`.
  *
  * Immutable (makes copies of constructor args), except for param values
  * (which aren't normally supposed to change; they're marked readonly in CaseParams).
  */
-
 export class TestQueryMultiCase extends TestQueryMultiTest {
   constructor(suite, file, test, params) {
     super(suite, file, test);
-
-    _defineProperty(this, "level", 3);
-
-    _defineProperty(this, "isMultiTest", false);
-
-    _defineProperty(this, "isMultiCase", true);
-
-    _defineProperty(this, "params", void 0);
-
+    _defineProperty(this, 'level', 3);
+    _defineProperty(this, 'isMultiTest', false);
+    _defineProperty(this, 'isMultiCase', true);
+    _defineProperty(this, 'params', void 0);
     assert(test.length > 0, 'multi-case (or finer) query must have test-path');
-    this.params = { ...params
-    };
+    this.params = { ...params };
   }
 
   toStringHelper() {
     const paramsParts = stringifyPublicParams(this.params);
-    return [this.suite, this.filePathParts.join(kPathSeparator), this.testPathParts.join(kPathSeparator), [...paramsParts, kWildcard].join(kParamSeparator)];
+    return [
+      this.suite,
+      this.filePathParts.join(kPathSeparator),
+      this.testPathParts.join(kPathSeparator),
+      [...paramsParts, kWildcard].join(kParamSeparator),
+    ];
   }
-
 }
+
 /**
  * A multi-case test query, like `s:f:t:` or `s:f:t:a=1,b=1`.
  *
  * Immutable (makes copies of constructor args).
  */
-
 export class TestQuerySingleCase extends TestQueryMultiCase {
   constructor(...args) {
     super(...args);
-
-    _defineProperty(this, "level", 4);
-
-    _defineProperty(this, "isMultiCase", false);
+    _defineProperty(this, 'level', 4);
+    _defineProperty(this, 'isMultiCase', false);
   }
 
   toStringHelper() {
     const paramsParts = stringifyPublicParams(this.params);
-    return [this.suite, this.filePathParts.join(kPathSeparator), this.testPathParts.join(kPathSeparator), paramsParts.join(kParamSeparator)];
+    return [
+      this.suite,
+      this.filePathParts.join(kPathSeparator),
+      this.testPathParts.join(kPathSeparator),
+      paramsParts.join(kParamSeparator),
+    ];
   }
-
 }
-//# sourceMappingURL=query.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/query/separators.js b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/query/separators.js
index e378a9d..8fbaebd 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/query/separators.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/query/separators.js
@@ -1,19 +1,14 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-/** Separator between big parts: suite:file:test:case */
-export const kBigSeparator = ':';
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ /** Separator between big parts: suite:file:test:case */ export const kBigSeparator = ':';
 /** Separator between path,to,file or path,to,test */
-
 export const kPathSeparator = ',';
+
 /** Separator between k=v;k=v */
-
 export const kParamSeparator = ';';
+
 /** Separator between key and value in k=v */
-
 export const kParamKVSeparator = '=';
-/** Final wildcard, if query is not single-case */
 
+/** Final wildcard, if query is not single-case */
 export const kWildcard = '*';
-//# sourceMappingURL=separators.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/query/stringify_params.js b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/query/stringify_params.js
index b5503ff..b9c9cde 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/query/stringify_params.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/query/stringify_params.js
@@ -1,20 +1,27 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-import { badParamValueChars, paramKeyIsPublic } from '../params_utils.js';
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ import { badParamValueChars, paramKeyIsPublic } from '../params_utils.js';
 import { assert } from '../util/util.js';
+
+import { stringifyParamValue } from './json_param_value.js';
 import { kParamKVSeparator } from './separators.js';
+
 export function stringifyPublicParams(p) {
-  return Object.keys(p).filter(k => paramKeyIsPublic(k)).map(k => stringifySingleParam(k, p[k]));
+  return Object.keys(p)
+    .filter(k => paramKeyIsPublic(k))
+    .map(k => stringifySingleParam(k, p[k]));
 }
+
 export function stringifySingleParam(k, v) {
   return `${k}${kParamKVSeparator}${stringifySingleParamValue(v)}`;
 }
 
 function stringifySingleParamValue(v) {
-  const s = v === undefined ? 'undefined' : JSON.stringify(v);
-  assert(!badParamValueChars.test(s), `JSON.stringified param value must not match ${badParamValueChars} - was ${s}`);
+  const s = stringifyParamValue(v);
+  assert(
+    !badParamValueChars.test(s),
+    `JSON.stringified param value must not match ${badParamValueChars} - was ${s}`
+  );
+
   return s;
 }
-//# sourceMappingURL=stringify_params.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/query/validQueryPart.js b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/query/validQueryPart.js
index 3c051af..ca21627 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/query/validQueryPart.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/query/validQueryPart.js
@@ -1,7 +1,3 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-/** Applies to group parts, test parts, params keys. */
-export const validQueryPart = /^[a-zA-Z0-9_]+$/;
-//# sourceMappingURL=validQueryPart.js.map
\ No newline at end of file
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ /** Applies to group parts, test parts, params keys. */ export const validQueryPart = /^[a-zA-Z0-9_]+$/;
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/test_group.js b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/test_group.js
index 5ced9cd..4515bca 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/test_group.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/test_group.js
@@ -1,17 +1,29 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
-
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ function _defineProperty(obj, key, value) {
+  if (key in obj) {
+    Object.defineProperty(obj, key, {
+      value: value,
+      enumerable: true,
+      configurable: true,
+      writable: true,
+    });
+  } else {
+    obj[key] = value;
+  }
+  return obj;
+}
 import { extractPublicParams, publicParamsEquals } from './params_utils.js';
 import { kPathSeparator } from './query/separators.js';
 import { stringifyPublicParams } from './query/stringify_params.js';
 import { validQueryPart } from './query/validQueryPart.js';
 import { assert } from './util/util.js';
+
 export function makeTestGroup(fixture) {
   return new TestGroup(fixture);
-} // Interface for running tests
+}
+
+// Interface for running tests
 
 export function makeTestGroupForUnitTesting(fixture) {
   return new TestGroup(fixture);
@@ -19,12 +31,9 @@
 
 class TestGroup {
   constructor(fixture) {
-    _defineProperty(this, "fixture", void 0);
-
-    _defineProperty(this, "seen", new Set());
-
-    _defineProperty(this, "tests", []);
-
+    _defineProperty(this, 'fixture', void 0);
+    _defineProperty(this, 'seen', new Set());
+    _defineProperty(this, 'tests', []);
     this.fixture = fixture;
   }
 
@@ -35,18 +44,23 @@
   }
 
   checkName(name) {
-    assert( // Shouldn't happen due to the rule above. Just makes sure that treated
-    // unencoded strings as encoded strings is OK.
-    name === decodeURIComponent(name), `Not decodeURIComponent-idempotent: ${name} !== ${decodeURIComponent(name)}`);
+    assert(
+      // Shouldn't happen due to the rule above. Just makes sure that treated
+      // unencoded strings as encoded strings is OK.
+      name === decodeURIComponent(name),
+      `Not decodeURIComponent-idempotent: ${name} !== ${decodeURIComponent(name)}`
+    );
+
     assert(!this.seen.has(name), `Duplicate test name: ${name}`);
+
     this.seen.add(name);
-  } // TODO: This could take a fixture, too, to override the one for the group.
+  }
 
-
+  // TODO: This could take a fixture, too, to override the one for the group.
   test(name) {
     this.checkName(name);
-    const parts = name.split(kPathSeparator);
 
+    const parts = name.split(kPathSeparator);
     for (const p of parts) {
       assert(validQueryPart.test(p), `Invalid test name part ${p}; must match ${validQueryPart}`);
     }
@@ -61,19 +75,14 @@
       test.checkCaseNamesAndDuplicates();
     }
   }
-
 }
 
 class TestBuilder {
   constructor(testPath, fixture) {
-    _defineProperty(this, "testPath", void 0);
-
-    _defineProperty(this, "fixture", void 0);
-
-    _defineProperty(this, "testFn", void 0);
-
-    _defineProperty(this, "cases", undefined);
-
+    _defineProperty(this, 'testPath', void 0);
+    _defineProperty(this, 'fixture', void 0);
+    _defineProperty(this, 'testFn', void 0);
+    _defineProperty(this, 'cases', undefined);
     this.testPath = testPath;
     this.fixture = fixture;
   }
@@ -85,15 +94,18 @@
   checkCaseNamesAndDuplicates() {
     if (this.cases === undefined) {
       return;
-    } // This is n^2.
+    }
 
-
+    // This is n^2.
     const seen = [];
-
     for (const testcase of this.cases) {
       // stringifyPublicParams also checks for invalid params values
       const testcaseString = stringifyPublicParams(testcase);
-      assert(!seen.some(x => publicParamsEquals(x, testcase)), `Duplicate public test case params: ${testcaseString}`);
+      assert(
+        !seen.some(x => publicParamsEquals(x, testcase)),
+        `Duplicate public test case params: ${testcaseString}`
+      );
+
       seen.push(testcase);
     }
   }
@@ -101,33 +113,25 @@
   params(casesIterable) {
     assert(this.cases === undefined, 'test case is already parameterized');
     this.cases = Array.from(casesIterable);
+
     return this;
   }
 
   *iterate() {
     assert(this.testFn !== undefined, 'No test function (.fn()) for test');
-
     for (const params of this.cases || [{}]) {
       yield new RunCaseSpecific(this.testPath, params, this.fixture, this.testFn);
     }
   }
-
 }
 
 class RunCaseSpecific {
   constructor(testPath, params, fixture, fn) {
-    _defineProperty(this, "id", void 0);
-
-    _defineProperty(this, "params", void 0);
-
-    _defineProperty(this, "fixture", void 0);
-
-    _defineProperty(this, "fn", void 0);
-
-    this.id = {
-      test: testPath,
-      params: extractPublicParams(params)
-    };
+    _defineProperty(this, 'id', void 0);
+    _defineProperty(this, 'params', void 0);
+    _defineProperty(this, 'fixture', void 0);
+    _defineProperty(this, 'fn', void 0);
+    this.id = { test: testPath, params: extractPublicParams(params) };
     this.params = params;
     this.fixture = fixture;
     this.fn = fn;
@@ -135,12 +139,12 @@
 
   async run(rec) {
     rec.start();
-
     try {
       const inst = new this.fixture(rec, this.params || {});
 
       try {
         await inst.init();
+
         await this.fn(inst);
       } finally {
         // Runs as long as constructor succeeded, even if initialization or the test failed.
@@ -153,9 +157,6 @@
       // or unexpected validation/OOM error from the GPUDevice.
       rec.threw(ex);
     }
-
     rec.finish();
   }
-
 }
-//# sourceMappingURL=test_group.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/test_suite_listing.js b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/test_suite_listing.js
index 99c9002..d387dc3e 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/test_suite_listing.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/test_suite_listing.js
@@ -1,4 +1,3 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-//# sourceMappingURL=test_suite_listing.js.map
\ No newline at end of file
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/tree.js b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/tree.js
index 69dfac2..87ff0d22 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/tree.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/tree.js
@@ -1,14 +1,31 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
-
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ function _defineProperty(obj, key, value) {
+  if (key in obj) {
+    Object.defineProperty(obj, key, {
+      value: value,
+      enumerable: true,
+      configurable: true,
+      writable: true,
+    });
+  } else {
+    obj[key] = value;
+  }
+  return obj;
+}
 import { compareQueries, Ordering } from './query/compare.js';
-import { TestQueryMultiCase, TestQuerySingleCase, TestQueryMultiFile, TestQueryMultiTest } from './query/query.js';
+import {
+  TestQueryMultiCase,
+  TestQuerySingleCase,
+  TestQueryMultiFile,
+  TestQueryMultiTest,
+} from './query/query.js';
 import { kBigSeparator, kWildcard, kPathSeparator, kParamSeparator } from './query/separators.js';
 import { stringifySingleParam } from './query/stringify_params.js';
-import { assert } from './util/util.js'; // `loadTreeForQuery()` loads a TestTree for a given queryToLoad.
+
+import { assert } from './util/util.js';
+
+// `loadTreeForQuery()` loads a TestTree for a given queryToLoad.
 // The resulting tree is a linked-list all the way from `suite:*` to queryToLoad,
 // and under queryToLoad is a tree containing every case matched by queryToLoad.
 //
@@ -34,8 +51,7 @@
 
 export class TestTree {
   constructor(root) {
-    _defineProperty(this, "root", void 0);
-
+    _defineProperty(this, 'root', void 0);
     this.root = root;
   }
 
@@ -46,6 +62,7 @@
   iterateLeaves() {
     return TestTree.iterateSubtreeLeaves(this.root);
   }
+
   /**
    * If a parent and its child are at different levels, then
    * generally the parent has only one child, i.e.:
@@ -54,8 +71,6 @@
    *   a,* { a,b:* { ... } }
    * which is less needlessly verbose when displaying the tree in the standalone runner.
    */
-
-
   dissolveLevelBoundaries() {
     const newRoot = dissolveLevelBoundaries(this.root);
     assert(newRoot === this.root);
@@ -88,7 +103,6 @@
   static subtreeToString(name, tree, indent) {
     const collapsible = 'run' in tree ? '>' : tree.collapsible ? '+' : '-';
     let s = indent + `${collapsible} ${JSON.stringify(name)} => ${tree.query}`;
-
     if ('children' in tree) {
       if (tree.description !== undefined) {
         s += `\n${indent}  | ${JSON.stringify(tree.description)}`;
@@ -98,35 +112,36 @@
         s += '\n' + TestTree.subtreeToString(name, child, indent + '  ');
       }
     }
-
     return s;
   }
+}
 
-} // TODO: Consider having subqueriesToExpand actually impact the depth-order of params in the tree.
-
+// TODO: Consider having subqueriesToExpand actually impact the depth-order of params in the tree.
 export async function loadTreeForQuery(loader, queryToLoad, subqueriesToExpand) {
   const suite = queryToLoad.suite;
   const specs = await loader.listing(suite);
+
   const subqueriesToExpandEntries = Array.from(subqueriesToExpand.entries());
   const seenSubqueriesToExpand = new Array(subqueriesToExpand.length);
   seenSubqueriesToExpand.fill(false);
 
-  const isCollapsible = subquery => subqueriesToExpandEntries.every(([i, toExpand]) => {
-    const ordering = compareQueries(toExpand, subquery); // If toExpand == subquery, no expansion is needed (but it's still "seen").
+  const isCollapsible = subquery =>
+    subqueriesToExpandEntries.every(([i, toExpand]) => {
+      const ordering = compareQueries(toExpand, subquery);
 
-    if (ordering === Ordering.Equal) seenSubqueriesToExpand[i] = true;
-    return ordering !== Ordering.StrictSubset;
-  }); // L0 = suite-level, e.g. suite:*
+      // If toExpand == subquery, no expansion is needed (but it's still "seen").
+      if (ordering === Ordering.Equal) seenSubqueriesToExpand[i] = true;
+      return ordering !== Ordering.StrictSubset;
+    });
+
+  // L0 = suite-level, e.g. suite:*
   // L1 =  file-level, e.g. suite:a,b:*
   // L2 =  test-level, e.g. suite:a,b:c,d:*
   // L3 =  case-level, e.g. suite:a,b:c,d:
-
-
-  let foundCase = false; // L0 is suite:*
-
+  let foundCase = false;
+  // L0 is suite:*
   const subtreeL0 = makeTreeForSuite(suite);
   isCollapsible(subtreeL0.query); // mark seenSubqueriesToExpand
-
   for (const entry of specs) {
     if (entry.file.length === 0 && 'readme' in entry) {
       // Suite-level readme.
@@ -138,7 +153,6 @@
     {
       const queryL1 = new TestQueryMultiFile(suite, entry.file);
       const orderingL1 = compareQueries(queryL1, queryToLoad);
-
       if (orderingL1 === Ordering.Unordered) {
         // File path is not matched by this query.
         continue;
@@ -148,47 +162,55 @@
     if ('readme' in entry) {
       // Entry is a README that is an ancestor or descendant of the query.
       // (It's included for display in the standalone runner.)
+
       // readmeSubtree is suite:a,b,*
       // (This is always going to dedup with a file path, if there are any test spec files under
       // the directory that has the README).
       const readmeSubtree = addSubtreeForDirPath(subtreeL0, entry.file);
+
       assert(readmeSubtree.description === undefined);
       readmeSubtree.description = entry.readme.trim();
       continue;
-    } // Entry is a spec file.
-
+    }
+    // Entry is a spec file.
 
     const spec = await loader.importSpecFile(queryToLoad.suite, entry.file);
-    const description = spec.description.trim(); // subtreeL1 is suite:a,b:*
+    const description = spec.description.trim();
+    // subtreeL1 is suite:a,b:*
+    const subtreeL1 = addSubtreeForFilePath(subtreeL0, entry.file, description, isCollapsible);
 
-    const subtreeL1 = addSubtreeForFilePath(subtreeL0, entry.file, description, isCollapsible); // TODO: If tree generation gets too slow, avoid actually iterating the cases in a file
+    // TODO: If tree generation gets too slow, avoid actually iterating the cases in a file
     // if there's no need to (based on the subqueriesToExpand).
-
     for (const t of spec.g.iterate()) {
       {
         const queryL3 = new TestQuerySingleCase(suite, entry.file, t.id.test, t.id.params);
         const orderingL3 = compareQueries(queryL3, queryToLoad);
-
         if (orderingL3 === Ordering.Unordered || orderingL3 === Ordering.StrictSuperset) {
           // Case is not matched by this query.
           continue;
         }
-      } // subtreeL2 is suite:a,b:c,d:*
+      }
 
-      const subtreeL2 = addSubtreeForTestPath(subtreeL1, t.id.test, isCollapsible); // Leaf for case is suite:a,b:c,d:x=1;y=2
+      // subtreeL2 is suite:a,b:c,d:*
+      const subtreeL2 = addSubtreeForTestPath(subtreeL1, t.id.test, isCollapsible);
 
+      // Leaf for case is suite:a,b:c,d:x=1;y=2
       addLeafForCase(subtreeL2, t, isCollapsible);
+
       foundCase = true;
     }
   }
 
   for (const [i, sq] of subqueriesToExpandEntries) {
     const seen = seenSubqueriesToExpand[i];
-    assert(seen, `subqueriesToExpand entry did not match anything \
-(can happen due to overlap with another subquery): ${sq.toString()}`);
+    assert(
+      seen,
+      `subqueriesToExpand entry did not match anything \
+(can happen due to overlap with another subquery): ${sq.toString()}`
+    );
   }
-
   assert(foundCase, 'Query does not match any cases');
+
   return new TestTree(subtreeL0);
 }
 
@@ -197,34 +219,29 @@
     readableRelativeName: suite + kBigSeparator,
     query: new TestQueryMultiFile(suite, []),
     children: new Map(),
-    collapsible: false
+    collapsible: false,
   };
 }
 
 function addSubtreeForDirPath(tree, file) {
-  const subqueryFile = []; // To start, tree is suite:*
+  const subqueryFile = [];
+  // To start, tree is suite:*
   // This loop goes from that -> suite:a,* -> suite:a,b,*
-
   for (const part of file) {
     subqueryFile.push(part);
     tree = getOrInsertSubtree(part, tree, () => {
       const query = new TestQueryMultiFile(tree.query.suite, subqueryFile);
-      return {
-        readableRelativeName: part + kPathSeparator + kWildcard,
-        query,
-        collapsible: false
-      };
+      return { readableRelativeName: part + kPathSeparator + kWildcard, query, collapsible: false };
     });
   }
-
   return tree;
 }
 
 function addSubtreeForFilePath(tree, file, description, checkCollapsible) {
   // To start, tree is suite:*
   // This goes from that -> suite:a,* -> suite:a,b,*
-  tree = addSubtreeForDirPath(tree, file); // This goes from that -> suite:a,b:*
-
+  tree = addSubtreeForDirPath(tree, file);
+  // This goes from that -> suite:a,b:*
   const subtree = getOrInsertSubtree('', tree, () => {
     const query = new TestQueryMultiTest(tree.query.suite, tree.query.filePathParts, []);
     assert(file.length > 0, 'file path is empty');
@@ -232,37 +249,47 @@
       readableRelativeName: file[file.length - 1] + kBigSeparator + kWildcard,
       query,
       description,
-      collapsible: checkCollapsible(query)
+      collapsible: checkCollapsible(query),
     };
   });
   return subtree;
 }
 
 function addSubtreeForTestPath(tree, test, isCollapsible) {
-  const subqueryTest = []; // To start, tree is suite:a,b:*
+  const subqueryTest = [];
+  // To start, tree is suite:a,b:*
   // This loop goes from that -> suite:a,b:c,* -> suite:a,b:c,d,*
-
   for (const part of test) {
     subqueryTest.push(part);
     tree = getOrInsertSubtree(part, tree, () => {
-      const query = new TestQueryMultiTest(tree.query.suite, tree.query.filePathParts, subqueryTest);
+      const query = new TestQueryMultiTest(
+        tree.query.suite,
+        tree.query.filePathParts,
+        subqueryTest
+      );
+
       return {
         readableRelativeName: part + kPathSeparator + kWildcard,
         query,
-        collapsible: isCollapsible(query)
+        collapsible: isCollapsible(query),
       };
     });
-  } // This goes from that -> suite:a,b:c,d:*
-
-
+  }
+  // This goes from that -> suite:a,b:c,d:*
   return getOrInsertSubtree('', tree, () => {
-    const query = new TestQueryMultiCase(tree.query.suite, tree.query.filePathParts, subqueryTest, {});
+    const query = new TestQueryMultiCase(
+      tree.query.suite,
+      tree.query.filePathParts,
+      subqueryTest,
+      {}
+    );
+
     assert(subqueryTest.length > 0, 'subqueryTest is empty');
     return {
       readableRelativeName: subqueryTest[subqueryTest.length - 1] + kBigSeparator + kWildcard,
       kWildcard,
       query,
-      collapsible: isCollapsible(query)
+      collapsible: isCollapsible(query),
     };
   });
 }
@@ -270,44 +297,52 @@
 function addLeafForCase(tree, t, checkCollapsible) {
   const query = tree.query;
   let name = '';
-  const subqueryParams = {}; // To start, tree is suite:a,b:c,d:*
-  // This loop goes from that -> suite:a,b:c,d:x=1;* -> suite:a,b:c,d:x=1;y=2;*
+  const subqueryParams = {};
 
+  // To start, tree is suite:a,b:c,d:*
+  // This loop goes from that -> suite:a,b:c,d:x=1;* -> suite:a,b:c,d:x=1;y=2;*
   for (const [k, v] of Object.entries(t.id.params)) {
     name = stringifySingleParam(k, v);
     subqueryParams[k] = v;
+
     tree = getOrInsertSubtree(name, tree, () => {
-      const subquery = new TestQueryMultiCase(query.suite, query.filePathParts, query.testPathParts, subqueryParams);
+      const subquery = new TestQueryMultiCase(
+        query.suite,
+        query.filePathParts,
+        query.testPathParts,
+        subqueryParams
+      );
+
       return {
         readableRelativeName: name + kParamSeparator + kWildcard,
         query: subquery,
-        collapsible: checkCollapsible(subquery)
+        collapsible: checkCollapsible(subquery),
       };
     });
-  } // This goes from that -> suite:a,b:c,d:x=1;y=2
+  }
 
+  // This goes from that -> suite:a,b:c,d:x=1;y=2
+  const subquery = new TestQuerySingleCase(
+    query.suite,
+    query.filePathParts,
+    query.testPathParts,
+    subqueryParams
+  );
 
-  const subquery = new TestQuerySingleCase(query.suite, query.filePathParts, query.testPathParts, subqueryParams);
   checkCollapsible(subquery); // mark seenSubqueriesToExpand
-
   insertLeaf(tree, subquery, t);
 }
 
 function getOrInsertSubtree(key, parent, createSubtree) {
   let v;
   const child = parent.children.get(key);
-
   if (child !== undefined) {
     assert('children' in child); // Make sure cached subtree is not actually a leaf
-
     v = child;
   } else {
-    v = { ...createSubtree(),
-      children: new Map()
-    };
+    v = { ...createSubtree(), children: new Map() };
     parent.children.set(key, v);
   }
-
   return v;
 }
 
@@ -316,8 +351,9 @@
   const leaf = {
     readableRelativeName: readableNameForCase(query),
     query,
-    run: rec => t.run(rec)
+    run: rec => t.run(rec),
   };
+
   assert(!parent.children.has(key));
   parent.children.set(key, leaf);
 }
@@ -329,6 +365,7 @@
       for (const [, child] of tree.children) {
         if (child.query.level > tree.query.level) {
           const newtree = dissolveLevelBoundaries(child);
+
           return newtree;
         }
       }
@@ -336,21 +373,17 @@
 
     for (const [k, child] of tree.children) {
       const newChild = dissolveLevelBoundaries(child);
-
       if (newChild !== child) {
         tree.children.set(k, newChild);
       }
     }
   }
-
   return tree;
 }
+
 /** Generate a readable relative name for a case (used in standalone). */
-
-
 function readableNameForCase(query) {
   const paramsKeys = Object.keys(query.params);
-
   if (paramsKeys.length === 0) {
     return query.testPathParts[query.testPathParts.length - 1] + kBigSeparator;
   } else {
@@ -358,4 +391,3 @@
     return stringifySingleParam(lastKey, query.params[lastKey]);
   }
 }
-//# sourceMappingURL=tree.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/util/async_mutex.js b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/util/async_mutex.js
index cb900605b..da461048 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/util/async_mutex.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/util/async_mutex.js
@@ -1,12 +1,21 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
-
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ function _defineProperty(obj, key, value) {
+  if (key in obj) {
+    Object.defineProperty(obj, key, {
+      value: value,
+      enumerable: true,
+      configurable: true,
+      writable: true,
+    });
+  } else {
+    obj[key] = value;
+  }
+  return obj;
+}
 export class AsyncMutex {
   constructor() {
-    _defineProperty(this, "newestQueueItem", void 0);
+    _defineProperty(this, 'newestQueueItem', void 0);
   }
 
   // Run an async function with a lock on this mutex.
@@ -18,15 +27,12 @@
       if (this.newestQueueItem) {
         await this.newestQueueItem;
       }
-
       return fn();
-    })(); // Push the newly-created Promise onto the queue by replacing the old "newest" item.
+    })();
 
-
-    this.newestQueueItem = p; // And return so the caller can wait on the result.
-
+    // Push the newly-created Promise onto the queue by replacing the old "newest" item.
+    this.newestQueueItem = p;
+    // And return so the caller can wait on the result.
     return p;
   }
-
 }
-//# sourceMappingURL=async_mutex.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/util/collect_garbage.js b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/util/collect_garbage.js
index d4b5bb1..d18982a2 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/util/collect_garbage.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/util/collect_garbage.js
@@ -1,11 +1,9 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ import { resolveOnTimeout } from './util.js';
 
-import { resolveOnTimeout } from './util.js';
 export async function attemptGarbageCollection() {
   const w = self;
-
   if (w.GCController) {
     w.GCController.collect();
     return;
@@ -17,7 +15,9 @@
   }
 
   try {
-    w.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowUtils).garbageCollect();
+    w.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+      .getInterface(Components.interfaces.nsIDOMWindowUtils)
+      .garbageCollect();
     return;
   } catch (e) {}
 
@@ -32,22 +32,16 @@
   }
 
   let i;
-
   function gcRec(n) {
     if (n < 1) return;
-    let temp = {
-      i: 'ab' + i + i / 100000
-    };
+    let temp = { i: 'ab' + i + i / 100000 };
     temp = temp + 'foo';
     temp; // dummy use of unused variable
-
     gcRec(n - 1);
   }
-
   for (i = 0; i < 1000; i++) {
     gcRec(10);
   }
 
   return resolveOnTimeout(35); // Let the event loop run a few frames in case it helps.
 }
-//# sourceMappingURL=collect_garbage.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/util/stack.js b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/util/stack.js
index 99653f8..00d08642 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/util/stack.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/util/stack.js
@@ -1,26 +1,22 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-// Returns the stack trace of an Error, but without the extra boilerplate at the bottom
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ // Returns the stack trace of an Error, but without the extra boilerplate at the bottom
 // (e.g. RunCaseSpecific, processTicksAndRejections, etc.), for logging.
 export function extractImportantStackTrace(e) {
   if (!e.stack) {
     return '';
   }
-
   const lines = e.stack.split('\n');
-
   for (let i = lines.length - 1; i >= 0; --i) {
     const line = lines[i];
-
     if (line.indexOf('.spec.') !== -1) {
       return lines.slice(0, i + 1).join('\n');
     }
   }
-
   return e.stack;
-} // *** Examples ***
+}
+
+// *** Examples ***
 //
 // Node fail()
 // > Error:
@@ -80,4 +76,3 @@
 // x     at async RunCaseSpecific.run (http://localhost:8080/out/framework/test_group.js:119:7)
 // x     at async runCase (http://localhost:8080/out/runtime/standalone.js:37:17)
 // x     at async http://localhost:8080/out/runtime/standalone.js:102:7
-//# sourceMappingURL=stack.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/util/timeout.js b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/util/timeout.js
index e565a51..db1e5cf 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/util/timeout.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/util/timeout.js
@@ -1,6 +1,3 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const timeout = typeof step_timeout !== 'undefined' ? step_timeout : setTimeout;
-//# sourceMappingURL=timeout.js.map
\ No newline at end of file
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const timeout = typeof step_timeout !== 'undefined' ? step_timeout : setTimeout;
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/util/util.js b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/util/util.js
index b643799..e747904 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/util/util.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/util/util.js
@@ -1,28 +1,32 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-import { timeout } from './timeout.js';
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ import { timeout } from './timeout.js';
 export function assert(condition, msg) {
   if (!condition) {
     throw new Error(msg && (typeof msg === 'string' ? msg : msg()));
   }
 }
+
 export async function assertReject(p, msg) {
   try {
     await p;
     unreachable(msg);
-  } catch (ex) {// Assertion OK
+  } catch (ex) {
+    // Assertion OK
   }
 }
+
 export function unreachable(msg) {
   throw new Error(msg);
-} // performance.now() is available in all browsers, but not in scope by default in Node.
+}
 
+// performance.now() is available in all browsers, but not in scope by default in Node.
 const perf = typeof performance !== 'undefined' ? performance : require('perf_hooks').performance;
+
 export function now() {
   return perf.now();
 }
+
 export function resolveOnTimeout(ms) {
   return new Promise(resolve => {
     timeout(() => {
@@ -30,7 +34,9 @@
     }, ms);
   });
 }
+
 export class PromiseTimeoutError extends Error {}
+
 export function rejectOnTimeout(ms, msg) {
   return new Promise((_resolve, reject) => {
     timeout(() => {
@@ -38,9 +44,11 @@
     }, ms);
   });
 }
+
 export function raceWithRejectOnTimeout(p, ms, msg) {
   return Promise.race([p, rejectOnTimeout(ms, msg)]);
 }
+
 export function objectEquals(x, y) {
   if (typeof x !== 'object' || typeof y !== 'object') return x === y;
   if (x === null || y === null) return x === y;
@@ -52,12 +60,13 @@
   if (x instanceof Date) return false;
   if (!(x instanceof Object)) return false;
   if (!(y instanceof Object)) return false;
+
   const x1 = x;
   const y1 = y;
   const p = Object.keys(x);
   return Object.keys(y).every(i => p.indexOf(i) !== -1) && p.every(i => objectEquals(x1[i], y1[i]));
 }
+
 export function range(n, fn) {
   return [...new Array(n)].map((_, i) => fn(i));
 }
-//# sourceMappingURL=util.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/version.js b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/version.js
index 4232e52..4ff269d 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/version.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/common/framework/version.js
@@ -1,3 +1,3 @@
 // AUTO-GENERATED - DO NOT EDIT. See tools/gen_version.
 
-export const version = '6371fe6f1acf4b614155acfb468dec4c55c50d27';
+export const version = 'a60596ec57527c989f1beaa37fc4bf1aa22bf084';
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/common/runtime/helper/options.js b/third_party/blink/web_tests/wpt_internal/webgpu/common/runtime/helper/options.js
index 1a90bead..e70671a5 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/common/runtime/helper/options.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/common/runtime/helper/options.js
@@ -1,10 +1,7 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-const url = new URL(window.location.toString());
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ const url = new URL(window.location.toString());
 export function optionEnabled(opt) {
   const val = url.searchParams.get(opt);
   return val !== null && val !== '0';
 }
-//# sourceMappingURL=options.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/common/runtime/helper/test_worker-worker.js b/third_party/blink/web_tests/wpt_internal/webgpu/common/runtime/helper/test_worker-worker.js
index a02c98c2b..2402c6c 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/common/runtime/helper/test_worker-worker.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/common/runtime/helper/test_worker-worker.js
@@ -1,26 +1,26 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-import { DefaultTestFileLoader } from '../../framework/file_loader.js';
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ import { DefaultTestFileLoader } from '../../framework/file_loader.js';
 import { Logger } from '../../framework/logging/logger.js';
 import { parseQuery } from '../../framework/query/parseQuery.js';
 import { assert } from '../../framework/util/util.js';
+
 // should be DedicatedWorkerGlobalScope
+
 const loader = new DefaultTestFileLoader();
 
 self.onmessage = async ev => {
   const query = ev.data.query;
   const debug = ev.data.debug;
+
   const log = new Logger(debug);
+
   const testcases = Array.from(await loader.loadCases(parseQuery(query)));
   assert(testcases.length === 1, 'worker query resulted in != 1 cases');
+
   const testcase = testcases[0];
   const [rec, result] = log.record(testcase.query.toString());
   await testcase.run(rec);
-  self.postMessage({
-    query,
-    result
-  });
+
+  self.postMessage({ query, result });
 };
-//# sourceMappingURL=test_worker-worker.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/common/runtime/helper/test_worker.js b/third_party/blink/web_tests/wpt_internal/webgpu/common/runtime/helper/test_worker.js
index 7e8b9a4..f844920b5 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/common/runtime/helper/test_worker.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/common/runtime/helper/test_worker.js
@@ -1,51 +1,51 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
-
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ function _defineProperty(obj, key, value) {
+  if (key in obj) {
+    Object.defineProperty(obj, key, {
+      value: value,
+      enumerable: true,
+      configurable: true,
+      writable: true,
+    });
+  } else {
+    obj[key] = value;
+  }
+  return obj;
+}
 import { LogMessageWithStack } from '../../framework/logging/log_message.js';
+
 export class TestWorker {
   constructor(debug) {
-    _defineProperty(this, "debug", void 0);
-
-    _defineProperty(this, "worker", void 0);
-
-    _defineProperty(this, "resolvers", new Map());
-
+    _defineProperty(this, 'debug', void 0);
+    _defineProperty(this, 'worker', void 0);
+    _defineProperty(this, 'resolvers', new Map());
     this.debug = debug;
+
     const selfPath = import.meta.url;
     const selfPathDir = selfPath.substring(0, selfPath.lastIndexOf('/'));
     const workerPath = selfPathDir + '/test_worker-worker.js';
-    this.worker = new Worker(workerPath, {
-      type: 'module'
-    });
-
+    this.worker = new Worker(workerPath, { type: 'module' });
     this.worker.onmessage = ev => {
       const query = ev.data.query;
       const result = ev.data.result;
-
       if (result.logs) {
         for (const l of result.logs) {
           Object.setPrototypeOf(l, LogMessageWithStack.prototype);
         }
       }
+      this.resolvers.get(query)(result);
 
-      this.resolvers.get(query)(result); // TODO(kainino0x): update the Logger with this result (or don't have a logger and update the
+      // TODO(kainino0x): update the Logger with this result (or don't have a logger and update the
       // entire results JSON somehow at some point).
     };
   }
 
   async run(rec, query) {
-    this.worker.postMessage({
-      query,
-      debug: this.debug
-    });
+    this.worker.postMessage({ query, debug: this.debug });
     const workerResult = await new Promise(resolve => {
       this.resolvers.set(query, resolve);
     });
     rec.injectResult(workerResult);
   }
-
 }
-//# sourceMappingURL=test_worker.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/common/runtime/wpt.js b/third_party/blink/web_tests/wpt_internal/webgpu/common/runtime/wpt.js
index dc38b77..5749159 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/common/runtime/wpt.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/common/runtime/wpt.js
@@ -1,12 +1,12 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-import { DefaultTestFileLoader } from '../framework/file_loader.js';
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ import { DefaultTestFileLoader } from '../framework/file_loader.js';
 import { Logger } from '../framework/logging/logger.js';
 import { parseQuery } from '../framework/query/parseQuery.js';
+
 import { AsyncMutex } from '../framework/util/async_mutex.js';
 import { assert } from '../framework/util/util.js';
+
 import { optionEnabled } from './helper/options.js';
 import { TestWorker } from './helper/test_worker.js';
 
@@ -15,23 +15,23 @@
   const qs = new URLSearchParams(window.location.search).getAll('q');
   assert(qs.length === 1, 'currently, there must be exactly one ?q=');
   const testcases = await loader.loadCases(parseQuery(qs[0]));
+
   await addWPTTests(testcases);
-})(); // Note: `async_test`s must ALL be added within the same task. This function *must not* be async.
+})();
 
-
+// Note: `async_test`s must ALL be added within the same task. This function *must not* be async.
 function addWPTTests(testcases) {
   const worker = optionEnabled('worker') ? new TestWorker(false) : undefined;
+
   const log = new Logger(false);
   const mutex = new AsyncMutex();
   const running = [];
 
   for (const testcase of testcases) {
     const name = testcase.query.toString();
-
     const wpt_fn = function () {
       const p = mutex.with(async () => {
         const [rec, res] = log.record(name);
-
         if (worker) {
           await worker.run(rec, name);
         } else {
@@ -46,6 +46,7 @@
         });
         this.done();
       });
+
       running.push(p);
       return p;
     };
@@ -55,4 +56,3 @@
 
   return Promise.all(running).then(() => log);
 }
-//# sourceMappingURL=wpt.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/cts.html b/third_party/blink/web_tests/wpt_internal/webgpu/cts.html
index 58ae26f..09df6cd4 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/cts.html
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/cts.html
@@ -21,7 +21,6 @@
 <script type=module src=/wpt_internal/webgpu/common/runtime/wpt.js></script>
 
 <!-- Manually-selected tests to run on worker: -->
-<meta name=variant content='?worker=1&q=webgpu:api,operation,buffers,create_mapped:*'>
 <meta name=variant content='?worker=1&q=webgpu:api,operation,buffers,map:*'>
 <meta name=variant content='?worker=1&q=webgpu:api,operation,buffers,map_detach:*'>
 <meta name=variant content='?worker=1&q=webgpu:api,operation,command_buffer,basic:*'>
@@ -34,12 +33,11 @@
 
 <!-- Variant list is auto-generated below this line: -->
 
-<meta name=variant content='?q=webgpu:api,operation,buffers,create_mapped:*'>
 <meta name=variant content='?q=webgpu:api,operation,buffers,map:*'>
 <meta name=variant content='?q=webgpu:api,operation,buffers,map_detach:*'>
-<meta name=variant content='?q=webgpu:api,operation,buffers,map_oom:mapWriteAsync:'>
-<meta name=variant content='?q=webgpu:api,operation,buffers,map_oom:mapReadAsync:'>
-<meta name=variant content='?q=webgpu:api,operation,buffers,map_oom:createBufferMapped,*'>
+<meta name=variant content='?q=webgpu:api,operation,buffers,map_oom:mapAsync:*'>
+<meta name=variant content='?q=webgpu:api,operation,buffers,map_oom:mappedAtCreation:*'>
+<meta name=variant content='?q=webgpu:api,operation,buffers,map_oom:mappedAtCreation,smaller_getMappedRange:*'>
 <meta name=variant content='?q=webgpu:api,operation,command_buffer,basic:*'>
 <meta name=variant content='?q=webgpu:api,operation,command_buffer,compute,basic:*'>
 <meta name=variant content='?q=webgpu:api,operation,command_buffer,copies:*'>
@@ -47,9 +45,14 @@
 <meta name=variant content='?q=webgpu:api,operation,command_buffer,render,rendering:*'>
 <meta name=variant content='?q=webgpu:api,operation,command_buffer,render,storeop:*'>
 <meta name=variant content='?q=webgpu:api,operation,fences:*'>
+<meta name=variant content='?q=webgpu:api,operation,render_pass,storeOp:*'>
+<meta name=variant content='?q=webgpu:api,operation,render_pipeline,culling_tests:*'>
 <meta name=variant content='?q=webgpu:api,operation,resource_init,copied_texture_clear:*'>
 <meta name=variant content='?q=webgpu:api,operation,resource_init,depth_stencil_attachment_clear:*'>
 <meta name=variant content='?q=webgpu:api,operation,resource_init,sampled_texture_clear:*'>
+<meta name=variant content='?q=webgpu:api,validation,copyBufferToBuffer:*'>
+<meta name=variant content='?q=webgpu:api,validation,copy_between_linear_data_and_texture,copyBetweenLinearDataAndTexture_dataRelated:*'>
+<meta name=variant content='?q=webgpu:api,validation,copy_between_linear_data_and_texture,copyBetweenLinearDataAndTexture_textureRelated:*'>
 <meta name=variant content='?q=webgpu:api,validation,createBindGroup:binding_count_mismatch,*'>
 <meta name=variant content='?q=webgpu:api,validation,createBindGroup:binding_must_be_present_in_layout,*'>
 <meta name=variant content='?q=webgpu:api,validation,createBindGroup:buffer_binding_must_contain_exactly_one_buffer_of_its_type,*'>
@@ -58,36 +61,40 @@
 <meta name=variant content='?q=webgpu:api,validation,createBindGroup:texture_must_have_correct_dimension,*'>
 <meta name=variant content='?q=webgpu:api,validation,createBindGroup:buffer_offset_and_size_for_bind_groups_match:offset=0;size=512;*'>
 <meta name=variant content='?q=webgpu:api,validation,createBindGroup:buffer_offset_and_size_for_bind_groups_match:offset=0;size=1024;*'>
-<meta name=variant content='?q=webgpu:api,validation,createBindGroup:buffer_offset_and_size_for_bind_groups_match:offset=0;size=undefined;*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroup:buffer_offset_and_size_for_bind_groups_match:offset=0;size="_undef_";*'>
 <meta name=variant content='?q=webgpu:api,validation,createBindGroup:buffer_offset_and_size_for_bind_groups_match:offset=0;size=0'>
 <meta name=variant content='?q=webgpu:api,validation,createBindGroup:buffer_offset_and_size_for_bind_groups_match:offset=0;size=1280;*'>
 <meta name=variant content='?q=webgpu:api,validation,createBindGroup:buffer_offset_and_size_for_bind_groups_match:offset=256;size=256;*'>
 <meta name=variant content='?q=webgpu:api,validation,createBindGroup:buffer_offset_and_size_for_bind_groups_match:offset=256;size=0'>
 <meta name=variant content='?q=webgpu:api,validation,createBindGroup:buffer_offset_and_size_for_bind_groups_match:offset=768;*'>
 <meta name=variant content='?q=webgpu:api,validation,createBindGroup:buffer_offset_and_size_for_bind_groups_match:offset=1024;size=0'>
-<meta name=variant content='?q=webgpu:api,validation,createBindGroup:buffer_offset_and_size_for_bind_groups_match:offset=1024;size=undefined'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroup:buffer_offset_and_size_for_bind_groups_match:offset=1024;size="_undef_"'>
 <meta name=variant content='?q=webgpu:api,validation,createBindGroup:buffer_offset_and_size_for_bind_groups_match:offset=1024;size=1;*'>
 <meta name=variant content='?q=webgpu:api,validation,createBindGroup:buffer_offset_and_size_for_bind_groups_match:offset=1;*'>
 <meta name=variant content='?q=webgpu:api,validation,createBindGroup:buffer_offset_and_size_for_bind_groups_match:offset=128;*'>
 <meta name=variant content='?q=webgpu:api,validation,createBindGroup:buffer_offset_and_size_for_bind_groups_match:offset=255;*'>
 <meta name=variant content='?q=webgpu:api,validation,createBindGroup:buffer_offset_and_size_for_bind_groups_match:offset=1280;*'>
 <meta name=variant content='?q=webgpu:api,validation,createBindGroupLayout:some_binding_index_was_specified_more_than_once,*'>
-<meta name=variant content='?q=webgpu:api,validation,createBindGroupLayout:visibility_of_bindings_can_be_0,*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroupLayout:visibility,*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroupLayout:bindingTypeSpecific_optional_members,*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroupLayout:multisample_requires_2d_view_dimension,*'>
 <meta name=variant content='?q=webgpu:api,validation,createBindGroupLayout:number_of_dynamic_buffers_exceeds_the_maximum_value,*'>
-<meta name=variant content='?q=webgpu:api,validation,createBindGroupLayout:dynamic_set_to_true_is_allowed_only_for_buffers,*'>
 <meta name=variant content='?q=webgpu:api,validation,createBindGroupLayout:max_resources_per_stage,in_bind_group_layout,*'>
 <meta name=variant content='?q=webgpu:api,validation,createBindGroupLayout:max_resources_per_stage,in_pipeline_layout:*'>
 <meta name=variant content='?q=webgpu:api,validation,createPipelineLayout:number_of_dynamic_buffers_exceeds_the_maximum_value:*'>
-<meta name=variant content='?q=webgpu:api,validation,createPipelineLayout:visibility_and_dynamic_offsets,*'>
 <meta name=variant content='?q=webgpu:api,validation,createPipelineLayout:number_of_bind_group_layouts_exceeds_the_maximum_value,*'>
 <meta name=variant content='?q=webgpu:api,validation,createRenderPipeline:*'>
 <meta name=variant content='?q=webgpu:api,validation,createTexture:*'>
 <meta name=variant content='?q=webgpu:api,validation,createView:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,index_access:*'>
 <meta name=variant content='?q=webgpu:api,validation,error_scope:*'>
 <meta name=variant content='?q=webgpu:api,validation,fences:*'>
 <meta name=variant content='?q=webgpu:api,validation,queue_submit:*'>
 <meta name=variant content='?q=webgpu:api,validation,render_pass:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,resolve:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,storeOp:*'>
 <meta name=variant content='?q=webgpu:api,validation,render_pass_descriptor:*'>
+<meta name=variant content='?q=webgpu:api,validation,resource_usages,textureUsageInRender:*'>
 <meta name=variant content='?q=webgpu:api,validation,setBindGroup:*'>
 <meta name=variant content='?q=webgpu:api,validation,setBlendColor:*'>
 <meta name=variant content='?q=webgpu:api,validation,setScissorRect:*'>
@@ -98,6 +105,7 @@
 <meta name=variant content='?q=webgpu:examples:*'>
 <meta name=variant content='?q=webgpu:idl,constants,flags:*'>
 <meta name=variant content='?q=webgpu:shader,execution,robust_access:*'>
+<meta name=variant content='?q=webgpu:shader,execution,robust_access_vertex:*'>
 <meta name=variant content='?q=webgpu:web-platform,canvas,context_creation:*'>
 <meta name=variant content='?q=webgpu:web-platform,copyImageBitmapToTexture:from_ImageData:*'>
 <meta name=variant content='?q=webgpu:web-platform,copyImageBitmapToTexture:from_canvas,*'>
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/buffers/create_mapped.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/buffers/create_mapped.spec.js
deleted file mode 100644
index 1c3b125..0000000
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/buffers/create_mapped.spec.js
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = '';
-import { params, pbool, poptions } from '../../../../common/framework/params_builder.js';
-import { makeTestGroup } from '../../../../common/framework/test_group.js';
-import { MappingTest } from './mapping_test.js';
-export const g = makeTestGroup(MappingTest);
-g.test('createBufferMapped').params(params().combine(poptions('size', [12, 512 * 1024])).combine(pbool('mappable'))).fn(t => {
-  const {
-    size,
-    mappable
-  } = t.params;
-  const [buffer, arrayBuffer] = t.device.createBufferMapped({
-    size,
-    usage: GPUBufferUsage.COPY_SRC | (mappable ? GPUBufferUsage.MAP_WRITE : 0)
-  });
-  t.checkMapWrite(buffer, arrayBuffer, size);
-});
-//# sourceMappingURL=create_mapped.spec.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/buffers/map.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/buffers/map.spec.js
index e8c86ec..f751b72 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/buffers/map.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/buffers/map.spec.js
@@ -1,51 +1,110 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = '';
-import { pbool, poptions, params } from '../../../../common/framework/params_builder.js';
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = '';
+import { pbool, params } from '../../../../common/framework/params_builder.js';
 import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { assert } from '../../../../common/framework/util/util.js';
+
 import { MappingTest } from './mapping_test.js';
+
 export const g = makeTestGroup(MappingTest);
-g.test('mapWriteAsync').params(poptions('size', [12, 512 * 1024])).fn(async t => {
-  const {
-    size
-  } = t.params;
-  const buffer = t.device.createBuffer({
-    size,
-    usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.MAP_WRITE
-  });
-  const arrayBuffer = await buffer.mapWriteAsync();
-  t.checkMapWrite(buffer, arrayBuffer, size);
-});
-g.test('mapReadAsync').params(poptions('size', [12, 512 * 1024])).fn(async t => {
-  const {
-    size
-  } = t.params;
-  const [buffer, init] = t.device.createBufferMapped({
-    size,
-    usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
-  });
-  const expected = new Uint32Array(new ArrayBuffer(size));
-  const data = new Uint32Array(init);
 
-  for (let i = 0; i < data.length; ++i) {
-    data[i] = expected[i] = i + 1;
-  }
+const kCases = [
+  { size: 0, range: [] },
+  { size: 0, range: [undefined] },
+  { size: 0, range: [undefined, undefined] },
+  { size: 0, range: [0] },
+  { size: 0, range: [0, undefined] },
+  { size: 0, range: [0, 0] },
+  { size: 12, range: [] },
+  { size: 12, range: [undefined] },
+  { size: 12, range: [undefined, undefined] },
+  { size: 12, range: [0] },
+  { size: 12, range: [0, undefined] },
+  { size: 12, range: [0, 12] },
+  { size: 12, range: [0, 0] },
+  { size: 12, range: [8] },
+  { size: 12, range: [8, undefined] },
+  { size: 12, range: [8, 4] },
+  { size: 28, range: [8, 8] },
+  { size: 28, range: [8, 12] },
+  { size: 512 * 1024, range: [] },
+];
 
-  buffer.unmap();
-  const actual = new Uint8Array(await buffer.mapReadAsync());
-  t.expectBuffer(actual, new Uint8Array(expected.buffer));
-});
-g.test('createBufferMapped').params(params().combine(poptions('size', [12, 512 * 1024])).combine(pbool('mappable'))).fn(async t => {
-  const {
-    size,
-    mappable
-  } = t.params;
-  const [buffer, arrayBuffer] = t.device.createBufferMapped({
-    size,
-    usage: GPUBufferUsage.COPY_SRC | (mappable ? GPUBufferUsage.MAP_WRITE : 0)
+function reifyMapRange(bufferSize, range) {
+  var _range$, _range$2;
+  const offset = (_range$ = range[0]) !== null && _range$ !== void 0 ? _range$ : 0;
+  return [
+    offset,
+    (_range$2 = range[1]) !== null && _range$2 !== void 0 ? _range$2 : bufferSize - offset,
+  ];
+}
+
+g.test('mapAsync,write')
+  .params(kCases)
+  .fn(async t => {
+    const { size, range } = t.params;
+    const [rangeOffset, rangeSize] = reifyMapRange(size, range);
+
+    const buffer = t.device.createBuffer({
+      size,
+      usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.MAP_WRITE,
+    });
+
+    await buffer.mapAsync(GPUMapMode.WRITE);
+    const arrayBuffer = buffer.getMappedRange(...range);
+    t.checkMapWrite(buffer, rangeOffset, arrayBuffer, rangeSize);
   });
-  t.checkMapWrite(buffer, arrayBuffer, size);
-});
-//# sourceMappingURL=map.spec.js.map
\ No newline at end of file
+
+g.test('mapAsync,read')
+  .params(kCases)
+  .fn(async t => {
+    const { size, range } = t.params;
+    const [, rangeSize] = reifyMapRange(size, range);
+
+    const buffer = t.device.createBuffer({
+      mappedAtCreation: true,
+      size,
+      usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
+    });
+
+    const init = buffer.getMappedRange(...range);
+
+    assert(init.byteLength === rangeSize);
+    const expected = new Uint32Array(new ArrayBuffer(rangeSize));
+    const data = new Uint32Array(init);
+    for (let i = 0; i < data.length; ++i) {
+      data[i] = expected[i] = i + 1;
+    }
+    buffer.unmap();
+
+    await buffer.mapAsync(GPUMapMode.READ);
+    const actual = new Uint8Array(buffer.getMappedRange(...range));
+    t.expectBuffer(actual, new Uint8Array(expected.buffer));
+  });
+
+g.test('mappedAtCreation')
+  .params(
+    params()
+      .combine(kCases) //
+      .combine(pbool('mappable'))
+  )
+  .fn(async t => {
+    var _range$3;
+    const { size, range, mappable } = t.params;
+    const [, rangeSize] = reifyMapRange(size, range);
+
+    const buffer = t.device.createBuffer({
+      mappedAtCreation: true,
+      size,
+      usage: GPUBufferUsage.COPY_SRC | (mappable ? GPUBufferUsage.MAP_WRITE : 0),
+    });
+
+    const arrayBuffer = buffer.getMappedRange(...range);
+    t.checkMapWrite(
+      buffer,
+      (_range$3 = range[0]) !== null && _range$3 !== void 0 ? _range$3 : 0,
+      arrayBuffer,
+      rangeSize
+    );
+  });
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/buffers/map_detach.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/buffers/map_detach.spec.js
index 8ae0be0..aca8dc4 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/buffers/map_detach.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/buffers/map_detach.spec.js
@@ -1,8 +1,6 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = '';
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = '';
 import { makeTestGroup } from '../../../../common/framework/test_group.js';
 import { GPUTest } from '../../../gpu_test.js';
 
@@ -11,72 +9,65 @@
     const view = new Uint8Array(arrayBuffer);
     this.expect(arrayBuffer.byteLength === 4);
     this.expect(view.length === 4);
+
     if (unmap) buffer.unmap();
     if (destroy) buffer.destroy();
+
     this.expect(arrayBuffer.byteLength === 0, 'ArrayBuffer should be detached');
     this.expect(view.byteLength === 0, 'ArrayBufferView should be detached');
   }
-
 }
 
 export const g = makeTestGroup(F);
-g.test('mapWriteAsync').params([{
-  unmap: true,
-  destroy: false
-}, //
-{
-  unmap: false,
-  destroy: true
-}, {
-  unmap: true,
-  destroy: true
-}]).fn(async t => {
-  const buffer = t.device.createBuffer({
-    size: 4,
-    usage: GPUBufferUsage.MAP_WRITE
+
+g.test('mapAsync,write')
+  .params([
+    { unmap: true, destroy: false }, //
+    { unmap: false, destroy: true },
+    { unmap: true, destroy: true },
+  ])
+  .fn(async t => {
+    const buffer = t.device.createBuffer({ size: 4, usage: GPUBufferUsage.MAP_WRITE });
+    await buffer.mapAsync(GPUMapMode.WRITE);
+    const arrayBuffer = buffer.getMappedRange();
+    t.checkDetach(buffer, arrayBuffer, t.params.unmap, t.params.destroy);
   });
-  const arrayBuffer = await buffer.mapWriteAsync();
-  t.checkDetach(buffer, arrayBuffer, t.params.unmap, t.params.destroy);
-});
-g.test('mapReadAsync').params([{
-  unmap: true,
-  destroy: false
-}, //
-{
-  unmap: false,
-  destroy: true
-}, {
-  unmap: true,
-  destroy: true
-}]).fn(async t => {
-  const buffer = t.device.createBuffer({
-    size: 4,
-    usage: GPUBufferUsage.MAP_READ
+
+g.test('mapAsync,read')
+  .params([
+    { unmap: true, destroy: false }, //
+    { unmap: false, destroy: true },
+    { unmap: true, destroy: true },
+  ])
+  .fn(async t => {
+    const buffer = t.device.createBuffer({ size: 4, usage: GPUBufferUsage.MAP_READ });
+    await buffer.mapAsync(GPUMapMode.READ);
+    const arrayBuffer = buffer.getMappedRange();
+    t.checkDetach(buffer, arrayBuffer, t.params.unmap, t.params.destroy);
   });
-  const arrayBuffer = await buffer.mapReadAsync();
-  t.checkDetach(buffer, arrayBuffer, t.params.unmap, t.params.destroy);
-});
-g.test('create_mapped').params([{
-  unmap: true,
-  destroy: false
-}, {
-  unmap: false,
-  destroy: true
-}, {
-  unmap: true,
-  destroy: true
-}]).fn(async t => {
-  const desc = {
-    size: 4,
-    usage: GPUBufferUsage.MAP_WRITE
-  };
-  const [buffer, arrayBuffer] = t.device.createBufferMapped(desc);
-  const view = new Uint8Array(arrayBuffer);
-  t.expect(arrayBuffer.byteLength === 4);
-  t.expect(view.length === 4);
-  if (t.params.unmap) buffer.unmap();
-  if (t.params.destroy) buffer.destroy();
-  t.expect(arrayBuffer.byteLength === 0, 'ArrayBuffer should be detached');
-  t.expect(view.byteLength === 0, 'ArrayBufferView should be detached');
-});
-//# sourceMappingURL=map_detach.spec.js.map
\ No newline at end of file
+
+g.test('create_mapped')
+  .params([
+    { unmap: true, destroy: false },
+    { unmap: false, destroy: true },
+    { unmap: true, destroy: true },
+  ])
+  .fn(async t => {
+    const desc = {
+      mappedAtCreation: true,
+      size: 4,
+      usage: GPUBufferUsage.MAP_WRITE,
+    };
+
+    const buffer = t.device.createBuffer(desc);
+    const arrayBuffer = buffer.getMappedRange();
+
+    const view = new Uint8Array(arrayBuffer);
+    t.expect(arrayBuffer.byteLength === 4);
+    t.expect(view.length === 4);
+
+    if (t.params.unmap) buffer.unmap();
+    if (t.params.destroy) buffer.destroy();
+    t.expect(arrayBuffer.byteLength === 0, 'ArrayBuffer should be detached');
+    t.expect(view.byteLength === 0, 'ArrayBufferView should be detached');
+  });
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/buffers/map_oom.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/buffers/map_oom.spec.js
index 8494dc7..205f114 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/buffers/map_oom.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/buffers/map_oom.spec.js
@@ -1,34 +1,95 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = '';
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = '';
+import { poptions, params, pbool } from '../../../../common/framework/params_builder.js';
 import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { kBufferUsages } from '../../../capability_info.js';
 import { GPUTest } from '../../../gpu_test.js';
 
-function getBufferDesc(usage) {
-  return {
-    size: Number.MAX_SAFE_INTEGER,
-    usage
-  };
-}
+// A multiple of 8 guaranteed to be way too large to allocate (just under 8 pebibytes).
+// (Note this is likely to exceed limitations other than just the system's
+// physical memory - so may test codepaths other than "true" OOM.)
+const MAX_ALIGNED_SAFE_INTEGER = Number.MAX_SAFE_INTEGER - 7;
 
 export const g = makeTestGroup(GPUTest);
-g.test('mapWriteAsync').fn(async t => {
-  const buffer = t.expectGPUError('out-of-memory', () => {
-    return t.device.createBuffer(getBufferDesc(GPUBufferUsage.MAP_WRITE));
+
+g.test('mapAsync')
+  .params(
+    params()
+      .combine(pbool('oom')) //
+      .combine(pbool('write'))
+  )
+  .fn(async t => {
+    const { oom, write } = t.params;
+    const size = oom ? MAX_ALIGNED_SAFE_INTEGER : 16;
+
+    const buffer = t.expectGPUError(
+      'out-of-memory',
+      () =>
+        t.device.createBuffer({
+          size,
+          usage: write ? GPUBufferUsage.MAP_WRITE : GPUBufferUsage.MAP_READ,
+        }),
+
+      oom
+    );
+
+    const promise = t.expectGPUError(
+      'validation', // Should be a validation error since the buffer is invalid.
+      () => buffer.mapAsync(write ? GPUMapMode.WRITE : GPUMapMode.READ),
+      oom
+    );
+
+    if (oom) {
+      // Should also reject in addition to the validation error.
+      t.shouldReject('OperationError', promise);
+    } else {
+      await promise;
+      const arraybuffer = buffer.getMappedRange();
+      t.expect(arraybuffer.byteLength === size);
+      buffer.unmap();
+      t.expect(arraybuffer.byteLength === 0);
+    }
   });
-  t.shouldReject('OperationError', buffer.mapWriteAsync());
-});
-g.test('mapReadAsync').fn(async t => {
-  const buffer = t.expectGPUError('out-of-memory', () => {
-    return t.device.createBuffer(getBufferDesc(GPUBufferUsage.MAP_READ));
+
+g.test('mappedAtCreation')
+  .params(
+    params()
+      .combine(pbool('oom')) //
+      .combine(poptions('usage', kBufferUsages))
+  )
+  .fn(async t => {
+    const { oom, usage } = t.params;
+    const size = oom ? MAX_ALIGNED_SAFE_INTEGER : 16;
+
+    const buffer = t.expectGPUError(
+      'out-of-memory',
+      () => t.device.createBuffer({ mappedAtCreation: true, size, usage }),
+      oom
+    );
+
+    const f = () => buffer.getMappedRange(0, size);
+
+    if (oom) {
+      t.shouldThrow('RangeError', f);
+    } else {
+      f();
+    }
   });
-  t.shouldReject('OperationError', buffer.mapReadAsync());
-});
-g.test('createBufferMapped').fn(async t => {
-  t.shouldThrow('RangeError', () => {
-    t.device.createBufferMapped(getBufferDesc(GPUBufferUsage.COPY_SRC));
+
+g.test('mappedAtCreation,smaller_getMappedRange')
+  .params(poptions('usage', kBufferUsages))
+  .fn(async t => {
+    const { usage } = t.params;
+    const size = MAX_ALIGNED_SAFE_INTEGER;
+
+    const buffer = t.expectGPUError('out-of-memory', () =>
+      t.device.createBuffer({ mappedAtCreation: true, size, usage })
+    );
+
+    // Smaller range inside a too-big mapping
+    const mapping = buffer.getMappedRange(0, 16);
+    t.expect(mapping.byteLength === 16);
+    buffer.unmap();
+    t.expect(mapping.byteLength === 0);
   });
-});
-//# sourceMappingURL=map_oom.spec.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/buffers/mapping_test.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/buffers/mapping_test.js
index b734905..45ce419 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/buffers/mapping_test.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/buffers/mapping_test.js
@@ -1,21 +1,20 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ import { assert } from '../../../../common/framework/util/util.js';
 import { GPUTest } from '../../../gpu_test.js';
 export class MappingTest extends GPUTest {
-  checkMapWrite(buffer, mappedContents, size) {
+  checkMapWrite(buffer, offset, mappedContents, size) {
     this.checkMapWriteZeroed(mappedContents, size);
+
     const mappedView = new Uint32Array(mappedContents);
     const expected = new Uint32Array(new ArrayBuffer(size));
-    this.expect(mappedView.byteLength === size);
-
+    assert(mappedView.byteLength === size);
     for (let i = 0; i < mappedView.length; ++i) {
       mappedView[i] = expected[i] = i + 1;
     }
-
     buffer.unmap();
-    this.expectContents(buffer, expected);
+
+    this.expectContents(buffer, expected, offset);
   }
 
   checkMapWriteZeroed(arrayBuffer, expectedSize) {
@@ -26,7 +25,6 @@
 
   expectZero(actual) {
     const size = actual.byteLength;
-
     for (let i = 0; i < size; ++i) {
       if (actual[i] !== 0) {
         this.fail(`at [${i}], expected zero, got ${actual[i]}`);
@@ -34,6 +32,4 @@
       }
     }
   }
-
 }
-//# sourceMappingURL=mapping_test.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/command_buffer/basic.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/command_buffer/basic.spec.js
index 547a0868..7f4dd29 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/command_buffer/basic.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/command_buffer/basic.spec.js
@@ -1,16 +1,15 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = `
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `
 Basic tests.
 `;
 import { makeTestGroup } from '../../../../common/framework/test_group.js';
 import { GPUTest } from '../../../gpu_test.js';
+
 export const g = makeTestGroup(GPUTest);
+
 g.test('empty').fn(async t => {
   const encoder = t.device.createCommandEncoder();
   const cmd = encoder.finish();
   t.device.defaultQueue.submit([cmd]);
 });
-//# sourceMappingURL=basic.spec.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/command_buffer/compute/basic.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/command_buffer/compute/basic.spec.js
index dce4d5f..230e8920 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/command_buffer/compute/basic.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/command_buffer/compute/basic.spec.js
@@ -1,54 +1,46 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = `
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `
 Basic command buffer compute tests.
 `;
 import { makeTestGroup } from '../../../../../common/framework/test_group.js';
 import { GPUTest } from '../../../../gpu_test.js';
+
 export const g = makeTestGroup(GPUTest);
+
 g.test('memcpy').fn(async t => {
   const data = new Uint32Array([0x01020304]);
-  const [src, srcData] = t.device.createBufferMapped({
+
+  const src = t.device.createBuffer({
+    mappedAtCreation: true,
     size: 4,
-    usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE
+    usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE,
   });
-  new Uint32Array(srcData).set(data);
+
+  new Uint32Array(src.getMappedRange()).set(data);
   src.unmap();
+
   const dst = t.device.createBuffer({
     size: 4,
-    usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE
+    usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE,
   });
+
   const bgl = t.device.createBindGroupLayout({
-    entries: [{
-      binding: 0,
-      visibility: 4,
-      type: 'storage-buffer'
-    }, {
-      binding: 1,
-      visibility: 4,
-      type: 'storage-buffer'
-    }]
+    entries: [
+      { binding: 0, visibility: 4, type: 'storage-buffer' },
+      { binding: 1, visibility: 4, type: 'storage-buffer' },
+    ],
   });
+
   const bg = t.device.createBindGroup({
-    entries: [{
-      binding: 0,
-      resource: {
-        buffer: src,
-        offset: 0,
-        size: 4
-      }
-    }, {
-      binding: 1,
-      resource: {
-        buffer: dst,
-        offset: 0,
-        size: 4
-      }
-    }],
-    layout: bgl
+    entries: [
+      { binding: 0, resource: { buffer: src, offset: 0, size: 4 } },
+      { binding: 1, resource: { buffer: dst, offset: 0, size: 4 } },
+    ],
+
+    layout: bgl,
   });
+
   const module = t.makeShaderModule('compute', {
     glsl: `
       #version 310 es
@@ -62,18 +54,15 @@
       void main() {
         dst.value = src.value;
       }
-    `
+    `,
   });
-  const pl = t.device.createPipelineLayout({
-    bindGroupLayouts: [bgl]
-  });
+
+  const pl = t.device.createPipelineLayout({ bindGroupLayouts: [bgl] });
   const pipeline = t.device.createComputePipeline({
-    computeStage: {
-      module,
-      entryPoint: 'main'
-    },
-    layout: pl
+    computeStage: { module, entryPoint: 'main' },
+    layout: pl,
   });
+
   const encoder = t.device.createCommandEncoder();
   const pass = encoder.beginComputePass();
   pass.setPipeline(pipeline);
@@ -81,6 +70,6 @@
   pass.dispatch(1, 1, 1);
   pass.endPass();
   t.device.defaultQueue.submit([encoder.finish()]);
+
   t.expectContents(dst, data);
 });
-//# sourceMappingURL=basic.spec.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/command_buffer/copies.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/command_buffer/copies.spec.js
index cc2ea6d..a6ecc3c 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/command_buffer/copies.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/command_buffer/copies.spec.js
@@ -1,165 +1,124 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = `
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `
 copy{Buffer,Texture}To{Buffer,Texture} tests.
 `;
 import { makeTestGroup } from '../../../../common/framework/test_group.js';
 import { GPUTest } from '../../../gpu_test.js';
+
 export const g = makeTestGroup(GPUTest);
+
 g.test('b2b').fn(async t => {
   const data = new Uint32Array([0x01020304]);
-  const [src, map] = t.device.createBufferMapped({
+
+  const src = t.device.createBuffer({
+    mappedAtCreation: true,
     size: 4,
-    usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+    usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
   });
-  new Uint32Array(map).set(data);
+
+  new Uint32Array(src.getMappedRange()).set(data);
   src.unmap();
+
   const dst = t.device.createBuffer({
     size: 4,
-    usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+    usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
   });
+
   const encoder = t.device.createCommandEncoder();
   encoder.copyBufferToBuffer(src, 0, dst, 0, 4);
   t.device.defaultQueue.submit([encoder.finish()]);
+
   t.expectContents(dst, data);
 });
+
 g.test('b2t2b').fn(async t => {
   const data = new Uint32Array([0x01020304]);
-  const [src, map] = t.device.createBufferMapped({
+
+  const src = t.device.createBuffer({
+    mappedAtCreation: true,
     size: 4,
-    usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+    usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
   });
-  new Uint32Array(map).set(data);
+
+  new Uint32Array(src.getMappedRange()).set(data);
   src.unmap();
+
   const dst = t.device.createBuffer({
     size: 4,
-    usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+    usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
   });
+
   const mid = t.device.createTexture({
-    size: {
-      width: 1,
-      height: 1,
-      depth: 1
-    },
+    size: { width: 1, height: 1, depth: 1 },
     format: 'rgba8uint',
-    usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+    usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
   });
+
   const encoder = t.device.createCommandEncoder();
-  encoder.copyBufferToTexture({
-    buffer: src,
-    bytesPerRow: 256
-  }, {
-    texture: mid,
-    mipLevel: 0,
-    origin: {
-      x: 0,
-      y: 0,
-      z: 0
-    }
-  }, {
-    width: 1,
-    height: 1,
-    depth: 1
-  });
-  encoder.copyTextureToBuffer({
-    texture: mid,
-    mipLevel: 0,
-    origin: {
-      x: 0,
-      y: 0,
-      z: 0
-    }
-  }, {
-    buffer: dst,
-    bytesPerRow: 256
-  }, {
-    width: 1,
-    height: 1,
-    depth: 1
-  });
+  encoder.copyBufferToTexture(
+    { buffer: src, bytesPerRow: 256 },
+    { texture: mid, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
+    { width: 1, height: 1, depth: 1 }
+  );
+
+  encoder.copyTextureToBuffer(
+    { texture: mid, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
+    { buffer: dst, bytesPerRow: 256 },
+    { width: 1, height: 1, depth: 1 }
+  );
+
   t.device.defaultQueue.submit([encoder.finish()]);
+
   t.expectContents(dst, data);
 });
+
 g.test('b2t2t2b').fn(async t => {
   const data = new Uint32Array([0x01020304]);
-  const [src, map] = t.device.createBufferMapped({
+
+  const src = t.device.createBuffer({
+    mappedAtCreation: true,
     size: 4,
-    usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+    usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
   });
-  new Uint32Array(map).set(data);
+
+  new Uint32Array(src.getMappedRange()).set(data);
   src.unmap();
+
   const dst = t.device.createBuffer({
     size: 4,
-    usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+    usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
   });
+
   const midDesc = {
-    size: {
-      width: 1,
-      height: 1,
-      depth: 1
-    },
+    size: { width: 1, height: 1, depth: 1 },
     format: 'rgba8uint',
-    usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+    usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
   };
+
   const mid1 = t.device.createTexture(midDesc);
   const mid2 = t.device.createTexture(midDesc);
+
   const encoder = t.device.createCommandEncoder();
-  encoder.copyBufferToTexture({
-    buffer: src,
-    bytesPerRow: 256
-  }, {
-    texture: mid1,
-    mipLevel: 0,
-    origin: {
-      x: 0,
-      y: 0,
-      z: 0
-    }
-  }, {
-    width: 1,
-    height: 1,
-    depth: 1
-  });
-  encoder.copyTextureToTexture({
-    texture: mid1,
-    mipLevel: 0,
-    origin: {
-      x: 0,
-      y: 0,
-      z: 0
-    }
-  }, {
-    texture: mid2,
-    mipLevel: 0,
-    origin: {
-      x: 0,
-      y: 0,
-      z: 0
-    }
-  }, {
-    width: 1,
-    height: 1,
-    depth: 1
-  });
-  encoder.copyTextureToBuffer({
-    texture: mid2,
-    mipLevel: 0,
-    origin: {
-      x: 0,
-      y: 0,
-      z: 0
-    }
-  }, {
-    buffer: dst,
-    bytesPerRow: 256
-  }, {
-    width: 1,
-    height: 1,
-    depth: 1
-  });
+  encoder.copyBufferToTexture(
+    { buffer: src, bytesPerRow: 256 },
+    { texture: mid1, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
+    { width: 1, height: 1, depth: 1 }
+  );
+
+  encoder.copyTextureToTexture(
+    { texture: mid1, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
+    { texture: mid2, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
+    { width: 1, height: 1, depth: 1 }
+  );
+
+  encoder.copyTextureToBuffer(
+    { texture: mid2, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
+    { buffer: dst, bytesPerRow: 256 },
+    { width: 1, height: 1, depth: 1 }
+  );
+
   t.device.defaultQueue.submit([encoder.finish()]);
+
   t.expectContents(dst, data);
 });
-//# sourceMappingURL=copies.spec.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/command_buffer/render/basic.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/command_buffer/render/basic.spec.js
index 815eb5b7..d3913ef 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/command_buffer/render/basic.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/command_buffer/render/basic.spec.js
@@ -1,59 +1,46 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = `
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `
 Basic command buffer rendering tests.
 `;
 import { makeTestGroup } from '../../../../../common/framework/test_group.js';
 import { GPUTest } from '../../../../gpu_test.js';
+
 export const g = makeTestGroup(GPUTest);
+
 g.test('clear').fn(async t => {
   const dst = t.device.createBuffer({
     size: 4,
-    usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+    usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
   });
+
   const colorAttachment = t.device.createTexture({
     format: 'rgba8unorm',
-    size: {
-      width: 1,
-      height: 1,
-      depth: 1
-    },
-    usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT
+    size: { width: 1, height: 1, depth: 1 },
+    usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT,
   });
+
   const colorAttachmentView = colorAttachment.createView();
+
   const encoder = t.device.createCommandEncoder();
   const pass = encoder.beginRenderPass({
-    colorAttachments: [{
-      attachment: colorAttachmentView,
-      loadValue: {
-        r: 0.0,
-        g: 1.0,
-        b: 0.0,
-        a: 1.0
+    colorAttachments: [
+      {
+        attachment: colorAttachmentView,
+        loadValue: { r: 0.0, g: 1.0, b: 0.0, a: 1.0 },
+        storeOp: 'store',
       },
-      storeOp: 'store'
-    }]
+    ],
   });
+
   pass.endPass();
-  encoder.copyTextureToBuffer({
-    texture: colorAttachment,
-    mipLevel: 0,
-    origin: {
-      x: 0,
-      y: 0,
-      z: 0
-    }
-  }, {
-    buffer: dst,
-    bytesPerRow: 256
-  }, {
-    width: 1,
-    height: 1,
-    depth: 1
-  });
+  encoder.copyTextureToBuffer(
+    { texture: colorAttachment, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
+    { buffer: dst, bytesPerRow: 256 },
+    { width: 1, height: 1, depth: 1 }
+  );
+
   t.device.defaultQueue.submit([encoder.finish()]);
+
   t.expectContents(dst, new Uint8Array([0x00, 0xff, 0x00, 0xff]));
 });
-//# sourceMappingURL=basic.spec.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/command_buffer/render/rendering.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/command_buffer/render/rendering.spec.js
index ad138c6..60e843a 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/command_buffer/render/rendering.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/command_buffer/render/rendering.spec.js
@@ -1,26 +1,25 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = '';
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = '';
 import { makeTestGroup } from '../../../../../common/framework/test_group.js';
 import { GPUTest } from '../../../../gpu_test.js';
+
 export const g = makeTestGroup(GPUTest);
+
 g.test('fullscreen_quad').fn(async t => {
   const dst = t.device.createBuffer({
     size: 4,
-    usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+    usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
   });
+
   const colorAttachment = t.device.createTexture({
     format: 'rgba8unorm',
-    size: {
-      width: 1,
-      height: 1,
-      depth: 1
-    },
-    usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT
+    size: { width: 1, height: 1, depth: 1 },
+    usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT,
   });
+
   const colorAttachmentView = colorAttachment.createView();
+
   const vertexModule = t.makeShaderModule('vertex', {
     glsl: `
       #version 310 es
@@ -29,8 +28,9 @@
             vec2(-1.f, -3.f), vec2(3.f, 1.f), vec2(-1.f, 1.f));
         gl_Position = vec4(pos[gl_VertexIndex], 0.f, 1.f);
       }
-    `
+    `,
   });
+
   const fragmentModule = t.makeShaderModule('fragment', {
     glsl: `
       #version 310 es
@@ -39,68 +39,47 @@
       void main() {
         fragColor = vec4(0.0, 1.0, 0.0, 1.0);
       }
-    `
+    `,
   });
-  const pl = t.device.createPipelineLayout({
-    bindGroupLayouts: []
-  });
+
+  const pl = t.device.createPipelineLayout({ bindGroupLayouts: [] });
   const pipeline = t.device.createRenderPipeline({
-    vertexStage: {
-      module: vertexModule,
-      entryPoint: 'main'
-    },
-    fragmentStage: {
-      module: fragmentModule,
-      entryPoint: 'main'
-    },
+    vertexStage: { module: vertexModule, entryPoint: 'main' },
+    fragmentStage: { module: fragmentModule, entryPoint: 'main' },
     layout: pl,
     primitiveTopology: 'triangle-list',
     rasterizationState: {
-      frontFace: 'ccw'
+      frontFace: 'ccw',
     },
-    colorStates: [{
-      format: 'rgba8unorm',
-      alphaBlend: {},
-      colorBlend: {}
-    }],
+
+    colorStates: [{ format: 'rgba8unorm', alphaBlend: {}, colorBlend: {} }],
     vertexState: {
       indexFormat: 'uint16',
-      vertexBuffers: []
-    }
+      vertexBuffers: [],
+    },
   });
+
   const encoder = t.device.createCommandEncoder();
   const pass = encoder.beginRenderPass({
-    colorAttachments: [{
-      attachment: colorAttachmentView,
-      storeOp: 'store',
-      loadValue: {
-        r: 1.0,
-        g: 0.0,
-        b: 0.0,
-        a: 1.0
-      }
-    }]
+    colorAttachments: [
+      {
+        attachment: colorAttachmentView,
+        storeOp: 'store',
+        loadValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+      },
+    ],
   });
+
   pass.setPipeline(pipeline);
   pass.draw(3, 1, 0, 0);
   pass.endPass();
-  encoder.copyTextureToBuffer({
-    texture: colorAttachment,
-    mipLevel: 0,
-    origin: {
-      x: 0,
-      y: 0,
-      z: 0
-    }
-  }, {
-    buffer: dst,
-    bytesPerRow: 256
-  }, {
-    width: 1,
-    height: 1,
-    depth: 1
-  });
+  encoder.copyTextureToBuffer(
+    { texture: colorAttachment, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
+    { buffer: dst, bytesPerRow: 256 },
+    { width: 1, height: 1, depth: 1 }
+  );
+
   t.device.defaultQueue.submit([encoder.finish()]);
+
   t.expectContents(dst, new Uint8Array([0x00, 0xff, 0x00, 0xff]));
 });
-//# sourceMappingURL=rendering.spec.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/command_buffer/render/storeop.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/command_buffer/render/storeop.spec.js
index c5a0954..cd0c9d6 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/command_buffer/render/storeop.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/command_buffer/render/storeop.spec.js
@@ -1,32 +1,27 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = `
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `
 renderPass store op test that drawn quad is either stored or cleared based on storeop`;
 import { makeTestGroup } from '../../../../../common/framework/test_group.js';
 import { GPUTest } from '../../../../gpu_test.js';
-export const g = makeTestGroup(GPUTest);
-g.test('storeOp_controls_whether_1x1_drawn_quad_is_stored').params([{
-  storeOp: 'store',
-  _expected: 1
-}, //
-{
-  storeOp: 'clear',
-  _expected: 0
-}]).fn(async t => {
-  const renderTexture = t.device.createTexture({
-    size: {
-      width: 1,
-      height: 1,
-      depth: 1
-    },
-    format: 'r8unorm',
-    usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT
-  }); // create render pipeline
 
-  const vertexModule = t.makeShaderModule('vertex', {
-    glsl: `
+export const g = makeTestGroup(GPUTest);
+
+g.test('storeOp_controls_whether_1x1_drawn_quad_is_stored')
+  .params([
+    { storeOp: 'store', _expected: 1 }, //
+    { storeOp: 'clear', _expected: 0 },
+  ])
+  .fn(async t => {
+    const renderTexture = t.device.createTexture({
+      size: { width: 1, height: 1, depth: 1 },
+      format: 'r8unorm',
+      usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT,
+    });
+
+    // create render pipeline
+    const vertexModule = t.makeShaderModule('vertex', {
+      glsl: `
         #version 450
         const vec2 pos[3] = vec2[3](
                                 vec2( 1.0f, -1.0f),
@@ -37,58 +32,47 @@
         void main() {
             gl_Position = vec4(pos[gl_VertexIndex], 0.0, 1.0);
         }
-      `
-  });
-  const fragmentModule = t.makeShaderModule('fragment', {
-    glsl: `
+      `,
+    });
+
+    const fragmentModule = t.makeShaderModule('fragment', {
+      glsl: `
       #version 450
       layout(location = 0) out vec4 fragColor;
       void main() {
           fragColor = vec4(1.0, 0.0, 0.0, 1.0);
       }
-    `
-  });
-  const renderPipeline = t.device.createRenderPipeline({
-    vertexStage: {
-      module: vertexModule,
-      entryPoint: 'main'
-    },
-    fragmentStage: {
-      module: fragmentModule,
-      entryPoint: 'main'
-    },
-    layout: t.device.createPipelineLayout({
-      bindGroupLayouts: []
-    }),
-    primitiveTopology: 'triangle-list',
-    colorStates: [{
-      format: 'r8unorm'
-    }]
-  }); // encode pass and submit
+    `,
+    });
 
-  const encoder = t.device.createCommandEncoder();
-  const pass = encoder.beginRenderPass({
-    colorAttachments: [{
-      attachment: renderTexture.createView(),
-      storeOp: t.params.storeOp,
-      loadValue: {
-        r: 0.0,
-        g: 0.0,
-        b: 0.0,
-        a: 0.0
-      }
-    }]
-  });
-  pass.setPipeline(renderPipeline);
-  pass.draw(3, 1, 0, 0);
-  pass.endPass();
-  t.device.defaultQueue.submit([encoder.finish()]); // expect the buffer to be clear
+    const renderPipeline = t.device.createRenderPipeline({
+      vertexStage: { module: vertexModule, entryPoint: 'main' },
+      fragmentStage: { module: fragmentModule, entryPoint: 'main' },
+      layout: t.device.createPipelineLayout({ bindGroupLayouts: [] }),
+      primitiveTopology: 'triangle-list',
+      colorStates: [{ format: 'r8unorm' }],
+    });
 
-  t.expectSingleColor(renderTexture, 'r8unorm', {
-    size: [1, 1, 1],
-    exp: {
-      R: t.params._expected
-    }
+    // encode pass and submit
+    const encoder = t.device.createCommandEncoder();
+    const pass = encoder.beginRenderPass({
+      colorAttachments: [
+        {
+          attachment: renderTexture.createView(),
+          storeOp: t.params.storeOp,
+          loadValue: { r: 0.0, g: 0.0, b: 0.0, a: 0.0 },
+        },
+      ],
+    });
+
+    pass.setPipeline(renderPipeline);
+    pass.draw(3, 1, 0, 0);
+    pass.endPass();
+    t.device.defaultQueue.submit([encoder.finish()]);
+
+    // expect the buffer to be clear
+    t.expectSingleColor(renderTexture, 'r8unorm', {
+      size: [1, 1, 1],
+      exp: { R: t.params._expected },
+    });
   });
-});
-//# sourceMappingURL=storeop.spec.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/fences.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/fences.spec.js
index f9f079b..98f9130 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/fences.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/fences.spec.js
@@ -1,68 +1,74 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = '';
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = '';
 import { makeTestGroup } from '../../../common/framework/test_group.js';
 import { attemptGarbageCollection } from '../../../common/framework/util/collect_garbage.js';
 import { raceWithRejectOnTimeout } from '../../../common/framework/util/util.js';
 import { GPUTest } from '../../gpu_test.js';
+
 export const g = makeTestGroup(GPUTest);
+
 g.test('initial,no_descriptor').fn(t => {
   const fence = t.queue.createFence();
   t.expect(fence.getCompletedValue() === 0);
 });
+
 g.test('initial,empty_descriptor').fn(t => {
   const fence = t.queue.createFence({});
   t.expect(fence.getCompletedValue() === 0);
 });
-g.test('initial,descriptor_with_initialValue').fn(t => {
-  const fence = t.queue.createFence({
-    initialValue: 2
-  });
-  t.expect(fence.getCompletedValue() === 2);
-}); // Promise resolves when onCompletion value is less than signal value.
 
+g.test('initial,descriptor_with_initialValue').fn(t => {
+  const fence = t.queue.createFence({ initialValue: 2 });
+  t.expect(fence.getCompletedValue() === 2);
+});
+
+// Promise resolves when onCompletion value is less than signal value.
 g.test('wait,less_than_signaled').fn(async t => {
   const fence = t.queue.createFence();
   t.queue.signal(fence, 2);
   await fence.onCompletion(1);
   t.expect(fence.getCompletedValue() === 2);
-}); // Promise resolves when onCompletion value is equal to signal value.
+});
 
+// Promise resolves when onCompletion value is equal to signal value.
 g.test('wait,equal_to_signaled').fn(async t => {
   const fence = t.queue.createFence();
   t.queue.signal(fence, 2);
   await fence.onCompletion(2);
   t.expect(fence.getCompletedValue() === 2);
-}); // All promises resolve when signal is called once.
+});
 
+// All promises resolve when signal is called once.
 g.test('wait,signaled_once').fn(async t => {
   const fence = t.queue.createFence();
   t.queue.signal(fence, 20);
   const promises = [];
-
   for (let i = 0; i <= 20; ++i) {
-    promises.push(fence.onCompletion(i).then(() => {
-      t.expect(fence.getCompletedValue() >= i);
-    }));
+    promises.push(
+      fence.onCompletion(i).then(() => {
+        t.expect(fence.getCompletedValue() >= i);
+      })
+    );
   }
-
   await Promise.all(promises);
-}); // Promise resolves when signal is called multiple times.
+});
 
+// Promise resolves when signal is called multiple times.
 g.test('wait,signaled_multiple_times').fn(async t => {
   const fence = t.queue.createFence();
   t.queue.signal(fence, 1);
   t.queue.signal(fence, 2);
   await fence.onCompletion(2);
   t.expect(fence.getCompletedValue() === 2);
-}); // Promise resolves if fence has already completed.
+});
 
+// Promise resolves if fence has already completed.
 g.test('wait,already_completed').fn(async t => {
   const fence = t.queue.createFence();
-  t.queue.signal(fence, 2); // Wait for value to update.
+  t.queue.signal(fence, 2);
 
+  // Wait for value to update.
   while (fence.getCompletedValue() < 2) {
     await new Promise(resolve => {
       requestAnimationFrame(resolve);
@@ -70,44 +76,53 @@
   }
 
   t.expect(fence.getCompletedValue() === 2);
+
   await fence.onCompletion(2);
   t.expect(fence.getCompletedValue() === 2);
-}); // Test many calls to signal and wait on fence values one at a time.
+});
 
+// Test many calls to signal and wait on fence values one at a time.
 g.test('wait,many,serially').fn(async t => {
   const fence = t.queue.createFence();
-
   for (let i = 1; i <= 20; ++i) {
     t.queue.signal(fence, i);
     await fence.onCompletion(i);
     t.expect(fence.getCompletedValue() === i);
   }
-}); // Test many calls to signal and wait on all fence values.
+});
 
+// Test many calls to signal and wait on all fence values.
 g.test('wait,many,parallel').fn(async t => {
   const fence = t.queue.createFence();
   const promises = [];
-
   for (let i = 1; i <= 20; ++i) {
     t.queue.signal(fence, i);
-    promises.push(fence.onCompletion(i).then(() => {
-      t.expect(fence.getCompletedValue() >= i);
-    }));
+    promises.push(
+      fence.onCompletion(i).then(() => {
+        t.expect(fence.getCompletedValue() >= i);
+      })
+    );
   }
-
   await Promise.all(promises);
   t.expect(fence.getCompletedValue() === 20);
-}); // Test onCompletion promise resolves within a time limit.
+});
 
+// Test onCompletion promise resolves within a time limit.
 g.test('wait,resolves_within_timeout').fn(t => {
   const fence = t.queue.createFence();
   t.queue.signal(fence, 2);
-  return raceWithRejectOnTimeout((async () => {
-    await fence.onCompletion(2);
-    t.expect(fence.getCompletedValue() === 2);
-  })(), 100, 'The fence has not been resolved within time limit.');
-}); // Test dropping references to the fence and onCompletion promise does not crash.
 
+  return raceWithRejectOnTimeout(
+    (async () => {
+      await fence.onCompletion(2);
+      t.expect(fence.getCompletedValue() === 2);
+    })(),
+    100,
+    'The fence has not been resolved within time limit.'
+  );
+});
+
+// Test dropping references to the fence and onCompletion promise does not crash.
 g.test('drop,fence_and_promise').fn(async t => {
   {
     const fence = t.queue.createFence();
@@ -115,8 +130,9 @@
     fence.onCompletion(2);
   }
   await attemptGarbageCollection();
-}); // Test dropping references to the fence and holding the promise does not crash.
+});
 
+// Test dropping references to the fence and holding the promise does not crash.
 g.test('drop,promise').fn(async t => {
   let promise;
   {
@@ -127,4 +143,3 @@
   await attemptGarbageCollection();
   await promise;
 });
-//# sourceMappingURL=fences.spec.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/render_pass/storeOp.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/render_pass/storeOp.spec.js
new file mode 100644
index 0000000..83ae865
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/render_pass/storeOp.spec.js
@@ -0,0 +1,324 @@
+/**
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `API Operation Tests for RenderPass StoreOp.
+
+  Test Coverage:
+
+  - Tests that color and depth-stencil store operations {'clear', 'store'} work correctly for a
+    render pass with both a color attachment and depth-stencil attachment.
+      TODO: use depth24plus-stencil8
+
+  - Tests that store operations {'clear', 'store'} work correctly for a render pass with multiple
+    color attachments.
+      TODO: test with more interesting loadOp values
+
+  - Tests that store operations {'clear', 'store'} work correctly for a render pass with a color
+    attachment for:
+      - All renderable color formats
+      - mip level set to {'0', mip > '0'}
+      - array layer set to {'0', layer > '1'} for 2D textures
+      TODO: depth slice set to {'0', slice > '0'} for 3D textures
+
+  - Tests that store operations {'clear', 'store'} work correctly for a render pass with a
+    depth-stencil attachment for:
+      - All renderable depth-stencil formats
+      - mip level set to {'0', mip > '0'}
+      - array layer set to {'0', layer > '1'} for 2D textures
+      TODO: test depth24plus and depth24plus-stencil8 formats
+      TODO: test that depth and stencil aspects are set seperately
+      TODO: depth slice set to {'0', slice > '0'} for 3D textures
+      TODO: test with more interesting loadOp values`;
+import { params, poptions } from '../../../../common/framework/params_builder.js';
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import {
+  kEncodableTextureFormatInfo,
+  kEncodableTextureFormats,
+  kSizedDepthStencilFormats,
+} from '../../../capability_info.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+// Test with a zero and non-zero mip.
+const kMipLevel = [0, 1];
+const kMipLevelCount = 2;
+
+// Test with different numbers of color attachments.
+
+const kNumColorAttachments = [1, 2, 3, 4];
+
+// Test with a zero and non-zero array layer.
+const kArrayLayers = [0, 1];
+
+const kStoreOps = ['clear', 'store'];
+
+const kHeight = 2;
+const kWidth = 2;
+
+export const g = makeTestGroup(GPUTest);
+
+// Tests a render pass with both a color and depth stencil attachment to ensure store operations are
+// set independently.
+g.test('render_pass_store_op,color_attachment_with_depth_stencil_attachment')
+  .params(
+    params()
+      .combine(poptions('colorStoreOperation', kStoreOps))
+      .combine(poptions('depthStencilStoreOperation', kStoreOps))
+  )
+  .fn(t => {
+    // Create a basic color attachment.
+    const kColorFormat = 'rgba8unorm';
+    const colorAttachment = t.device.createTexture({
+      format: kColorFormat,
+      size: { width: kWidth, height: kHeight, depth: 1 },
+      usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT,
+    });
+
+    const colorAttachmentView = colorAttachment.createView();
+
+    // Create a basic depth/stencil attachment.
+    const kDepthStencilFormat = 'depth32float';
+    const depthStencilAttachment = t.device.createTexture({
+      format: kDepthStencilFormat,
+      size: { width: kWidth, height: kHeight, depth: 1 },
+      usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT,
+    });
+
+    // Color load operation will clear to {1.0, 1.0, 1.0, 1.0}.
+    // Depth & stencil load operations will clear to 1.0.
+    // Store operations are determined by test the params.
+    const encoder = t.device.createCommandEncoder();
+    const pass = encoder.beginRenderPass({
+      colorAttachments: [
+        {
+          attachment: colorAttachmentView,
+          loadValue: { r: 1.0, g: 1.0, b: 1.0, a: 1.0 },
+          storeOp: t.params.colorStoreOperation,
+        },
+      ],
+
+      depthStencilAttachment: {
+        attachment: depthStencilAttachment.createView(),
+        depthLoadValue: 1.0,
+        depthStoreOp: t.params.depthStencilStoreOperation,
+        stencilLoadValue: 1.0,
+        stencilStoreOp: t.params.depthStencilStoreOperation,
+      },
+    });
+
+    pass.endPass();
+
+    t.device.defaultQueue.submit([encoder.finish()]);
+
+    // Check that the correct store operation occurred.
+    let expectedColorValue = {};
+    if (t.params.colorStoreOperation === 'clear') {
+      // If colorStoreOp was clear, the texture should now contain {0.0, 0.0, 0.0, 0.0}.
+      expectedColorValue = { R: 0.0, G: 0.0, B: 0.0, A: 0.0 };
+    } else if (t.params.colorStoreOperation === 'store') {
+      // If colorStoreOP was store, the texture should still contain {1.0, 1.0, 1.0, 1.0}.
+      expectedColorValue = { R: 1.0, G: 1.0, B: 1.0, A: 1.0 };
+    }
+    t.expectSingleColor(colorAttachment, kColorFormat, {
+      size: [kHeight, kWidth, 1],
+      exp: expectedColorValue,
+    });
+
+    // Check that the correct store operation occurred.
+    let expectedDepthValue = {};
+    if (t.params.depthStencilStoreOperation === 'clear') {
+      // If depthStencilStoreOperation was clear, the texture's depth component should be 0.0, and
+      // the stencil component should be 0.0.
+      expectedDepthValue = { Depth: 0.0 };
+    } else if (t.params.depthStencilStoreOperation === 'store') {
+      // If depthStencilStoreOperation was store, the texture's depth component should be 1.0, and
+      // the stencil component should be 1.0.
+      expectedDepthValue = { Depth: 1.0 };
+    }
+    t.expectSingleColor(depthStencilAttachment, kDepthStencilFormat, {
+      size: [kHeight, kWidth, 1],
+      exp: expectedDepthValue,
+    });
+  });
+
+// Tests that render pass color attachment store operations work correctly for all renderable color
+// formats, mip levels and array layers.
+g.test('render_pass_store_op,color_attachment_only')
+  .params(
+    params()
+      .combine(poptions('colorFormat', kEncodableTextureFormats))
+      // Filter out any non-renderable formats
+      .filter(({ colorFormat }) => kEncodableTextureFormatInfo[colorFormat].renderable)
+      .combine(poptions('storeOperation', kStoreOps))
+      .combine(poptions('mipLevel', kMipLevel))
+      .combine(poptions('arrayLayer', kArrayLayers))
+  )
+  .fn(t => {
+    const colorAttachment = t.device.createTexture({
+      format: t.params.colorFormat,
+      size: { width: kWidth, height: kHeight, depth: t.params.arrayLayer + 1 },
+      mipLevelCount: kMipLevelCount,
+      usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT,
+    });
+
+    const colorViewDesc = {
+      baseArrayLayer: t.params.arrayLayer,
+      baseMipLevel: t.params.mipLevel,
+      mipLevelCount: 1,
+      arrayLayerCount: 1,
+    };
+
+    const colorAttachmentView = colorAttachment.createView(colorViewDesc);
+
+    // Color load operation will clear to {1.0, 0.0, 0.0, 1.0}.
+    // Color store operation is determined by the test params.
+    const encoder = t.device.createCommandEncoder();
+    const pass = encoder.beginRenderPass({
+      colorAttachments: [
+        {
+          attachment: colorAttachmentView,
+          loadValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+          storeOp: t.params.storeOperation,
+        },
+      ],
+    });
+
+    pass.endPass();
+    t.device.defaultQueue.submit([encoder.finish()]);
+
+    // Check that the correct store operation occurred.
+    let expectedValue = {};
+    if (t.params.storeOperation === 'clear') {
+      // If colorStoreOp was clear, the texture should now contain {0.0, 0.0, 0.0, 0.0}.
+      expectedValue = { R: 0.0, G: 0.0, B: 0.0, A: 0.0 };
+    } else if (t.params.storeOperation === 'store') {
+      // If colorStoreOP was store, the texture should still contain {1.0, 0.0, 0.0, 1.0}.
+      expectedValue = { R: 1.0, G: 0.0, B: 0.0, A: 1.0 };
+    }
+
+    t.expectSingleColor(colorAttachment, t.params.colorFormat, {
+      size: [kHeight, kWidth, 1],
+      slice: t.params.arrayLayer,
+      exp: expectedValue,
+      layout: { mipLevel: t.params.mipLevel },
+    });
+  });
+
+// Test with multiple color attachments to ensure each attachment's storeOp is set independently.
+g.test('render_pass_store_op,multiple_color_attachments')
+  .params(
+    params()
+      .combine(poptions('colorAttachments', kNumColorAttachments))
+      .combine(poptions('storeOperation1', kStoreOps))
+      .combine(poptions('storeOperation2', kStoreOps))
+  )
+  .fn(t => {
+    const kColorFormat = 'rgba8unorm';
+    const colorAttachments = [];
+
+    for (let i = 0; i < t.params.colorAttachments; i++) {
+      colorAttachments.push(
+        t.device.createTexture({
+          format: kColorFormat,
+          size: { width: kWidth, height: kHeight, depth: 1 },
+          usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT,
+        })
+      );
+    }
+
+    // Color load operation will clear to {1.0, 1.0, 1.0, 1.0}
+    // Color store operation is determined by test params. Use storeOperation1 for even numbered
+    // attachments and storeOperation2 for odd numbered attachments.
+    const renderPassColorAttachmentDescriptors = [];
+    for (let i = 0; i < t.params.colorAttachments; i++) {
+      renderPassColorAttachmentDescriptors.push({
+        attachment: colorAttachments[i].createView(),
+        loadValue: { r: 1.0, g: 1.0, b: 1.0, a: 1.0 },
+        storeOp: i % 2 === 0 ? t.params.storeOperation1 : t.params.storeOperation2,
+      });
+    }
+
+    const encoder = t.device.createCommandEncoder();
+    const pass = encoder.beginRenderPass({
+      colorAttachments: renderPassColorAttachmentDescriptors,
+    });
+
+    pass.endPass();
+    t.device.defaultQueue.submit([encoder.finish()]);
+
+    // Check that the correct store operation occurred.
+    let expectedValue = {};
+    for (let i = 0; i < t.params.colorAttachments; i++) {
+      if (renderPassColorAttachmentDescriptors[i].storeOp === 'clear') {
+        // If colorStoreOp was clear, the texture should now contain {0.0, 0.0, 0.0, 0.0}.
+        expectedValue = { R: 0.0, G: 0.0, B: 0.0, A: 0.0 };
+      } else if (renderPassColorAttachmentDescriptors[i].storeOp === 'store') {
+        // If colorStoreOP was store, the texture should still contain {1.0, 1.0, 1.0, 1.0}.
+        expectedValue = { R: 1.0, G: 1.0, B: 1.0, A: 1.0 };
+      }
+      t.expectSingleColor(colorAttachments[i], kColorFormat, {
+        size: [kHeight, kWidth, 1],
+        exp: expectedValue,
+      });
+    }
+  });
+
+// Tests that render pass depth stencil store operations work correctly for all renderable color
+// formats, mip levels and array layers.
+g.test('render_pass_store_op,depth_stencil_attachment_only')
+  .params(
+    params()
+      // TODO: Also test unsized depth/stencil formats
+      .combine(poptions('depthStencilFormat', kSizedDepthStencilFormats))
+      .combine(poptions('storeOperation', kStoreOps))
+      .combine(poptions('mipLevel', kMipLevel))
+      .combine(poptions('arrayLayer', kArrayLayers))
+  )
+  .fn(t => {
+    const depthStencilAttachment = t.device.createTexture({
+      format: t.params.depthStencilFormat,
+      size: { width: kWidth, height: kHeight, depth: t.params.arrayLayer + 1 },
+      mipLevelCount: kMipLevelCount,
+      usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT,
+    });
+
+    const depthStencilViewDesc = {
+      baseArrayLayer: t.params.arrayLayer,
+      baseMipLevel: t.params.mipLevel,
+      mipLevelCount: 1,
+      arrayLayerCount: 1,
+    };
+
+    const depthStencilAttachmentView = depthStencilAttachment.createView(depthStencilViewDesc);
+
+    // Depth-stencil load operation will clear to depth = 1.0, stencil = 1.0.
+    // Depth-stencil store operate is determined by test params.
+    const encoder = t.device.createCommandEncoder();
+    const pass = encoder.beginRenderPass({
+      colorAttachments: [],
+      depthStencilAttachment: {
+        attachment: depthStencilAttachmentView,
+        depthLoadValue: 1.0,
+        depthStoreOp: t.params.storeOperation,
+        stencilLoadValue: 1.0,
+        stencilStoreOp: t.params.storeOperation,
+      },
+    });
+
+    pass.endPass();
+    t.device.defaultQueue.submit([encoder.finish()]);
+
+    let expectedValue = {};
+    if (t.params.storeOperation === 'clear') {
+      // If depthStencilStoreOperation was clear, the texture's depth component should be 0.0,
+      expectedValue = { Depth: 0.0 };
+    } else if (t.params.storeOperation === 'store') {
+      // If depthStencilStoreOperation was store, the texture's depth component should be 1.0,
+      expectedValue = { Depth: 1.0 };
+    }
+
+    t.expectSingleColor(depthStencilAttachment, t.params.depthStencilFormat, {
+      size: [kHeight, kWidth, 1],
+      slice: t.params.arrayLayer,
+      exp: expectedValue,
+      layout: { mipLevel: t.params.mipLevel },
+    });
+  });
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/render_pipeline/culling_tests.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/render_pipeline/culling_tests.spec.js
new file mode 100644
index 0000000..a10a987520
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/render_pipeline/culling_tests.spec.js
@@ -0,0 +1,165 @@
+/**
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `Test culling and rasterizaion state.
+
+Test coverage:
+Test all culling combinations of GPUFrontFace and GPUCullMode show the correct output.
+
+Use 2 triangles with different winding orders:
+
+- Test that the counter-clock wise triangle has correct output for:
+  - All FrontFaces (ccw, cw)
+  - All CullModes (none, front, back)
+  - All depth stencil attachment types (none, depth24plus, depth32float, depth24plus-stencil8)
+  - Some primitive topologies (triangle-list, TODO: triangle-strip)
+
+- Test that the clock wise triangle has correct output for:
+  - All FrontFaces (ccw, cw)
+  - All CullModes (none, front, back)
+  - All depth stencil attachment types (none, depth24plus, depth32float, depth24plus-stencil8)
+  - Some primitive topologies (triangle-list, TODO: triangle-strip)
+`;
+import { poptions, params } from '../../../../common/framework/params_builder.js';
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+function faceIsCulled(face, frontFace, cullMode) {
+  return cullMode !== 'none' && (frontFace === face) === (cullMode === 'front');
+}
+
+function faceColor(face, frontFace, cullMode) {
+  // front facing color is green, non front facing is red, background is blue
+  const isCulled = faceIsCulled(face, frontFace, cullMode);
+  if (!isCulled && face === frontFace) {
+    return new Uint8Array([0x00, 0xff, 0x00, 0xff]);
+  } else if (isCulled) {
+    return new Uint8Array([0x00, 0x00, 0xff, 0xff]);
+  } else {
+    return new Uint8Array([0xff, 0x00, 0x00, 0xff]);
+  }
+}
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('culling')
+  .params(
+    params()
+      .combine(poptions('frontFace', ['ccw', 'cw']))
+      .combine(poptions('cullMode', ['none', 'front', 'back']))
+      .combine(
+        poptions('depthStencilFormat', [
+          null,
+          'depth24plus',
+          'depth32float',
+          'depth24plus-stencil8',
+        ])
+      )
+
+      // TODO: test triangle-strip as well
+      .combine(poptions('primitiveTopology', ['triangle-list']))
+  )
+  .fn(t => {
+    const size = 4;
+    const format = 'rgba8unorm';
+
+    const texture = t.device.createTexture({
+      size: { width: size, height: size, depth: 1 },
+      format,
+      usage: GPUTextureUsage.OUTPUT_ATTACHMENT | GPUTextureUsage.COPY_SRC,
+    });
+
+    const depthTexture = t.params.depthStencilFormat
+      ? t.device.createTexture({
+          size: { width: size, height: size, depth: 1 },
+          format: t.params.depthStencilFormat,
+          usage: GPUTextureUsage.OUTPUT_ATTACHMENT,
+        })
+      : null;
+
+    const encoder = t.device.createCommandEncoder();
+    const pass = encoder.beginRenderPass({
+      colorAttachments: [
+        {
+          attachment: texture.createView(),
+          loadValue: { r: 0.0, g: 0.0, b: 1.0, a: 1.0 },
+        },
+      ],
+
+      depthStencilAttachment: depthTexture
+        ? {
+            attachment: depthTexture.createView(),
+            depthLoadValue: 1.0,
+            depthStoreOp: 'store',
+            stencilLoadValue: 0,
+            stencilStoreOp: 'store',
+          }
+        : undefined,
+    });
+
+    // Draw two triangles with different winding orders:
+    // 1. The top-left one is counterclockwise (CCW)
+    // 2. The bottom-right one is clockwise (CW)
+    const vertexModule = t.makeShaderModule('vertex', {
+      glsl: `#version 450
+            const vec2 pos[6] = vec2[6](vec2(-1.0f,  1.0f),
+                                        vec2(-1.0f,  0.0f),
+                                        vec2( 0.0f,  1.0f),
+                                        vec2( 0.0f, -1.0f),
+                                        vec2( 1.0f,  0.0f),
+                                        vec2( 1.0f, -1.0f));
+            void main() {
+                gl_Position = vec4(pos[gl_VertexIndex], 0.0, 1.0);
+            }`,
+    });
+
+    const fragmentModule = t.makeShaderModule('fragment', {
+      glsl: `#version 450
+      layout(location = 0) out vec4 fragColor;
+      void main() {
+        if (gl_FrontFacing) {
+          fragColor = vec4(0.0, 1.0, 0.0, 1.0);
+        } else {
+          fragColor = vec4(1.0, 0.0, 0.0, 1.0);
+        }
+      }`,
+    });
+
+    pass.setPipeline(
+      t.device.createRenderPipeline({
+        vertexStage: { module: vertexModule, entryPoint: 'main' },
+        fragmentStage: { module: fragmentModule, entryPoint: 'main' },
+        primitiveTopology: t.params.primitiveTopology,
+        rasterizationState: {
+          frontFace: t.params.frontFace,
+          cullMode: t.params.cullMode,
+        },
+
+        colorStates: [{ format }],
+        depthStencilState: depthTexture ? { format: t.params.depthStencilFormat } : undefined,
+      })
+    );
+
+    pass.draw(6, 1, 0, 0);
+    pass.endPass();
+
+    t.device.defaultQueue.submit([encoder.finish()]);
+
+    // front facing color is green, non front facing is red, background is blue
+    const kCCWTriangleTopLeftColor = faceColor('ccw', t.params.frontFace, t.params.cullMode);
+    t.expectSinglePixelIn2DTexture(
+      texture,
+      format,
+      { x: 0, y: 0 },
+      { exp: kCCWTriangleTopLeftColor }
+    );
+
+    const kCWTriangleBottomRightColor = faceColor('cw', t.params.frontFace, t.params.cullMode);
+    t.expectSinglePixelIn2DTexture(
+      texture,
+      format,
+      { x: size - 1, y: size - 1 },
+      { exp: kCWTriangleBottomRightColor }
+    );
+
+    // TODO: check the contents of the depth and stencil outputs
+  });
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/resource_init/copied_texture_clear.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/resource_init/copied_texture_clear.spec.js
index f5b8b69..28fb5b3 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/resource_init/copied_texture_clear.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/resource_init/copied_texture_clear.spec.js
@@ -1,62 +1,51 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = 'Test uninitialized textures are initialized to zero when copied.';
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = 'Test uninitialized textures are initialized to zero when copied.';
 import { makeTestGroup } from '../../../../common/framework/test_group.js';
 import { assert, unreachable } from '../../../../common/framework/util/util.js';
+
 import { ReadMethod, TextureZeroInitTest } from './texture_zero_init_test.js';
 
 class CopiedTextureClearTest extends TextureZeroInitTest {
   checkContentsByBufferCopy(texture, state, subresourceRange) {
-    for (const {
-      level: mipLevel,
-      slice
-    } of subresourceRange.each()) {
+    for (const { level: mipLevel, slice } of subresourceRange.each()) {
       assert(this.params.dimension === '2d');
+
       this.expectSingleColor(texture, this.params.format, {
         size: [this.textureWidth, this.textureHeight, 1],
         dimension: this.params.dimension,
         slice,
-        layout: {
-          mipLevel
-        },
-        exp: this.stateToTexelComponents[state]
+        layout: { mipLevel },
+        exp: this.stateToTexelComponents[state],
       });
     }
   }
 
   checkContentsByTextureCopy(texture, state, subresourceRange) {
-    for (const {
-      level,
-      slice
-    } of subresourceRange.each()) {
+    for (const { level, slice } of subresourceRange.each()) {
       assert(this.params.dimension === '2d');
+
       const width = this.textureWidth >> level;
       const height = this.textureHeight >> level;
+
       const dst = this.device.createTexture({
         size: [width, height, 1],
         format: this.params.format,
-        usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC
+        usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC,
       });
+
       const commandEncoder = this.device.createCommandEncoder();
-      commandEncoder.copyTextureToTexture({
-        texture,
-        mipLevel: level,
-        arrayLayer: slice
-      }, {
-        texture: dst,
-        mipLevel: 0,
-        arrayLayer: 0
-      }, {
-        width,
-        height,
-        depth: 1
-      });
+      commandEncoder.copyTextureToTexture(
+        { texture, mipLevel: level, origin: { x: 0, y: 0, z: slice } },
+        { texture: dst, mipLevel: 0 },
+        { width, height, depth: 1 }
+      );
+
       this.queue.submit([commandEncoder.finish()]);
+
       this.expectSingleColor(dst, this.params.format, {
         size: [width, height, 1],
-        exp: this.stateToTexelComponents[state]
+        exp: this.stateToTexelComponents[state],
       });
     }
   }
@@ -75,11 +64,12 @@
         unreachable();
     }
   }
-
 }
 
 export const g = makeTestGroup(CopiedTextureClearTest);
-g.test('uninitialized_texture_is_zero').params(TextureZeroInitTest.generateParams([ReadMethod.CopyToBuffer, ReadMethod.CopyToTexture])).fn(t => {
-  t.run();
-});
-//# sourceMappingURL=copied_texture_clear.spec.js.map
\ No newline at end of file
+
+g.test('uninitialized_texture_is_zero')
+  .params(TextureZeroInitTest.generateParams([ReadMethod.CopyToBuffer, ReadMethod.CopyToTexture]))
+  .fn(t => {
+    t.run();
+  });
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/resource_init/depth_stencil_attachment_clear.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/resource_init/depth_stencil_attachment_clear.spec.js
index 45d9f24..06004ea 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/resource_init/depth_stencil_attachment_clear.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/resource_init/depth_stencil_attachment_clear.spec.js
@@ -1,11 +1,16 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = 'Test uninitialized textures are initialized to zero when used as a depth/stencil attachment.';
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description =
+  'Test uninitialized textures are initialized to zero when used as a depth/stencil attachment.';
 import { makeTestGroup } from '../../../../common/framework/test_group.js';
 import { unreachable } from '../../../../common/framework/util/util.js';
-import { ReadMethod, TextureZeroInitTest, initializedStateAsDepth, initializedStateAsStencil } from './texture_zero_init_test.js';
+
+import {
+  ReadMethod,
+  TextureZeroInitTest,
+  initializedStateAsDepth,
+  initializedStateAsStencil,
+} from './texture_zero_init_test.js';
 
 class DepthStencilAttachmentClearTest extends TextureZeroInitTest {
   // Construct a pipeline which will render a single triangle with depth
@@ -23,9 +28,10 @@
                 vec2(-1.f, -3.f), vec2(3.f, 1.f), vec2(-1.f, 1.f));
             gl_Position = vec4(pos[gl_VertexIndex], 0.f, 1.f);
           }
-          `
-        })
+          `,
+        }),
       },
+
       fragmentStage: {
         entryPoint: 'main',
         module: this.makeShaderModule('fragment', {
@@ -37,25 +43,30 @@
             gl_FragDepth = float(${initializedStateAsDepth(state)});
             outSuccess = 1.0;
           }
-          `
-        })
+          `,
+        }),
       },
-      colorStates: [{
-        format: 'r8unorm'
-      }],
+
+      colorStates: [
+        {
+          format: 'r8unorm',
+        },
+      ],
+
       depthStencilState: {
         format,
-        depthCompare: 'equal'
+        depthCompare: 'equal',
       },
+
       primitiveTopology: 'triangle-list',
-      sampleCount
+      sampleCount,
     });
-  } // Construct a pipeline which will render a single triangle.
+  }
+
+  // Construct a pipeline which will render a single triangle.
   // The stencil compare function is set to "equal" so the fragment shader
   // will only write 1.0 to the R8Unorm output if the stencil buffer contains
   // exactly the stencil reference value.
-
-
   getStencilTestReadbackPipeline(format, sampleCount) {
     return this.device.createRenderPipeline({
       vertexStage: {
@@ -67,9 +78,10 @@
                 vec2(-1.f, -3.f), vec2(3.f, 1.f), vec2(-1.f, 1.f));
             gl_Position = vec4(pos[gl_VertexIndex], 0.f, 1.f);
           }
-          `
-        })
+          `,
+        }),
       },
+
       fragmentStage: {
         entryPoint: 'main',
         module: this.makeShaderModule('fragment', {
@@ -80,80 +92,100 @@
           void main() {
             outSuccess = 1.0;
           }
-          `
-        })
+          `,
+        }),
       },
-      colorStates: [{
-        format: 'r8unorm'
-      }],
+
+      colorStates: [
+        {
+          format: 'r8unorm',
+        },
+      ],
+
       depthStencilState: {
         format,
         stencilFront: {
-          compare: 'equal'
+          compare: 'equal',
         },
+
         stencilBack: {
-          compare: 'equal'
-        }
+          compare: 'equal',
+        },
       },
+
       primitiveTopology: 'triangle-list',
-      sampleCount
+      sampleCount,
     });
-  } // Check the contents by running either a depth or stencil test. The test will
+  }
+
+  // Check the contents by running either a depth or stencil test. The test will
   // render 1.0 to an R8Unorm texture if the depth/stencil buffer is equal to the expected
   // value. This is done by using a depth compare function and explicitly setting the depth
   // value with gl_FragDepth in the shader, or by using a stencil compare function and
   // setting the stencil reference value in the render pass.
-
-
   checkContents(texture, state, subresourceRange) {
-    for (const viewDescriptor of this.generateTextureViewDescriptorsForRendering(this.params.aspect, subresourceRange)) {
+    for (const viewDescriptor of this.generateTextureViewDescriptorsForRendering(
+      this.params.aspect,
+      subresourceRange
+    )) {
       const width = this.textureWidth >> viewDescriptor.baseMipLevel;
       const height = this.textureHeight >> viewDescriptor.baseMipLevel;
+
       const renderTexture = this.device.createTexture({
         size: [width, height, 1],
         format: 'r8unorm',
         usage: GPUTextureUsage.OUTPUT_ATTACHMENT | GPUTextureUsage.COPY_SRC,
-        sampleCount: this.params.sampleCount
+        sampleCount: this.params.sampleCount,
       });
+
       let resolveTexture = undefined;
       let resolveTarget = undefined;
-
       if (this.params.sampleCount > 1) {
         resolveTexture = this.device.createTexture({
           size: [width, height, 1],
           format: 'r8unorm',
-          usage: GPUTextureUsage.OUTPUT_ATTACHMENT | GPUTextureUsage.COPY_SRC
+          usage: GPUTextureUsage.OUTPUT_ATTACHMENT | GPUTextureUsage.COPY_SRC,
         });
+
         resolveTarget = resolveTexture.createView();
       }
 
       const commandEncoder = this.device.createCommandEncoder();
       const pass = commandEncoder.beginRenderPass({
-        colorAttachments: [{
-          attachment: renderTexture.createView(),
-          resolveTarget,
-          loadValue: [0, 0, 0, 0],
-          storeOp: 'store'
-        }],
+        colorAttachments: [
+          {
+            attachment: renderTexture.createView(),
+            resolveTarget,
+            loadValue: [0, 0, 0, 0],
+            storeOp: 'store',
+          },
+        ],
+
         depthStencilAttachment: {
           attachment: texture.createView(viewDescriptor),
           depthStoreOp: 'store',
           depthLoadValue: 'load',
           stencilStoreOp: 'store',
-          stencilLoadValue: 'load'
-        }
+          stencilLoadValue: 'load',
+        },
       });
 
       switch (this.params.readMethod) {
         case ReadMethod.DepthTest:
-          pass.setPipeline(this.getDepthTestReadbackPipeline(state, this.params.format, this.params.sampleCount));
+          pass.setPipeline(
+            this.getDepthTestReadbackPipeline(state, this.params.format, this.params.sampleCount)
+          );
+
           break;
 
         case ReadMethod.StencilTest:
-          pass.setPipeline(this.getStencilTestReadbackPipeline(this.params.format, this.params.sampleCount)); // Set the stencil reference. The rendering pipeline uses stencil compare function "equal"
+          pass.setPipeline(
+            this.getStencilTestReadbackPipeline(this.params.format, this.params.sampleCount)
+          );
+
+          // Set the stencil reference. The rendering pipeline uses stencil compare function "equal"
           // so this pass will write 1.0 to the output only if the stencil buffer is equal to this
           // reference value.
-
           pass.setStencilReference(initializedStateAsStencil(state));
           break;
 
@@ -163,18 +195,19 @@
 
       pass.draw(3, 1, 0, 0);
       pass.endPass();
+
       this.queue.submit([commandEncoder.finish()]);
+
       this.expectSingleColor(resolveTexture || renderTexture, 'r8unorm', {
         size: [width, height, 1],
-        exp: {
-          R: 1
-        }
+        exp: { R: 1 },
       });
     }
   }
-
 }
 
 export const g = makeTestGroup(DepthStencilAttachmentClearTest);
-g.test('uninitialized_texture_is_zero').params(TextureZeroInitTest.generateParams([ReadMethod.DepthTest, ReadMethod.StencilTest])).fn(t => t.run());
-//# sourceMappingURL=depth_stencil_attachment_clear.spec.js.map
\ No newline at end of file
+
+g.test('uninitialized_texture_is_zero')
+  .params(TextureZeroInitTest.generateParams([ReadMethod.DepthTest, ReadMethod.StencilTest]))
+  .fn(t => t.run());
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/resource_init/sampled_texture_clear.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/resource_init/sampled_texture_clear.spec.js
index cac944e..efbf5e7 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/resource_init/sampled_texture_clear.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/resource_init/sampled_texture_clear.spec.js
@@ -1,12 +1,18 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = 'Test uninitialized textures are initialized to zero when sampled.';
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = 'Test uninitialized textures are initialized to zero when sampled.';
 import { makeTestGroup } from '../../../../common/framework/test_group.js';
 import { assert } from '../../../../common/framework/util/util.js';
+
 import { getTexelDataRepresentation } from '../../../util/texture/texelData.js';
-import { ReadMethod, TextureZeroInitTest, initializedStateAsFloat, initializedStateAsSint, initializedStateAsUint } from './texture_zero_init_test.js';
+
+import {
+  ReadMethod,
+  TextureZeroInitTest,
+  initializedStateAsFloat,
+  initializedStateAsSint,
+  initializedStateAsUint,
+} from './texture_zero_init_test.js';
 
 class SampledTextureClearTest extends TextureZeroInitTest {
   getSamplingReadbackPipeline(prefix, sampleCount, dimension) {
@@ -14,7 +20,11 @@
     const MS = sampleCount > 1 ? 'MS' : '';
     const XD = dimension.toUpperCase();
     const componentCount = componentOrder.length;
-    const indexExpression = componentCount === 1 ? componentOrder[0].toLowerCase() : componentOrder.map(c => c.toLowerCase()).join('') + '[i]';
+    const indexExpression =
+      componentCount === 1
+        ? componentOrder[0].toLowerCase()
+        : componentOrder.map(c => c.toLowerCase()).join('') + '[i]';
+
     const glsl = `#version 310 es
       precision highp float;
       precision highp ${prefix}texture${XD}${MS};
@@ -57,31 +67,28 @@
           ivec2(gl_GlobalInvocationID.xy), level));
       }
       `;
+
     return this.device.createComputePipeline({
       layout: undefined,
       computeStage: {
         entryPoint: 'main',
-        module: this.makeShaderModule('compute', {
-          glsl
-        })
-      }
+        module: this.makeShaderModule('compute', { glsl }),
+      },
     });
   }
 
   checkContents(texture, state, subresourceRange) {
     assert(this.params.dimension === '2d');
+
     const sampler = this.device.createSampler();
 
-    for (const {
-      level,
-      slices
-    } of subresourceRange.mipLevels()) {
+    for (const { level, slices } of subresourceRange.mipLevels()) {
       const width = this.textureWidth >> level;
       const height = this.textureHeight >> level;
+
       let readbackTypedArray = Float32Array;
       let prefix = '';
       let expectedShaderValue = initializedStateAsFloat(state);
-
       if (this.params.format.indexOf('sint') !== -1) {
         prefix = 'i';
         expectedShaderValue = initializedStateAsSint(state);
@@ -92,45 +99,60 @@
         readbackTypedArray = Uint32Array;
       }
 
-      const computePipeline = this.getSamplingReadbackPipeline(prefix, this.params.sampleCount, this.params.dimension);
+      const computePipeline = this.getSamplingReadbackPipeline(
+        prefix,
+        this.params.sampleCount,
+        this.params.dimension
+      );
 
       for (const slice of slices) {
-        const [ubo, uboMapping] = this.device.createBufferMapped({
+        const ubo = this.device.createBuffer({
+          mappedAtCreation: true,
           size: 4,
-          usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
+          usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
         });
-        new Int32Array(uboMapping, 0, 1)[0] = level;
+
+        new Int32Array(ubo.getMappedRange(), 0, 1)[0] = level;
         ubo.unmap();
-        const byteLength = width * height * Uint32Array.BYTES_PER_ELEMENT * getTexelDataRepresentation(this.params.format).componentOrder.length;
+
+        const byteLength =
+          width *
+          height *
+          Uint32Array.BYTES_PER_ELEMENT *
+          getTexelDataRepresentation(this.params.format).componentOrder.length;
         const resultBuffer = this.device.createBuffer({
           size: byteLength,
-          usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+          usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
         });
+
         const bindGroup = this.device.createBindGroup({
           layout: computePipeline.getBindGroupLayout(0),
-          entries: [{
-            binding: 0,
-            resource: {
-              buffer: ubo
-            }
-          }, {
-            binding: 1,
-            resource: texture.createView({
-              baseMipLevel: 0,
-              mipLevelCount: this.params.mipLevelCount,
-              baseArrayLayer: slice,
-              arrayLayerCount: 1
-            })
-          }, {
-            binding: 2,
-            resource: sampler
-          }, {
-            binding: 3,
-            resource: {
-              buffer: resultBuffer
-            }
-          }]
+          entries: [
+            {
+              binding: 0,
+              resource: { buffer: ubo },
+            },
+
+            {
+              binding: 1,
+              resource: texture.createView({
+                baseMipLevel: 0,
+                mipLevelCount: this.params.mipLevelCount,
+                baseArrayLayer: slice,
+                arrayLayerCount: 1,
+              }),
+            },
+
+            { binding: 2, resource: sampler },
+            {
+              binding: 3,
+              resource: {
+                buffer: resultBuffer,
+              },
+            },
+          ],
         });
+
         const commandEncoder = this.device.createCommandEncoder();
         const pass = commandEncoder.beginComputePass();
         pass.setPipeline(computePipeline);
@@ -139,15 +161,18 @@
         pass.endPass();
         this.queue.submit([commandEncoder.finish()]);
         ubo.destroy();
+
         const mappedResultBuffer = this.createCopyForMapRead(resultBuffer, 0, byteLength);
         resultBuffer.destroy();
+
         this.eventualAsyncExpectation(async niceStack => {
-          const actual = await mappedResultBuffer.mapReadAsync();
+          await mappedResultBuffer.mapAsync(GPUMapMode.READ);
+          const actual = mappedResultBuffer.getMappedRange();
           const expected = new readbackTypedArray(new ArrayBuffer(actual.byteLength));
-          expected.fill(expectedShaderValue); // TODO: Have a better way to determine approximately equal, maybe in ULPs.
+          expected.fill(expectedShaderValue);
 
+          // TODO: Have a better way to determine approximately equal, maybe in ULPs.
           let tolerance;
-
           if (this.params.format === 'rgb10a2unorm') {
             tolerance = i => {
               // The alpha component is only two bits. Use a generous tolerance.
@@ -158,20 +183,19 @@
           }
 
           const check = this.checkBuffer(new readbackTypedArray(actual), expected, tolerance);
-
           if (check !== undefined) {
             niceStack.message = check;
             this.rec.expectationFailed(niceStack);
           }
-
           mappedResultBuffer.destroy();
         });
       }
     }
   }
-
 }
 
 export const g = makeTestGroup(SampledTextureClearTest);
-g.test('uninitialized_texture_is_zero').params(TextureZeroInitTest.generateParams([ReadMethod.Sample])).fn(t => t.run());
-//# sourceMappingURL=sampled_texture_clear.spec.js.map
\ No newline at end of file
+
+g.test('uninitialized_texture_is_zero')
+  .params(TextureZeroInitTest.generateParams([ReadMethod.Sample]))
+  .fn(t => t.run());
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/resource_init/texture_zero_init_test.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/resource_init/texture_zero_init_test.js
index 77a100f..381d7dc 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/resource_init/texture_zero_init_test.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/operation/resource_init/texture_zero_init_test.js
@@ -1,136 +1,130 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
-
-import { TextureUsage } from '../../../../common/constants.js';
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ function _defineProperty(obj, key, value) {
+  if (key in obj) {
+    Object.defineProperty(obj, key, {
+      value: value,
+      enumerable: true,
+      configurable: true,
+      writable: true,
+    });
+  } else {
+    obj[key] = value;
+  }
+  return obj;
+}
 import { params, poptions, pbool } from '../../../../common/framework/params_builder.js';
 import { assert, unreachable } from '../../../../common/framework/util/util.js';
-import { kTextureAspects, kTextureFormatInfo, kTextureFormats } from '../../../capability_info.js';
+import {
+  kTextureAspects,
+  kUncompressedTextureFormatInfo,
+  kUncompressedTextureFormats,
+} from '../../../capability_info.js';
 import { GPUTest } from '../../../gpu_test.js';
 import { createTextureUploadBuffer } from '../../../util/texture/layout.js';
 import { SubresourceRange } from '../../../util/texture/subresource.js';
 import { getTexelDataRepresentation } from '../../../util/texture/texelData.js';
 var UninitializeMethod;
-
 (function (UninitializeMethod) {
-  UninitializeMethod["Creation"] = "Creation";
-  UninitializeMethod["StoreOpClear"] = "StoreOpClear";
+  UninitializeMethod['Creation'] = 'Creation';
+  UninitializeMethod['StoreOpClear'] = 'StoreOpClear';
 })(UninitializeMethod || (UninitializeMethod = {}));
 
 const kUninitializeMethods = Object.keys(UninitializeMethod);
-export let ReadMethod;
 
+export let ReadMethod;
 (function (ReadMethod) {
-  ReadMethod["Sample"] = "Sample";
-  ReadMethod["CopyToBuffer"] = "CopyToBuffer";
-  ReadMethod["CopyToTexture"] = "CopyToTexture";
-  ReadMethod["DepthTest"] = "DepthTest";
-  ReadMethod["StencilTest"] = "StencilTest";
-  ReadMethod["ColorBlending"] = "ColorBlending";
-  ReadMethod["Storage"] = "Storage";
+  ReadMethod['Sample'] = 'Sample';
+  ReadMethod['CopyToBuffer'] = 'CopyToBuffer';
+  ReadMethod['CopyToTexture'] = 'CopyToTexture';
+  ReadMethod['DepthTest'] = 'DepthTest';
+  ReadMethod['StencilTest'] = 'StencilTest';
+  ReadMethod['ColorBlending'] = 'ColorBlending';
+  ReadMethod['Storage'] = 'Storage';
 })(ReadMethod || (ReadMethod = {}));
 
-const kMipLevelCounts = [1, 5]; // For each mip level count, define the mip ranges to leave uninitialized.
+const kMipLevelCounts = [1, 5];
 
+// For each mip level count, define the mip ranges to leave uninitialized.
 const kUninitializedMipRangesToTest = {
-  1: [{
-    begin: 0,
-    end: 1
-  }],
-  // Test the only mip
-  5: [{
-    begin: 0,
-    end: 2
-  }, {
-    begin: 3,
-    end: 4
-  }] // Test a range and a single mip
+  1: [{ begin: 0, end: 1 }], // Test the only mip
+  5: [
+    { begin: 0, end: 2 },
+    { begin: 3, end: 4 },
+  ],
+  // Test a range and a single mip
+};
 
-}; // Test with these sample counts.
+// Test with these sample counts.
 
-const kSampleCounts = [1, 4]; // Test with these slice counts. This means the depth of a 3d texture or the number
+const kSampleCounts = [1, 4];
+
+// Test with these slice counts. This means the depth of a 3d texture or the number
 // or layers in a 2D or a 1D texture array.
 
 // For each slice count, define the slices to leave uninitialized.
 const kUninitializedSliceRangesToTest = {
-  1: [{
-    begin: 0,
-    end: 1
-  }],
-  // Test the only slice
-  7: [{
-    begin: 2,
-    end: 4
-  }, {
-    begin: 6,
-    end: 7
-  }] // Test a range and a single slice
+  1: [{ begin: 0, end: 1 }], // Test the only slice
+  7: [
+    { begin: 2, end: 4 },
+    { begin: 6, end: 7 },
+  ],
+  // Test a range and a single slice
+};
 
-}; // Test with these combinations of texture dimension and sliceCount.
+// Test with these combinations of texture dimension and sliceCount.
+const kCreationSizes = [
+  // { dimension: '1d', sliceCount: 7 }, // TODO: 1d textures
+  { dimension: '2d', sliceCount: 1 }, // 2d textures
+  { dimension: '2d', sliceCount: 7 }, // 2d array textures
+  // { dimension: '3d', sliceCount: 7 }, // TODO: 3d textures
+];
 
-const kCreationSizes = [// { dimension: '1d', sliceCount: 7 }, // TODO: 1d textures
-{
-  dimension: '2d',
-  sliceCount: 1
-}, // 2d textures
-{
-  dimension: '2d',
-  sliceCount: 7
-} // 2d array textures
-// { dimension: '3d', sliceCount: 7 }, // TODO: 3d textures
-]; // Enums to abstract over color / depth / stencil values in textures. Depending on the texture format,
+// Enums to abstract over color / depth / stencil values in textures. Depending on the texture format,
 // the data for each value may have a different representation. These enums are converted to a
 // representation such that their values can be compared. ex.) An integer is needed to upload to an
 // unsigned normalized format, but its value is read as a float in the shader.
-
 export let InitializedState;
-
 (function (InitializedState) {
-  InitializedState[InitializedState["Canary"] = 0] = "Canary";
-  InitializedState[InitializedState["Zero"] = 1] = "Zero";
+  InitializedState[(InitializedState['Canary'] = 0)] = 'Canary';
+  InitializedState[(InitializedState['Zero'] = 1)] = 'Zero';
 })(InitializedState || (InitializedState = {}));
 
 export function initializedStateAsFloat(state) {
   switch (state) {
     case InitializedState.Zero:
       return 0;
-
     case InitializedState.Canary:
       return 1;
-
     default:
       unreachable();
   }
 }
+
 export function initializedStateAsUint(state) {
   switch (state) {
     case InitializedState.Zero:
       return 0;
-
     case InitializedState.Canary:
       return 255;
-
     default:
       unreachable();
   }
 }
+
 export function initializedStateAsSint(state) {
   switch (state) {
     case InitializedState.Zero:
       return 0;
-
     case InitializedState.Canary:
       return -1;
-
     default:
       unreachable();
   }
 }
+
 export function initializedStateAsColor(state, format) {
   let value;
-
   if (format.indexOf('uint') !== -1) {
     value = initializedStateAsUint(state);
   } else if (format.indexOf('sint') !== -1) {
@@ -138,45 +132,40 @@
   } else {
     value = initializedStateAsFloat(state);
   }
-
   return [value, value, value, value];
 }
+
 export function initializedStateAsDepth(state) {
   switch (state) {
     case InitializedState.Zero:
       return 0;
-
     case InitializedState.Canary:
       return 1;
-
     default:
       unreachable();
   }
 }
+
 export function initializedStateAsStencil(state) {
   switch (state) {
     case InitializedState.Zero:
       return 0;
-
     case InitializedState.Canary:
       return 42;
-
     default:
       unreachable();
   }
 }
 
 function getRequiredTextureUsage(format, sampleCount, uninitializeMethod, readMethod) {
-  let usage = TextureUsage.CopyDst;
+  let usage = GPUTextureUsage.COPY_DST;
 
   switch (uninitializeMethod) {
     case UninitializeMethod.Creation:
       break;
-
     case UninitializeMethod.StoreOpClear:
-      usage |= TextureUsage.OutputAttachment;
+      usage |= GPUTextureUsage.OUTPUT_ATTACHMENT;
       break;
-
     default:
       unreachable();
   }
@@ -184,23 +173,19 @@
   switch (readMethod) {
     case ReadMethod.CopyToBuffer:
     case ReadMethod.CopyToTexture:
-      usage |= TextureUsage.CopySrc;
+      usage |= GPUTextureUsage.COPY_SRC;
       break;
-
     case ReadMethod.Sample:
-      usage |= TextureUsage.Sampled;
+      usage |= GPUTextureUsage.SAMPLED;
       break;
-
     case ReadMethod.Storage:
-      usage |= TextureUsage.Storage;
+      usage |= GPUTextureUsage.STORAGE;
       break;
-
     case ReadMethod.DepthTest:
     case ReadMethod.StencilTest:
     case ReadMethod.ColorBlending:
-      usage |= TextureUsage.OutputAttachment;
+      usage |= GPUTextureUsage.OUTPUT_ATTACHMENT;
       break;
-
     default:
       unreachable();
   }
@@ -208,14 +193,14 @@
   if (sampleCount > 1) {
     // Copies to multisampled textures are not allowed. We need OutputAttachment to initialize
     // canary data in multisampled textures.
-    usage |= TextureUsage.OutputAttachment;
+    usage |= GPUTextureUsage.OUTPUT_ATTACHMENT;
   }
 
-  if (!kTextureFormatInfo[format].copyable) {
+  if (!kUncompressedTextureFormatInfo[format].copyDst) {
     // Copies are not possible. We need OutputAttachment to initialize
     // canary data.
-    assert(kTextureFormatInfo[format].renderable);
-    usage |= TextureUsage.OutputAttachment;
+    assert(kUncompressedTextureFormatInfo[format].renderable);
+    usage |= GPUTextureUsage.OUTPUT_ATTACHMENT;
   }
 
   return usage;
@@ -224,8 +209,7 @@
 export class TextureZeroInitTest extends GPUTest {
   constructor(rec, params) {
     super(rec, params);
-
-    _defineProperty(this, "stateToTexelComponents", void 0);
+    _defineProperty(this, 'stateToTexelComponents', void 0);
 
     const stateToTexelComponents = state => {
       const [R, G, B, A] = initializedStateAsColor(state, this.params.format);
@@ -235,13 +219,13 @@
         B,
         A,
         Depth: initializedStateAsDepth(state),
-        Stencil: initializedStateAsStencil(state)
+        Stencil: initializedStateAsStencil(state),
       };
     };
 
     this.stateToTexelComponents = {
       [InitializedState.Zero]: stateToTexelComponents(InitializedState.Zero),
-      [InitializedState.Canary]: stateToTexelComponents(InitializedState.Canary)
+      [InitializedState.Canary]: stateToTexelComponents(InitializedState.Canary),
     };
   }
 
@@ -251,67 +235,48 @@
 
   get textureWidth() {
     let width = 1 << this.params.mipLevelCount;
-
     if (this.params.nonPowerOfTwo) {
       width = 2 * width - 1;
     }
-
     return width;
   }
 
   get textureHeight() {
     let height = 1 << this.params.mipLevelCount;
-
     if (this.params.nonPowerOfTwo) {
       height = 2 * height - 1;
     }
-
     return height;
-  } // Used to iterate subresources and check that their uninitialized contents are zero when accessed
+  }
 
-
+  // Used to iterate subresources and check that their uninitialized contents are zero when accessed
   *iterateUninitializedSubresources() {
     for (const mipRange of kUninitializedMipRangesToTest[this.params.mipLevelCount]) {
       for (const sliceRange of kUninitializedSliceRangesToTest[this.params.sliceCount]) {
-        yield new SubresourceRange({
-          mipRange,
-          sliceRange
-        });
+        yield new SubresourceRange({ mipRange, sliceRange });
       }
     }
-  } // Used to iterate and initialize other subresources not checked for zero-initialization.
+  }
+
+  // Used to iterate and initialize other subresources not checked for zero-initialization.
   // Zero-initialization of uninitialized subresources should not have side effects on already
   // initialized subresources.
-
-
   *iterateInitializedSubresources() {
     const uninitialized = new Array(this.params.mipLevelCount);
-
     for (let level = 0; level < uninitialized.length; ++level) {
       uninitialized[level] = new Array(this.params.sliceCount);
     }
-
     for (const subresources of this.iterateUninitializedSubresources()) {
-      for (const {
-        level,
-        slice
-      } of subresources.each()) {
+      for (const { level, slice } of subresources.each()) {
         uninitialized[level][slice] = true;
       }
     }
-
     for (let level = 0; level < uninitialized.length; ++level) {
       for (let slice = 0; slice < uninitialized[level].length; ++slice) {
         if (!uninitialized[level][slice]) {
           yield new SubresourceRange({
-            mipRange: {
-              begin: level,
-              count: 1
-            },
-            sliceRange: {
-              begin: slice,
-              count: 1
-            }
+            mipRange: { begin: level, count: 1 },
+            sliceRange: { begin: slice, count: 1 },
           });
         }
       }
@@ -321,52 +286,57 @@
   *generateTextureViewDescriptorsForRendering(aspect, subresourceRange) {
     const viewDescriptor = {
       dimension: '2d',
-      aspect
+      aspect,
     };
 
     if (subresourceRange === undefined) {
       return viewDescriptor;
     }
 
-    for (const {
-      level,
-      slice
-    } of subresourceRange.each()) {
-      yield { ...viewDescriptor,
+    for (const { level, slice } of subresourceRange.each()) {
+      yield {
+        ...viewDescriptor,
         baseMipLevel: level,
         mipLevelCount: 1,
         baseArrayLayer: slice,
-        arrayLayerCount: 1
+        arrayLayerCount: 1,
       };
     }
   }
 
   initializeWithStoreOp(state, texture, subresourceRange) {
     const commandEncoder = this.device.createCommandEncoder();
-
-    for (const viewDescriptor of this.generateTextureViewDescriptorsForRendering(this.params.aspect, subresourceRange)) {
-      if (kTextureFormatInfo[this.params.format].color) {
-        commandEncoder.beginRenderPass({
-          colorAttachments: [{
-            attachment: texture.createView(viewDescriptor),
-            storeOp: 'store',
-            loadValue: initializedStateAsColor(state, this.params.format)
-          }]
-        }).endPass();
+    for (const viewDescriptor of this.generateTextureViewDescriptorsForRendering(
+      this.params.aspect,
+      subresourceRange
+    )) {
+      if (kUncompressedTextureFormatInfo[this.params.format].color) {
+        commandEncoder
+          .beginRenderPass({
+            colorAttachments: [
+              {
+                attachment: texture.createView(viewDescriptor),
+                storeOp: 'store',
+                loadValue: initializedStateAsColor(state, this.params.format),
+              },
+            ],
+          })
+          .endPass();
       } else {
-        commandEncoder.beginRenderPass({
-          colorAttachments: [],
-          depthStencilAttachment: {
-            attachment: texture.createView(viewDescriptor),
-            depthStoreOp: 'store',
-            depthLoadValue: initializedStateAsDepth(state),
-            stencilStoreOp: 'store',
-            stencilLoadValue: initializedStateAsStencil(state)
-          }
-        }).endPass();
+        commandEncoder
+          .beginRenderPass({
+            colorAttachments: [],
+            depthStencilAttachment: {
+              attachment: texture.createView(viewDescriptor),
+              depthStoreOp: 'store',
+              depthLoadValue: initializedStateAsDepth(state),
+              stencilStoreOp: 'store',
+              stencilLoadValue: initializedStateAsStencil(state),
+            },
+          })
+          .endPass();
       }
     }
-
     this.queue.submit([commandEncoder.finish()]);
   }
 
@@ -379,46 +349,51 @@
 
     const firstSubresource = subresourceRange.each().next().value;
     assert(typeof firstSubresource !== 'undefined');
+
     const largestWidth = this.textureWidth >> firstSubresource.level;
     const largestHeight = this.textureHeight >> firstSubresource.level;
-    const texelData = new Uint8Array(getTexelDataRepresentation(this.params.format).getBytes(this.stateToTexelComponents[state]));
-    const {
-      buffer,
-      bytesPerRow,
-      rowsPerImage
-    } = createTextureUploadBuffer(texelData, this.device, this.params.format, this.params.dimension, [largestWidth, largestHeight, 1]);
+
+    const texelData = new Uint8Array(
+      getTexelDataRepresentation(this.params.format).getBytes(this.stateToTexelComponents[state])
+    );
+
+    const { buffer, bytesPerRow, rowsPerImage } = createTextureUploadBuffer(
+      texelData,
+      this.device,
+      this.params.format,
+      this.params.dimension,
+      [largestWidth, largestHeight, 1]
+    );
+
     const commandEncoder = this.device.createCommandEncoder();
 
-    for (const {
-      level,
-      slice
-    } of subresourceRange.each()) {
+    for (const { level, slice } of subresourceRange.each()) {
       const width = this.textureWidth >> level;
       const height = this.textureHeight >> level;
-      commandEncoder.copyBufferToTexture({
-        buffer,
-        bytesPerRow,
-        rowsPerImage
-      }, {
-        texture,
-        mipLevel: level,
-        arrayLayer: slice
-      }, {
-        width,
-        height,
-        depth: 1
-      });
-    }
 
+      commandEncoder.copyBufferToTexture(
+        {
+          buffer,
+          bytesPerRow,
+          rowsPerImage,
+        },
+
+        { texture, mipLevel: level, origin: { x: 0, y: 0, z: slice } },
+        { width, height, depth: 1 }
+      );
+    }
     this.queue.submit([commandEncoder.finish()]);
     buffer.destroy();
   }
 
   initializeTexture(texture, state, subresourceRange) {
-    if (this.params.sampleCount > 1 || !kTextureFormatInfo[this.params.format].copyable) {
+    if (
+      this.params.sampleCount > 1 ||
+      !kUncompressedTextureFormatInfo[this.params.format].copyDst
+    ) {
       // Copies to multisampled textures not yet specified.
       // Use a storeOp for now.
-      assert(kTextureFormatInfo[this.params.format].renderable);
+      assert(kUncompressedTextureFormatInfo[this.params.format].renderable);
       this.initializeWithStoreOp(state, texture, subresourceRange);
     } else {
       this.initializeWithCopy(texture, state, subresourceRange);
@@ -428,78 +403,106 @@
   discardTexture(texture, subresourceRange) {
     const commandEncoder = this.device.createCommandEncoder();
 
-    for (const desc of this.generateTextureViewDescriptorsForRendering(this.params.aspect, subresourceRange)) {
-      if (kTextureFormatInfo[this.params.format].color) {
-        commandEncoder.beginRenderPass({
-          colorAttachments: [{
-            attachment: texture.createView(desc),
-            storeOp: 'clear',
-            loadValue: 'load'
-          }]
-        }).endPass();
+    for (const desc of this.generateTextureViewDescriptorsForRendering(
+      this.params.aspect,
+      subresourceRange
+    )) {
+      if (kUncompressedTextureFormatInfo[this.params.format].color) {
+        commandEncoder
+          .beginRenderPass({
+            colorAttachments: [
+              {
+                attachment: texture.createView(desc),
+                storeOp: 'clear',
+                loadValue: 'load',
+              },
+            ],
+          })
+          .endPass();
       } else {
-        commandEncoder.beginRenderPass({
-          colorAttachments: [],
-          depthStencilAttachment: {
-            attachment: texture.createView(desc),
-            depthStoreOp: 'clear',
-            depthLoadValue: 'load',
-            stencilStoreOp: 'clear',
-            stencilLoadValue: 'load'
-          }
-        }).endPass();
+        commandEncoder
+          .beginRenderPass({
+            colorAttachments: [],
+            depthStencilAttachment: {
+              attachment: texture.createView(desc),
+              depthStoreOp: 'clear',
+              depthLoadValue: 'load',
+              stencilStoreOp: 'clear',
+              stencilLoadValue: 'load',
+            },
+          })
+          .endPass();
       }
     }
-
     this.queue.submit([commandEncoder.finish()]);
   }
 
   static generateParams(readMethods) {
-    return (// TODO: Consider making a list of "valid" texture descriptors in capability_info.
-      params().combine(poptions('format', kTextureFormats)).combine(poptions('aspect', kTextureAspects)).unless(({
-        format,
-        aspect
-      }) => aspect === 'depth-only' && !kTextureFormatInfo[format].depth || aspect === 'stencil-only' && !kTextureFormatInfo[format].stencil).combine(poptions('mipLevelCount', kMipLevelCounts)).combine(poptions('sampleCount', kSampleCounts)) // Multisampled textures may only have one mip
-      .unless(({
-        sampleCount,
-        mipLevelCount
-      }) => sampleCount > 1 && mipLevelCount > 1).combine(poptions('uninitializeMethod', kUninitializeMethods)).combine(poptions('readMethod', readMethods)).unless(({
-        readMethod,
-        format
-      }) => // It doesn't make sense to copy from a packed depth format.
-      // This is not specified yet, but it will probably be disallowed as the bits may
-      // be vendor-specific.
-      // TODO: Test copying out of the stencil aspect.
-      (readMethod === ReadMethod.CopyToBuffer || readMethod === ReadMethod.CopyToTexture) && (format === 'depth24plus' || format === 'depth24plus-stencil8')).unless(({
-        readMethod,
-        format
-      }) => readMethod === ReadMethod.DepthTest && !kTextureFormatInfo[format].depth || readMethod === ReadMethod.StencilTest && !kTextureFormatInfo[format].stencil || readMethod === ReadMethod.ColorBlending && !kTextureFormatInfo[format].color || // TODO: Test with depth sampling
-      readMethod === ReadMethod.Sample && kTextureFormatInfo[format].depth).unless(({
-        readMethod,
-        sampleCount
-      }) => // We can only read from multisampled textures by sampling.
-      sampleCount > 1 && (readMethod === ReadMethod.CopyToBuffer || readMethod === ReadMethod.CopyToTexture)).combine(kCreationSizes) // Multisampled 3D / 2D array textures not supported.
-      .unless(({
-        sampleCount,
-        sliceCount
-      }) => sampleCount > 1 && sliceCount > 1).filter(({
-        format,
-        sampleCount,
-        uninitializeMethod,
-        readMethod
-      }) => {
-        const usage = getRequiredTextureUsage(format, sampleCount, uninitializeMethod, readMethod);
+    return (
+      // TODO: Consider making a list of "valid" texture descriptors in capability_info.
+      params()
+        .combine(poptions('format', kUncompressedTextureFormats))
+        .combine(poptions('aspect', kTextureAspects))
+        .unless(
+          ({ format, aspect }) =>
+            (aspect === 'depth-only' && !kUncompressedTextureFormatInfo[format].depth) ||
+            (aspect === 'stencil-only' && !kUncompressedTextureFormatInfo[format].stencil)
+        )
+        .combine(poptions('mipLevelCount', kMipLevelCounts))
+        .combine(poptions('sampleCount', kSampleCounts))
+        // Multisampled textures may only have one mip
+        .unless(({ sampleCount, mipLevelCount }) => sampleCount > 1 && mipLevelCount > 1)
+        .combine(poptions('uninitializeMethod', kUninitializeMethods))
+        .combine(poptions('readMethod', readMethods))
+        .unless(
+          ({ readMethod, format }) =>
+            // It doesn't make sense to copy from a packed depth format.
+            // This is not specified yet, but it will probably be disallowed as the bits may
+            // be vendor-specific.
+            // TODO: Test copying out of the stencil aspect.
+            (readMethod === ReadMethod.CopyToBuffer || readMethod === ReadMethod.CopyToTexture) &&
+            (format === 'depth24plus' || format === 'depth24plus-stencil8')
+        )
+        .unless(({ readMethod, format }) => {
+          const info = kUncompressedTextureFormatInfo[format];
+          return (
+            (readMethod === ReadMethod.DepthTest && !info.depth) ||
+            (readMethod === ReadMethod.StencilTest && !info.stencil) ||
+            (readMethod === ReadMethod.ColorBlending && !info.color) ||
+            // TODO: Test with depth sampling
+            (readMethod === ReadMethod.Sample && info.depth)
+          );
+        })
+        .unless(
+          ({ readMethod, sampleCount }) =>
+            // We can only read from multisampled textures by sampling.
+            sampleCount > 1 &&
+            (readMethod === ReadMethod.CopyToBuffer || readMethod === ReadMethod.CopyToTexture)
+        )
+        .combine(kCreationSizes)
+        // Multisampled 3D / 2D array textures not supported.
+        .unless(({ sampleCount, sliceCount }) => sampleCount > 1 && sliceCount > 1)
+        .filter(({ format, sampleCount, uninitializeMethod, readMethod }) => {
+          const usage = getRequiredTextureUsage(
+            format,
+            sampleCount,
+            uninitializeMethod,
+            readMethod
+          );
 
-        if (usage & TextureUsage.OutputAttachment && !kTextureFormatInfo[format].renderable) {
-          return false;
-        }
+          const info = kUncompressedTextureFormatInfo[format];
 
-        if (usage & TextureUsage.Storage && !kTextureFormatInfo[format].storage) {
-          return false;
-        }
+          if (usage & GPUTextureUsage.OUTPUT_ATTACHMENT && !info.renderable) {
+            return false;
+          }
 
-        return true;
-      }).combine(pbool('nonPowerOfTwo'))
+          if (usage & GPUTextureUsage.STORAGE && !info.storage) {
+            return false;
+          }
+
+          return true;
+        })
+        .combine(pbool('nonPowerOfTwo'))
     );
   }
 
@@ -511,18 +514,21 @@
       sliceCount,
       sampleCount,
       uninitializeMethod,
-      readMethod
+      readMethod,
     } = this.params;
+
     const usage = getRequiredTextureUsage(format, sampleCount, uninitializeMethod, readMethod);
+
     const texture = this.device.createTexture({
       size: [this.textureWidth, this.textureHeight, sliceCount],
       format,
       dimension,
       usage,
       mipLevelCount,
-      sampleCount
-    }); // Initialize some subresources with canary values
+      sampleCount,
+    });
 
+    // Initialize some subresources with canary values
     for (const subresourceRange of this.iterateInitializedSubresources()) {
       this.initializeTexture(texture, InitializedState.Canary, subresourceRange);
     }
@@ -530,34 +536,28 @@
     switch (uninitializeMethod) {
       case UninitializeMethod.Creation:
         break;
-
       case UninitializeMethod.StoreOpClear:
         // Initialize the rest of the resources.
         for (const subresourceRange of this.iterateUninitializedSubresources()) {
           this.initializeTexture(texture, InitializedState.Canary, subresourceRange);
-        } // Then use a store op to discard their contents.
-
-
+        }
+        // Then use a store op to discard their contents.
         for (const subresourceRange of this.iterateUninitializedSubresources()) {
           this.discardTexture(texture, subresourceRange);
         }
-
         break;
-
       default:
         unreachable();
-    } // Check that all uninitialized resources are zero.
+    }
 
-
+    // Check that all uninitialized resources are zero.
     for (const subresourceRange of this.iterateUninitializedSubresources()) {
       this.checkContents(texture, InitializedState.Zero, subresourceRange);
-    } // Check the all other resources are unchanged.
+    }
 
-
+    // Check the all other resources are unchanged.
     for (const subresourceRange of this.iterateInitializedSubresources()) {
       this.checkContents(texture, InitializedState.Canary, subresourceRange);
     }
   }
-
 }
-//# sourceMappingURL=texture_zero_init_test.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/copyBufferToBuffer.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/copyBufferToBuffer.spec.js
new file mode 100644
index 0000000..5fd48773
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/copyBufferToBuffer.spec.js
@@ -0,0 +1,271 @@
+/**
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `
+copyBufferToBuffer tests.
+
+Test Plan:
+* Buffer is valid/invalid
+  - the source buffer is invalid
+  - the destination buffer is invalid
+* Buffer usages
+  - the source buffer is created without GPUBufferUsage::COPY_SRC
+  - the destination buffer is created without GPUBufferUsage::COPY_DEST
+* CopySize
+  - copySize is not a multiple of 4
+  - copySize is 0
+* copy offsets
+  - sourceOffset is not a multiple of 4
+  - destinationOffset is not a multiple of 4
+* Arthimetic overflow
+  - (sourceOffset + copySize) is overflow
+  - (destinationOffset + copySize) is overflow
+* Out of bounds
+  - (sourceOffset + copySize) > size of source buffer
+  - (destinationOffset + copySize) > size of destination buffer
+* Source buffer and destination buffer are the same buffer
+`;
+import { poptions, params } from '../../../common/framework/params_builder.js';
+import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { kBufferUsages } from '../../capability_info.js';
+import { kMaxSafeMultipleOf8 } from '../../util/math.js';
+
+import { ValidationTest } from './validation_test.js';
+
+class F extends ValidationTest {
+  TestCopyBufferToBuffer(options) {
+    const { srcBuffer, srcOffset, dstBuffer, dstOffset, copySize, isSuccess } = options;
+
+    const commandEncoder = this.device.createCommandEncoder();
+    commandEncoder.copyBufferToBuffer(srcBuffer, srcOffset, dstBuffer, dstOffset, copySize);
+
+    this.expectValidationError(() => {
+      commandEncoder.finish();
+    }, !isSuccess);
+  }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('copy_with_invalid_buffer').fn(async t => {
+  const validBuffer = t.device.createBuffer({
+    size: 16,
+    usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
+  });
+
+  const errorBuffer = t.getErrorBuffer();
+
+  t.TestCopyBufferToBuffer({
+    srcBuffer: errorBuffer,
+    srcOffset: 0,
+    dstBuffer: validBuffer,
+    dstOffset: 0,
+    copySize: 8,
+    isSuccess: false,
+  });
+
+  t.TestCopyBufferToBuffer({
+    srcBuffer: validBuffer,
+    srcOffset: 0,
+    dstBuffer: errorBuffer,
+    dstOffset: 0,
+    copySize: 8,
+    isSuccess: false,
+  });
+});
+
+g.test('buffer_usage')
+  .params(
+    params()
+      .combine(poptions('srcUsage', kBufferUsages))
+      .combine(poptions('dstUsage', kBufferUsages))
+  )
+  .fn(async t => {
+    const { srcUsage, dstUsage } = t.params;
+
+    const srcBuffer = t.device.createBuffer({
+      size: 16,
+      usage: srcUsage,
+    });
+
+    const dstBuffer = t.device.createBuffer({
+      size: 16,
+      usage: dstUsage,
+    });
+
+    const isSuccess = srcUsage === GPUBufferUsage.COPY_SRC && dstUsage === GPUBufferUsage.COPY_DST;
+
+    t.TestCopyBufferToBuffer({
+      srcBuffer,
+      srcOffset: 0,
+      dstBuffer,
+      dstOffset: 0,
+      copySize: 8,
+      isSuccess,
+    });
+  });
+
+g.test('copy_size_alignment')
+  .params([
+    { copySize: 0, _isSuccess: true },
+    { copySize: 2, _isSuccess: false },
+    { copySize: 4, _isSuccess: true },
+    { copySize: 5, _isSuccess: false },
+    { copySize: 8, _isSuccess: true },
+  ])
+  .fn(async t => {
+    const { copySize, _isSuccess: isSuccess } = t.params;
+
+    const srcBuffer = t.device.createBuffer({
+      size: 16,
+      usage: GPUBufferUsage.COPY_SRC,
+    });
+
+    const dstBuffer = t.device.createBuffer({
+      size: 16,
+      usage: GPUBufferUsage.COPY_DST,
+    });
+
+    t.TestCopyBufferToBuffer({
+      srcBuffer,
+      srcOffset: 0,
+      dstBuffer,
+      dstOffset: 0,
+      copySize,
+      isSuccess,
+    });
+  });
+
+g.test('copy_offset_alignment')
+  .params([
+    { srcOffset: 0, dstOffset: 0, _isSuccess: true },
+    { srcOffset: 2, dstOffset: 0, _isSuccess: false },
+    { srcOffset: 4, dstOffset: 0, _isSuccess: true },
+    { srcOffset: 5, dstOffset: 0, _isSuccess: false },
+    { srcOffset: 8, dstOffset: 0, _isSuccess: true },
+    { srcOffset: 0, dstOffset: 2, _isSuccess: false },
+    { srcOffset: 0, dstOffset: 4, _isSuccess: true },
+    { srcOffset: 0, dstOffset: 5, _isSuccess: false },
+    { srcOffset: 0, dstOffset: 8, _isSuccess: true },
+    { srcOffset: 4, dstOffset: 4, _isSuccess: true },
+  ])
+  .fn(async t => {
+    const { srcOffset, dstOffset, _isSuccess: isSuccess } = t.params;
+
+    const srcBuffer = t.device.createBuffer({
+      size: 16,
+      usage: GPUBufferUsage.COPY_SRC,
+    });
+
+    const dstBuffer = t.device.createBuffer({
+      size: 16,
+      usage: GPUBufferUsage.COPY_DST,
+    });
+
+    t.TestCopyBufferToBuffer({
+      srcBuffer,
+      srcOffset,
+      dstBuffer,
+      dstOffset,
+      copySize: 8,
+      isSuccess,
+    });
+  });
+
+g.test('copy_overflow')
+  .params([
+    { srcOffset: 0, dstOffset: 0, copySize: kMaxSafeMultipleOf8 },
+    { srcOffset: 16, dstOffset: 0, copySize: kMaxSafeMultipleOf8 },
+    { srcOffset: 0, dstOffset: 16, copySize: kMaxSafeMultipleOf8 },
+    { srcOffset: kMaxSafeMultipleOf8, dstOffset: 0, copySize: 16 },
+    { srcOffset: 0, dstOffset: kMaxSafeMultipleOf8, copySize: 16 },
+    { srcOffset: kMaxSafeMultipleOf8, dstOffset: 0, copySize: kMaxSafeMultipleOf8 },
+    { srcOffset: 0, dstOffset: kMaxSafeMultipleOf8, copySize: kMaxSafeMultipleOf8 },
+    {
+      srcOffset: kMaxSafeMultipleOf8,
+      dstOffset: kMaxSafeMultipleOf8,
+      copySize: kMaxSafeMultipleOf8,
+    },
+  ])
+  .fn(async t => {
+    const { srcOffset, dstOffset, copySize } = t.params;
+
+    const srcBuffer = t.device.createBuffer({
+      size: 16,
+      usage: GPUBufferUsage.COPY_SRC,
+    });
+
+    const dstBuffer = t.device.createBuffer({
+      size: 16,
+      usage: GPUBufferUsage.COPY_DST,
+    });
+
+    t.TestCopyBufferToBuffer({
+      srcBuffer,
+      srcOffset,
+      dstBuffer,
+      dstOffset,
+      copySize,
+      isSuccess: false,
+    });
+  });
+
+g.test('copy_out_of_bounds')
+  .params([
+    { srcOffset: 0, dstOffset: 0, copySize: 32, _isSuccess: true },
+    { srcOffset: 0, dstOffset: 0, copySize: 36 },
+    { srcOffset: 36, dstOffset: 0, copySize: 4 },
+    { srcOffset: 0, dstOffset: 36, copySize: 4 },
+    { srcOffset: 36, dstOffset: 0, copySize: 0 },
+    { srcOffset: 0, dstOffset: 36, copySize: 0 },
+    { srcOffset: 20, dstOffset: 0, copySize: 16 },
+    { srcOffset: 20, dstOffset: 0, copySize: 12, _isSuccess: true },
+    { srcOffset: 0, dstOffset: 20, copySize: 16 },
+    { srcOffset: 0, dstOffset: 20, copySize: 12, _isSuccess: true },
+  ])
+  .fn(async t => {
+    const { srcOffset, dstOffset, copySize, _isSuccess = false } = t.params;
+
+    const srcBuffer = t.device.createBuffer({
+      size: 32,
+      usage: GPUBufferUsage.COPY_SRC,
+    });
+
+    const dstBuffer = t.device.createBuffer({
+      size: 32,
+      usage: GPUBufferUsage.COPY_DST,
+    });
+
+    t.TestCopyBufferToBuffer({
+      srcBuffer,
+      srcOffset,
+      dstBuffer,
+      dstOffset,
+      copySize,
+      isSuccess: _isSuccess,
+    });
+  });
+
+g.test('copy_within_same_buffer')
+  .params([
+    { srcOffset: 0, dstOffset: 8, copySize: 4 },
+    { srcOffset: 8, dstOffset: 0, copySize: 4 },
+    { srcOffset: 0, dstOffset: 4, copySize: 8 },
+    { srcOffset: 4, dstOffset: 0, copySize: 8 },
+  ])
+  .fn(async t => {
+    const { srcOffset, dstOffset, copySize } = t.params;
+
+    const buffer = t.device.createBuffer({
+      size: 16,
+      usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
+    });
+
+    t.TestCopyBufferToBuffer({
+      srcBuffer: buffer,
+      srcOffset,
+      dstBuffer: buffer,
+      dstOffset,
+      copySize,
+      isSuccess: false,
+    });
+  });
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/copy_between_linear_data_and_texture/copyBetweenLinearDataAndTexture.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/copy_between_linear_data_and_texture/copyBetweenLinearDataAndTexture.js
new file mode 100644
index 0000000..0db6e68d
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/copy_between_linear_data_and_texture/copyBetweenLinearDataAndTexture.js
@@ -0,0 +1,179 @@
+/**
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ import { poptions } from '../../../../common/framework/params_builder.js';
+import { assert } from '../../../../common/framework/util/util.js';
+import { kSizedTextureFormatInfo } from '../../../capability_info.js';
+import { ValidationTest } from '../validation_test.js';
+
+export const kAllTestMethods = ['WriteTexture', 'CopyBufferToTexture', 'CopyTextureToBuffer'];
+
+export class CopyBetweenLinearDataAndTextureTest extends ValidationTest {
+  bytesInACompleteRow(copyWidth, format) {
+    const info = kSizedTextureFormatInfo[format];
+    assert(copyWidth % info.blockWidth === 0);
+    return (info.bytesPerBlock * copyWidth) / info.blockWidth;
+  }
+
+  requiredBytesInCopy(layout, format, copyExtent) {
+    const info = kSizedTextureFormatInfo[format];
+    assert(layout.rowsPerImage % info.blockHeight === 0);
+    assert(copyExtent.height % info.blockHeight === 0);
+    assert(copyExtent.width % info.blockWidth === 0);
+    if (copyExtent.width === 0 || copyExtent.height === 0 || copyExtent.depth === 0) {
+      return 0;
+    } else {
+      const texelBlockRowsPerImage = layout.rowsPerImage / info.blockHeight;
+      const bytesPerImage = layout.bytesPerRow * texelBlockRowsPerImage;
+      const bytesInLastSlice =
+        layout.bytesPerRow * (copyExtent.height / info.blockHeight - 1) +
+        (copyExtent.width / info.blockWidth) * info.bytesPerBlock;
+      return bytesPerImage * (copyExtent.depth - 1) + bytesInLastSlice;
+    }
+  }
+
+  testRun(
+    textureCopyView,
+    textureDataLayout,
+    size,
+    {
+      dataSize,
+      method,
+      success,
+      submit = false, // If submit is true, the validaton error is expected to come from the submit and encoding should succeed.
+    }
+  ) {
+    switch (method) {
+      case 'WriteTexture': {
+        const data = new Uint8Array(dataSize);
+
+        this.expectValidationError(() => {
+          this.device.defaultQueue.writeTexture(textureCopyView, data, textureDataLayout, size);
+        }, !success);
+
+        break;
+      }
+      case 'CopyBufferToTexture': {
+        const buffer = this.device.createBuffer({
+          size: dataSize,
+          usage: GPUBufferUsage.COPY_SRC,
+        });
+
+        const encoder = this.device.createCommandEncoder();
+        encoder.copyBufferToTexture({ buffer, ...textureDataLayout }, textureCopyView, size);
+
+        if (submit) {
+          const cmd = encoder.finish();
+          this.expectValidationError(() => {
+            this.device.defaultQueue.submit([cmd]);
+          }, !success);
+        } else {
+          this.expectValidationError(() => {
+            encoder.finish();
+          }, !success);
+        }
+
+        break;
+      }
+      case 'CopyTextureToBuffer': {
+        const buffer = this.device.createBuffer({
+          size: dataSize,
+          usage: GPUBufferUsage.COPY_DST,
+        });
+
+        const encoder = this.device.createCommandEncoder();
+        encoder.copyTextureToBuffer(textureCopyView, { buffer, ...textureDataLayout }, size);
+
+        if (submit) {
+          const cmd = encoder.finish();
+          this.expectValidationError(() => {
+            this.device.defaultQueue.submit([cmd]);
+          }, !success);
+        } else {
+          this.expectValidationError(() => {
+            encoder.finish();
+          }, !success);
+        }
+
+        break;
+      }
+    }
+  }
+
+  // This is a helper function used for creating a texture when we don't have to be very
+  // precise about its size as long as it's big enough and properly aligned.
+  createAlignedTexture(
+    format,
+    copySize = { width: 1, height: 1, depth: 1 },
+    origin = { x: 0, y: 0, z: 0 }
+  ) {
+    const info = kSizedTextureFormatInfo[format];
+    return this.device.createTexture({
+      size: {
+        width: Math.max(1, copySize.width + origin.x) * info.blockWidth,
+        height: Math.max(1, copySize.height + origin.y) * info.blockHeight,
+        depth: Math.max(1, copySize.depth + origin.z),
+      },
+
+      format,
+      usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
+    });
+  }
+}
+
+// For testing divisibility by a number we test all the values returned by this function:
+function valuesToTestDivisibilityBy(number) {
+  const values = [];
+  for (let i = 0; i <= 2 * number; ++i) {
+    values.push(i);
+  }
+  values.push(3 * number);
+  return values;
+}
+
+// This is a helper function used for expanding test parameters for texel block alignment tests on offset
+export function texelBlockAlignmentTestExpanderForOffset({ format }) {
+  return poptions(
+    'offset',
+    valuesToTestDivisibilityBy(kSizedTextureFormatInfo[format].bytesPerBlock)
+  );
+}
+
+// This is a helper function used for expanding test parameters for texel block alignment tests on rowsPerImage
+export function texelBlockAlignmentTestExpanderForRowsPerImage({ format }) {
+  return poptions(
+    'rowsPerImage',
+    valuesToTestDivisibilityBy(kSizedTextureFormatInfo[format].blockHeight)
+  );
+}
+
+// This is a helper function used for expanding test parameters for texel block alignment tests on origin and size
+export function texelBlockAlignmentTestExpanderForValueToCoordinate({ format, coordinateToTest }) {
+  switch (coordinateToTest) {
+    case 'x':
+    case 'width':
+      return poptions(
+        'valueToCoordinate',
+        valuesToTestDivisibilityBy(kSizedTextureFormatInfo[format].blockWidth)
+      );
+
+    case 'y':
+    case 'height':
+      return poptions(
+        'valueToCoordinate',
+        valuesToTestDivisibilityBy(kSizedTextureFormatInfo[format].blockHeight)
+      );
+
+    case 'z':
+    case 'depth':
+      return poptions('valueToCoordinate', valuesToTestDivisibilityBy(1));
+  }
+}
+
+// This is a helper function used for filtering test parameters
+export function formatCopyableWithMethod({ format, method }) {
+  if (method === 'CopyTextureToBuffer') {
+    return kSizedTextureFormatInfo[format].copySrc;
+  } else {
+    return kSizedTextureFormatInfo[format].copyDst;
+  }
+}
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/copy_between_linear_data_and_texture/copyBetweenLinearDataAndTexture_dataRelated.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/copy_between_linear_data_and_texture/copyBetweenLinearDataAndTexture_dataRelated.spec.js
new file mode 100644
index 0000000..be1395b
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/copy_between_linear_data_and_texture/copyBetweenLinearDataAndTexture_dataRelated.spec.js
@@ -0,0 +1,299 @@
+/**
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = '';
+import { params, poptions } from '../../../../common/framework/params_builder.js';
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import {
+  kUncompressedTextureFormatInfo,
+  kSizedTextureFormats,
+  kSizedTextureFormatInfo,
+} from '../../../capability_info.js';
+import { align } from '../../../util/math.js';
+
+import {
+  CopyBetweenLinearDataAndTextureTest,
+  kAllTestMethods,
+  texelBlockAlignmentTestExpanderForOffset,
+  texelBlockAlignmentTestExpanderForRowsPerImage,
+  formatCopyableWithMethod,
+} from './copyBetweenLinearDataAndTexture.js';
+
+export const g = makeTestGroup(CopyBetweenLinearDataAndTextureTest);
+
+g.test('bound_on_rows_per_image')
+  .params(
+    params()
+      .combine(poptions('method', kAllTestMethods))
+      .combine(poptions('rowsPerImageInBlocks', [0, 1, 2]))
+      .combine(poptions('copyHeightInBlocks', [0, 1, 2]))
+      .combine(poptions('copyDepth', [1, 3]))
+  )
+  .fn(async t => {
+    const { rowsPerImageInBlocks, copyHeightInBlocks, copyDepth, method } = t.params;
+
+    const format = 'rgba8unorm';
+    const rowsPerImage = rowsPerImageInBlocks * kUncompressedTextureFormatInfo[format].blockHeight;
+    const copyHeight = copyHeightInBlocks * kUncompressedTextureFormatInfo[format].blockHeight;
+
+    const texture = t.device.createTexture({
+      size: { width: 4, height: 4, depth: 3 },
+      format,
+      usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
+    });
+
+    // The WebGPU spec:
+    // If layout.rowsPerImage is not 0, it must be greater than or equal to copyExtent.height.
+    // If copyExtent.depth is greater than 1: layout.rowsPerImage must be greater than or equal to copyExtent.height.
+    // TODO: Update this if https://github.com/gpuweb/gpuweb/issues/984 changes the spec.
+
+    let success = true;
+    if (rowsPerImage !== 0 && rowsPerImage < copyHeight) {
+      success = false;
+    }
+    if (copyDepth > 1 && rowsPerImage < copyHeight) {
+      success = false;
+    }
+
+    t.testRun(
+      { texture },
+      { bytesPerRow: 1024, rowsPerImage },
+      { width: 0, height: copyHeight, depth: copyDepth },
+      { dataSize: 1, method, success }
+    );
+  });
+
+// Test with offset + requiredBytesIsCopy overflowing GPUSize64.
+g.test('offset_plus_required_bytes_in_copy_overflow')
+  .params(
+    params()
+      .combine(poptions('method', kAllTestMethods))
+      .combine([
+        { bytesPerRow: 2 ** 31, rowsPerImage: 2 ** 31, depth: 1, _success: true }, // success case
+        { bytesPerRow: 2 ** 31, rowsPerImage: 2 ** 31, depth: 16, _success: false }, // bytesPerRow * rowsPerImage * (depth - 1) overflows.
+      ])
+  )
+  .fn(async t => {
+    const { method, bytesPerRow, rowsPerImage, depth, _success } = t.params;
+
+    const texture = t.device.createTexture({
+      size: [1, 1, depth],
+      format: 'rgba8unorm',
+      usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
+    });
+
+    t.testRun(
+      { texture },
+      { bytesPerRow, rowsPerImage },
+      { width: 1, height: 1, depth },
+      {
+        dataSize: 10000,
+        method,
+        success: _success,
+      }
+    );
+  });
+
+// Testing that the minimal data size condition is checked correctly.
+// In the success case, we test the exact value.
+// In the failing case, we test the exact value minus 1.
+g.test('required_bytes_in_copy')
+  .params(
+    params()
+      .combine(poptions('method', kAllTestMethods))
+      .combine([
+        { bytesPerRowPadding: 0, rowsPerImagePaddingInBlocks: 0 }, // no padding
+        { bytesPerRowPadding: 0, rowsPerImagePaddingInBlocks: 6 }, // rowsPerImage padding
+        { bytesPerRowPadding: 6, rowsPerImagePaddingInBlocks: 0 }, // bytesPerRow padding
+        { bytesPerRowPadding: 15, rowsPerImagePaddingInBlocks: 17 }, // both paddings
+      ])
+      .combine([
+        { copyWidthInBlocks: 3, copyHeightInBlocks: 4, copyDepth: 5, offsetInBlocks: 0 }, // standard copy
+        { copyWidthInBlocks: 5, copyHeightInBlocks: 4, copyDepth: 3, offsetInBlocks: 11 }, // standard copy, offset > 0
+        { copyWidthInBlocks: 256, copyHeightInBlocks: 3, copyDepth: 2, offsetInBlocks: 0 }, // copyWidth is 256-aligned
+        { copyWidthInBlocks: 0, copyHeightInBlocks: 4, copyDepth: 5, offsetInBlocks: 0 }, // empty copy because of width
+        { copyWidthInBlocks: 3, copyHeightInBlocks: 0, copyDepth: 5, offsetInBlocks: 0 }, // empty copy because of height
+        { copyWidthInBlocks: 3, copyHeightInBlocks: 4, copyDepth: 0, offsetInBlocks: 13 }, // empty copy because of depth, offset > 0
+        { copyWidthInBlocks: 1, copyHeightInBlocks: 4, copyDepth: 5, offsetInBlocks: 0 }, // copyWidth = 1
+        { copyWidthInBlocks: 3, copyHeightInBlocks: 1, copyDepth: 5, offsetInBlocks: 15 }, // copyHeight = 1, offset > 0
+        { copyWidthInBlocks: 5, copyHeightInBlocks: 4, copyDepth: 1, offsetInBlocks: 0 }, // copyDepth = 1
+        { copyWidthInBlocks: 7, copyHeightInBlocks: 1, copyDepth: 1, offsetInBlocks: 0 }, // copyHeight = 1 and copyDepth = 1
+      ])
+      .combine(poptions('format', kSizedTextureFormats))
+      .filter(formatCopyableWithMethod)
+  )
+  .fn(async t => {
+    const {
+      offsetInBlocks,
+      bytesPerRowPadding,
+      rowsPerImagePaddingInBlocks,
+      copyWidthInBlocks,
+      copyHeightInBlocks,
+      copyDepth,
+      format,
+      method,
+    } = t.params;
+
+    // In the CopyB2T and CopyT2B cases we need to have bytesPerRow 256-aligned,
+    // to make this happen we align the bytesInACompleteRow value and multiply
+    // bytesPerRowPadding by 256.
+    const bytesPerRowAlignment = method === 'WriteTexture' ? 1 : 256;
+
+    const info = kSizedTextureFormatInfo[format];
+    const copyWidth = copyWidthInBlocks * info.blockWidth;
+    const copyHeight = copyHeightInBlocks * info.blockHeight;
+    const offset = offsetInBlocks * info.bytesPerBlock;
+    const rowsPerImage = copyHeight + rowsPerImagePaddingInBlocks * info.blockHeight;
+    const bytesPerRow =
+      align(t.bytesInACompleteRow(copyWidth, format), bytesPerRowAlignment) +
+      bytesPerRowPadding * bytesPerRowAlignment;
+    const size = { width: copyWidth, height: copyHeight, depth: copyDepth };
+
+    const minDataSize =
+      offset + t.requiredBytesInCopy({ offset, bytesPerRow, rowsPerImage }, format, size);
+
+    const texture = t.createAlignedTexture(format, size);
+
+    t.testRun({ texture }, { offset, bytesPerRow, rowsPerImage }, size, {
+      dataSize: minDataSize,
+      method,
+      success: true,
+    });
+
+    if (minDataSize > 0) {
+      t.testRun({ texture }, { offset, bytesPerRow, rowsPerImage }, size, {
+        dataSize: minDataSize - 1,
+        method,
+        success: false,
+      });
+    }
+  });
+
+g.test('texel_block_alignment_on_rows_per_image')
+  .params(
+    params()
+      .combine(poptions('method', kAllTestMethods))
+      .combine(poptions('format', kSizedTextureFormats))
+      .filter(formatCopyableWithMethod)
+      .expand(texelBlockAlignmentTestExpanderForRowsPerImage)
+  )
+  .fn(async t => {
+    const { rowsPerImage, format, method } = t.params;
+    const size = { width: 0, height: 0, depth: 0 };
+
+    const texture = t.createAlignedTexture(format, size);
+
+    const success = rowsPerImage % kSizedTextureFormatInfo[format].blockHeight === 0;
+
+    t.testRun({ texture }, { bytesPerRow: 0, rowsPerImage }, size, {
+      dataSize: 1,
+      method,
+      success,
+    });
+  });
+
+// TODO: Update this if https://github.com/gpuweb/gpuweb/issues/985 changes the spec.
+g.test('texel_block_alignment_on_offset')
+  .params(
+    params()
+      .combine(poptions('method', kAllTestMethods))
+      .combine(poptions('format', kSizedTextureFormats))
+      .filter(formatCopyableWithMethod)
+      .expand(texelBlockAlignmentTestExpanderForOffset)
+  )
+  .fn(async t => {
+    const { format, offset, method } = t.params;
+    const size = { width: 0, height: 0, depth: 0 };
+
+    const texture = t.createAlignedTexture(format, size);
+
+    const success = offset % kSizedTextureFormatInfo[format].bytesPerBlock === 0;
+
+    t.testRun({ texture }, { offset, bytesPerRow: 0 }, size, { dataSize: offset, method, success });
+  });
+
+g.test('bound_on_bytes_per_row')
+  .params(
+    params()
+      .combine(poptions('method', kAllTestMethods))
+      .combine([
+        { blocksPerRow: 2, additionalPaddingPerRow: 0, copyWidthInBlocks: 2 }, // success
+        { blocksPerRow: 2, additionalPaddingPerRow: 5, copyWidthInBlocks: 3 }, // success if bytesPerBlock <= 5
+        { blocksPerRow: 1, additionalPaddingPerRow: 0, copyWidthInBlocks: 2 }, // failure, bytesPerRow > 0
+        { blocksPerRow: 0, additionalPaddingPerRow: 0, copyWidthInBlocks: 1 }, // failure, bytesPerRow = 0
+      ])
+      .combine([
+        { copyHeightInBlocks: 0, copyDepth: 1 }, // we don't have to check the bound
+        { copyHeightInBlocks: 1, copyDepth: 0 }, // we don't have to check the bound
+        { copyHeightInBlocks: 2, copyDepth: 1 }, // we have to check the bound
+        { copyHeightInBlocks: 0, copyDepth: 2 }, // we have to check the bound
+      ])
+      .combine(poptions('format', kSizedTextureFormats))
+      .filter(formatCopyableWithMethod)
+  )
+  .fn(async t => {
+    const {
+      blocksPerRow,
+      additionalPaddingPerRow,
+      copyWidthInBlocks,
+      copyHeightInBlocks,
+      copyDepth,
+      format,
+      method,
+    } = t.params;
+
+    // In the CopyB2T and CopyT2B cases we need to have bytesPerRow 256-aligned,
+    // to make this happen we multiply copyWidth and bytesPerRow by 256, so that
+    // the appropriate inequalities still hold.
+    const bytesPerRowAlignment = method === 'WriteTexture' ? 1 : 256;
+
+    const info = kSizedTextureFormatInfo[format];
+    const copyWidth = copyWidthInBlocks * info.blockWidth * bytesPerRowAlignment;
+    const copyHeight = copyHeightInBlocks * info.blockHeight;
+    const bytesPerRow =
+      (blocksPerRow * info.bytesPerBlock + additionalPaddingPerRow) * bytesPerRowAlignment;
+    const size = { width: copyWidth, height: copyHeight, depth: copyDepth };
+
+    const texture = t.createAlignedTexture(format, size);
+
+    let success = true;
+    if (copyHeight > 1 || copyDepth > 1) {
+      success = bytesPerRow >= t.bytesInACompleteRow(copyWidth, format);
+    }
+
+    t.testRun({ texture }, { bytesPerRow, rowsPerImage: copyHeight }, size, {
+      dataSize: 1024,
+      method,
+      success,
+    });
+  });
+
+g.test('bound_on_offset')
+  .params(
+    params()
+      .combine(poptions('method', kAllTestMethods))
+      .combine(poptions('offsetInBlocks', [0, 1, 2]))
+      .combine(poptions('dataSizeInBlocks', [0, 1, 2]))
+  )
+  .fn(async t => {
+    const { offsetInBlocks, dataSizeInBlocks, method } = t.params;
+
+    const format = 'rgba8unorm';
+    const info = kSizedTextureFormatInfo[format];
+    const offset = offsetInBlocks * info.bytesPerBlock;
+    const dataSize = dataSizeInBlocks * info.bytesPerBlock;
+
+    const texture = t.device.createTexture({
+      size: { width: 4, height: 4, depth: 1 },
+      format,
+      usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
+    });
+
+    const success = offset <= dataSize;
+
+    t.testRun(
+      { texture },
+      { offset, bytesPerRow: 0 },
+      { width: 0, height: 0, depth: 0 },
+      { dataSize, method, success }
+    );
+  });
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/copy_between_linear_data_and_texture/copyBetweenLinearDataAndTexture_textureRelated.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/copy_between_linear_data_and_texture/copyBetweenLinearDataAndTexture_textureRelated.spec.js
new file mode 100644
index 0000000..bef71943
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/copy_between_linear_data_and_texture/copyBetweenLinearDataAndTexture_textureRelated.spec.js
@@ -0,0 +1,307 @@
+/**
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = '';
+import { params, poptions } from '../../../../common/framework/params_builder.js';
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { kSizedTextureFormats, kSizedTextureFormatInfo } from '../../../capability_info.js';
+
+import {
+  CopyBetweenLinearDataAndTextureTest,
+  kAllTestMethods,
+  texelBlockAlignmentTestExpanderForValueToCoordinate,
+  formatCopyableWithMethod,
+} from './copyBetweenLinearDataAndTexture.js';
+
+export const g = makeTestGroup(CopyBetweenLinearDataAndTextureTest);
+
+g.test('texture_must_be_valid')
+  .params(
+    params()
+      .combine(poptions('method', kAllTestMethods))
+      .combine(poptions('textureState', ['valid', 'destroyed', 'error']))
+  )
+  .fn(async t => {
+    const { method, textureState } = t.params;
+
+    // A valid texture.
+    let texture = t.device.createTexture({
+      size: { width: 4, height: 4, depth: 1 },
+      format: 'rgba8unorm',
+      usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
+    });
+
+    switch (textureState) {
+      case 'destroyed': {
+        texture.destroy();
+        break;
+      }
+      case 'error': {
+        texture = t.getErrorTexture();
+        break;
+      }
+    }
+
+    const success = textureState === 'valid';
+    const submit = textureState === 'destroyed';
+
+    t.testRun(
+      { texture },
+      { bytesPerRow: 0 },
+      { width: 0, height: 0, depth: 0 },
+      { dataSize: 1, method, success, submit }
+    );
+  });
+
+g.test('texture_usage_must_be_valid')
+  .params(
+    params()
+      .combine(poptions('method', kAllTestMethods))
+      .combine(
+        poptions('usage', [
+          GPUTextureUsage.COPY_SRC | GPUTextureUsage.SAMPLED,
+          GPUTextureUsage.COPY_DST | GPUTextureUsage.SAMPLED,
+          GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
+        ])
+      )
+  )
+  .fn(async t => {
+    const { usage, method } = t.params;
+
+    const texture = t.device.createTexture({
+      size: { width: 4, height: 4, depth: 1 },
+      format: 'rgba8unorm',
+      usage,
+    });
+
+    const success =
+      method === 'CopyTextureToBuffer'
+        ? (usage & GPUTextureUsage.COPY_SRC) !== 0
+        : (usage & GPUTextureUsage.COPY_DST) !== 0;
+
+    t.testRun(
+      { texture },
+      { bytesPerRow: 0 },
+      { width: 0, height: 0, depth: 0 },
+      { dataSize: 1, method, success }
+    );
+  });
+
+g.test('sample_count_must_be_1')
+  .params(
+    params()
+      .combine(poptions('method', kAllTestMethods))
+      .combine(poptions('sampleCount', [1, 4]))
+  )
+  .fn(async t => {
+    const { sampleCount, method } = t.params;
+
+    const texture = t.device.createTexture({
+      size: { width: 4, height: 4, depth: 1 },
+      sampleCount,
+      format: 'rgba8unorm',
+      usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST | GPUTextureUsage.SAMPLED,
+    });
+
+    const success = sampleCount === 1;
+
+    t.testRun(
+      { texture },
+      { bytesPerRow: 0 },
+      { width: 0, height: 0, depth: 0 },
+      { dataSize: 1, method, success }
+    );
+  });
+
+g.test('mip_level_must_be_in_range')
+  .params(
+    params()
+      .combine(poptions('method', kAllTestMethods))
+      .combine(poptions('mipLevelCount', [3, 5]))
+      .combine(poptions('mipLevel', [3, 4]))
+  )
+  .fn(async t => {
+    const { mipLevelCount, mipLevel, method } = t.params;
+
+    const texture = t.device.createTexture({
+      size: { width: 32, height: 32, depth: 1 },
+      mipLevelCount,
+      format: 'rgba8unorm',
+      usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
+    });
+
+    const success = mipLevel < mipLevelCount;
+
+    t.testRun(
+      { texture, mipLevel },
+      { bytesPerRow: 0 },
+      { width: 0, height: 0, depth: 0 },
+      { dataSize: 1, method, success }
+    );
+  });
+
+g.test('texel_block_alignments_on_origin')
+  .params(
+    params()
+      .combine(poptions('method', kAllTestMethods))
+      .combine(poptions('coordinateToTest', ['x', 'y', 'z']))
+      .combine(poptions('format', kSizedTextureFormats))
+      .filter(formatCopyableWithMethod)
+      .expand(texelBlockAlignmentTestExpanderForValueToCoordinate)
+  )
+  .fn(async t => {
+    const { valueToCoordinate, coordinateToTest, format, method } = t.params;
+    const info = kSizedTextureFormatInfo[format];
+
+    const origin = { x: 0, y: 0, z: 0 };
+    const size = { width: 0, height: 0, depth: 0 };
+    let success = true;
+
+    origin[coordinateToTest] = valueToCoordinate;
+    switch (coordinateToTest) {
+      case 'x': {
+        success = origin.x % info.blockWidth === 0;
+        break;
+      }
+      case 'y': {
+        success = origin.y % info.blockHeight === 0;
+        break;
+      }
+    }
+
+    const texture = t.createAlignedTexture(format, size, origin);
+
+    t.testRun({ texture, origin }, { bytesPerRow: 0 }, size, {
+      dataSize: 1,
+      method,
+      success,
+    });
+  });
+
+g.test('1d_texture')
+  .params(
+    params()
+      .combine(poptions('method', kAllTestMethods))
+      .combine(poptions('width', [0, 1]))
+      .combine([
+        { height: 1, depth: 1 },
+        { height: 1, depth: 0 },
+        { height: 1, depth: 2 },
+        { height: 0, depth: 1 },
+        { height: 2, depth: 1 },
+      ])
+  )
+  .fn(async t => {
+    const { method, width, height, depth } = t.params;
+    const size = { width, height, depth };
+
+    const texture = t.device.createTexture({
+      size: { width: 2, height: 1, depth: 1 },
+      dimension: '1d',
+      format: 'rgba8unorm',
+      usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
+    });
+
+    // For 1d textures we require copyHeight and copyDepth to be 1,
+    // copyHeight or copyDepth being 0 should cause a validation error.
+    const success = size.height === 1 && size.depth === 1;
+
+    t.testRun({ texture }, { bytesPerRow: 256, rowsPerImage: 4 }, size, {
+      dataSize: 16,
+      method,
+      success,
+    });
+  });
+
+g.test('texel_block_alignments_on_size')
+  .params(
+    params()
+      .combine(poptions('method', kAllTestMethods))
+      .combine(poptions('coordinateToTest', ['width', 'height', 'depth']))
+      .combine(poptions('format', kSizedTextureFormats))
+      .filter(formatCopyableWithMethod)
+      .expand(texelBlockAlignmentTestExpanderForValueToCoordinate)
+  )
+  .fn(async t => {
+    const { valueToCoordinate, coordinateToTest, format, method } = t.params;
+    const info = kSizedTextureFormatInfo[format];
+
+    const origin = { x: 0, y: 0, z: 0 };
+    const size = { width: 0, height: 0, depth: 0 };
+    let success = true;
+
+    size[coordinateToTest] = valueToCoordinate;
+    switch (coordinateToTest) {
+      case 'width': {
+        success = size.width % info.blockWidth === 0;
+        break;
+      }
+      case 'height': {
+        success = size.height % info.blockHeight === 0;
+        break;
+      }
+    }
+
+    const texture = t.createAlignedTexture(format, size, origin);
+
+    t.testRun({ texture, origin }, { bytesPerRow: 0 }, size, {
+      dataSize: 1,
+      method,
+      success,
+    });
+  });
+
+g.test('texture_range_conditions')
+  .params(
+    params()
+      .combine(poptions('method', kAllTestMethods))
+      .combine(poptions('originValue', [7, 8]))
+      .combine(poptions('copySizeValue', [7, 8]))
+      .combine(poptions('textureSizeValue', [14, 15]))
+      .combine(poptions('mipLevel', [0, 2]))
+      .combine(poptions('coordinateToTest', [0, 1, 2]))
+  )
+  .fn(async t => {
+    const {
+      originValue,
+      copySizeValue,
+      textureSizeValue,
+      mipLevel,
+      coordinateToTest,
+      method,
+    } = t.params;
+
+    const origin = [0, 0, 0];
+    const copySize = [0, 0, 0];
+    const textureSize = { width: 16 << mipLevel, height: 16 << mipLevel, depth: 16 };
+    const success = originValue + copySizeValue <= textureSizeValue;
+
+    origin[coordinateToTest] = originValue;
+    copySize[coordinateToTest] = copySizeValue;
+    switch (coordinateToTest) {
+      case 0: {
+        textureSize.width = textureSizeValue << mipLevel;
+        break;
+      }
+      case 1: {
+        textureSize.height = textureSizeValue << mipLevel;
+        break;
+      }
+      case 2: {
+        textureSize.depth = textureSizeValue;
+        break;
+      }
+    }
+
+    const texture = t.device.createTexture({
+      size: textureSize,
+      mipLevelCount: 3,
+      format: 'rgba8unorm',
+      usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
+    });
+
+    t.testRun({ texture, origin, mipLevel }, { bytesPerRow: 0 }, copySize, {
+      dataSize: 1,
+      method,
+      success,
+    });
+  });
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/createBindGroup.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/createBindGroup.spec.js
index 5deed893..fa58503 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/createBindGroup.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/createBindGroup.spec.js
@@ -1,14 +1,20 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = `
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `
 createBindGroup validation tests.
 `;
 import { poptions, params } from '../../../common/framework/params_builder.js';
 import { makeTestGroup } from '../../../common/framework/test_group.js';
 import { unreachable } from '../../../common/framework/util/util.js';
-import { kBindingTypes, kBindingTypeInfo, kBindableResources, kTextureUsages, kTextureBindingTypes, kTextureBindingTypeInfo } from '../../capability_info.js';
+import {
+  kBindingTypes,
+  kBindingTypeInfo,
+  kBindableResources,
+  kTextureUsages,
+  kTextureBindingTypes,
+  kTextureBindingTypeInfo,
+} from '../../capability_info.js';
+
 import { ValidationTest } from './validation_test.js';
 
 function clone(descriptor) {
@@ -16,366 +22,282 @@
 }
 
 export const g = makeTestGroup(ValidationTest);
+
 g.test('binding_count_mismatch').fn(async t => {
   const bindGroupLayout = t.device.createBindGroupLayout({
-    entries: [{
-      binding: 0,
-      visibility: GPUShaderStage.COMPUTE,
-      type: 'storage-buffer'
-    }]
+    entries: [{ binding: 0, visibility: GPUShaderStage.COMPUTE, type: 'storage-buffer' }],
   });
+
   const goodDescriptor = {
-    entries: [{
-      binding: 0,
-      resource: {
-        buffer: t.getStorageBuffer()
-      }
-    }],
-    layout: bindGroupLayout
-  }; // Control case
-
-  t.device.createBindGroup(goodDescriptor); // Another binding is not expected.
-
-  const badDescriptor = {
-    entries: [{
-      binding: 0,
-      resource: {
-        buffer: t.getStorageBuffer()
-      }
-    }, // Another binding is added.
-    {
-      binding: 1,
-      resource: {
-        buffer: t.getStorageBuffer()
-      }
-    }],
-    layout: bindGroupLayout
+    entries: [{ binding: 0, resource: { buffer: t.getStorageBuffer() } }],
+    layout: bindGroupLayout,
   };
+
+  // Control case
+  t.device.createBindGroup(goodDescriptor);
+
+  // Another binding is not expected.
+  const badDescriptor = {
+    entries: [
+      { binding: 0, resource: { buffer: t.getStorageBuffer() } },
+      // Another binding is added.
+      { binding: 1, resource: { buffer: t.getStorageBuffer() } },
+    ],
+
+    layout: bindGroupLayout,
+  };
+
   t.expectValidationError(() => {
     t.device.createBindGroup(badDescriptor);
   });
 });
+
 g.test('binding_must_be_present_in_layout').fn(async t => {
   const bindGroupLayout = t.device.createBindGroupLayout({
-    entries: [{
-      binding: 0,
-      visibility: GPUShaderStage.COMPUTE,
-      type: 'storage-buffer'
-    }]
+    entries: [{ binding: 0, visibility: GPUShaderStage.COMPUTE, type: 'storage-buffer' }],
   });
+
   const goodDescriptor = {
-    entries: [{
-      binding: 0,
-      resource: {
-        buffer: t.getStorageBuffer()
-      }
-    }],
-    layout: bindGroupLayout
-  }; // Control case
-
-  t.device.createBindGroup(goodDescriptor); // Binding index 0 must be present.
-
-  const badDescriptor = {
-    entries: [{
-      binding: 1,
-      resource: {
-        buffer: t.getStorageBuffer()
-      }
-    }],
-    layout: bindGroupLayout
+    entries: [{ binding: 0, resource: { buffer: t.getStorageBuffer() } }],
+    layout: bindGroupLayout,
   };
+
+  // Control case
+  t.device.createBindGroup(goodDescriptor);
+
+  // Binding index 0 must be present.
+  const badDescriptor = {
+    entries: [{ binding: 1, resource: { buffer: t.getStorageBuffer() } }],
+    layout: bindGroupLayout,
+  };
+
   t.expectValidationError(() => {
     t.device.createBindGroup(badDescriptor);
   });
 });
-g.test('buffer_binding_must_contain_exactly_one_buffer_of_its_type').params(params().combine(poptions('bindingType', kBindingTypes)).combine(poptions('resourceType', kBindableResources))).fn(t => {
-  const {
-    bindingType,
-    resourceType
-  } = t.params;
-  const info = kBindingTypeInfo[bindingType];
-  const storageTextureFormat = info.resource === 'storageTex' ? 'rgba8unorm' : undefined;
-  const layout = t.device.createBindGroupLayout({
-    entries: [{
-      binding: 0,
-      visibility: GPUShaderStage.COMPUTE,
-      type: bindingType,
-      storageTextureFormat
-    }]
-  });
-  const resource = t.getBindingResource(resourceType);
-  const resourceBindingMatches = info.resource === resourceType;
-  t.expectValidationError(() => {
-    t.device.createBindGroup({
-      layout,
-      entries: [{
-        binding: 0,
-        resource
-      }]
+
+g.test('buffer_binding_must_contain_exactly_one_buffer_of_its_type')
+  .params(
+    params()
+      .combine(poptions('bindingType', kBindingTypes))
+      .combine(poptions('resourceType', kBindableResources))
+  )
+  .fn(t => {
+    const { bindingType, resourceType } = t.params;
+    const info = kBindingTypeInfo[bindingType];
+
+    const storageTextureFormat = info.resource === 'storageTex' ? 'rgba8unorm' : undefined;
+    const layout = t.device.createBindGroupLayout({
+      entries: [
+        { binding: 0, visibility: GPUShaderStage.COMPUTE, type: bindingType, storageTextureFormat },
+      ],
     });
-  }, !resourceBindingMatches);
-});
-g.test('texture_binding_must_have_correct_usage').params(params().combine(poptions('type', kTextureBindingTypes)).combine(poptions('usage', kTextureUsages))).fn(async t => {
-  const {
-    type,
-    usage
-  } = t.params;
-  const info = kTextureBindingTypeInfo[type];
-  const storageTextureFormat = info.resource === 'storageTex' ? 'rgba8unorm' : undefined;
-  const bindGroupLayout = t.device.createBindGroupLayout({
-    entries: [{
-      binding: 0,
-      visibility: GPUShaderStage.FRAGMENT,
-      type,
-      storageTextureFormat
-    }]
+
+    const resource = t.getBindingResource(resourceType);
+
+    const resourceBindingMatches = info.resource === resourceType;
+    t.expectValidationError(() => {
+      t.device.createBindGroup({ layout, entries: [{ binding: 0, resource }] });
+    }, !resourceBindingMatches);
   });
-  const descriptor = {
-    size: {
-      width: 16,
-      height: 16,
-      depth: 1
-    },
-    format: 'rgba8unorm',
-    usage
-  };
-  const shouldError = usage !== info.usage;
-  t.expectValidationError(() => {
-    t.device.createBindGroup({
-      entries: [{
-        binding: 0,
-        resource: t.device.createTexture(descriptor).createView()
-      }],
-      layout: bindGroupLayout
+
+g.test('texture_binding_must_have_correct_usage')
+  .params(
+    params()
+      .combine(poptions('type', kTextureBindingTypes))
+      .combine(poptions('usage', kTextureUsages))
+  )
+  .fn(async t => {
+    const { type, usage } = t.params;
+    const info = kTextureBindingTypeInfo[type];
+
+    const storageTextureFormat = info.resource === 'storageTex' ? 'rgba8unorm' : undefined;
+    const bindGroupLayout = t.device.createBindGroupLayout({
+      entries: [{ binding: 0, visibility: GPUShaderStage.FRAGMENT, type, storageTextureFormat }],
     });
-  }, shouldError);
-});
-g.test('texture_must_have_correct_component_type').params(poptions('textureComponentType', ['float', 'sint', 'uint'])).fn(async t => {
-  const {
-    textureComponentType
-  } = t.params;
-  const bindGroupLayout = t.device.createBindGroupLayout({
-    entries: [{
-      binding: 0,
-      visibility: GPUShaderStage.FRAGMENT,
-      type: 'sampled-texture',
-      textureComponentType
-    }]
-  }); // TODO: Test more texture component types.
 
-  let format;
+    const descriptor = {
+      size: { width: 16, height: 16, depth: 1 },
+      format: 'rgba8unorm',
+      usage,
+    };
 
-  if (textureComponentType === 'float') {
-    format = 'r8unorm';
-  } else if (textureComponentType === 'sint') {
-    format = 'r8sint';
-  } else if (textureComponentType === 'uint') {
-    format = 'r8uint';
-  } else {
-    unreachable('Unexpected texture component type');
-  }
-
-  const goodDescriptor = {
-    size: {
-      width: 16,
-      height: 16,
-      depth: 1
-    },
-    format,
-    usage: GPUTextureUsage.SAMPLED
-  }; // Control case
-
-  t.device.createBindGroup({
-    entries: [{
-      binding: 0,
-      resource: t.device.createTexture(goodDescriptor).createView()
-    }],
-    layout: bindGroupLayout
-  });
-
-  function* mismatchedTextureFormats() {
-    if (textureComponentType !== 'float') {
-      yield 'r8unorm';
-    }
-
-    if (textureComponentType !== 'sint') {
-      yield 'r8sint';
-    }
-
-    if (textureComponentType !== 'uint') {
-      yield 'r8uint';
-    }
-  } // Mismatched texture binding formats are not valid.
-
-
-  for (const mismatchedTextureFormat of mismatchedTextureFormats()) {
-    const badDescriptor = clone(goodDescriptor);
-    badDescriptor.format = mismatchedTextureFormat;
+    const shouldError = usage !== info.usage;
     t.expectValidationError(() => {
       t.device.createBindGroup({
-        entries: [{
-          binding: 0,
-          resource: t.device.createTexture(badDescriptor).createView()
-        }],
-        layout: bindGroupLayout
+        entries: [{ binding: 0, resource: t.device.createTexture(descriptor).createView() }],
+        layout: bindGroupLayout,
       });
-    });
-  }
-}); // TODO: Write test for all dimensions.
+    }, shouldError);
+  });
 
+g.test('texture_must_have_correct_component_type')
+  .params(poptions('textureComponentType', ['float', 'sint', 'uint']))
+  .fn(async t => {
+    const { textureComponentType } = t.params;
+
+    const bindGroupLayout = t.device.createBindGroupLayout({
+      entries: [
+        {
+          binding: 0,
+          visibility: GPUShaderStage.FRAGMENT,
+          type: 'sampled-texture',
+          textureComponentType,
+        },
+      ],
+    });
+
+    // TODO: Test more texture component types.
+    let format;
+    if (textureComponentType === 'float') {
+      format = 'r8unorm';
+    } else if (textureComponentType === 'sint') {
+      format = 'r8sint';
+    } else if (textureComponentType === 'uint') {
+      format = 'r8uint';
+    } else {
+      unreachable('Unexpected texture component type');
+    }
+
+    const goodDescriptor = {
+      size: { width: 16, height: 16, depth: 1 },
+      format,
+      usage: GPUTextureUsage.SAMPLED,
+    };
+
+    // Control case
+    t.device.createBindGroup({
+      entries: [
+        {
+          binding: 0,
+          resource: t.device.createTexture(goodDescriptor).createView(),
+        },
+      ],
+
+      layout: bindGroupLayout,
+    });
+
+    function* mismatchedTextureFormats() {
+      if (textureComponentType !== 'float') {
+        yield 'r8unorm';
+      }
+      if (textureComponentType !== 'sint') {
+        yield 'r8sint';
+      }
+      if (textureComponentType !== 'uint') {
+        yield 'r8uint';
+      }
+    }
+
+    // Mismatched texture binding formats are not valid.
+    for (const mismatchedTextureFormat of mismatchedTextureFormats()) {
+      const badDescriptor = clone(goodDescriptor);
+      badDescriptor.format = mismatchedTextureFormat;
+
+      t.expectValidationError(() => {
+        t.device.createBindGroup({
+          entries: [{ binding: 0, resource: t.device.createTexture(badDescriptor).createView() }],
+          layout: bindGroupLayout,
+        });
+      });
+    }
+  });
+
+// TODO: Write test for all dimensions.
 g.test('texture_must_have_correct_dimension').fn(async t => {
   const bindGroupLayout = t.device.createBindGroupLayout({
-    entries: [{
-      binding: 0,
-      visibility: GPUShaderStage.FRAGMENT,
-      type: 'sampled-texture',
-      viewDimension: '2d'
-    }]
-  });
-  const goodDescriptor = {
-    size: {
-      width: 16,
-      height: 16,
-      depth: 1
-    },
-    format: 'rgba8unorm',
-    usage: GPUTextureUsage.SAMPLED
-  }; // Control case
-
-  t.device.createBindGroup({
-    entries: [{
-      binding: 0,
-      resource: t.device.createTexture(goodDescriptor).createView()
-    }],
-    layout: bindGroupLayout
-  }); // Mismatched texture binding formats are not valid.
-
-  const badDescriptor = clone(goodDescriptor);
-  badDescriptor.size.depth = 2;
-  t.expectValidationError(() => {
-    t.device.createBindGroup({
-      entries: [{
+    entries: [
+      {
         binding: 0,
-        resource: t.device.createTexture(badDescriptor).createView()
-      }],
-      layout: bindGroupLayout
-    });
+        visibility: GPUShaderStage.FRAGMENT,
+        type: 'sampled-texture',
+        viewDimension: '2d',
+      },
+    ],
   });
-});
-g.test('buffer_offset_and_size_for_bind_groups_match').params([{
-  offset: 0,
-  size: 512,
-  _success: true
-}, // offset 0 is valid
-{
-  offset: 256,
-  size: 256,
-  _success: true
-}, // offset 256 (aligned) is valid
-// Touching the end of the buffer
-{
-  offset: 0,
-  size: 1024,
-  _success: true
-}, {
-  offset: 0,
-  size: undefined,
-  _success: true
-}, {
-  offset: 256 * 3,
-  size: 256,
-  _success: true
-}, {
-  offset: 256 * 3,
-  size: undefined,
-  _success: true
-}, // Zero-sized bindings
-{
-  offset: 0,
-  size: 0,
-  _success: true
-}, {
-  offset: 256,
-  size: 0,
-  _success: true
-}, {
-  offset: 1024,
-  size: 0,
-  _success: true
-}, {
-  offset: 1024,
-  size: undefined,
-  _success: true
-}, // Unaligned buffer offset is invalid
-{
-  offset: 1,
-  size: 256,
-  _success: false
-}, {
-  offset: 1,
-  size: undefined,
-  _success: false
-}, {
-  offset: 128,
-  size: 256,
-  _success: false
-}, {
-  offset: 255,
-  size: 256,
-  _success: false
-}, // Out-of-bounds
-{
-  offset: 256 * 5,
-  size: 0,
-  _success: false
-}, // offset is OOB
-{
-  offset: 0,
-  size: 256 * 5,
-  _success: false
-}, // size is OOB
-{
-  offset: 1024,
-  size: 1,
-  _success: false
-} // offset+size is OOB
-]).fn(async t => {
-  const {
-    offset,
-    size,
-    _success
-  } = t.params;
-  const bindGroupLayout = t.device.createBindGroupLayout({
-    entries: [{
-      binding: 0,
-      visibility: GPUShaderStage.COMPUTE,
-      type: 'storage-buffer'
-    }]
-  });
-  const buffer = t.device.createBuffer({
-    size: 1024,
-    usage: GPUBufferUsage.STORAGE
-  });
-  const descriptor = {
-    entries: [{
-      binding: 0,
-      resource: {
-        buffer,
-        offset,
-        size
-      }
-    }],
-    layout: bindGroupLayout
+
+  const goodDescriptor = {
+    size: { width: 16, height: 16, depth: 1 },
+    format: 'rgba8unorm',
+    usage: GPUTextureUsage.SAMPLED,
   };
 
-  if (_success) {
-    // Control case
-    t.device.createBindGroup(descriptor);
-  } else {
-    // Buffer offset and/or size don't match in bind groups.
-    t.expectValidationError(() => {
-      t.device.createBindGroup(descriptor);
+  // Control case
+  t.device.createBindGroup({
+    entries: [{ binding: 0, resource: t.device.createTexture(goodDescriptor).createView() }],
+    layout: bindGroupLayout,
+  });
+
+  // Mismatched texture binding formats are not valid.
+  const badDescriptor = clone(goodDescriptor);
+  badDescriptor.size.depth = 2;
+
+  t.expectValidationError(() => {
+    t.device.createBindGroup({
+      entries: [{ binding: 0, resource: t.device.createTexture(badDescriptor).createView() }],
+      layout: bindGroupLayout,
     });
-  }
+  });
 });
-//# sourceMappingURL=createBindGroup.spec.js.map
\ No newline at end of file
+
+g.test('buffer_offset_and_size_for_bind_groups_match')
+  .params([
+    { offset: 0, size: 512, _success: true }, // offset 0 is valid
+    { offset: 256, size: 256, _success: true }, // offset 256 (aligned) is valid
+
+    // Touching the end of the buffer
+    { offset: 0, size: 1024, _success: true },
+    { offset: 0, size: undefined, _success: true },
+    { offset: 256 * 3, size: 256, _success: true },
+    { offset: 256 * 3, size: undefined, _success: true },
+
+    // Zero-sized bindings
+    { offset: 0, size: 0, _success: true },
+    { offset: 256, size: 0, _success: true },
+    { offset: 1024, size: 0, _success: true },
+    { offset: 1024, size: undefined, _success: true },
+
+    // Unaligned buffer offset is invalid
+    { offset: 1, size: 256, _success: false },
+    { offset: 1, size: undefined, _success: false },
+    { offset: 128, size: 256, _success: false },
+    { offset: 255, size: 256, _success: false },
+
+    // Out-of-bounds
+    { offset: 256 * 5, size: 0, _success: false }, // offset is OOB
+    { offset: 0, size: 256 * 5, _success: false }, // size is OOB
+    { offset: 1024, size: 1, _success: false }, // offset+size is OOB
+  ])
+  .fn(async t => {
+    const { offset, size, _success } = t.params;
+
+    const bindGroupLayout = t.device.createBindGroupLayout({
+      entries: [{ binding: 0, visibility: GPUShaderStage.COMPUTE, type: 'storage-buffer' }],
+    });
+
+    const buffer = t.device.createBuffer({
+      size: 1024,
+      usage: GPUBufferUsage.STORAGE,
+    });
+
+    const descriptor = {
+      entries: [
+        {
+          binding: 0,
+          resource: { buffer, offset, size },
+        },
+      ],
+
+      layout: bindGroupLayout,
+    };
+
+    if (_success) {
+      // Control case
+      t.device.createBindGroup(descriptor);
+    } else {
+      // Buffer offset and/or size don't match in bind groups.
+      t.expectValidationError(() => {
+        t.device.createBindGroup(descriptor);
+      });
+    }
+  });
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/createBindGroupLayout.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/createBindGroupLayout.spec.js
index 2fec032..43fd923 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/createBindGroupLayout.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/createBindGroupLayout.spec.js
@@ -1,13 +1,25 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = `
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `
 createBindGroupLayout validation tests.
 `;
-import { poptions, params } from '../../../common/framework/params_builder.js';
+import { pbool, poptions, params } from '../../../common/framework/params_builder.js';
 import { makeTestGroup } from '../../../common/framework/test_group.js';
-import { kBindingTypeInfo, kBindingTypes, kMaxBindingsPerBindGroup, kShaderStages } from '../../capability_info.js';
+import {
+  kBindingTypeInfo,
+  kBindingTypes,
+  kBufferBindingTypeInfo,
+  kMaxBindingsPerBindGroup,
+  kShaderStages,
+  kShaderStageCombinations,
+  kTextureBindingTypeInfo,
+  kTextureComponentTypes,
+  kTextureViewDimensions,
+  kTextureViewDimensionInfo,
+  kAllTextureFormats,
+  kAllTextureFormatInfo,
+} from '../../capability_info.js';
+
 import { ValidationTest } from './validation_test.js';
 
 function clone(descriptor) {
@@ -15,105 +27,204 @@
 }
 
 export const g = makeTestGroup(ValidationTest);
+
 g.test('some_binding_index_was_specified_more_than_once').fn(async t => {
   const goodDescriptor = {
-    entries: [{
-      binding: 0,
-      visibility: GPUShaderStage.COMPUTE,
-      type: 'storage-buffer'
-    }, {
-      binding: 1,
-      visibility: GPUShaderStage.COMPUTE,
-      type: 'storage-buffer'
-    }]
-  }; // Control case
-
-  t.device.createBindGroupLayout(goodDescriptor);
-  const badDescriptor = clone(goodDescriptor);
-  badDescriptor.entries[1].binding = 0; // Binding index 0 can't be specified twice.
-
-  t.expectValidationError(() => {
-    t.device.createBindGroupLayout(badDescriptor);
-  });
-});
-g.test('visibility_of_bindings_can_be_0').fn(async t => {
-  t.device.createBindGroupLayout({
-    entries: [{
-      binding: 0,
-      visibility: 0,
-      type: 'storage-buffer'
-    }]
-  });
-});
-g.test('number_of_dynamic_buffers_exceeds_the_maximum_value').params([{
-  type: 'storage-buffer',
-  maxDynamicBufferCount: 4
-}, {
-  type: 'uniform-buffer',
-  maxDynamicBufferCount: 8
-}]).fn(async t => {
-  const {
-    type,
-    maxDynamicBufferCount
-  } = t.params;
-  const maxDynamicBufferBindings = [];
-
-  for (let i = 0; i < maxDynamicBufferCount; i++) {
-    maxDynamicBufferBindings.push({
-      binding: i,
-      visibility: GPUShaderStage.COMPUTE,
-      type,
-      hasDynamicOffset: true
-    });
-  }
-
-  const goodDescriptor = {
-    entries: [...maxDynamicBufferBindings, {
-      binding: maxDynamicBufferBindings.length,
-      visibility: GPUShaderStage.COMPUTE,
-      type,
-      hasDynamicOffset: false
-    }]
-  }; // Control case
-
-  t.device.createBindGroupLayout(goodDescriptor); // Dynamic buffers exceed maximum in a bind group layout.
-
-  const badDescriptor = clone(goodDescriptor);
-  badDescriptor.entries[maxDynamicBufferCount].hasDynamicOffset = true;
-  t.expectValidationError(() => {
-    t.device.createBindGroupLayout(badDescriptor);
-  });
-});
-g.test('dynamic_set_to_true_is_allowed_only_for_buffers').params(poptions('type', kBindingTypes)).fn(async t => {
-  const {
-    type
-  } = t.params;
-  const success = kBindingTypeInfo[type].perPipelineLimitClass.maxDynamic > 0;
-  const descriptor = {
-    entries: [{
-      binding: 0,
-      visibility: GPUShaderStage.FRAGMENT,
-      type,
-      hasDynamicOffset: true
-    }]
+    entries: [
+      { binding: 0, visibility: GPUShaderStage.COMPUTE, type: 'storage-buffer' },
+      { binding: 1, visibility: GPUShaderStage.COMPUTE, type: 'storage-buffer' },
+    ],
   };
+
+  // Control case
+  t.device.createBindGroupLayout(goodDescriptor);
+
+  const badDescriptor = clone(goodDescriptor);
+  badDescriptor.entries[1].binding = 0;
+
+  // Binding index 0 can't be specified twice.
   t.expectValidationError(() => {
-    t.device.createBindGroupLayout(descriptor);
-  }, !success);
-}); // One bind group layout will be filled with kPerStageBindingLimit[...] of the type |type|.
+    t.device.createBindGroupLayout(badDescriptor);
+  });
+});
+
+g.test('visibility')
+  .params(
+    params()
+      .combine(poptions('type', kBindingTypes))
+      .combine(poptions('visibility', kShaderStageCombinations))
+  )
+  .fn(async t => {
+    const { type, visibility } = t.params;
+
+    const success = (visibility & ~kBindingTypeInfo[type].validStages) === 0;
+
+    t.expectValidationError(() => {
+      t.device.createBindGroupLayout({
+        entries: [{ binding: 0, visibility, type }],
+      });
+    }, !success);
+  });
+
+g.test('bindingTypeSpecific_optional_members')
+  .params(
+    params()
+      .combine(poptions('type', kBindingTypes))
+      .combine([
+        // Case with every member set to `undefined`.
+        {
+          // Workaround for TS inferring the type of [ {}, ...x ] overly conservatively, as {}[].
+          _: 0,
+        },
+
+        // Cases with one member set.
+        ...pbool('hasDynamicOffset'),
+        ...poptions('minBufferBindingSize', [0, 4]),
+        ...poptions('textureComponentType', kTextureComponentTypes),
+        ...pbool('multisampled'),
+        ...poptions('viewDimension', kTextureViewDimensions),
+        ...poptions('storageTextureFormat', kAllTextureFormats),
+      ])
+  )
+  .fn(t => {
+    const {
+      type,
+      hasDynamicOffset,
+      minBufferBindingSize,
+      textureComponentType,
+      multisampled,
+      viewDimension,
+      storageTextureFormat,
+    } = t.params;
+
+    let success = true;
+    if (!(type in kBufferBindingTypeInfo)) {
+      if (hasDynamicOffset !== undefined) success = false;
+      if (minBufferBindingSize !== undefined) success = false;
+    }
+    if (!(type in kTextureBindingTypeInfo)) {
+      if (viewDimension !== undefined) success = false;
+    }
+    if (kBindingTypeInfo[type].resource !== 'sampledTex') {
+      if (textureComponentType !== undefined) success = false;
+      if (multisampled !== undefined) success = false;
+    }
+    if (kBindingTypeInfo[type].resource !== 'storageTex') {
+      if (storageTextureFormat !== undefined) success = false;
+    } else {
+      if (viewDimension !== undefined && !kTextureViewDimensionInfo[viewDimension].storage) {
+        success = false;
+      }
+      if (
+        storageTextureFormat !== undefined &&
+        !kAllTextureFormatInfo[storageTextureFormat].storage
+      ) {
+        success = false;
+      }
+    }
+
+    t.expectValidationError(() => {
+      t.device.createBindGroupLayout({
+        entries: [
+          {
+            binding: 0,
+            visibility: GPUShaderStage.COMPUTE,
+            type,
+            hasDynamicOffset,
+            minBufferBindingSize,
+            textureComponentType,
+            multisampled,
+            viewDimension,
+            storageTextureFormat,
+          },
+        ],
+      });
+    }, !success);
+  });
+
+g.test('multisample_requires_2d_view_dimension')
+  .params(
+    params()
+      .combine(poptions('multisampled', [undefined, false, true]))
+      .combine(poptions('viewDimension', [undefined, ...kTextureViewDimensions]))
+  )
+  .fn(async t => {
+    const { multisampled, viewDimension } = t.params;
+
+    const success = multisampled !== true || viewDimension === '2d' || viewDimension === undefined;
+
+    t.expectValidationError(() => {
+      t.device.createBindGroupLayout({
+        entries: [
+          {
+            binding: 0,
+            visibility: GPUShaderStage.COMPUTE,
+            type: 'sampled-texture',
+            multisampled,
+            viewDimension,
+          },
+        ],
+      });
+    }, !success);
+  });
+
+g.test('number_of_dynamic_buffers_exceeds_the_maximum_value')
+  .params([
+    { type: 'storage-buffer', maxDynamicBufferCount: 4 },
+    { type: 'uniform-buffer', maxDynamicBufferCount: 8 },
+  ])
+  .fn(async t => {
+    const { type, maxDynamicBufferCount } = t.params;
+
+    const maxDynamicBufferBindings = [];
+    for (let i = 0; i < maxDynamicBufferCount; i++) {
+      maxDynamicBufferBindings.push({
+        binding: i,
+        visibility: GPUShaderStage.COMPUTE,
+        type,
+        hasDynamicOffset: true,
+      });
+    }
+
+    const goodDescriptor = {
+      entries: [
+        ...maxDynamicBufferBindings,
+        {
+          binding: maxDynamicBufferBindings.length,
+          visibility: GPUShaderStage.COMPUTE,
+          type,
+          hasDynamicOffset: false,
+        },
+      ],
+    };
+
+    // Control case
+    t.device.createBindGroupLayout(goodDescriptor);
+
+    // Dynamic buffers exceed maximum in a bind group layout.
+    const badDescriptor = clone(goodDescriptor);
+    badDescriptor.entries[maxDynamicBufferCount].hasDynamicOffset = true;
+
+    t.expectValidationError(() => {
+      t.device.createBindGroupLayout(badDescriptor);
+    });
+  });
+
+// One bind group layout will be filled with kPerStageBindingLimit[...] of the type |type|.
 // For each item in the array returned here, a case will be generated which tests a pipeline
 // layout with one extra bind group layout with one extra binding. That extra binding will have:
 //
 //   - If extraTypeSame, any of the binding types which counts toward the same limit as |type|.
 //     (i.e. 'storage-buffer' <-> 'readonly-storage-buffer').
 //   - Otherwise, an arbitrary other type.
-
 function* pickExtraBindingTypes(bindingType, extraTypeSame) {
   const info = kBindingTypeInfo[bindingType];
-
   if (extraTypeSame) {
     for (const extraBindingType of kBindingTypes) {
-      if (info.perStageLimitClass.class === kBindingTypeInfo[extraBindingType].perStageLimitClass.class) {
+      if (
+        info.perStageLimitClass.class ===
+        kBindingTypeInfo[extraBindingType].perStageLimitClass.class
+      ) {
         yield extraBindingType;
       }
     }
@@ -122,97 +233,103 @@
   }
 }
 
-const kCasesForMaxResourcesPerStageTests = params().combine(poptions('maxedType', kBindingTypes)).combine(poptions('maxedVisibility', kShaderStages)).filter(p => (kBindingTypeInfo[p.maxedType].validStages & p.maxedVisibility) !== 0).expand(function* (p) {
-  for (const extraTypeSame of [true, false]) {
-    yield* poptions('extraType', pickExtraBindingTypes(p.maxedType, extraTypeSame));
-  }
-}).combine(poptions('extraVisibility', kShaderStages)).filter(p => (kBindingTypeInfo[p.extraType].validStages & p.extraVisibility) !== 0); // Should never fail unless kMaxBindingsPerBindGroup is exceeded, because the validation for
+const kCasesForMaxResourcesPerStageTests = params()
+  .combine(poptions('maxedType', kBindingTypes))
+  .combine(poptions('maxedVisibility', kShaderStages))
+  .filter(p => (kBindingTypeInfo[p.maxedType].validStages & p.maxedVisibility) !== 0)
+  .expand(function* (p) {
+    for (const extraTypeSame of [true, false]) {
+      yield* poptions('extraType', pickExtraBindingTypes(p.maxedType, extraTypeSame));
+    }
+  })
+  .combine(poptions('extraVisibility', kShaderStages))
+  .filter(p => (kBindingTypeInfo[p.extraType].validStages & p.extraVisibility) !== 0);
+
+// Should never fail unless kMaxBindingsPerBindGroup is exceeded, because the validation for
 // resources-of-type-per-stage is in pipeline layout creation.
+g.test('max_resources_per_stage,in_bind_group_layout')
+  .params(kCasesForMaxResourcesPerStageTests)
+  .fn(async t => {
+    const { maxedType, extraType, maxedVisibility, extraVisibility } = t.params;
+    const maxedTypeInfo = kBindingTypeInfo[maxedType];
+    const maxedCount = maxedTypeInfo.perStageLimitClass.max;
+    const extraTypeInfo = kBindingTypeInfo[extraType];
 
-g.test('max_resources_per_stage,in_bind_group_layout').params(kCasesForMaxResourcesPerStageTests).fn(async t => {
-  const {
-    maxedType,
-    extraType,
-    maxedVisibility,
-    extraVisibility
-  } = t.params;
-  const maxedTypeInfo = kBindingTypeInfo[maxedType];
-  const maxedCount = maxedTypeInfo.perStageLimitClass.max;
-  const extraTypeInfo = kBindingTypeInfo[extraType];
-  const maxResourceBindings = [];
+    const maxResourceBindings = [];
+    for (let i = 0; i < maxedCount; i++) {
+      maxResourceBindings.push({
+        binding: i,
+        visibility: maxedVisibility,
+        type: maxedType,
+        storageTextureFormat: maxedTypeInfo.resource === 'storageTex' ? 'rgba8unorm' : undefined,
+      });
+    }
 
-  for (let i = 0; i < maxedCount; i++) {
-    maxResourceBindings.push({
-      binding: i,
-      visibility: maxedVisibility,
-      type: maxedType,
-      storageTextureFormat: maxedTypeInfo.resource === 'storageTex' ? 'rgba8unorm' : undefined
-    });
-  }
+    const goodDescriptor = { entries: maxResourceBindings };
 
-  const goodDescriptor = {
-    entries: maxResourceBindings
-  }; // Control
+    // Control
+    t.device.createBindGroupLayout(goodDescriptor);
 
-  t.device.createBindGroupLayout(goodDescriptor);
-  const newDescriptor = clone(goodDescriptor);
-  newDescriptor.entries.push({
-    binding: maxedCount,
-    visibility: extraVisibility,
-    type: extraType,
-    storageTextureFormat: extraTypeInfo.resource === 'storageTex' ? 'rgba8unorm' : undefined
-  });
-  const shouldError = maxedCount >= kMaxBindingsPerBindGroup;
-  t.expectValidationError(() => {
-    t.device.createBindGroupLayout(newDescriptor);
-  }, shouldError);
-}); // One pipeline layout can have a maximum number of each type of binding *per stage* (which is
-// different for each type). Test that the max works, then add one more binding of same-or-different
-// type and same-or-different visibility.
-
-g.test('max_resources_per_stage,in_pipeline_layout').params(kCasesForMaxResourcesPerStageTests).fn(async t => {
-  const {
-    maxedType,
-    extraType,
-    maxedVisibility,
-    extraVisibility
-  } = t.params;
-  const maxedTypeInfo = kBindingTypeInfo[maxedType];
-  const maxedCount = maxedTypeInfo.perStageLimitClass.max;
-  const extraTypeInfo = kBindingTypeInfo[extraType];
-  const maxResourceBindings = [];
-
-  for (let i = 0; i < maxedCount; i++) {
-    maxResourceBindings.push({
-      binding: i,
-      visibility: maxedVisibility,
-      type: maxedType,
-      storageTextureFormat: maxedTypeInfo.resource === 'storageTex' ? 'rgba8unorm' : undefined
-    });
-  }
-
-  const goodLayout = t.device.createBindGroupLayout({
-    entries: maxResourceBindings
-  }); // Control
-
-  t.device.createPipelineLayout({
-    bindGroupLayouts: [goodLayout]
-  });
-  const extraLayout = t.device.createBindGroupLayout({
-    entries: [{
-      binding: 0,
+    const newDescriptor = clone(goodDescriptor);
+    newDescriptor.entries.push({
+      binding: maxedCount,
       visibility: extraVisibility,
       type: extraType,
-      storageTextureFormat: extraTypeInfo.resource === 'storageTex' ? 'rgba8unorm' : undefined
-    }]
-  }); // Some binding types use the same limit, e.g. 'storage-buffer' and 'readonly-storage-buffer'.
-
-  const newBindingCountsTowardSamePerStageLimit = (maxedVisibility & extraVisibility) !== 0 && kBindingTypeInfo[maxedType].perStageLimitClass.class === kBindingTypeInfo[extraType].perStageLimitClass.class;
-  const layoutExceedsPerStageLimit = newBindingCountsTowardSamePerStageLimit;
-  t.expectValidationError(() => {
-    t.device.createPipelineLayout({
-      bindGroupLayouts: [goodLayout, extraLayout]
+      storageTextureFormat: extraTypeInfo.resource === 'storageTex' ? 'rgba8unorm' : undefined,
     });
-  }, layoutExceedsPerStageLimit);
-});
-//# sourceMappingURL=createBindGroupLayout.spec.js.map
\ No newline at end of file
+
+    const shouldError = maxedCount >= kMaxBindingsPerBindGroup;
+
+    t.expectValidationError(() => {
+      t.device.createBindGroupLayout(newDescriptor);
+    }, shouldError);
+  });
+
+// One pipeline layout can have a maximum number of each type of binding *per stage* (which is
+// different for each type). Test that the max works, then add one more binding of same-or-different
+// type and same-or-different visibility.
+g.test('max_resources_per_stage,in_pipeline_layout')
+  .params(kCasesForMaxResourcesPerStageTests)
+  .fn(async t => {
+    const { maxedType, extraType, maxedVisibility, extraVisibility } = t.params;
+    const maxedTypeInfo = kBindingTypeInfo[maxedType];
+    const maxedCount = maxedTypeInfo.perStageLimitClass.max;
+    const extraTypeInfo = kBindingTypeInfo[extraType];
+
+    const maxResourceBindings = [];
+    for (let i = 0; i < maxedCount; i++) {
+      maxResourceBindings.push({
+        binding: i,
+        visibility: maxedVisibility,
+        type: maxedType,
+        storageTextureFormat: maxedTypeInfo.resource === 'storageTex' ? 'rgba8unorm' : undefined,
+      });
+    }
+
+    const goodLayout = t.device.createBindGroupLayout({ entries: maxResourceBindings });
+
+    // Control
+    t.device.createPipelineLayout({ bindGroupLayouts: [goodLayout] });
+
+    const extraLayout = t.device.createBindGroupLayout({
+      entries: [
+        {
+          binding: 0,
+          visibility: extraVisibility,
+          type: extraType,
+          storageTextureFormat: extraTypeInfo.resource === 'storageTex' ? 'rgba8unorm' : undefined,
+        },
+      ],
+    });
+
+    // Some binding types use the same limit, e.g. 'storage-buffer' and 'readonly-storage-buffer'.
+    const newBindingCountsTowardSamePerStageLimit =
+      (maxedVisibility & extraVisibility) !== 0 &&
+      kBindingTypeInfo[maxedType].perStageLimitClass.class ===
+        kBindingTypeInfo[extraType].perStageLimitClass.class;
+    const layoutExceedsPerStageLimit = newBindingCountsTowardSamePerStageLimit;
+
+    t.expectValidationError(() => {
+      t.device.createPipelineLayout({ bindGroupLayouts: [goodLayout, extraLayout] });
+    }, layoutExceedsPerStageLimit);
+  });
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/createPipelineLayout.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/createPipelineLayout.spec.js
index 5472012..689ddae 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/createPipelineLayout.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/createPipelineLayout.spec.js
@@ -1,13 +1,12 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = `
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `
 createPipelineLayout validation tests.
 `;
-import { pbool, poptions, params } from '../../../common/framework/params_builder.js';
+import { poptions, params } from '../../../common/framework/params_builder.js';
 import { makeTestGroup } from '../../../common/framework/test_group.js';
-import { kBindingTypeInfo, kBindingTypes, kShaderStageCombinations } from '../../capability_info.js';
+import { kBindingTypeInfo } from '../../capability_info.js';
+
 import { ValidationTest } from './validation_test.js';
 
 function clone(descriptor) {
@@ -15,95 +14,82 @@
 }
 
 export const g = makeTestGroup(ValidationTest);
-g.test('number_of_dynamic_buffers_exceeds_the_maximum_value').params(params().combine(poptions('visibility', [0, 2, 4, 6])).combine(poptions('type', ['uniform-buffer', 'storage-buffer', 'readonly-storage-buffer']))).fn(async t => {
-  const {
-    type,
-    visibility
-  } = t.params;
-  const {
-    maxDynamic
-  } = kBindingTypeInfo[type].perPipelineLimitClass;
-  const maxDynamicBufferBindings = [];
 
-  for (let binding = 0; binding < maxDynamic; binding++) {
-    maxDynamicBufferBindings.push({
-      binding,
-      visibility,
-      type,
-      hasDynamicOffset: true
+g.test('number_of_dynamic_buffers_exceeds_the_maximum_value')
+  .params(
+    params()
+      .combine(poptions('visibility', [0, 2, 4, 6]))
+      .combine(poptions('type', ['uniform-buffer', 'storage-buffer', 'readonly-storage-buffer']))
+  )
+  .fn(async t => {
+    const { type, visibility } = t.params;
+    const { maxDynamic } = kBindingTypeInfo[type].perPipelineLimitClass;
+
+    const maxDynamicBufferBindings = [];
+    for (let binding = 0; binding < maxDynamic; binding++) {
+      maxDynamicBufferBindings.push({ binding, visibility, type, hasDynamicOffset: true });
+    }
+
+    const maxDynamicBufferBindGroupLayout = t.device.createBindGroupLayout({
+      entries: maxDynamicBufferBindings,
     });
-  }
 
-  const maxDynamicBufferBindGroupLayout = t.device.createBindGroupLayout({
-    entries: maxDynamicBufferBindings
-  });
-  const goodDescriptor = {
-    entries: [{
-      binding: 0,
-      visibility,
-      type,
-      hasDynamicOffset: false
-    }]
-  };
-  const goodPipelineLayoutDescriptor = {
-    bindGroupLayouts: [maxDynamicBufferBindGroupLayout, t.device.createBindGroupLayout(goodDescriptor)]
-  }; // Control case
+    const goodDescriptor = {
+      entries: [{ binding: 0, visibility, type, hasDynamicOffset: false }],
+    };
 
-  t.device.createPipelineLayout(goodPipelineLayoutDescriptor); // Check dynamic buffers exceed maximum in pipeline layout.
+    const goodPipelineLayoutDescriptor = {
+      bindGroupLayouts: [
+        maxDynamicBufferBindGroupLayout,
+        t.device.createBindGroupLayout(goodDescriptor),
+      ],
+    };
 
-  const badDescriptor = clone(goodDescriptor);
-  badDescriptor.entries[0].hasDynamicOffset = true;
-  const badPipelineLayoutDescriptor = {
-    bindGroupLayouts: [maxDynamicBufferBindGroupLayout, t.device.createBindGroupLayout(badDescriptor)]
-  };
-  t.expectValidationError(() => {
-    t.device.createPipelineLayout(badPipelineLayoutDescriptor);
-  });
-});
-g.test('visibility_and_dynamic_offsets').params(params().combine(poptions('type', kBindingTypes)).combine(pbool('hasDynamicOffset')).combine(poptions('visibility', kShaderStageCombinations))).fn(t => {
-  const {
-    type,
-    hasDynamicOffset,
-    visibility
-  } = t.params;
-  const info = kBindingTypeInfo[type];
-  const storageTextureFormat = info.resource === 'storageTex' ? 'r32uint' : undefined;
-  const descriptor = {
-    entries: [{
-      binding: 0,
-      visibility,
-      type,
-      hasDynamicOffset,
-      storageTextureFormat
-    }]
-  };
-  const supportsDynamicOffset = kBindingTypeInfo[type].perPipelineLimitClass.maxDynamic > 0;
-  let success = true;
-  if (!supportsDynamicOffset && hasDynamicOffset) success = false;
-  if ((visibility & ~info.validStages) !== 0) success = false;
-  t.expectValidationError(() => {
-    t.device.createPipelineLayout({
-      bindGroupLayouts: [t.device.createBindGroupLayout(descriptor)]
+    // Control case
+    t.device.createPipelineLayout(goodPipelineLayoutDescriptor);
+
+    // Check dynamic buffers exceed maximum in pipeline layout.
+    const badDescriptor = clone(goodDescriptor);
+    badDescriptor.entries[0].hasDynamicOffset = true;
+
+    const badPipelineLayoutDescriptor = {
+      bindGroupLayouts: [
+        maxDynamicBufferBindGroupLayout,
+        t.device.createBindGroupLayout(badDescriptor),
+      ],
+    };
+
+    t.expectValidationError(() => {
+      t.device.createPipelineLayout(badPipelineLayoutDescriptor);
     });
-  }, !success);
-});
+  });
+
 g.test('number_of_bind_group_layouts_exceeds_the_maximum_value').fn(async t => {
   const bindGroupLayoutDescriptor = {
-    entries: []
-  }; // 4 is the maximum number of bind group layouts.
-
-  const maxBindGroupLayouts = [1, 2, 3, 4].map(() => t.device.createBindGroupLayout(bindGroupLayoutDescriptor));
-  const goodPipelineLayoutDescriptor = {
-    bindGroupLayouts: maxBindGroupLayouts
-  }; // Control case
-
-  t.device.createPipelineLayout(goodPipelineLayoutDescriptor); // Check bind group layouts exceed maximum in pipeline layout.
-
-  const badPipelineLayoutDescriptor = {
-    bindGroupLayouts: [...maxBindGroupLayouts, t.device.createBindGroupLayout(bindGroupLayoutDescriptor)]
+    entries: [],
   };
+
+  // 4 is the maximum number of bind group layouts.
+  const maxBindGroupLayouts = [1, 2, 3, 4].map(() =>
+    t.device.createBindGroupLayout(bindGroupLayoutDescriptor)
+  );
+
+  const goodPipelineLayoutDescriptor = {
+    bindGroupLayouts: maxBindGroupLayouts,
+  };
+
+  // Control case
+  t.device.createPipelineLayout(goodPipelineLayoutDescriptor);
+
+  // Check bind group layouts exceed maximum in pipeline layout.
+  const badPipelineLayoutDescriptor = {
+    bindGroupLayouts: [
+      ...maxBindGroupLayouts,
+      t.device.createBindGroupLayout(bindGroupLayoutDescriptor),
+    ],
+  };
+
   t.expectValidationError(() => {
     t.device.createPipelineLayout(badPipelineLayoutDescriptor);
   });
 });
-//# sourceMappingURL=createPipelineLayout.spec.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/createRenderPipeline.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/createRenderPipeline.spec.js
index de1f675..30ca11e 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/createRenderPipeline.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/createRenderPipeline.spec.js
@@ -1,27 +1,26 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = `
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `
 createRenderPipeline validation tests.
 `;
 import { poptions } from '../../../common/framework/params_builder.js';
 import { makeTestGroup } from '../../../common/framework/test_group.js';
-import { kTextureFormatInfo, kTextureFormats } from '../../capability_info.js';
+import { kAllTextureFormats, kAllTextureFormatInfo } from '../../capability_info.js';
+
 import { ValidationTest } from './validation_test.js';
 
 class F extends ValidationTest {
   getDescriptor(options = {}) {
-    const defaultColorStates = [{
-      format: 'rgba8unorm'
-    }];
+    const defaultColorStates = [{ format: 'rgba8unorm' }];
     const {
       primitiveTopology = 'triangle-list',
       colorStates = defaultColorStates,
       sampleCount = 1,
-      depthStencilState
+      depthStencilState,
     } = options;
+
     const format = colorStates.length ? colorStates[0].format : 'rgba8unorm';
+
     return {
       vertexStage: this.getVertexStage(),
       fragmentStage: this.getFragmentStage(format),
@@ -29,7 +28,7 @@
       primitiveTopology,
       colorStates,
       sampleCount,
-      depthStencilState
+      depthStencilState,
     };
   }
 
@@ -41,15 +40,15 @@
           void main() {
             gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
           }
-        `
+        `,
       }),
-      entryPoint: 'main'
+
+      entryPoint: 'main',
     };
   }
 
   getFragmentStage(format) {
     let fragColorType;
-
     if (format.endsWith('sint')) {
       fragColorType = 'ivec4';
     } else if (format.endsWith('uint')) {
@@ -65,197 +64,171 @@
         fragColor = ${fragColorType}(0.0, 1.0, 0.0, 1.0);
       }
     `;
+
     return {
-      module: this.makeShaderModule('fragment', {
-        glsl
-      }),
-      entryPoint: 'main'
+      module: this.makeShaderModule('fragment', { glsl }),
+      entryPoint: 'main',
     };
   }
 
   getPipelineLayout() {
-    return this.device.createPipelineLayout({
-      bindGroupLayouts: []
-    });
+    return this.device.createPipelineLayout({ bindGroupLayouts: [] });
   }
 
   createTexture(params) {
-    const {
-      format,
-      sampleCount
-    } = params;
+    const { format, sampleCount } = params;
+
     return this.device.createTexture({
-      size: {
-        width: 4,
-        height: 4,
-        depth: 1
-      },
+      size: { width: 4, height: 4, depth: 1 },
       usage: GPUTextureUsage.OUTPUT_ATTACHMENT,
       format,
-      sampleCount
+      sampleCount,
     });
   }
-
 }
 
 export const g = makeTestGroup(F);
+
 g.test('basic_use_of_createRenderPipeline').fn(t => {
   const descriptor = t.getDescriptor();
+
   t.device.createRenderPipeline(descriptor);
 });
+
 g.test('at_least_one_color_state_is_required').fn(async t => {
   const goodDescriptor = t.getDescriptor({
-    colorStates: [{
-      format: 'rgba8unorm'
-    }]
-  }); // Control case
-
-  t.device.createRenderPipeline(goodDescriptor); // Fail because lack of color states
-
-  const badDescriptor = t.getDescriptor({
-    colorStates: []
+    colorStates: [{ format: 'rgba8unorm' }],
   });
+
+  // Control case
+  t.device.createRenderPipeline(goodDescriptor);
+
+  // Fail because lack of color states
+  const badDescriptor = t.getDescriptor({
+    colorStates: [],
+  });
+
   t.expectValidationError(() => {
     t.device.createRenderPipeline(badDescriptor);
   });
 });
-g.test('color_formats_must_be_renderable').params(poptions('format', kTextureFormats)).fn(async t => {
-  const format = t.params.format;
-  const info = kTextureFormatInfo[format];
-  const descriptor = t.getDescriptor({
-    colorStates: [{
-      format
-    }]
-  });
 
-  if (info.renderable && info.color) {
-    // Succeeds when color format is renderable
-    t.device.createRenderPipeline(descriptor);
-  } else {
-    // Fails because when format is non-renderable
-    t.expectValidationError(() => {
-      t.device.createRenderPipeline(descriptor);
-    });
-  }
-});
-g.test('sample_count_must_be_valid').params([{
-  sampleCount: 0,
-  _success: false
-}, {
-  sampleCount: 1,
-  _success: true
-}, {
-  sampleCount: 2,
-  _success: false
-}, {
-  sampleCount: 3,
-  _success: false
-}, {
-  sampleCount: 4,
-  _success: true
-}, {
-  sampleCount: 8,
-  _success: false
-}, {
-  sampleCount: 16,
-  _success: false
-}]).fn(async t => {
-  const {
-    sampleCount,
-    _success
-  } = t.params;
-  const descriptor = t.getDescriptor({
-    sampleCount
-  });
+g.test('color_formats_must_be_renderable')
+  .params(poptions('format', kAllTextureFormats))
+  .fn(async t => {
+    const format = t.params.format;
+    const info = kAllTextureFormatInfo[format];
 
-  if (_success) {
-    // Succeeds when sample count is valid
-    t.device.createRenderPipeline(descriptor);
-  } else {
-    // Fails when sample count is not 4 or 1
-    t.expectValidationError(() => {
+    const descriptor = t.getDescriptor({ colorStates: [{ format }] });
+
+    if (info.renderable && info.color) {
+      // Succeeds when color format is renderable
       t.device.createRenderPipeline(descriptor);
-    });
-  }
-});
-g.test('sample_count_must_be_equal_to_the_one_of_every_attachment_in_the_render_pass').params([{
-  attachmentSamples: 4,
-  pipelineSamples: 4,
-  _success: true
-}, // It is allowed to use multisampled render pass and multisampled render pipeline.
-{
-  attachmentSamples: 4,
-  pipelineSamples: 1,
-  _success: false
-}, // It is not allowed to use multisampled render pass and non-multisampled render pipeline.
-{
-  attachmentSamples: 1,
-  pipelineSamples: 4,
-  _success: false
-} // It is not allowed to use non-multisampled render pass and multisampled render pipeline.
-]).fn(async t => {
-  const {
-    attachmentSamples,
-    pipelineSamples,
-    _success
-  } = t.params;
-  const colorTexture = t.createTexture({
-    format: 'rgba8unorm',
-    sampleCount: attachmentSamples
-  });
-  const depthStencilTexture = t.createTexture({
-    format: 'depth24plus-stencil8',
-    sampleCount: attachmentSamples
-  });
-  const renderPassDescriptorWithoutDepthStencil = {
-    colorAttachments: [{
-      attachment: colorTexture.createView(),
-      loadValue: {
-        r: 1.0,
-        g: 0.0,
-        b: 0.0,
-        a: 1.0
-      }
-    }]
-  };
-  const renderPassDescriptorWithDepthStencilOnly = {
-    colorAttachments: [],
-    depthStencilAttachment: {
-      attachment: depthStencilTexture.createView(),
-      depthLoadValue: 1.0,
-      depthStoreOp: 'store',
-      stencilLoadValue: 0,
-      stencilStoreOp: 'store'
+    } else {
+      // Fails because when format is non-renderable
+      t.expectValidationError(() => {
+        t.device.createRenderPipeline(descriptor);
+      });
     }
-  };
-  const pipelineWithoutDepthStencil = t.device.createRenderPipeline(t.getDescriptor({
-    sampleCount: pipelineSamples
-  }));
-  const pipelineWithDepthStencilOnly = t.device.createRenderPipeline(t.getDescriptor({
-    colorStates: [],
-    depthStencilState: {
-      format: 'depth24plus-stencil8'
-    },
-    sampleCount: pipelineSamples
-  }));
+  });
 
-  for (const {
-    renderPassDescriptor,
-    pipeline
-  } of [{
-    renderPassDescriptor: renderPassDescriptorWithoutDepthStencil,
-    pipeline: pipelineWithoutDepthStencil
-  }, {
-    renderPassDescriptor: renderPassDescriptorWithDepthStencilOnly,
-    pipeline: pipelineWithDepthStencilOnly
-  }]) {
-    const commandEncoder = t.device.createCommandEncoder();
-    const renderPass = commandEncoder.beginRenderPass(renderPassDescriptor);
-    renderPass.setPipeline(pipeline);
-    renderPass.endPass();
-    t.expectValidationError(() => {
-      commandEncoder.finish();
-    }, !_success);
-  }
-});
-//# sourceMappingURL=createRenderPipeline.spec.js.map
\ No newline at end of file
+g.test('sample_count_must_be_valid')
+  .params([
+    { sampleCount: 0, _success: false },
+    { sampleCount: 1, _success: true },
+    { sampleCount: 2, _success: false },
+    { sampleCount: 3, _success: false },
+    { sampleCount: 4, _success: true },
+    { sampleCount: 8, _success: false },
+    { sampleCount: 16, _success: false },
+  ])
+  .fn(async t => {
+    const { sampleCount, _success } = t.params;
+
+    const descriptor = t.getDescriptor({ sampleCount });
+
+    if (_success) {
+      // Succeeds when sample count is valid
+      t.device.createRenderPipeline(descriptor);
+    } else {
+      // Fails when sample count is not 4 or 1
+      t.expectValidationError(() => {
+        t.device.createRenderPipeline(descriptor);
+      });
+    }
+  });
+
+g.test('sample_count_must_be_equal_to_the_one_of_every_attachment_in_the_render_pass')
+  .params([
+    { attachmentSamples: 4, pipelineSamples: 4, _success: true }, // It is allowed to use multisampled render pass and multisampled render pipeline.
+    { attachmentSamples: 4, pipelineSamples: 1, _success: false }, // It is not allowed to use multisampled render pass and non-multisampled render pipeline.
+    { attachmentSamples: 1, pipelineSamples: 4, _success: false }, // It is not allowed to use non-multisampled render pass and multisampled render pipeline.
+  ])
+  .fn(async t => {
+    const { attachmentSamples, pipelineSamples, _success } = t.params;
+
+    const colorTexture = t.createTexture({
+      format: 'rgba8unorm',
+      sampleCount: attachmentSamples,
+    });
+
+    const depthStencilTexture = t.createTexture({
+      format: 'depth24plus-stencil8',
+      sampleCount: attachmentSamples,
+    });
+
+    const renderPassDescriptorWithoutDepthStencil = {
+      colorAttachments: [
+        {
+          attachment: colorTexture.createView(),
+          loadValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+        },
+      ],
+    };
+
+    const renderPassDescriptorWithDepthStencilOnly = {
+      colorAttachments: [],
+      depthStencilAttachment: {
+        attachment: depthStencilTexture.createView(),
+        depthLoadValue: 1.0,
+        depthStoreOp: 'store',
+        stencilLoadValue: 0,
+        stencilStoreOp: 'store',
+      },
+    };
+
+    const pipelineWithoutDepthStencil = t.device.createRenderPipeline(
+      t.getDescriptor({
+        sampleCount: pipelineSamples,
+      })
+    );
+
+    const pipelineWithDepthStencilOnly = t.device.createRenderPipeline(
+      t.getDescriptor({
+        colorStates: [],
+        depthStencilState: { format: 'depth24plus-stencil8' },
+        sampleCount: pipelineSamples,
+      })
+    );
+
+    for (const { renderPassDescriptor, pipeline } of [
+      {
+        renderPassDescriptor: renderPassDescriptorWithoutDepthStencil,
+        pipeline: pipelineWithoutDepthStencil,
+      },
+
+      {
+        renderPassDescriptor: renderPassDescriptorWithDepthStencilOnly,
+        pipeline: pipelineWithDepthStencilOnly,
+      },
+    ]) {
+      const commandEncoder = t.device.createCommandEncoder();
+      const renderPass = commandEncoder.beginRenderPass(renderPassDescriptor);
+      renderPass.setPipeline(pipeline);
+      renderPass.endPass();
+
+      t.expectValidationError(() => {
+        commandEncoder.finish();
+      }, !_success);
+    }
+  });
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/createTexture.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/createTexture.spec.js
index 2ef8d19..ceff54e 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/createTexture.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/createTexture.spec.js
@@ -1,13 +1,12 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = `
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `
 createTexture validation tests.
 `;
 import { poptions } from '../../../common/framework/params_builder.js';
 import { makeTestGroup } from '../../../common/framework/test_group.js';
-import { kTextureFormatInfo, kTextureFormats } from '../../capability_info.js';
+import { kAllTextureFormats, kAllTextureFormatInfo } from '../../capability_info.js';
+
 import { ValidationTest } from './validation_test.js';
 
 class F extends ValidationTest {
@@ -18,220 +17,129 @@
       arrayLayerCount = 1,
       mipLevelCount = 1,
       sampleCount = 1,
-      format = 'rgba8unorm'
+      format = 'rgba8unorm',
     } = options;
     return {
-      size: {
-        width,
-        height,
-        depth: arrayLayerCount
-      },
+      size: { width, height, depth: arrayLayerCount },
       mipLevelCount,
       sampleCount,
       dimension: '2d',
       format,
-      usage: GPUTextureUsage.OUTPUT_ATTACHMENT | GPUTextureUsage.SAMPLED
+      usage: GPUTextureUsage.OUTPUT_ATTACHMENT | GPUTextureUsage.SAMPLED,
     };
   }
-
 }
 
 export const g = makeTestGroup(F);
-g.test('validation_of_sampleCount').params([// TODO: Consider making a list of "valid"+"invalid" texture descriptors in capability_info.
-{
-  sampleCount: 0,
-  _success: false
-}, // sampleCount of 0 is not allowed
-{
-  sampleCount: 1,
-  _success: true
-}, // sampleCount of 1 is allowed
-{
-  sampleCount: 2,
-  _success: false
-}, // sampleCount of 2 is not allowed
-{
-  sampleCount: 3,
-  _success: false
-}, // sampleCount of 3 is not allowed
-{
-  sampleCount: 4,
-  _success: true
-}, // sampleCount of 4 is allowed
-{
-  sampleCount: 8,
-  _success: false
-}, // sampleCount of 8 is not allowed
-{
-  sampleCount: 16,
-  _success: false
-}, // sampleCount of 16 is not allowed
-{
-  sampleCount: 4,
-  mipLevelCount: 2,
-  _success: false
-}, // multisampled multi-level not allowed
-{
-  sampleCount: 4,
-  arrayLayerCount: 2,
-  _success: false
-} // multisampled multi-layer is not allowed
-]).fn(async t => {
-  const {
-    sampleCount,
-    mipLevelCount,
-    arrayLayerCount,
-    _success
-  } = t.params;
-  const descriptor = t.getDescriptor({
-    sampleCount,
-    mipLevelCount,
-    arrayLayerCount
+
+g.test('validation_of_sampleCount')
+  .params([
+    // TODO: Consider making a list of "valid"+"invalid" texture descriptors in capability_info.
+    { sampleCount: 0, _success: false }, // sampleCount of 0 is not allowed
+    { sampleCount: 1, _success: true }, // sampleCount of 1 is allowed
+    { sampleCount: 2, _success: false }, // sampleCount of 2 is not allowed
+    { sampleCount: 3, _success: false }, // sampleCount of 3 is not allowed
+    { sampleCount: 4, _success: true }, // sampleCount of 4 is allowed
+    { sampleCount: 8, _success: false }, // sampleCount of 8 is not allowed
+    { sampleCount: 16, _success: false }, // sampleCount of 16 is not allowed
+    { sampleCount: 4, mipLevelCount: 2, _success: false }, // multisampled multi-level not allowed
+    { sampleCount: 4, arrayLayerCount: 2, _success: false }, // multisampled multi-layer is not allowed
+  ])
+  .fn(async t => {
+    const { sampleCount, mipLevelCount, arrayLayerCount, _success } = t.params;
+
+    const descriptor = t.getDescriptor({ sampleCount, mipLevelCount, arrayLayerCount });
+
+    t.expectValidationError(() => {
+      t.device.createTexture(descriptor);
+    }, !_success);
   });
-  t.expectValidationError(() => {
-    t.device.createTexture(descriptor);
-  }, !_success);
-});
-g.test('validation_of_mipLevelCount').params([{
-  width: 32,
-  height: 32,
-  mipLevelCount: 1,
-  _success: true
-}, // mipLevelCount of 1 is allowed
-{
-  width: 32,
-  height: 32,
-  mipLevelCount: 0,
-  _success: false
-}, // mipLevelCount of 0 is not allowed
-{
-  width: 32,
-  height: 32,
-  mipLevelCount: 6,
-  _success: true
-}, // full mip chains are allowed (Mip level sizes: 32, 16, 8, 4, 2, 1)
-{
-  width: 31,
-  height: 32,
-  mipLevelCount: 6,
-  _success: true
-}, // full mip chains are allowed (Mip level sizes: 31x32, 15x16, 7x8, 3x4, 1x2, 1x1)
-{
-  width: 32,
-  height: 31,
-  mipLevelCount: 6,
-  _success: true
-}, // full mip chains are allowed (Mip level sizes: 32x31, 16x15, 8x7, 4x3, 2x1, 1x1)
-{
-  width: 31,
-  height: 32,
-  mipLevelCount: 7,
-  _success: false
-}, // too big mip chains on width are disallowed (Mip level sizes: 31x32, 15x16, 7x8, 3x4, 1x2, 1x1, ?x?)
-{
-  width: 32,
-  height: 31,
-  mipLevelCount: 7,
-  _success: false
-}, // too big mip chains on height are disallowed (Mip level sizes: 32x31, 16x15, 8x7, 4x3, 2x1, 1x1, ?x?)
-{
-  width: 32,
-  height: 32,
-  mipLevelCount: 100,
-  _success: false
-}, // undefined shift check if miplevel is bigger than the integer bit width
-{
-  width: 32,
-  height: 8,
-  mipLevelCount: 6,
-  _success: true
-} // non square mip map halves the resolution until a 1x1 dimension. (Mip maps: 32 * 8, 16 * 4, 8 * 2, 4 * 1, 2 * 1, 1 * 1)
-]).fn(async t => {
-  const {
-    width,
-    height,
-    mipLevelCount,
-    _success
-  } = t.params;
-  const descriptor = t.getDescriptor({
-    width,
-    height,
-    mipLevelCount
+
+g.test('validation_of_mipLevelCount')
+  .params([
+    { width: 32, height: 32, mipLevelCount: 1, _success: true }, // mipLevelCount of 1 is allowed
+    { width: 32, height: 32, mipLevelCount: 0, _success: false }, // mipLevelCount of 0 is not allowed
+    { width: 32, height: 32, mipLevelCount: 6, _success: true }, // full mip chains are allowed (Mip level sizes: 32, 16, 8, 4, 2, 1)
+    { width: 31, height: 32, mipLevelCount: 6, _success: true }, // full mip chains are allowed (Mip level sizes: 31x32, 15x16, 7x8, 3x4, 1x2, 1x1)
+    { width: 32, height: 31, mipLevelCount: 6, _success: true }, // full mip chains are allowed (Mip level sizes: 32x31, 16x15, 8x7, 4x3, 2x1, 1x1)
+    { width: 31, height: 32, mipLevelCount: 7, _success: false }, // too big mip chains on width are disallowed (Mip level sizes: 31x32, 15x16, 7x8, 3x4, 1x2, 1x1, ?x?)
+    { width: 32, height: 31, mipLevelCount: 7, _success: false }, // too big mip chains on height are disallowed (Mip level sizes: 32x31, 16x15, 8x7, 4x3, 2x1, 1x1, ?x?)
+    { width: 32, height: 32, mipLevelCount: 100, _success: false }, // undefined shift check if miplevel is bigger than the integer bit width
+    { width: 32, height: 8, mipLevelCount: 6, _success: true }, // non square mip map halves the resolution until a 1x1 dimension. (Mip maps: 32 * 8, 16 * 4, 8 * 2, 4 * 1, 2 * 1, 1 * 1)
+  ])
+  .fn(async t => {
+    const { width, height, mipLevelCount, _success } = t.params;
+
+    const descriptor = t.getDescriptor({ width, height, mipLevelCount });
+
+    t.expectValidationError(() => {
+      t.device.createTexture(descriptor);
+    }, !_success);
   });
-  t.expectValidationError(() => {
-    t.device.createTexture(descriptor);
-  }, !_success);
-});
+
 g.test('it_is_valid_to_destroy_a_texture').fn(t => {
   const descriptor = t.getDescriptor();
   const texture = t.device.createTexture(descriptor);
   texture.destroy();
 });
+
 g.test('it_is_valid_to_destroy_a_destroyed_texture').fn(t => {
   const descriptor = t.getDescriptor();
   const texture = t.device.createTexture(descriptor);
   texture.destroy();
   texture.destroy();
 });
-g.test('it_is_invalid_to_submit_a_destroyed_texture_before_and_after_encode').params([{
-  destroyBeforeEncode: false,
-  destroyAfterEncode: false,
-  _success: true
-}, {
-  destroyBeforeEncode: true,
-  destroyAfterEncode: false,
-  _success: false
-}, {
-  destroyBeforeEncode: false,
-  destroyAfterEncode: true,
-  _success: false
-}]).fn(async t => {
-  const {
-    destroyBeforeEncode,
-    destroyAfterEncode,
-    _success
-  } = t.params;
-  const descriptor = t.getDescriptor();
-  const texture = t.device.createTexture(descriptor);
-  const textureView = texture.createView();
 
-  if (destroyBeforeEncode) {
-    texture.destroy();
-  }
+g.test('it_is_invalid_to_submit_a_destroyed_texture_before_and_after_encode')
+  .params([
+    { destroyBeforeEncode: false, destroyAfterEncode: false, _success: true },
+    { destroyBeforeEncode: true, destroyAfterEncode: false, _success: false },
+    { destroyBeforeEncode: false, destroyAfterEncode: true, _success: false },
+  ])
+  .fn(async t => {
+    const { destroyBeforeEncode, destroyAfterEncode, _success } = t.params;
 
-  const commandEncoder = t.device.createCommandEncoder();
-  const renderPass = commandEncoder.beginRenderPass({
-    colorAttachments: [{
-      attachment: textureView,
-      loadValue: {
-        r: 1.0,
-        g: 0.0,
-        b: 0.0,
-        a: 1.0
-      }
-    }]
+    const descriptor = t.getDescriptor();
+    const texture = t.device.createTexture(descriptor);
+    const textureView = texture.createView();
+
+    if (destroyBeforeEncode) {
+      texture.destroy();
+    }
+
+    const commandEncoder = t.device.createCommandEncoder();
+    const renderPass = commandEncoder.beginRenderPass({
+      colorAttachments: [
+        {
+          attachment: textureView,
+          loadValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+        },
+      ],
+    });
+
+    renderPass.endPass();
+    const commandBuffer = commandEncoder.finish();
+
+    if (destroyAfterEncode) {
+      texture.destroy();
+    }
+
+    t.expectValidationError(() => {
+      t.queue.submit([commandBuffer]);
+    }, !_success);
   });
-  renderPass.endPass();
-  const commandBuffer = commandEncoder.finish();
 
-  if (destroyAfterEncode) {
-    texture.destroy();
-  }
+g.test('it_is_invalid_to_have_an_output_attachment_texture_with_non_renderable_format')
+  .params(poptions('format', kAllTextureFormats))
+  .fn(async t => {
+    const format = t.params.format;
+    const info = kAllTextureFormatInfo[format];
 
-  t.expectValidationError(() => {
-    t.queue.submit([commandBuffer]);
-  }, !_success);
-});
-g.test('it_is_invalid_to_have_an_output_attachment_texture_with_non_renderable_format').params(poptions('format', kTextureFormats)).fn(async t => {
-  const format = t.params.format;
-  const info = kTextureFormatInfo[format];
-  const descriptor = t.getDescriptor({
-    width: 1,
-    height: 1,
-    format
+    const descriptor = t.getDescriptor({ width: 1, height: 1, format });
+
+    t.expectValidationError(() => {
+      t.device.createTexture(descriptor);
+    }, !info.renderable);
   });
-  t.expectValidationError(() => {
-    t.device.createTexture(descriptor);
-  }, !info.renderable);
-}); // TODO: Add tests for compressed texture formats
-//# sourceMappingURL=createTexture.spec.js.map
\ No newline at end of file
+
+// TODO: Add tests for compressed texture formats
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/createView.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/createView.spec.js
index 3ad1ca97..d5b1656 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/createView.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/createView.spec.js
@@ -1,12 +1,12 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = `
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `
 createView validation tests.
 `;
 import { makeTestGroup } from '../../../common/framework/test_group.js';
+
 import { ValidationTest } from './validation_test.js';
+
 const ARRAY_LAYER_COUNT_2D = 6;
 const MIP_LEVEL_COUNT = 6;
 const FORMAT = 'rgba8unorm';
@@ -18,19 +18,16 @@
       height = 32,
       arrayLayerCount = 1,
       mipLevelCount = MIP_LEVEL_COUNT,
-      sampleCount = 1
+      sampleCount = 1,
     } = options;
+
     return this.device.createTexture({
-      size: {
-        width,
-        height,
-        depth: arrayLayerCount
-      },
+      size: { width, height, depth: arrayLayerCount },
       mipLevelCount,
       sampleCount,
       dimension: '2d',
       format: FORMAT,
-      usage: GPUTextureUsage.SAMPLED
+      usage: GPUTextureUsage.SAMPLED,
     });
   }
 
@@ -41,7 +38,7 @@
       baseMipLevel = 0,
       mipLevelCount = MIP_LEVEL_COUNT,
       baseArrayLayer = 0,
-      arrayLayerCount = 1
+      arrayLayerCount = 1,
     } = options;
     return {
       format,
@@ -49,368 +46,222 @@
       baseMipLevel,
       mipLevelCount,
       baseArrayLayer,
-      arrayLayerCount
+      arrayLayerCount,
     };
   }
-
 }
 
 export const g = makeTestGroup(F);
-g.test('creating_texture_view_on_a_2D_non_array_texture').params([{
-  _success: true
-}, // default view works
-{
-  arrayLayerCount: 1,
-  _success: true
-}, // it is OK to create a 2D texture view on a 2D texture
-{
-  arrayLayerCount: 2,
-  _success: false
-}, // it is an error to view a layer past the end of the texture
-{
-  dimension: '2d-array',
-  arrayLayerCount: 1,
-  _success: true
-}, // it is OK to create a 1-layer 2D array texture view on a 2D texture
-// mip level is in range
-{
-  mipLevelCount: 1,
-  baseMipLevel: MIP_LEVEL_COUNT - 1,
-  _success: true
-}, {
-  mipLevelCount: 2,
-  baseMipLevel: MIP_LEVEL_COUNT - 2,
-  _success: true
-}, // baseMipLevel == k && mipLevelCount == 0 means to use levels k..end
-{
-  mipLevelCount: 0,
-  baseMipLevel: 0,
-  _success: true
-}, {
-  mipLevelCount: 0,
-  baseMipLevel: 1,
-  _success: true
-}, {
-  mipLevelCount: 0,
-  baseMipLevel: MIP_LEVEL_COUNT - 1,
-  _success: true
-}, {
-  mipLevelCount: 0,
-  baseMipLevel: MIP_LEVEL_COUNT,
-  _success: false
-}, // it is an error to make the mip level out of range
-{
-  mipLevelCount: MIP_LEVEL_COUNT + 1,
-  baseMipLevel: 0,
-  _success: false
-}, {
-  mipLevelCount: MIP_LEVEL_COUNT,
-  baseMipLevel: 1,
-  _success: false
-}, {
-  mipLevelCount: 2,
-  baseMipLevel: MIP_LEVEL_COUNT - 1,
-  _success: false
-}, {
-  mipLevelCount: 1,
-  baseMipLevel: MIP_LEVEL_COUNT,
-  _success: false
-}]).fn(async t => {
-  const {
-    dimension = '2d',
-    arrayLayerCount,
-    mipLevelCount,
-    baseMipLevel,
-    _success
-  } = t.params;
-  const texture = t.createTexture({
-    arrayLayerCount: 1
-  });
-  const descriptor = t.getDescriptor({
-    dimension,
-    arrayLayerCount,
-    mipLevelCount,
-    baseMipLevel
-  });
-  t.expectValidationError(() => {
-    texture.createView(descriptor);
-  }, !_success);
-});
-g.test('creating_texture_view_on_a_2D_array_texture').params([{
-  _success: true
-}, // default view works
-{
-  dimension: '2d',
-  arrayLayerCount: 1,
-  _success: true
-}, // it is OK to create a 2D texture view on a 2D array texture
-{
-  arrayLayerCount: ARRAY_LAYER_COUNT_2D,
-  _success: true
-}, // it is OK to create a 2D array texture view on a 2D array texture
-// baseArrayLayer == k && arrayLayerCount == 0 means to use layers k..end.
-{
-  arrayLayerCount: 0,
-  baseArrayLayer: 0,
-  _success: true
-}, {
-  arrayLayerCount: 0,
-  baseArrayLayer: 1,
-  _success: true
-}, {
-  arrayLayerCount: 0,
-  baseArrayLayer: ARRAY_LAYER_COUNT_2D - 1,
-  _success: true
-}, {
-  arrayLayerCount: 0,
-  baseArrayLayer: ARRAY_LAYER_COUNT_2D,
-  _success: false
-}, // It is an error for the array layer range of the view to exceed that of the texture
-{
-  arrayLayerCount: ARRAY_LAYER_COUNT_2D + 1,
-  baseArrayLayer: 0,
-  _success: false
-}, {
-  arrayLayerCount: ARRAY_LAYER_COUNT_2D,
-  baseArrayLayer: 1,
-  _success: false
-}, {
-  arrayLayerCount: 2,
-  baseArrayLayer: ARRAY_LAYER_COUNT_2D - 1,
-  _success: false
-}, {
-  arrayLayerCount: 1,
-  baseArrayLayer: ARRAY_LAYER_COUNT_2D,
-  _success: false
-}]).fn(async t => {
-  const {
-    dimension = '2d-array',
-    arrayLayerCount,
-    baseArrayLayer,
-    _success
-  } = t.params;
-  const texture = t.createTexture({
-    arrayLayerCount: ARRAY_LAYER_COUNT_2D
-  });
-  const descriptor = t.getDescriptor({
-    dimension,
-    arrayLayerCount,
-    baseArrayLayer
-  });
-  t.expectValidationError(() => {
-    texture.createView(descriptor);
-  }, !_success);
-});
-g.test('Using_defaults_validates_the_same_as_setting_values_for_more_than_1_array_layer').params([{
-  _success: true
-}, {
-  format: 'rgba8unorm',
-  _success: true
-}, {
-  format: 'r8unorm',
-  _success: false
-}, {
-  dimension: '2d-array',
-  _success: true
-}, {
-  dimension: '2d',
-  _success: false
-}, {
-  arrayLayerCount: ARRAY_LAYER_COUNT_2D,
-  _success: false
-}, // setting array layers to non-0 means the dimensionality will default to 2D so by itself it causes an error.
-{
-  arrayLayerCount: ARRAY_LAYER_COUNT_2D,
-  dimension: '2d-array',
-  _success: true
-}, {
-  arrayLayerCount: ARRAY_LAYER_COUNT_2D,
-  dimension: '2d-array',
-  mipLevelCount: MIP_LEVEL_COUNT,
-  _success: true
-}]).fn(async t => {
-  const {
-    format,
-    dimension,
-    arrayLayerCount,
-    mipLevelCount,
-    _success
-  } = t.params;
-  const texture = t.createTexture({
-    arrayLayerCount: ARRAY_LAYER_COUNT_2D
-  });
-  const descriptor = {
-    format,
-    dimension,
-    arrayLayerCount,
-    mipLevelCount
-  };
-  t.expectValidationError(() => {
-    texture.createView(descriptor);
-  }, !_success);
-});
-g.test('Using_defaults_validates_the_same_as_setting_values_for_only_1_array_layer').params([{
-  _success: true
-}, {
-  format: 'rgba8unorm',
-  _success: true
-}, {
-  format: 'r8unorm',
-  _success: false
-}, {
-  dimension: '2d-array',
-  _success: true
-}, {
-  dimension: '2d',
-  _success: true
-}, {
-  arrayLayerCount: 0,
-  _success: true
-}, {
-  arrayLayerCount: 1,
-  _success: true
-}, {
-  arrayLayerCount: 2,
-  _success: false
-}, {
-  mipLevelCount: MIP_LEVEL_COUNT,
-  _success: true
-}, {
-  mipLevelCount: 1,
-  _success: true
-}]).fn(async t => {
-  const {
-    format,
-    dimension,
-    arrayLayerCount,
-    mipLevelCount,
-    _success
-  } = t.params;
-  const texture = t.createTexture({
-    arrayLayerCount: 1
-  });
-  const descriptor = {
-    format,
-    dimension,
-    arrayLayerCount,
-    mipLevelCount
-  };
-  t.expectValidationError(() => {
-    texture.createView(descriptor);
-  }, !_success);
-});
-g.test('creating_cube_map_texture_view').params([{
-  dimension: 'cube',
-  arrayLayerCount: 6,
-  _success: true
-}, // it is OK to create a cube map texture view with arrayLayerCount == 6
-// it is an error to create a cube map texture view with arrayLayerCount != 6
-{
-  dimension: 'cube',
-  arrayLayerCount: 3,
-  _success: false
-}, {
-  dimension: 'cube',
-  arrayLayerCount: 7,
-  _success: false
-}, {
-  dimension: 'cube',
-  arrayLayerCount: 12,
-  _success: false
-}, {
-  dimension: 'cube',
-  _success: false
-}, {
-  dimension: 'cube-array',
-  arrayLayerCount: 12,
-  _success: true
-}, // it is OK to create a cube map array texture view with arrayLayerCount % 6 == 0
-// it is an error to create a cube map array texture view with arrayLayerCount % 6 != 0
-{
-  dimension: 'cube-array',
-  arrayLayerCount: 11,
-  _success: false
-}, {
-  dimension: 'cube-array',
-  arrayLayerCount: 13,
-  _success: false
-}]).fn(async t => {
-  const {
-    dimension = '2d-array',
-    arrayLayerCount,
-    _success
-  } = t.params;
-  const texture = t.createTexture({
-    arrayLayerCount: 16
-  });
-  const descriptor = t.getDescriptor({
-    dimension,
-    arrayLayerCount
-  });
-  t.expectValidationError(() => {
-    texture.createView(descriptor);
-  }, !_success);
-});
-g.test('creating_cube_map_texture_view_with_a_non_square_texture').params([{
-  dimension: 'cube',
-  arrayLayerCount: 6
-}, // it is an error to create a cube map texture view with width != height.
-{
-  dimension: 'cube-array',
-  arrayLayerCount: 12
-} // it is an error to create a cube map array texture view with width != height.
-]).fn(async t => {
-  const {
-    dimension,
-    arrayLayerCount
-  } = t.params;
-  const nonSquareTexture = t.createTexture({
-    arrayLayerCount: 18,
-    width: 32,
-    height: 16,
-    mipLevelCount: 5
-  });
-  const descriptor = t.getDescriptor({
-    dimension,
-    arrayLayerCount
-  });
-  t.expectValidationError(() => {
-    nonSquareTexture.createView(descriptor);
-  });
-}); // TODO: add more tests when rules are fully implemented.
 
+g.test('creating_texture_view_on_a_2D_non_array_texture')
+  .params([
+    { _success: true }, // default view works
+    { arrayLayerCount: 1, _success: true }, // it is OK to create a 2D texture view on a 2D texture
+    { arrayLayerCount: 2, _success: false }, // it is an error to view a layer past the end of the texture
+    { dimension: '2d-array', arrayLayerCount: 1, _success: true }, // it is OK to create a 1-layer 2D array texture view on a 2D texture
+    // mip level is in range
+    { mipLevelCount: 1, baseMipLevel: MIP_LEVEL_COUNT - 1, _success: true },
+    { mipLevelCount: 2, baseMipLevel: MIP_LEVEL_COUNT - 2, _success: true },
+    // baseMipLevel == k && mipLevelCount == 0 means to use levels k..end
+    { mipLevelCount: 0, baseMipLevel: 0, _success: true },
+    { mipLevelCount: 0, baseMipLevel: 1, _success: true },
+    { mipLevelCount: 0, baseMipLevel: MIP_LEVEL_COUNT - 1, _success: true },
+    { mipLevelCount: 0, baseMipLevel: MIP_LEVEL_COUNT, _success: false },
+    // it is an error to make the mip level out of range
+    { mipLevelCount: MIP_LEVEL_COUNT + 1, baseMipLevel: 0, _success: false },
+    { mipLevelCount: MIP_LEVEL_COUNT, baseMipLevel: 1, _success: false },
+    { mipLevelCount: 2, baseMipLevel: MIP_LEVEL_COUNT - 1, _success: false },
+    { mipLevelCount: 1, baseMipLevel: MIP_LEVEL_COUNT, _success: false },
+  ])
+  .fn(async t => {
+    const { dimension = '2d', arrayLayerCount, mipLevelCount, baseMipLevel, _success } = t.params;
+
+    const texture = t.createTexture({ arrayLayerCount: 1 });
+
+    const descriptor = t.getDescriptor({
+      dimension,
+      arrayLayerCount,
+      mipLevelCount,
+      baseMipLevel,
+    });
+
+    t.expectValidationError(() => {
+      texture.createView(descriptor);
+    }, !_success);
+  });
+
+g.test('creating_texture_view_on_a_2D_array_texture')
+  .params([
+    { _success: true }, // default view works
+    { dimension: '2d', arrayLayerCount: 1, _success: true }, // it is OK to create a 2D texture view on a 2D array texture
+    { arrayLayerCount: ARRAY_LAYER_COUNT_2D, _success: true }, // it is OK to create a 2D array texture view on a 2D array texture
+    // baseArrayLayer == k && arrayLayerCount == 0 means to use layers k..end.
+    { arrayLayerCount: 0, baseArrayLayer: 0, _success: true },
+    { arrayLayerCount: 0, baseArrayLayer: 1, _success: true },
+    { arrayLayerCount: 0, baseArrayLayer: ARRAY_LAYER_COUNT_2D - 1, _success: true },
+    { arrayLayerCount: 0, baseArrayLayer: ARRAY_LAYER_COUNT_2D, _success: false },
+    // It is an error for the array layer range of the view to exceed that of the texture
+    { arrayLayerCount: ARRAY_LAYER_COUNT_2D + 1, baseArrayLayer: 0, _success: false },
+    { arrayLayerCount: ARRAY_LAYER_COUNT_2D, baseArrayLayer: 1, _success: false },
+    { arrayLayerCount: 2, baseArrayLayer: ARRAY_LAYER_COUNT_2D - 1, _success: false },
+    { arrayLayerCount: 1, baseArrayLayer: ARRAY_LAYER_COUNT_2D, _success: false },
+  ])
+  .fn(async t => {
+    const { dimension = '2d-array', arrayLayerCount, baseArrayLayer, _success } = t.params;
+
+    const texture = t.createTexture({ arrayLayerCount: ARRAY_LAYER_COUNT_2D });
+
+    const descriptor = t.getDescriptor({
+      dimension,
+      arrayLayerCount,
+      baseArrayLayer,
+    });
+
+    t.expectValidationError(() => {
+      texture.createView(descriptor);
+    }, !_success);
+  });
+
+g.test('Using_defaults_validates_the_same_as_setting_values_for_more_than_1_array_layer')
+  .params([
+    { _success: true },
+    { format: 'rgba8unorm', _success: true },
+    { format: 'r8unorm', _success: false },
+    { dimension: '2d-array', _success: true },
+    { dimension: '2d', _success: false },
+    { arrayLayerCount: ARRAY_LAYER_COUNT_2D, _success: false }, // setting array layers to non-0 means the dimensionality will default to 2D so by itself it causes an error.
+    { arrayLayerCount: ARRAY_LAYER_COUNT_2D, dimension: '2d-array', _success: true },
+    {
+      arrayLayerCount: ARRAY_LAYER_COUNT_2D,
+      dimension: '2d-array',
+      mipLevelCount: MIP_LEVEL_COUNT,
+      _success: true,
+    },
+  ])
+  .fn(async t => {
+    const { format, dimension, arrayLayerCount, mipLevelCount, _success } = t.params;
+
+    const texture = t.createTexture({ arrayLayerCount: ARRAY_LAYER_COUNT_2D });
+
+    const descriptor = { format, dimension, arrayLayerCount, mipLevelCount };
+
+    t.expectValidationError(() => {
+      texture.createView(descriptor);
+    }, !_success);
+  });
+
+g.test('Using_defaults_validates_the_same_as_setting_values_for_only_1_array_layer')
+  .params([
+    { _success: true },
+    { format: 'rgba8unorm', _success: true },
+    { format: 'r8unorm', _success: false },
+    { dimension: '2d-array', _success: true },
+    { dimension: '2d', _success: true },
+    { arrayLayerCount: 0, _success: true },
+    { arrayLayerCount: 1, _success: true },
+    { arrayLayerCount: 2, _success: false },
+    { mipLevelCount: MIP_LEVEL_COUNT, _success: true },
+    { mipLevelCount: 1, _success: true },
+  ])
+  .fn(async t => {
+    const { format, dimension, arrayLayerCount, mipLevelCount, _success } = t.params;
+
+    const texture = t.createTexture({ arrayLayerCount: 1 });
+
+    const descriptor = { format, dimension, arrayLayerCount, mipLevelCount };
+
+    t.expectValidationError(() => {
+      texture.createView(descriptor);
+    }, !_success);
+  });
+
+g.test('creating_cube_map_texture_view')
+  .params([
+    { dimension: 'cube', arrayLayerCount: 6, _success: true }, // it is OK to create a cube map texture view with arrayLayerCount == 6
+    // it is an error to create a cube map texture view with arrayLayerCount != 6
+    { dimension: 'cube', arrayLayerCount: 3, _success: false },
+    { dimension: 'cube', arrayLayerCount: 7, _success: false },
+    { dimension: 'cube', arrayLayerCount: 12, _success: false },
+    { dimension: 'cube', _success: false },
+    { dimension: 'cube-array', arrayLayerCount: 12, _success: true }, // it is OK to create a cube map array texture view with arrayLayerCount % 6 == 0
+    // it is an error to create a cube map array texture view with arrayLayerCount % 6 != 0
+    { dimension: 'cube-array', arrayLayerCount: 11, _success: false },
+    { dimension: 'cube-array', arrayLayerCount: 13, _success: false },
+  ])
+  .fn(async t => {
+    const { dimension = '2d-array', arrayLayerCount, _success } = t.params;
+
+    const texture = t.createTexture({ arrayLayerCount: 16 });
+
+    const descriptor = t.getDescriptor({
+      dimension,
+      arrayLayerCount,
+    });
+
+    t.expectValidationError(() => {
+      texture.createView(descriptor);
+    }, !_success);
+  });
+
+g.test('creating_cube_map_texture_view_with_a_non_square_texture')
+  .params([
+    { dimension: 'cube', arrayLayerCount: 6 }, // it is an error to create a cube map texture view with width != height.
+    { dimension: 'cube-array', arrayLayerCount: 12 }, // it is an error to create a cube map array texture view with width != height.
+  ])
+  .fn(async t => {
+    const { dimension, arrayLayerCount } = t.params;
+
+    const nonSquareTexture = t.createTexture({
+      arrayLayerCount: 18,
+      width: 32,
+      height: 16,
+      mipLevelCount: 5,
+    });
+
+    const descriptor = t.getDescriptor({
+      dimension,
+      arrayLayerCount,
+    });
+
+    t.expectValidationError(() => {
+      nonSquareTexture.createView(descriptor);
+    });
+  });
+
+// TODO: add more tests when rules are fully implemented.
 g.test('test_the_format_compatibility_rules_when_creating_a_texture_view').fn(async t => {
-  const texture = t.createTexture({
-    arrayLayerCount: 1
-  });
-  const descriptor = t.getDescriptor({
-    format: 'depth24plus-stencil8'
-  }); // it is invalid to create a view in depth-stencil format on a RGBA texture
+  const texture = t.createTexture({ arrayLayerCount: 1 });
 
+  const descriptor = t.getDescriptor({
+    format: 'depth24plus-stencil8',
+  });
+
+  // it is invalid to create a view in depth-stencil format on a RGBA texture
   t.expectValidationError(() => {
     texture.createView(descriptor);
   });
 });
+
 g.test('it_is_invalid_to_use_a_texture_view_created_from_a_destroyed_texture').fn(async t => {
-  const texture = t.createTexture({
-    arrayLayerCount: 1
-  });
+  const texture = t.createTexture({ arrayLayerCount: 1 });
+
   const commandEncoder = t.device.createCommandEncoder();
   const renderPass = commandEncoder.beginRenderPass({
-    colorAttachments: [{
-      attachment: texture.createView(),
-      loadValue: {
-        r: 1.0,
-        g: 0.0,
-        b: 0.0,
-        a: 1.0
-      }
-    }]
+    colorAttachments: [
+      {
+        attachment: texture.createView(),
+        loadValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+      },
+    ],
   });
+
   renderPass.endPass();
+
   texture.destroy();
+
   t.expectValidationError(() => {
     commandEncoder.finish();
   });
-}); // TODO: Add tests for TextureAspect
-//# sourceMappingURL=createView.spec.js.map
\ No newline at end of file
+});
+
+// TODO: Add tests for TextureAspect
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/encoding/cmds/index_access.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/encoding/cmds/index_access.spec.js
new file mode 100644
index 0000000..76783c88
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/encoding/cmds/index_access.spec.js
@@ -0,0 +1,137 @@
+/**
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `
+indexed draws validation tests.
+`;
+import { params, poptions, pbool } from '../../../../../common/framework/params_builder.js';
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+
+import { ValidationTest } from './../../validation_test.js';
+
+class F extends ValidationTest {
+  createIndexBuffer() {
+    const indexArray = new Uint32Array([0, 1, 2, 3, 1, 2]);
+
+    const indexBuffer = this.device.createBuffer({
+      mappedAtCreation: true,
+      size: indexArray.byteLength,
+      usage: GPUBufferUsage.INDEX,
+    });
+
+    new Uint32Array(indexBuffer.getMappedRange()).set(indexArray);
+    indexBuffer.unmap();
+
+    return indexBuffer;
+  }
+
+  createRenderPipeline() {
+    const vertexModule = this.makeShaderModule('vertex', {
+      glsl: `
+        #version 450
+        void main() {
+          gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
+        }
+      `,
+    });
+
+    const fragmentModule = this.makeShaderModule('fragment', {
+      glsl: `
+        #version 450
+        layout(location = 0) out vec4 fragColor;
+        void main() {
+            fragColor = vec4(0.0, 1.0, 0.0, 1.0);
+        }
+      `,
+    });
+
+    return this.device.createRenderPipeline({
+      layout: this.device.createPipelineLayout({ bindGroupLayouts: [] }),
+      vertexStage: { module: vertexModule, entryPoint: 'main' },
+      fragmentStage: { module: fragmentModule, entryPoint: 'main' },
+      primitiveTopology: 'triangle-strip',
+      colorStates: [{ format: 'rgba8unorm' }],
+    });
+  }
+
+  beginRenderPass(encoder) {
+    const colorAttachment = this.device.createTexture({
+      format: 'rgba8unorm',
+      size: { width: 1, height: 1, depth: 1 },
+      usage: GPUTextureUsage.OUTPUT_ATTACHMENT,
+    });
+
+    return encoder.beginRenderPass({
+      colorAttachments: [
+        {
+          attachment: colorAttachment.createView(),
+          loadValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
+          storeOp: 'store',
+        },
+      ],
+    });
+  }
+
+  drawIndexed(indexCount, instanceCount, firstIndex, baseVertex, firstInstance) {
+    const indexBuffer = this.createIndexBuffer();
+
+    const pipeline = this.createRenderPipeline();
+
+    const encoder = this.device.createCommandEncoder();
+    const pass = this.beginRenderPass(encoder);
+    pass.setPipeline(pipeline);
+    pass.setIndexBuffer(indexBuffer);
+    pass.drawIndexed(indexCount, instanceCount, firstIndex, baseVertex, firstInstance);
+    pass.endPass();
+
+    this.device.defaultQueue.submit([encoder.finish()]);
+  }
+
+  drawIndexedIndirect(bufferArray, indirectOffset) {
+    const indirectBuffer = this.device.createBuffer({
+      mappedAtCreation: true,
+      size: bufferArray.byteLength,
+      usage: GPUBufferUsage.INDIRECT,
+    });
+
+    new Uint32Array(indirectBuffer.getMappedRange()).set(bufferArray);
+    indirectBuffer.unmap();
+
+    const indexBuffer = this.createIndexBuffer();
+
+    const pipeline = this.createRenderPipeline();
+
+    const encoder = this.device.createCommandEncoder();
+    const pass = this.beginRenderPass(encoder);
+    pass.setPipeline(pipeline);
+    pass.setIndexBuffer(indexBuffer, 0);
+    pass.drawIndexedIndirect(indirectBuffer, indirectOffset);
+    pass.endPass();
+
+    this.device.defaultQueue.submit([encoder.finish()]);
+  }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('out_of_bounds')
+  .params(
+    params()
+      .combine(pbool('indirect')) // indirect drawIndexed
+      .combine([
+        { indexCount: 6, firstIndex: 1 }, // indexCount + firstIndex out of bound
+        { indexCount: 6, firstIndex: 6 }, // only firstIndex out of bound
+        { indexCount: 6, firstIndex: 10000 }, // firstIndex much larger than the bound
+        { indexCount: 7, firstIndex: 0 }, // only indexCount out of bound
+        { indexCount: 10000, firstIndex: 0 }, // indexCount much larger than the bound
+      ])
+      .combine(poptions('instanceCount', [1, 10000])) // normal and large instanceCount
+  )
+  .fn(t => {
+    const { indirect, indexCount, firstIndex, instanceCount } = t.params;
+
+    if (indirect) {
+      t.drawIndexedIndirect(new Uint32Array([indexCount, instanceCount, firstIndex, 0, 0]), 0);
+    } else {
+      t.drawIndexed(indexCount, instanceCount, firstIndex, 0, 0);
+    }
+  });
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/error_scope.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/error_scope.spec.js
index 3edbec2..6457554 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/error_scope.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/error_scope.spec.js
@@ -1,9 +1,18 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
-
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ function _defineProperty(obj, key, value) {
+  if (key in obj) {
+    Object.defineProperty(obj, key, {
+      value: value,
+      enumerable: true,
+      configurable: true,
+      writable: true,
+    });
+  } else {
+    obj[key] = value;
+  }
+  return obj;
+}
 export const description = `
 error scope validation tests.
 `;
@@ -15,8 +24,7 @@
 class F extends Fixture {
   constructor(...args) {
     super(...args);
-
-    _defineProperty(this, "_device", undefined);
+    _defineProperty(this, '_device', undefined);
   }
 
   get device() {
@@ -28,53 +36,65 @@
     super.init();
     const gpu = getGPU();
     const adapter = await gpu.requestAdapter();
-    this._device = await adapter.requestDevice();
+    assert(adapter !== null);
+    const device = await adapter.requestDevice();
+    assert(device !== null);
+    this._device = device;
   }
 
   createErrorBuffer() {
     this.device.createBuffer({
       size: 1024,
-      usage: 0xffff // Invalid GPUBufferUsage
-
-    }); // TODO: Remove when chrome does it automatically.
-
+      usage: 0xffff, // Invalid GPUBufferUsage
+    });
+    // TODO: Remove when chrome does it automatically.
     this.device.defaultQueue.submit([]);
-  } // Expect an uncapturederror event to occur. Note: this MUST be awaited, because
+  }
+
+  // Expect an uncapturederror event to occur. Note: this MUST be awaited, because
   // otherwise it could erroneously pass by capturing an error from later in the test.
-
-
   async expectUncapturedError(fn) {
     return this.immediateAsyncExpectation(() => {
       // TODO: Make arbitrary timeout value a test runner variable
       const TIMEOUT_IN_MS = 1000;
+
       const promise = new Promise(resolve => {
         const eventListener = event => {
           this.debug(`Got uncaptured error event with ${event.error}`);
           resolve(event);
         };
 
-        this.device.addEventListener('uncapturederror', eventListener, {
-          once: true
-        });
+        this.device.addEventListener('uncapturederror', eventListener, { once: true });
       });
+
       fn();
-      return raceWithRejectOnTimeout(promise, TIMEOUT_IN_MS, 'Timeout occurred waiting for uncaptured error');
+
+      return raceWithRejectOnTimeout(
+        promise,
+        TIMEOUT_IN_MS,
+        'Timeout occurred waiting for uncaptured error'
+      );
     });
   }
-
 }
 
 export const g = makeTestGroup(F);
+
 g.test('simple_case_where_the_error_scope_catches_an_error').fn(async t => {
   t.device.pushErrorScope('validation');
+
   t.createErrorBuffer();
+
   const error = await t.device.popErrorScope();
   t.expect(error instanceof GPUValidationError);
 });
+
 g.test('errors_bubble_to_the_parent_scope_if_not_handled_by_the_current_scope').fn(async t => {
   t.device.pushErrorScope('validation');
   t.device.pushErrorScope('out-of-memory');
+
   t.createErrorBuffer();
+
   {
     const error = await t.device.popErrorScope();
     t.expect(error === null);
@@ -84,10 +104,13 @@
     t.expect(error instanceof GPUValidationError);
   }
 });
+
 g.test('if_an_error_scope_matches_an_error_it_does_not_bubble_to_the_parent_scope').fn(async t => {
   t.device.pushErrorScope('validation');
   t.device.pushErrorScope('validation');
+
   t.createErrorBuffer();
+
   {
     const error = await t.device.popErrorScope();
     t.expect(error instanceof GPUValidationError);
@@ -97,54 +120,57 @@
     t.expect(error === null);
   }
 });
+
 g.test('if_no_error_scope_handles_an_error_it_fires_an_uncapturederror_event').fn(async t => {
   t.device.pushErrorScope('out-of-memory');
+
   const uncapturedErrorEvent = await t.expectUncapturedError(() => {
     t.createErrorBuffer();
   });
   t.expect(uncapturedErrorEvent.error instanceof GPUValidationError);
+
   const error = await t.device.popErrorScope();
   t.expect(error === null);
 });
+
 g.test('push,popping_sibling_error_scopes_must_be_balanced').fn(async t => {
   {
     const promise = t.device.popErrorScope();
     t.shouldReject('OperationError', promise);
   }
-  const promises = [];
 
+  const promises = [];
   for (let i = 0; i < 1000; i++) {
     t.device.pushErrorScope('validation');
     promises.push(t.device.popErrorScope());
   }
-
   const errors = await Promise.all(promises);
   t.expect(errors.every(e => e === null));
+
   {
     const promise = t.device.popErrorScope();
     t.shouldReject('OperationError', promise);
   }
 });
+
 g.test('push,popping_nested_error_scopes_must_be_balanced').fn(async t => {
   {
     const promise = t.device.popErrorScope();
     t.shouldReject('OperationError', promise);
   }
-  const promises = [];
 
+  const promises = [];
   for (let i = 0; i < 1000; i++) {
     t.device.pushErrorScope('validation');
   }
-
   for (let i = 0; i < 1000; i++) {
     promises.push(t.device.popErrorScope());
   }
-
   const errors = await Promise.all(promises);
   t.expect(errors.every(e => e === null));
+
   {
     const promise = t.device.popErrorScope();
     t.shouldReject('OperationError', promise);
   }
 });
-//# sourceMappingURL=error_scope.spec.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/fences.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/fences.spec.js
index e2c62f50..e92bdd3 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/fences.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/fences.spec.js
@@ -1,77 +1,91 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = `
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `
 fences validation tests.
 `;
 import { makeTestGroup } from '../../../common/framework/test_group.js';
-import { ValidationTest } from './validation_test.js';
-export const g = makeTestGroup(ValidationTest); // TODO: Remove if https://github.com/gpuweb/gpuweb/issues/377 is decided
+import { assert } from '../../../common/framework/util/util.js';
 
+import { ValidationTest } from './validation_test.js';
+
+export const g = makeTestGroup(ValidationTest);
+
+// TODO: Remove if https://github.com/gpuweb/gpuweb/issues/377 is decided
 g.test('wait_on_a_fence_without_signaling_the_value_is_invalid').fn(async t => {
   const fence = t.queue.createFence();
+
   t.expectValidationError(() => {
     const promise = fence.onCompletion(2);
     t.shouldReject('OperationError', promise);
   });
-}); // TODO: Remove if https://github.com/gpuweb/gpuweb/issues/377 is decided
+});
 
+// TODO: Remove if https://github.com/gpuweb/gpuweb/issues/377 is decided
 g.test('wait_on_a_fence_with_a_value_greater_than_signaled_value_is_invalid').fn(async t => {
   const fence = t.queue.createFence();
   t.queue.signal(fence, 2);
+
   t.expectValidationError(() => {
     const promise = fence.onCompletion(3);
     t.shouldReject('OperationError', promise);
   });
 });
+
 g.test('signal_a_value_lower_than_signaled_value_is_invalid').fn(async t => {
-  const fence = t.queue.createFence({
-    initialValue: 1
-  });
+  const fence = t.queue.createFence({ initialValue: 1 });
+
   t.expectValidationError(() => {
     t.queue.signal(fence, 0);
   });
 });
+
 g.test('signal_a_value_equal_to_signaled_value_is_invalid').fn(async t => {
-  const fence = t.queue.createFence({
-    initialValue: 1
-  });
+  const fence = t.queue.createFence({ initialValue: 1 });
+
   t.expectValidationError(() => {
     t.queue.signal(fence, 1);
   });
 });
+
 g.test('increasing_fence_value_by_more_than_1_succeeds').fn(async t => {
   const fence = t.queue.createFence();
+
   t.queue.signal(fence, 2);
   await fence.onCompletion(2);
+
   t.queue.signal(fence, 6);
   await fence.onCompletion(6);
 });
+
 g.test('signal_a_fence_on_a_different_device_than_it_was_created_on_is_invalid').fn(async t => {
   const anotherDevice = await t.device.adapter.requestDevice();
+  assert(anotherDevice !== null);
   const fence = anotherDevice.defaultQueue.createFence();
+
   t.expectValidationError(() => {
     t.queue.signal(fence, 2);
   });
 });
+
 g.test('signal_a_fence_on_a_different_device_does_not_update_fence_signaled_value').fn(async t => {
   const anotherDevice = await t.device.adapter.requestDevice();
-  const fence = anotherDevice.defaultQueue.createFence({
-    initialValue: 1
-  });
+  assert(anotherDevice !== null);
+  const fence = anotherDevice.defaultQueue.createFence({ initialValue: 1 });
+
   t.expectValidationError(() => {
     t.queue.signal(fence, 2);
   });
+
   t.expect(fence.getCompletedValue() === 1);
+
   anotherDevice.pushErrorScope('validation');
+
   anotherDevice.defaultQueue.signal(fence, 2);
   await fence.onCompletion(2);
   t.expect(fence.getCompletedValue() === 2);
-  const gpuValidationError = await anotherDevice.popErrorScope();
 
+  const gpuValidationError = await anotherDevice.popErrorScope();
   if (gpuValidationError instanceof GPUValidationError) {
     t.fail(`Captured validation error - ${gpuValidationError.message}`);
   }
 });
-//# sourceMappingURL=fences.spec.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/queue_submit.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/queue_submit.spec.js
index 24e386b..4e77076 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/queue_submit.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/queue_submit.spec.js
@@ -1,39 +1,43 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = `
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `
 queue submit validation tests.
 `;
 import { makeTestGroup } from '../../../common/framework/test_group.js';
+
 import { ValidationTest } from './validation_test.js';
+
 export const g = makeTestGroup(ValidationTest);
+
 g.test('submitting_with_a_mapped_buffer_is_disallowed').fn(async t => {
   const buffer = t.device.createBuffer({
     size: 4,
-    usage: GPUBufferUsage.MAP_WRITE | GPUBufferUsage.COPY_SRC
+    usage: GPUBufferUsage.MAP_WRITE | GPUBufferUsage.COPY_SRC,
   });
+
   const targetBuffer = t.device.createBuffer({
     size: 4,
-    usage: GPUBufferUsage.COPY_DST
+    usage: GPUBufferUsage.COPY_DST,
   });
 
   const getCommandBuffer = () => {
     const commandEncoder = t.device.createCommandEncoder();
     commandEncoder.copyBufferToBuffer(buffer, 0, targetBuffer, 0, 4);
     return commandEncoder.finish();
-  }; // Submitting when the buffer has never been mapped should succeed
+  };
 
+  // Submitting when the buffer has never been mapped should succeed
+  t.queue.submit([getCommandBuffer()]);
 
-  t.queue.submit([getCommandBuffer()]); // Map the buffer, submitting when the buffer is mapped should fail
-
-  await buffer.mapWriteAsync();
+  // Map the buffer, submitting when the buffer is mapped (even with no getMappedRange) should fail
+  await buffer.mapAsync(GPUMapMode.WRITE);
   t.queue.submit([]);
+
   t.expectValidationError(() => {
     t.queue.submit([getCommandBuffer()]);
-  }); // Unmap the buffer, queue submit should succeed
+  });
 
+  // Unmap the buffer, queue submit should succeed
   buffer.unmap();
   t.queue.submit([getCommandBuffer()]);
 });
-//# sourceMappingURL=queue_submit.spec.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/render_pass.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/render_pass.spec.js
index b113863..b9db03b 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/render_pass.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/render_pass.spec.js
@@ -1,18 +1,17 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = `
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `
 render pass validation tests.
 `;
 import { makeTestGroup } from '../../../common/framework/test_group.js';
+
 import { ValidationTest } from './validation_test.js';
 
 class F extends ValidationTest {
   getUniformBuffer() {
     return this.device.createBuffer({
       size: 8 * Float32Array.BYTES_PER_ELEMENT,
-      usage: GPUBufferUsage.UNIFORM
+      usage: GPUBufferUsage.UNIFORM,
     });
   }
 
@@ -26,8 +25,9 @@
               const vec2 pos[3] = vec2[3](vec2(-1.f, -1.f), vec2(1.f, -1.f), vec2(-1.f, 1.f));
               gl_Position = vec4(transform * pos[gl_VertexIndex], 0.f, 1.f);
           }
-        `
+        `,
     });
+
     const fragmentModule = this.makeShaderModule('fragment', {
       glsl: `
         #version 450
@@ -37,127 +37,116 @@
         layout(location = 0) out vec4 fragColor;
         void main() {
         }
-      `
+      `,
     });
+
     const pipeline = this.device.createRenderPipeline({
-      vertexStage: {
-        module: vertexModule,
-        entryPoint: 'main'
-      },
-      fragmentStage: {
-        module: fragmentModule,
-        entryPoint: 'main'
-      },
+      vertexStage: { module: vertexModule, entryPoint: 'main' },
+      fragmentStage: { module: fragmentModule, entryPoint: 'main' },
       layout: pipelineLayout,
       primitiveTopology: 'triangle-list',
-      colorStates: [{
-        format: 'rgba8unorm'
-      }]
+      colorStates: [{ format: 'rgba8unorm' }],
     });
+
     return pipeline;
   }
 
   beginRenderPass(commandEncoder) {
     const attachmentTexture = this.device.createTexture({
       format: 'rgba8unorm',
-      size: {
-        width: 16,
-        height: 16,
-        depth: 1
-      },
-      usage: GPUTextureUsage.OUTPUT_ATTACHMENT
+      size: { width: 16, height: 16, depth: 1 },
+      usage: GPUTextureUsage.OUTPUT_ATTACHMENT,
     });
+
     return commandEncoder.beginRenderPass({
-      colorAttachments: [{
-        attachment: attachmentTexture.createView(),
-        loadValue: {
-          r: 1.0,
-          g: 0.0,
-          b: 0.0,
-          a: 1.0
-        }
-      }]
+      colorAttachments: [
+        {
+          attachment: attachmentTexture.createView(),
+          loadValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+        },
+      ],
     });
   }
-
 }
 
 export const g = makeTestGroup(F);
-g.test('it_is_invalid_to_draw_in_a_render_pass_with_missing_bind_groups').params([{
-  setBindGroup1: true,
-  setBindGroup2: true,
-  _success: true
-}, {
-  setBindGroup1: true,
-  setBindGroup2: false,
-  _success: false
-}, {
-  setBindGroup1: false,
-  setBindGroup2: true,
-  _success: false
-}, {
-  setBindGroup1: false,
-  setBindGroup2: false,
-  _success: false
-}]).fn(async t => {
-  const {
-    setBindGroup1,
-    setBindGroup2,
-    _success
-  } = t.params;
-  const uniformBuffer = t.getUniformBuffer();
-  const bindGroupLayout1 = t.device.createBindGroupLayout({
-    entries: [{
-      binding: 0,
-      visibility: GPUShaderStage.VERTEX,
-      type: 'uniform-buffer'
-    }]
-  });
-  const bindGroup1 = t.device.createBindGroup({
-    entries: [{
-      binding: 0,
-      resource: {
-        buffer: uniformBuffer
-      }
-    }],
-    layout: bindGroupLayout1
-  });
-  const bindGroupLayout2 = t.device.createBindGroupLayout({
-    entries: [{
-      binding: 0,
-      visibility: GPUShaderStage.FRAGMENT,
-      type: 'uniform-buffer'
-    }]
-  });
-  const bindGroup2 = t.device.createBindGroup({
-    entries: [{
-      binding: 0,
-      resource: {
-        buffer: uniformBuffer
-      }
-    }],
-    layout: bindGroupLayout2
-  });
-  const pipelineLayout = t.device.createPipelineLayout({
-    bindGroupLayouts: [bindGroupLayout1, bindGroupLayout2]
-  });
-  const pipeline = t.createRenderPipeline(pipelineLayout);
-  const commandEncoder = t.device.createCommandEncoder();
-  const renderPass = t.beginRenderPass(commandEncoder);
-  renderPass.setPipeline(pipeline);
 
-  if (setBindGroup1) {
-    renderPass.setBindGroup(0, bindGroup1);
-  }
+g.test('it_is_invalid_to_draw_in_a_render_pass_with_missing_bind_groups')
+  .params([
+    { setBindGroup1: true, setBindGroup2: true, _success: true },
+    { setBindGroup1: true, setBindGroup2: false, _success: false },
+    { setBindGroup1: false, setBindGroup2: true, _success: false },
+    { setBindGroup1: false, setBindGroup2: false, _success: false },
+  ])
+  .fn(async t => {
+    const { setBindGroup1, setBindGroup2, _success } = t.params;
 
-  if (setBindGroup2) {
-    renderPass.setBindGroup(1, bindGroup2);
-  }
+    const uniformBuffer = t.getUniformBuffer();
 
-  renderPass.draw(3, 1, 0, 0);
-  renderPass.endPass();
-  t.expectValidationError(() => {
-    commandEncoder.finish();
-  }, !_success);
-});
-//# sourceMappingURL=render_pass.spec.js.map
\ No newline at end of file
+    const bindGroupLayout1 = t.device.createBindGroupLayout({
+      entries: [
+        {
+          binding: 0,
+          visibility: GPUShaderStage.VERTEX,
+          type: 'uniform-buffer',
+        },
+      ],
+    });
+
+    const bindGroup1 = t.device.createBindGroup({
+      entries: [
+        {
+          binding: 0,
+          resource: {
+            buffer: uniformBuffer,
+          },
+        },
+      ],
+
+      layout: bindGroupLayout1,
+    });
+
+    const bindGroupLayout2 = t.device.createBindGroupLayout({
+      entries: [
+        {
+          binding: 0,
+          visibility: GPUShaderStage.FRAGMENT,
+          type: 'uniform-buffer',
+        },
+      ],
+    });
+
+    const bindGroup2 = t.device.createBindGroup({
+      entries: [
+        {
+          binding: 0,
+          resource: {
+            buffer: uniformBuffer,
+          },
+        },
+      ],
+
+      layout: bindGroupLayout2,
+    });
+
+    const pipelineLayout = t.device.createPipelineLayout({
+      bindGroupLayouts: [bindGroupLayout1, bindGroupLayout2],
+    });
+
+    const pipeline = t.createRenderPipeline(pipelineLayout);
+
+    const commandEncoder = t.device.createCommandEncoder();
+    const renderPass = t.beginRenderPass(commandEncoder);
+    renderPass.setPipeline(pipeline);
+    if (setBindGroup1) {
+      renderPass.setBindGroup(0, bindGroup1);
+    }
+    if (setBindGroup2) {
+      renderPass.setBindGroup(1, bindGroup2);
+    }
+    renderPass.draw(3, 1, 0, 0);
+    renderPass.endPass();
+    t.expectValidationError(() => {
+      commandEncoder.finish();
+    }, !_success);
+  });
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/render_pass/resolve.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/render_pass/resolve.spec.js
new file mode 100644
index 0000000..78965d9
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/render_pass/resolve.spec.js
@@ -0,0 +1,185 @@
+/**
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `API Validation Tests for RenderPass Resolve.
+
+  Test Coverage:
+    - When resolveTarget is not null:
+      - Test that the colorAttachment is multisampled:
+        - A single sampled colorAttachment should generate an error.
+      - Test that the resolveTarget is single sampled:
+        - A multisampled resolveTarget should generate an error.
+      - Test that the resolveTarget has usage OUTPUT_ATTACHMENT:
+        - A resolveTarget without usage OUTPUT_ATTACHMENT should generate an error.
+      - Test that the resolveTarget's texture view describes a single subresource:
+        - A resolveTarget texture view with base mip {0, base mip > 0} and mip count of 1 should be
+          valid.
+          - An error should be generated when the resolve target view mip count is not 1 and base
+            mip is {0, base mip > 0}.
+        - A resolveTarget texture view with base array layer {0, base array layer > 0} and array
+          layer count of 1 should be valid.
+          - An error should be generated when the resolve target view array layer count is not 1 and
+            base array layer is {0, base array layer > 0}.
+      - Test that the resolveTarget's format is the same as the colorAttachment:
+        - An error should be generated when the resolveTarget's format does not match the
+          colorAttachment's format.
+      - Test that the resolveTarget's size is the same the colorAttachment:
+        - An error should be generated when the resolveTarget's height or width are not equal to
+          the colorAttachment's height or width.`;
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+
+import { ValidationTest } from './../validation_test.js';
+
+const kNumColorAttachments = 4;
+
+export const g = makeTestGroup(ValidationTest);
+
+g.test('resolve_attachment')
+  .params([
+    // control case should be valid
+    { _valid: true },
+    // a single sampled resolve source should cause a validation error.
+    { colorAttachmentSamples: 1, _valid: false },
+    // a multisampled resolve target should cause a validation error.
+    { resolveTargetSamples: 4, _valid: false },
+    // resolveTargetUsage without OUTPUT_ATTACHMENT usage should cause a validation error.
+    { resolveTargetUsage: GPUTextureUsage.COPY_SRC, _valid: false },
+    // non-zero resolve target base mip level should be valid.
+    {
+      resolveTargetViewBaseMipLevel: 1,
+      resolveTargetHeight: 4,
+      resolveTargetWidth: 4,
+      _valid: true,
+    },
+
+    // a validation error should be created when mip count > 1
+    { resolveTargetViewMipCount: 2, _valid: false },
+    {
+      resolveTargetViewBaseMipLevel: 1,
+      resolveTargetViewMipCount: 2,
+      resolveTargetHeight: 4,
+      resolveTargetWidth: 4,
+      _valid: false,
+    },
+
+    // non-zero resolve target base array layer should be valid.
+    { resolveTargetViewBaseArrayLayer: 1, _valid: true },
+    // a validation error should be created when array layer count > 1
+    { resolveTargetViewArrayLayerCount: 2, _valid: false },
+    { resolveTargetViewBaseArrayLayer: 1, resolveTargetViewArrayLayerCount: 2, _valid: false },
+    // other color attachments resolving with a different format should be valid.
+    { otherAttachmentFormat: 'bgra8unorm', _valid: true },
+    // mismatched colorAttachment and resolveTarget formats should cause a validation error.
+    { colorAttachmentFormat: 'bgra8unorm', _valid: false },
+    { colorAttachmentFormat: 'rgba8unorm-srgb', _valid: false },
+    { resolveTargetFormat: 'bgra8unorm', _valid: false },
+    { resolveTargetFormat: 'rgba8unorm-srgb', _valid: false },
+    // mismatched colorAttachment and resolveTarget sizes should cause a validation error.
+    { colorAttachmentHeight: 4, _valid: false },
+    { colorAttachmentWidth: 4, _valid: false },
+    { resolveTargetHeight: 4, _valid: false },
+    { resolveTargetWidth: 4, _valid: false },
+  ])
+  .fn(async t => {
+    const {
+      colorAttachmentFormat = 'rgba8unorm',
+      resolveTargetFormat = 'rgba8unorm',
+      otherAttachmentFormat = 'rgba8unorm',
+      colorAttachmentSamples = 4,
+      resolveTargetSamples = 1,
+      resolveTargetUsage = GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT,
+      resolveTargetViewMipCount = 1,
+      resolveTargetViewBaseMipLevel = 0,
+      resolveTargetViewArrayLayerCount = 1,
+      resolveTargetViewBaseArrayLayer = 0,
+      colorAttachmentHeight = 2,
+      colorAttachmentWidth = 2,
+      resolveTargetHeight = 2,
+      resolveTargetWidth = 2,
+      _valid,
+    } = t.params;
+
+    // Run the test in a nested loop such that the configured color attachment with resolve target
+    // is tested while occupying each individual colorAttachment slot.
+    for (let resolveSlot = 0; resolveSlot < kNumColorAttachments; resolveSlot++) {
+      const renderPassColorAttachmentDescriptors = [];
+      for (
+        let colorAttachmentSlot = 0;
+        colorAttachmentSlot < kNumColorAttachments;
+        colorAttachmentSlot++
+      ) {
+        // resolveSlot === colorAttachmentSlot denotes the color attachment slot that contains the color attachment with resolve
+        // target.
+        if (resolveSlot === colorAttachmentSlot) {
+          // Create the color attachment with resolve target with the configurable parameters.
+          const resolveSourceColorAttachment = t.device.createTexture({
+            format: colorAttachmentFormat,
+            size: { width: colorAttachmentWidth, height: colorAttachmentHeight, depth: 1 },
+            sampleCount: colorAttachmentSamples,
+            usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT,
+          });
+
+          const resolveTarget = t.device.createTexture({
+            format: resolveTargetFormat,
+            size: {
+              width: resolveTargetWidth,
+              height: resolveTargetHeight,
+              depth: resolveTargetViewBaseArrayLayer + resolveTargetViewArrayLayerCount,
+            },
+
+            sampleCount: resolveTargetSamples,
+            mipLevelCount: resolveTargetViewBaseMipLevel + resolveTargetViewMipCount,
+            usage: resolveTargetUsage,
+          });
+
+          renderPassColorAttachmentDescriptors.push({
+            attachment: resolveSourceColorAttachment.createView(),
+            loadValue: 'load',
+            resolveTarget: resolveTarget.createView({
+              dimension: resolveTargetViewArrayLayerCount === 1 ? '2d' : '2d-array',
+              mipLevelCount: resolveTargetViewMipCount,
+              arrayLayerCount: resolveTargetViewArrayLayerCount,
+              baseMipLevel: resolveTargetViewBaseMipLevel,
+              baseArrayLayer: resolveTargetViewBaseArrayLayer,
+            }),
+          });
+        } else {
+          // Create a basic texture to fill other color attachment slots. This texture's dimensions
+          // and sample count must match the resolve source color attachment to be valid.
+          const colorAttachment = t.device.createTexture({
+            format: otherAttachmentFormat,
+            size: { width: colorAttachmentWidth, height: colorAttachmentHeight, depth: 1 },
+            sampleCount: colorAttachmentSamples,
+            usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT,
+          });
+
+          const resolveTarget = t.device.createTexture({
+            format: otherAttachmentFormat,
+            size: {
+              width: colorAttachmentWidth,
+              height: colorAttachmentHeight,
+              depth: 1,
+            },
+
+            sampleCount: 1,
+            usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT,
+          });
+
+          renderPassColorAttachmentDescriptors.push({
+            attachment: colorAttachment.createView(),
+            loadValue: 'load',
+            resolveTarget: resolveTarget.createView(),
+          });
+        }
+      }
+      const encoder = t.device.createCommandEncoder();
+      const pass = encoder.beginRenderPass({
+        colorAttachments: renderPassColorAttachmentDescriptors,
+      });
+
+      pass.endPass();
+
+      t.expectValidationError(() => {
+        encoder.finish();
+      }, !_valid);
+    }
+  });
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/render_pass/storeOp.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/render_pass/storeOp.spec.js
new file mode 100644
index 0000000..e63540a9
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/render_pass/storeOp.spec.js
@@ -0,0 +1,76 @@
+/**
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `
+API Validation Tests for RenderPass StoreOp.
+
+Test Coverage:
+  - Tests that when depthReadOnly is true, depthStoreOp must be 'store'.
+    - When depthReadOnly is true and depthStoreOp is 'clear', an error should be generated.
+
+  - Tests that when stencilReadOnly is true, stencilStoreOp must be 'store'.
+    - When stencilReadOnly is true and stencilStoreOp is 'clear', an error should be generated.
+
+  - Tests that the depthReadOnly value matches the stencilReadOnly value.
+    - When depthReadOnly does not match stencilReadOnly, an error should be generated.
+
+  - Tests that depthReadOnly and stencilReadOnly default to false.`;
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+
+import { ValidationTest } from './../validation_test.js';
+
+export const g = makeTestGroup(ValidationTest);
+
+g.test('store_op_and_read_only')
+  .params([
+    { readonly: true, _valid: true },
+    // Using depthReadOnly=true and depthStoreOp='clear' should cause a validation error.
+    { readonly: true, depthStoreOp: 'clear', _valid: false },
+    // Using stencilReadOnly=true and stencilStoreOp='clear' should cause a validation error.
+    { readonly: true, stencilStoreOp: 'clear', _valid: false },
+    // Mismatched depthReadOnly and stencilReadOnly values should cause a validation error.
+    { readonly: false, _valid: true },
+    { readonly: false, depthReadOnly: true, _valid: false },
+    { readonly: false, stencilReadOnly: true, _valid: false },
+    // depthReadOnly and stencilReadOnly should default to false.
+    { readonly: undefined, _valid: true },
+    { readonly: undefined, depthReadOnly: true, _valid: false },
+    { readonly: undefined, stencilReadOnly: true, _valid: false },
+  ])
+  .fn(async t => {
+    const {
+      readonly,
+      depthStoreOp = 'store',
+      depthReadOnly = readonly,
+      stencilStoreOp = 'store',
+      stencilReadOnly = readonly,
+      _valid,
+    } = t.params;
+
+    const depthAttachment = t.device.createTexture({
+      format: 'depth24plus-stencil8',
+      size: { width: 1, height: 1, depth: 1 },
+      usage: GPUTextureUsage.OUTPUT_ATTACHMENT,
+    });
+
+    const depthAttachmentView = depthAttachment.createView();
+
+    const encoder = t.device.createCommandEncoder();
+    const pass = encoder.beginRenderPass({
+      colorAttachments: [],
+      depthStencilAttachment: {
+        attachment: depthAttachmentView,
+        depthLoadValue: 'load',
+        depthStoreOp,
+        depthReadOnly,
+        stencilLoadValue: 'load',
+        stencilStoreOp,
+        stencilReadOnly,
+      },
+    });
+
+    pass.endPass();
+
+    t.expectValidationError(() => {
+      encoder.finish();
+    }, !_valid);
+  });
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/render_pass_descriptor.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/render_pass_descriptor.spec.js
index 3c1b111..6b2b8c5 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/render_pass_descriptor.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/render_pass_descriptor.spec.js
@@ -1,11 +1,10 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = `
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `
 render pass descriptor validation tests.
 `;
 import { makeTestGroup } from '../../../common/framework/test_group.js';
+
 import { ValidationTest } from './validation_test.js';
 
 class F extends ValidationTest {
@@ -17,42 +16,36 @@
       arrayLayerCount = 1,
       mipLevelCount = 1,
       sampleCount = 1,
-      usage = GPUTextureUsage.OUTPUT_ATTACHMENT
+      usage = GPUTextureUsage.OUTPUT_ATTACHMENT,
     } = options;
+
     return this.device.createTexture({
-      size: {
-        width,
-        height,
-        depth: arrayLayerCount
-      },
+      size: { width, height, depth: arrayLayerCount },
       format,
       mipLevelCount,
       sampleCount,
-      usage
+      usage,
     });
   }
 
   getColorAttachment(texture, textureViewDescriptor) {
     const attachment = texture.createView(textureViewDescriptor);
+
     return {
       attachment,
-      loadValue: {
-        r: 1.0,
-        g: 0.0,
-        b: 0.0,
-        a: 1.0
-      }
+      loadValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
     };
   }
 
   getDepthStencilAttachment(texture, textureViewDescriptor) {
     const attachment = texture.createView(textureViewDescriptor);
+
     return {
       attachment,
       depthLoadValue: 1.0,
       depthStoreOp: 'store',
       stencilLoadValue: 0,
-      stencilStoreOp: 'store'
+      stencilStoreOp: 'store',
     };
   }
 
@@ -60,500 +53,516 @@
     const commandEncoder = this.device.createCommandEncoder();
     const renderPass = commandEncoder.beginRenderPass(descriptor);
     renderPass.endPass();
+
     this.expectValidationError(() => {
       commandEncoder.finish();
     }, !success);
   }
-
 }
 
 export const g = makeTestGroup(F);
+
 g.test('a_render_pass_with_only_one_color_is_ok').fn(t => {
-  const colorTexture = t.createTexture({
-    format: 'rgba8unorm'
-  });
+  const colorTexture = t.createTexture({ format: 'rgba8unorm' });
   const descriptor = {
-    colorAttachments: [t.getColorAttachment(colorTexture)]
+    colorAttachments: [t.getColorAttachment(colorTexture)],
   };
+
   t.tryRenderPass(true, descriptor);
 });
+
 g.test('a_render_pass_with_only_one_depth_attachment_is_ok').fn(t => {
-  const depthStencilTexture = t.createTexture({
-    format: 'depth24plus-stencil8'
-  });
+  const depthStencilTexture = t.createTexture({ format: 'depth24plus-stencil8' });
   const descriptor = {
     colorAttachments: [],
-    depthStencilAttachment: t.getDepthStencilAttachment(depthStencilTexture)
+    depthStencilAttachment: t.getDepthStencilAttachment(depthStencilTexture),
   };
+
   t.tryRenderPass(true, descriptor);
 });
-g.test('OOB_color_attachment_indices_are_handled').params([{
-  colorAttachmentsCount: 4,
-  _success: true
-}, // Control case
-{
-  colorAttachmentsCount: 5,
-  _success: false
-} // Out of bounds
-]).fn(async t => {
-  const {
-    colorAttachmentsCount,
-    _success
-  } = t.params;
-  const colorAttachments = [];
 
-  for (let i = 0; i < colorAttachmentsCount; i++) {
-    const colorTexture = t.createTexture();
-    colorAttachments.push(t.getColorAttachment(colorTexture));
-  }
+g.test('OOB_color_attachment_indices_are_handled')
+  .params([
+    { colorAttachmentsCount: 4, _success: true }, // Control case
+    { colorAttachmentsCount: 5, _success: false }, // Out of bounds
+  ])
+  .fn(async t => {
+    const { colorAttachmentsCount, _success } = t.params;
 
-  await t.tryRenderPass(_success, {
-    colorAttachments
+    const colorAttachments = [];
+    for (let i = 0; i < colorAttachmentsCount; i++) {
+      const colorTexture = t.createTexture();
+      colorAttachments.push(t.getColorAttachment(colorTexture));
+    }
+
+    await t.tryRenderPass(_success, { colorAttachments });
   });
-});
+
 g.test('attachments_must_have_the_same_size').fn(async t => {
-  const colorTexture1x1A = t.createTexture({
-    width: 1,
-    height: 1,
-    format: 'rgba8unorm'
-  });
-  const colorTexture1x1B = t.createTexture({
-    width: 1,
-    height: 1,
-    format: 'rgba8unorm'
-  });
-  const colorTexture2x2 = t.createTexture({
-    width: 2,
-    height: 2,
-    format: 'rgba8unorm'
-  });
+  const colorTexture1x1A = t.createTexture({ width: 1, height: 1, format: 'rgba8unorm' });
+  const colorTexture1x1B = t.createTexture({ width: 1, height: 1, format: 'rgba8unorm' });
+  const colorTexture2x2 = t.createTexture({ width: 2, height: 2, format: 'rgba8unorm' });
   const depthStencilTexture1x1 = t.createTexture({
     width: 1,
     height: 1,
-    format: 'depth24plus-stencil8'
+    format: 'depth24plus-stencil8',
   });
+
   const depthStencilTexture2x2 = t.createTexture({
     width: 2,
     height: 2,
-    format: 'depth24plus-stencil8'
+    format: 'depth24plus-stencil8',
   });
+
   {
     // Control case: all the same size (1x1)
     const descriptor = {
-      colorAttachments: [t.getColorAttachment(colorTexture1x1A), t.getColorAttachment(colorTexture1x1B)],
-      depthStencilAttachment: t.getDepthStencilAttachment(depthStencilTexture1x1)
+      colorAttachments: [
+        t.getColorAttachment(colorTexture1x1A),
+        t.getColorAttachment(colorTexture1x1B),
+      ],
+
+      depthStencilAttachment: t.getDepthStencilAttachment(depthStencilTexture1x1),
     };
+
     t.tryRenderPass(true, descriptor);
   }
   {
     // One of the color attachments has a different size
     const descriptor = {
-      colorAttachments: [t.getColorAttachment(colorTexture1x1A), t.getColorAttachment(colorTexture2x2)]
+      colorAttachments: [
+        t.getColorAttachment(colorTexture1x1A),
+        t.getColorAttachment(colorTexture2x2),
+      ],
     };
+
     await t.tryRenderPass(false, descriptor);
   }
   {
     // The depth stencil attachment has a different size
     const descriptor = {
-      colorAttachments: [t.getColorAttachment(colorTexture1x1A), t.getColorAttachment(colorTexture1x1B)],
-      depthStencilAttachment: t.getDepthStencilAttachment(depthStencilTexture2x2)
+      colorAttachments: [
+        t.getColorAttachment(colorTexture1x1A),
+        t.getColorAttachment(colorTexture1x1B),
+      ],
+
+      depthStencilAttachment: t.getDepthStencilAttachment(depthStencilTexture2x2),
     };
+
     await t.tryRenderPass(false, descriptor);
   }
 });
+
 g.test('attachments_must_match_whether_they_are_used_for_color_or_depth_stencil').fn(async t => {
-  const colorTexture = t.createTexture({
-    format: 'rgba8unorm'
-  });
-  const depthStencilTexture = t.createTexture({
-    format: 'depth24plus-stencil8'
-  });
+  const colorTexture = t.createTexture({ format: 'rgba8unorm' });
+  const depthStencilTexture = t.createTexture({ format: 'depth24plus-stencil8' });
+
   {
     // Using depth-stencil for color
     const descriptor = {
-      colorAttachments: [t.getColorAttachment(depthStencilTexture)]
+      colorAttachments: [t.getColorAttachment(depthStencilTexture)],
     };
+
     await t.tryRenderPass(false, descriptor);
   }
   {
     // Using color for depth-stencil
     const descriptor = {
       colorAttachments: [],
-      depthStencilAttachment: t.getDepthStencilAttachment(colorTexture)
+      depthStencilAttachment: t.getDepthStencilAttachment(colorTexture),
     };
+
     await t.tryRenderPass(false, descriptor);
   }
 });
-g.test('check_layer_count_for_color_or_depth_stencil').params([{
-  arrayLayerCount: 5,
-  baseArrayLayer: 0,
-  _success: false
-}, // using 2D array texture view with arrayLayerCount > 1 is not allowed
-{
-  arrayLayerCount: 1,
-  baseArrayLayer: 0,
-  _success: true
-}, // using 2D array texture view that covers the first layer of the texture is OK
-{
-  arrayLayerCount: 1,
-  baseArrayLayer: 9,
-  _success: true
-} // using 2D array texture view that covers the last layer is OK for depth stencil
-]).fn(async t => {
-  const {
-    arrayLayerCount,
-    baseArrayLayer,
-    _success
-  } = t.params;
-  const ARRAY_LAYER_COUNT = 10;
-  const MIP_LEVEL_COUNT = 1;
-  const COLOR_FORMAT = 'rgba8unorm';
-  const DEPTH_STENCIL_FORMAT = 'depth24plus-stencil8';
-  const colorTexture = t.createTexture({
-    format: COLOR_FORMAT,
-    width: 32,
-    height: 32,
-    mipLevelCount: MIP_LEVEL_COUNT,
-    arrayLayerCount: ARRAY_LAYER_COUNT
-  });
-  const depthStencilTexture = t.createTexture({
-    format: DEPTH_STENCIL_FORMAT,
-    width: 32,
-    height: 32,
-    mipLevelCount: MIP_LEVEL_COUNT,
-    arrayLayerCount: ARRAY_LAYER_COUNT
-  });
-  const baseTextureViewDescriptor = {
-    dimension: '2d-array',
-    baseArrayLayer,
-    arrayLayerCount,
-    baseMipLevel: 0,
-    mipLevelCount: MIP_LEVEL_COUNT
-  };
-  {
-    // Check 2D array texture view for color
-    const textureViewDescriptor = { ...baseTextureViewDescriptor,
-      format: COLOR_FORMAT
+
+g.test('check_layer_count_for_color_or_depth_stencil')
+  .params([
+    { arrayLayerCount: 5, baseArrayLayer: 0, _success: false }, // using 2D array texture view with arrayLayerCount > 1 is not allowed
+    { arrayLayerCount: 1, baseArrayLayer: 0, _success: true }, // using 2D array texture view that covers the first layer of the texture is OK
+    { arrayLayerCount: 1, baseArrayLayer: 9, _success: true }, // using 2D array texture view that covers the last layer is OK for depth stencil
+  ])
+  .fn(async t => {
+    const { arrayLayerCount, baseArrayLayer, _success } = t.params;
+
+    const ARRAY_LAYER_COUNT = 10;
+    const MIP_LEVEL_COUNT = 1;
+    const COLOR_FORMAT = 'rgba8unorm';
+    const DEPTH_STENCIL_FORMAT = 'depth24plus-stencil8';
+
+    const colorTexture = t.createTexture({
+      format: COLOR_FORMAT,
+      width: 32,
+      height: 32,
+      mipLevelCount: MIP_LEVEL_COUNT,
+      arrayLayerCount: ARRAY_LAYER_COUNT,
+    });
+
+    const depthStencilTexture = t.createTexture({
+      format: DEPTH_STENCIL_FORMAT,
+      width: 32,
+      height: 32,
+      mipLevelCount: MIP_LEVEL_COUNT,
+      arrayLayerCount: ARRAY_LAYER_COUNT,
+    });
+
+    const baseTextureViewDescriptor = {
+      dimension: '2d-array',
+      baseArrayLayer,
+      arrayLayerCount,
+      baseMipLevel: 0,
+      mipLevelCount: MIP_LEVEL_COUNT,
     };
+
+    {
+      // Check 2D array texture view for color
+      const textureViewDescriptor = {
+        ...baseTextureViewDescriptor,
+        format: COLOR_FORMAT,
+      };
+
+      const descriptor = {
+        colorAttachments: [t.getColorAttachment(colorTexture, textureViewDescriptor)],
+      };
+
+      await t.tryRenderPass(_success, descriptor);
+    }
+    {
+      // Check 2D array texture view for depth stencil
+      const textureViewDescriptor = {
+        ...baseTextureViewDescriptor,
+        format: DEPTH_STENCIL_FORMAT,
+      };
+
+      const descriptor = {
+        colorAttachments: [],
+        depthStencilAttachment: t.getDepthStencilAttachment(
+          depthStencilTexture,
+          textureViewDescriptor
+        ),
+      };
+
+      await t.tryRenderPass(_success, descriptor);
+    }
+  });
+
+g.test('check_mip_level_count_for_color_or_depth_stencil')
+  .params([
+    { mipLevelCount: 2, baseMipLevel: 0, _success: false }, // using 2D texture view with mipLevelCount > 1 is not allowed
+    { mipLevelCount: 1, baseMipLevel: 0, _success: true }, // using 2D texture view that covers the first level of the texture is OK
+    { mipLevelCount: 1, baseMipLevel: 3, _success: true }, // using 2D texture view that covers the last level of the texture is OK
+  ])
+  .fn(async t => {
+    const { mipLevelCount, baseMipLevel, _success } = t.params;
+
+    const ARRAY_LAYER_COUNT = 1;
+    const MIP_LEVEL_COUNT = 4;
+    const COLOR_FORMAT = 'rgba8unorm';
+    const DEPTH_STENCIL_FORMAT = 'depth24plus-stencil8';
+
+    const colorTexture = t.createTexture({
+      format: COLOR_FORMAT,
+      width: 32,
+      height: 32,
+      mipLevelCount: MIP_LEVEL_COUNT,
+      arrayLayerCount: ARRAY_LAYER_COUNT,
+    });
+
+    const depthStencilTexture = t.createTexture({
+      format: DEPTH_STENCIL_FORMAT,
+      width: 32,
+      height: 32,
+      mipLevelCount: MIP_LEVEL_COUNT,
+      arrayLayerCount: ARRAY_LAYER_COUNT,
+    });
+
+    const baseTextureViewDescriptor = {
+      dimension: '2d',
+      baseArrayLayer: 0,
+      arrayLayerCount: ARRAY_LAYER_COUNT,
+      baseMipLevel,
+      mipLevelCount,
+    };
+
+    {
+      // Check 2D texture view for color
+      const textureViewDescriptor = {
+        ...baseTextureViewDescriptor,
+        format: COLOR_FORMAT,
+      };
+
+      const descriptor = {
+        colorAttachments: [t.getColorAttachment(colorTexture, textureViewDescriptor)],
+      };
+
+      await t.tryRenderPass(_success, descriptor);
+    }
+    {
+      // Check 2D texture view for depth stencil
+      const textureViewDescriptor = {
+        ...baseTextureViewDescriptor,
+        format: DEPTH_STENCIL_FORMAT,
+      };
+
+      const descriptor = {
+        colorAttachments: [],
+        depthStencilAttachment: t.getDepthStencilAttachment(
+          depthStencilTexture,
+          textureViewDescriptor
+        ),
+      };
+
+      await t.tryRenderPass(_success, descriptor);
+    }
+  });
+
+g.test('it_is_invalid_to_set_resolve_target_if_color_attachment_is_non_multisampled').fn(
+  async t => {
+    const colorTexture = t.createTexture({ sampleCount: 1 });
+    const resolveTargetTexture = t.createTexture({ sampleCount: 1 });
+
     const descriptor = {
-      colorAttachments: [t.getColorAttachment(colorTexture, textureViewDescriptor)]
+      colorAttachments: [
+        {
+          attachment: colorTexture.createView(),
+          resolveTarget: resolveTargetTexture.createView(),
+          loadValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+        },
+      ],
     };
-    await t.tryRenderPass(_success, descriptor);
+
+    await t.tryRenderPass(false, descriptor);
   }
-  {
-    // Check 2D array texture view for depth stencil
-    const textureViewDescriptor = { ...baseTextureViewDescriptor,
-      format: DEPTH_STENCIL_FORMAT
-    };
-    const descriptor = {
-      colorAttachments: [],
-      depthStencilAttachment: t.getDepthStencilAttachment(depthStencilTexture, textureViewDescriptor)
-    };
-    await t.tryRenderPass(_success, descriptor);
-  }
-});
-g.test('check_mip_level_count_for_color_or_depth_stencil').params([{
-  mipLevelCount: 2,
-  baseMipLevel: 0,
-  _success: false
-}, // using 2D texture view with mipLevelCount > 1 is not allowed
-{
-  mipLevelCount: 1,
-  baseMipLevel: 0,
-  _success: true
-}, // using 2D texture view that covers the first level of the texture is OK
-{
-  mipLevelCount: 1,
-  baseMipLevel: 3,
-  _success: true
-} // using 2D texture view that covers the last level of the texture is OK
-]).fn(async t => {
-  const {
-    mipLevelCount,
-    baseMipLevel,
-    _success
-  } = t.params;
-  const ARRAY_LAYER_COUNT = 1;
-  const MIP_LEVEL_COUNT = 4;
-  const COLOR_FORMAT = 'rgba8unorm';
-  const DEPTH_STENCIL_FORMAT = 'depth24plus-stencil8';
-  const colorTexture = t.createTexture({
-    format: COLOR_FORMAT,
-    width: 32,
-    height: 32,
-    mipLevelCount: MIP_LEVEL_COUNT,
-    arrayLayerCount: ARRAY_LAYER_COUNT
-  });
-  const depthStencilTexture = t.createTexture({
-    format: DEPTH_STENCIL_FORMAT,
-    width: 32,
-    height: 32,
-    mipLevelCount: MIP_LEVEL_COUNT,
-    arrayLayerCount: ARRAY_LAYER_COUNT
-  });
-  const baseTextureViewDescriptor = {
-    dimension: '2d',
-    baseArrayLayer: 0,
-    arrayLayerCount: ARRAY_LAYER_COUNT,
-    baseMipLevel,
-    mipLevelCount
-  };
-  {
-    // Check 2D texture view for color
-    const textureViewDescriptor = { ...baseTextureViewDescriptor,
-      format: COLOR_FORMAT
-    };
-    const descriptor = {
-      colorAttachments: [t.getColorAttachment(colorTexture, textureViewDescriptor)]
-    };
-    await t.tryRenderPass(_success, descriptor);
-  }
-  {
-    // Check 2D texture view for depth stencil
-    const textureViewDescriptor = { ...baseTextureViewDescriptor,
-      format: DEPTH_STENCIL_FORMAT
-    };
-    const descriptor = {
-      colorAttachments: [],
-      depthStencilAttachment: t.getDepthStencilAttachment(depthStencilTexture, textureViewDescriptor)
-    };
-    await t.tryRenderPass(_success, descriptor);
-  }
-});
-g.test('it_is_invalid_to_set_resolve_target_if_color_attachment_is_non_multisampled').fn(async t => {
-  const colorTexture = t.createTexture({
-    sampleCount: 1
-  });
-  const resolveTargetTexture = t.createTexture({
-    sampleCount: 1
-  });
-  const descriptor = {
-    colorAttachments: [{
-      attachment: colorTexture.createView(),
-      resolveTarget: resolveTargetTexture.createView(),
-      loadValue: {
-        r: 1.0,
-        g: 0.0,
-        b: 0.0,
-        a: 1.0
-      }
-    }]
-  };
-  await t.tryRenderPass(false, descriptor);
-});
+);
+
 g.test('check_the_use_of_multisampled_textures_as_color_attachments').fn(async t => {
-  const colorTexture = t.createTexture({
-    sampleCount: 1
-  });
-  const multisampledColorTexture = t.createTexture({
-    sampleCount: 4
-  });
+  const colorTexture = t.createTexture({ sampleCount: 1 });
+  const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
+
   {
     // It is allowed to use a multisampled color attachment without setting resolve target
     const descriptor = {
-      colorAttachments: [t.getColorAttachment(multisampledColorTexture)]
+      colorAttachments: [t.getColorAttachment(multisampledColorTexture)],
     };
+
     t.tryRenderPass(true, descriptor);
   }
   {
     // It is not allowed to use multiple color attachments with different sample counts
     const descriptor = {
-      colorAttachments: [t.getColorAttachment(colorTexture), t.getColorAttachment(multisampledColorTexture)]
+      colorAttachments: [
+        t.getColorAttachment(colorTexture),
+        t.getColorAttachment(multisampledColorTexture),
+      ],
     };
+
     await t.tryRenderPass(false, descriptor);
   }
 });
+
 g.test('it_is_invalid_to_use_a_multisampled_resolve_target').fn(async t => {
-  const multisampledColorTexture = t.createTexture({
-    sampleCount: 4
-  });
-  const multisampledResolveTargetTexture = t.createTexture({
-    sampleCount: 4
-  });
+  const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
+  const multisampledResolveTargetTexture = t.createTexture({ sampleCount: 4 });
+
   const colorAttachment = t.getColorAttachment(multisampledColorTexture);
   colorAttachment.resolveTarget = multisampledResolveTargetTexture.createView();
+
   const descriptor = {
-    colorAttachments: [colorAttachment]
+    colorAttachments: [colorAttachment],
   };
+
   await t.tryRenderPass(false, descriptor);
 });
-g.test('it_is_invalid_to_use_a_resolve_target_with_array_layer_count_greater_than_1').fn(async t => {
-  const multisampledColorTexture = t.createTexture({
-    sampleCount: 4
-  });
-  const resolveTargetTexture = t.createTexture({
-    arrayLayerCount: 2
-  });
-  const colorAttachment = t.getColorAttachment(multisampledColorTexture);
-  colorAttachment.resolveTarget = resolveTargetTexture.createView();
-  const descriptor = {
-    colorAttachments: [colorAttachment]
-  };
-  await t.tryRenderPass(false, descriptor);
-});
-g.test('it_is_invalid_to_use_a_resolve_target_with_mipmap_level_count_greater_than_1').fn(async t => {
-  const multisampledColorTexture = t.createTexture({
-    sampleCount: 4
-  });
-  const resolveTargetTexture = t.createTexture({
-    mipLevelCount: 2
-  });
-  const colorAttachment = t.getColorAttachment(multisampledColorTexture);
-  colorAttachment.resolveTarget = resolveTargetTexture.createView();
-  const descriptor = {
-    colorAttachments: [colorAttachment]
-  };
-  await t.tryRenderPass(false, descriptor);
-});
+
+g.test('it_is_invalid_to_use_a_resolve_target_with_array_layer_count_greater_than_1').fn(
+  async t => {
+    const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
+    const resolveTargetTexture = t.createTexture({ arrayLayerCount: 2 });
+
+    const colorAttachment = t.getColorAttachment(multisampledColorTexture);
+    colorAttachment.resolveTarget = resolveTargetTexture.createView();
+
+    const descriptor = {
+      colorAttachments: [colorAttachment],
+    };
+
+    await t.tryRenderPass(false, descriptor);
+  }
+);
+
+g.test('it_is_invalid_to_use_a_resolve_target_with_mipmap_level_count_greater_than_1').fn(
+  async t => {
+    const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
+    const resolveTargetTexture = t.createTexture({ mipLevelCount: 2 });
+
+    const colorAttachment = t.getColorAttachment(multisampledColorTexture);
+    colorAttachment.resolveTarget = resolveTargetTexture.createView();
+
+    const descriptor = {
+      colorAttachments: [colorAttachment],
+    };
+
+    await t.tryRenderPass(false, descriptor);
+  }
+);
+
 g.test('it_is_invalid_to_use_a_resolve_target_whose_usage_is_not_output_attachment').fn(async t => {
-  const multisampledColorTexture = t.createTexture({
-    sampleCount: 4
-  });
+  const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
   const resolveTargetTexture = t.createTexture({
-    usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+    usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
   });
+
   const colorAttachment = t.getColorAttachment(multisampledColorTexture);
   colorAttachment.resolveTarget = resolveTargetTexture.createView();
+
   const descriptor = {
-    colorAttachments: [colorAttachment]
+    colorAttachments: [colorAttachment],
   };
+
   await t.tryRenderPass(false, descriptor);
 });
+
 g.test('it_is_invalid_to_use_a_resolve_target_in_error_state').fn(async t => {
   const ARRAY_LAYER_COUNT = 1;
-  const multisampledColorTexture = t.createTexture({
-    sampleCount: 4
-  });
-  const resolveTargetTexture = t.createTexture({
-    arrayLayerCount: ARRAY_LAYER_COUNT
-  });
+
+  const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
+  const resolveTargetTexture = t.createTexture({ arrayLayerCount: ARRAY_LAYER_COUNT });
+
   const colorAttachment = t.getColorAttachment(multisampledColorTexture);
   t.expectValidationError(() => {
     colorAttachment.resolveTarget = resolveTargetTexture.createView({
       dimension: '2d',
       format: 'rgba8unorm',
-      baseArrayLayer: ARRAY_LAYER_COUNT + 1
+      baseArrayLayer: ARRAY_LAYER_COUNT + 1,
     });
   });
+
   const descriptor = {
-    colorAttachments: [colorAttachment]
+    colorAttachments: [colorAttachment],
   };
+
   await t.tryRenderPass(false, descriptor);
 });
-g.test('use_of_multisampled_attachment_and_non_multisampled_resolve_target_is_allowed').fn(async t => {
-  const multisampledColorTexture = t.createTexture({
-    sampleCount: 4
-  });
-  const resolveTargetTexture = t.createTexture({
-    sampleCount: 1
-  });
-  const colorAttachment = t.getColorAttachment(multisampledColorTexture);
-  colorAttachment.resolveTarget = resolveTargetTexture.createView();
-  const descriptor = {
-    colorAttachments: [colorAttachment]
-  };
-  t.tryRenderPass(true, descriptor);
-});
-g.test('use_a_resolve_target_in_a_format_different_than_the_attachment_is_not_allowed').fn(async t => {
-  const multisampledColorTexture = t.createTexture({
-    sampleCount: 4
-  });
-  const resolveTargetTexture = t.createTexture({
-    format: 'bgra8unorm'
-  });
-  const colorAttachment = t.getColorAttachment(multisampledColorTexture);
-  colorAttachment.resolveTarget = resolveTargetTexture.createView();
-  const descriptor = {
-    colorAttachments: [colorAttachment]
-  };
-  await t.tryRenderPass(false, descriptor);
-});
+
+g.test('use_of_multisampled_attachment_and_non_multisampled_resolve_target_is_allowed').fn(
+  async t => {
+    const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
+    const resolveTargetTexture = t.createTexture({ sampleCount: 1 });
+
+    const colorAttachment = t.getColorAttachment(multisampledColorTexture);
+    colorAttachment.resolveTarget = resolveTargetTexture.createView();
+
+    const descriptor = {
+      colorAttachments: [colorAttachment],
+    };
+
+    t.tryRenderPass(true, descriptor);
+  }
+);
+
+g.test('use_a_resolve_target_in_a_format_different_than_the_attachment_is_not_allowed').fn(
+  async t => {
+    const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
+    const resolveTargetTexture = t.createTexture({ format: 'bgra8unorm' });
+
+    const colorAttachment = t.getColorAttachment(multisampledColorTexture);
+    colorAttachment.resolveTarget = resolveTargetTexture.createView();
+
+    const descriptor = {
+      colorAttachments: [colorAttachment],
+    };
+
+    await t.tryRenderPass(false, descriptor);
+  }
+);
+
 g.test('size_of_the_resolve_target_must_be_the_same_as_the_color_attachment').fn(async t => {
   const size = 16;
-  const multisampledColorTexture = t.createTexture({
-    width: size,
-    height: size,
-    sampleCount: 4
-  });
+  const multisampledColorTexture = t.createTexture({ width: size, height: size, sampleCount: 4 });
   const resolveTargetTexture = t.createTexture({
     width: size * 2,
     height: size * 2,
-    mipLevelCount: 2
+    mipLevelCount: 2,
   });
+
   {
     const resolveTargetTextureView = resolveTargetTexture.createView({
       baseMipLevel: 0,
-      mipLevelCount: 1
+      mipLevelCount: 1,
     });
+
     const colorAttachment = t.getColorAttachment(multisampledColorTexture);
     colorAttachment.resolveTarget = resolveTargetTextureView;
+
     const descriptor = {
-      colorAttachments: [colorAttachment]
+      colorAttachments: [colorAttachment],
     };
+
     await t.tryRenderPass(false, descriptor);
   }
   {
-    const resolveTargetTextureView = resolveTargetTexture.createView({
-      baseMipLevel: 1
-    });
+    const resolveTargetTextureView = resolveTargetTexture.createView({ baseMipLevel: 1 });
+
     const colorAttachment = t.getColorAttachment(multisampledColorTexture);
     colorAttachment.resolveTarget = resolveTargetTextureView;
+
     const descriptor = {
-      colorAttachments: [colorAttachment]
+      colorAttachments: [colorAttachment],
     };
+
     t.tryRenderPass(true, descriptor);
   }
 });
+
 g.test('check_depth_stencil_attachment_sample_counts_mismatch').fn(async t => {
   const multisampledDepthStencilTexture = t.createTexture({
     sampleCount: 4,
-    format: 'depth24plus-stencil8'
+    format: 'depth24plus-stencil8',
   });
+
   {
     // It is not allowed to use a depth stencil attachment whose sample count is different from the
     // one of the color attachment
     const depthStencilTexture = t.createTexture({
       sampleCount: 1,
-      format: 'depth24plus-stencil8'
+      format: 'depth24plus-stencil8',
     });
-    const multisampledColorTexture = t.createTexture({
-      sampleCount: 4
-    });
+
+    const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
     const descriptor = {
       colorAttachments: [t.getColorAttachment(multisampledColorTexture)],
-      depthStencilAttachment: t.getDepthStencilAttachment(depthStencilTexture)
+      depthStencilAttachment: t.getDepthStencilAttachment(depthStencilTexture),
     };
+
     await t.tryRenderPass(false, descriptor);
   }
   {
-    const colorTexture = t.createTexture({
-      sampleCount: 1
-    });
+    const colorTexture = t.createTexture({ sampleCount: 1 });
     const descriptor = {
       colorAttachments: [t.getColorAttachment(colorTexture)],
-      depthStencilAttachment: t.getDepthStencilAttachment(multisampledDepthStencilTexture)
+      depthStencilAttachment: t.getDepthStencilAttachment(multisampledDepthStencilTexture),
     };
+
     await t.tryRenderPass(false, descriptor);
   }
   {
     // It is allowed to use a multisampled depth stencil attachment whose sample count is equal to
     // the one of the color attachment.
-    const multisampledColorTexture = t.createTexture({
-      sampleCount: 4
-    });
+    const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
     const descriptor = {
       colorAttachments: [t.getColorAttachment(multisampledColorTexture)],
-      depthStencilAttachment: t.getDepthStencilAttachment(multisampledDepthStencilTexture)
+      depthStencilAttachment: t.getDepthStencilAttachment(multisampledDepthStencilTexture),
     };
+
     t.tryRenderPass(true, descriptor);
   }
   {
     // It is allowed to use a multisampled depth stencil attachment with no color attachment
     const descriptor = {
       colorAttachments: [],
-      depthStencilAttachment: t.getDepthStencilAttachment(multisampledDepthStencilTexture)
+      depthStencilAttachment: t.getDepthStencilAttachment(multisampledDepthStencilTexture),
     };
+
     t.tryRenderPass(true, descriptor);
   }
 });
-//# sourceMappingURL=render_pass_descriptor.spec.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/resource_usages/textureUsageInRender.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/resource_usages/textureUsageInRender.spec.js
new file mode 100644
index 0000000..2fc9021
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/resource_usages/textureUsageInRender.spec.js
@@ -0,0 +1,238 @@
+/**
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `
+Texture Usages Validation Tests in Render Pass.
+
+Test Coverage:
+ - Tests that read and write usages upon the same texture subresource, or different subresources
+   of the same texture. Different subresources of the same texture includes different mip levels,
+   different array layers, and different aspects.
+   - When read and write usages are binding to the same texture subresource, an error should be
+     generated. Otherwise, no error should be generated.
+`;
+import { poptions, params } from '../../../../common/framework/params_builder.js';
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import {
+  kShaderStages,
+  kDepthStencilFormats,
+  kDepthStencilFormatInfo,
+} from '../../../capability_info.js';
+import { ValidationTest } from '../validation_test.js';
+
+class TextureUsageTracking extends ValidationTest {
+  createTexture(options = {}) {
+    const {
+      width = 32,
+      height = 32,
+      arrayLayerCount = 1,
+      mipLevelCount = 1,
+      sampleCount = 1,
+      format = 'rgba8unorm',
+      usage = GPUTextureUsage.OUTPUT_ATTACHMENT | GPUTextureUsage.SAMPLED,
+    } = options;
+
+    return this.device.createTexture({
+      size: { width, height, depth: arrayLayerCount },
+      mipLevelCount,
+      sampleCount,
+      dimension: '2d',
+      format,
+      usage,
+    });
+  }
+}
+
+export const g = makeTestGroup(TextureUsageTracking);
+
+const READ_BASE_LEVEL = 3;
+const READ_BASE_LAYER = 0;
+
+g.test('readwrite_upon_subresources')
+  .params([
+    // read and write usages are binding to the same texture subresource.
+    {
+      writeBaseLevel: READ_BASE_LEVEL,
+      writeBaseLayer: READ_BASE_LAYER,
+      _success: false,
+    },
+
+    // read and write usages are binding to different mip levels of the same texture.
+    {
+      writeBaseLevel: READ_BASE_LEVEL + 1,
+      writeBaseLayer: READ_BASE_LAYER,
+      _success: true,
+    },
+
+    // read and write usages are binding to different array layers of the same texture.
+    {
+      writeBaseLevel: READ_BASE_LEVEL,
+      writeBaseLayer: READ_BASE_LAYER + 1,
+      _success: true,
+    },
+  ])
+  .fn(async t => {
+    const { writeBaseLevel, writeBaseLayer, _success } = t.params;
+
+    const texture = t.createTexture({ arrayLayerCount: 2, mipLevelCount: 6 });
+
+    const sampleView = texture.createView({
+      baseMipLevel: READ_BASE_LEVEL,
+      mipLevelCount: 1,
+      baseArrayLayer: READ_BASE_LAYER,
+      arrayLayerCount: 1,
+    });
+
+    const renderView = texture.createView({
+      baseMipLevel: writeBaseLevel,
+      mipLevelCount: 1,
+      baseArrayLayer: writeBaseLayer,
+      arrayLayerCount: 1,
+    });
+
+    const bindGroupLayout = t.device.createBindGroupLayout({
+      entries: [{ binding: 0, visibility: GPUShaderStage.FRAGMENT, type: 'sampled-texture' }],
+    });
+
+    const bindGroup = t.device.createBindGroup({
+      entries: [{ binding: 0, resource: sampleView }],
+      layout: bindGroupLayout,
+    });
+
+    const encoder = t.device.createCommandEncoder();
+    const pass = encoder.beginRenderPass({
+      colorAttachments: [
+        {
+          attachment: renderView,
+          loadValue: { r: 0.0, g: 1.0, b: 0.0, a: 1.0 },
+          storeOp: 'store',
+        },
+      ],
+    });
+
+    pass.setBindGroup(0, bindGroup);
+    pass.endPass();
+
+    t.expectValidationError(() => {
+      encoder.finish();
+    }, !_success);
+  });
+
+g.test('readwrite_upon_aspects')
+  .params(
+    params()
+      .combine(poptions('format', kDepthStencilFormats))
+      .combine(poptions('readAspect', ['all', 'depth-only', 'stencil-only']))
+      .combine(poptions('writeAspect', ['all', 'depth-only', 'stencil-only']))
+      .unless(
+        ({ format, readAspect, writeAspect }) =>
+          (readAspect === 'stencil-only' && !kDepthStencilFormatInfo[format].stencil) ||
+          (writeAspect === 'stencil-only' && !kDepthStencilFormatInfo[format].stencil)
+      )
+      .unless(
+        ({ format, readAspect, writeAspect }) =>
+          (readAspect === 'depth-only' && !kDepthStencilFormatInfo[format].depth) ||
+          (writeAspect === 'depth-only' && !kDepthStencilFormatInfo[format].depth)
+      )
+  )
+  .fn(async t => {
+    const { format, readAspect, writeAspect } = t.params;
+
+    const view = t.createTexture({ format }).createView();
+
+    const bindGroupLayout = t.device.createBindGroupLayout({
+      entries: [{ binding: 0, visibility: GPUShaderStage.FRAGMENT, type: 'sampled-texture' }],
+    });
+
+    const bindGroup = t.device.createBindGroup({
+      entries: [{ binding: 0, resource: view }],
+      layout: bindGroupLayout,
+    });
+
+    const success =
+      (readAspect === 'depth-only' && writeAspect === 'stencil-only') ||
+      (readAspect === 'stencil-only' && writeAspect === 'depth-only');
+
+    const encoder = t.device.createCommandEncoder();
+    const pass = encoder.beginRenderPass({
+      colorAttachments: [
+        {
+          attachment: t.createTexture().createView(),
+          loadValue: { r: 0.0, g: 1.0, b: 0.0, a: 1.0 },
+          storeOp: 'store',
+        },
+      ],
+
+      depthStencilAttachment: {
+        attachment: view,
+        depthStoreOp: 'clear',
+        depthLoadValue: 'load',
+        stencilStoreOp: 'clear',
+        stencilLoadValue: 'load',
+      },
+    });
+
+    pass.setBindGroup(0, bindGroup);
+    pass.endPass();
+
+    t.expectValidationError(() => {
+      encoder.finish();
+    }, !success);
+  });
+
+g.test('shader_stages_and_visibility')
+  .params(
+    params()
+      .combine(poptions('readVisibility', [0, ...kShaderStages]))
+      .combine(poptions('writeVisibility', [0, ...kShaderStages]))
+  )
+  .fn(async t => {
+    const { readVisibility, writeVisibility } = t.params;
+
+    // writeonly-storage-texture binding type is not supported in vertex stage. So, this test
+    // uses writeonly-storage-texture binding as writable binding upon the same subresource if
+    // vertex stage is not included. Otherwise, it uses output attachment instead.
+    const writeHasVertexStage = Boolean(writeVisibility & GPUShaderStage.VERTEX);
+    const texUsage = writeHasVertexStage
+      ? GPUTextureUsage.SAMPLED | GPUTextureUsage.OUTPUT_ATTACHMENT
+      : GPUTextureUsage.SAMPLED | GPUTextureUsage.STORAGE;
+
+    const texture = t.createTexture({ usage: texUsage });
+    const view = texture.createView();
+    const bglEntries = [{ binding: 0, visibility: readVisibility, type: 'sampled-texture' }];
+
+    const bgEntries = [{ binding: 0, resource: view }];
+    if (!writeHasVertexStage) {
+      bglEntries.push({
+        binding: 1,
+        visibility: writeVisibility,
+        type: 'writeonly-storage-texture',
+        storageTextureFormat: 'rgba8unorm',
+      });
+
+      bgEntries.push({ binding: 1, resource: view });
+    }
+    const bindGroup = t.device.createBindGroup({
+      entries: bgEntries,
+      layout: t.device.createBindGroupLayout({ entries: bglEntries }),
+    });
+
+    const encoder = t.device.createCommandEncoder();
+    const pass = encoder.beginRenderPass({
+      colorAttachments: [
+        {
+          attachment: writeHasVertexStage ? view : t.createTexture().createView(),
+          loadValue: { r: 0.0, g: 1.0, b: 0.0, a: 1.0 },
+          storeOp: 'store',
+        },
+      ],
+    });
+
+    pass.setBindGroup(0, bindGroup);
+    pass.endPass();
+
+    // Texture usages in bindings with invisible shader stages should be tracked. Invisible shader
+    // stages include shader stage with visibility none and compute shader stage in render pass.
+    t.expectValidationError(() => {
+      encoder.finish();
+    });
+  });
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/setBindGroup.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/setBindGroup.spec.js
index 95786a8..fca85a83 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/setBindGroup.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/setBindGroup.spec.js
@@ -1,24 +1,19 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = `
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `
 setBindGroup validation tests.
 `;
 import { poptions, params } from '../../../common/framework/params_builder.js';
 import { makeTestGroup } from '../../../common/framework/test_group.js';
+
 import { ValidationTest } from './validation_test.js';
 
 class F extends ValidationTest {
   makeAttachmentTexture() {
     return this.device.createTexture({
       format: 'rgba8unorm',
-      size: {
-        width: 16,
-        height: 16,
-        depth: 1
-      },
-      usage: GPUTextureUsage.OUTPUT_ATTACHMENT
+      size: { width: 16, height: 16, depth: 1 },
+      usage: GPUTextureUsage.OUTPUT_ATTACHMENT,
     });
   }
 
@@ -33,16 +28,14 @@
   testRenderPass(bindGroup, dynamicOffsets) {
     const encoder = this.device.createCommandEncoder();
     const renderPass = encoder.beginRenderPass({
-      colorAttachments: [{
-        attachment: this.makeAttachmentTexture().createView(),
-        loadValue: {
-          r: 1.0,
-          g: 0.0,
-          b: 0.0,
-          a: 1.0
-        }
-      }]
+      colorAttachments: [
+        {
+          attachment: this.makeAttachmentTexture().createView(),
+          loadValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+        },
+      ],
     });
+
     renderPass.setBindGroup(0, bindGroup, dynamicOffsets);
     renderPass.endPass();
     encoder.finish();
@@ -50,157 +43,151 @@
 
   testRenderBundle(bindGroup, dynamicOffsets) {
     const encoder = this.device.createRenderBundleEncoder({
-      colorFormats: ['rgba8unorm']
+      colorFormats: ['rgba8unorm'],
     });
+
     encoder.setBindGroup(0, bindGroup, dynamicOffsets);
     encoder.finish();
   }
-
 }
 
 export const g = makeTestGroup(F);
-g.test('dynamic_offsets_passed_but_not_expected,compute_pass').params(poptions('type', ['compute', 'renderpass', 'renderbundle'])).fn(async t => {
-  const bindGroupLayout = t.device.createBindGroupLayout({
-    entries: []
-  });
-  const bindGroup = t.device.createBindGroup({
-    layout: bindGroupLayout,
-    entries: []
-  });
-  const {
-    type
-  } = t.params;
-  const dynamicOffsets = [0];
-  t.expectValidationError(() => {
-    if (type === 'compute') {
-      const encoder = t.device.createCommandEncoder();
-      const computePass = encoder.beginComputePass();
-      computePass.setBindGroup(0, bindGroup, dynamicOffsets);
-      computePass.endPass();
-      encoder.finish();
-    } else if (type === 'renderpass') {
-      const encoder = t.device.createCommandEncoder();
-      const renderPass = encoder.beginRenderPass({
-        colorAttachments: [{
-          attachment: t.makeAttachmentTexture().createView(),
-          loadValue: {
-            r: 1.0,
-            g: 0.0,
-            b: 0.0,
-            a: 1.0
-          }
-        }]
-      });
-      renderPass.setBindGroup(0, bindGroup, dynamicOffsets);
-      renderPass.endPass();
-      encoder.finish();
-    } else if (type === 'renderbundle') {
-      const encoder = t.device.createRenderBundleEncoder({
-        colorFormats: ['rgba8unorm']
-      });
-      encoder.setBindGroup(0, bindGroup, dynamicOffsets);
-      encoder.finish();
-    } else {
-      t.fail();
-    }
-  });
-});
-g.test('dynamic_offsets_match_expectations_in_pass_encoder').params(params().combine(poptions('type', ['compute', 'renderpass', 'renderbundle'])).combine([{
-  dynamicOffsets: [256, 0],
-  _success: true
-}, // Dynamic offsets aligned
-{
-  dynamicOffsets: [1, 2],
-  _success: false
-}, // Dynamic offsets not aligned
-// Wrong number of dynamic offsets
-{
-  dynamicOffsets: [256, 0, 0],
-  _success: false
-}, {
-  dynamicOffsets: [256],
-  _success: false
-}, {
-  dynamicOffsets: [],
-  _success: false
-}, // Dynamic uniform buffer out of bounds because of binding size
-{
-  dynamicOffsets: [512, 0],
-  _success: false
-}, {
-  dynamicOffsets: [1024, 0],
-  _success: false
-}, {
-  dynamicOffsets: [0xffffffff, 0],
-  _success: false
-}, // Dynamic storage buffer out of bounds because of binding size
-{
-  dynamicOffsets: [0, 512],
-  _success: false
-}, {
-  dynamicOffsets: [0, 1024],
-  _success: false
-}, {
-  dynamicOffsets: [0, 0xffffffff],
-  _success: false
-}])).fn(async t => {
-  // Dynamic buffer offsets require offset to be divisible by 256
-  const MIN_DYNAMIC_BUFFER_OFFSET_ALIGNMENT = 256;
-  const BINDING_SIZE = 9;
-  const bindGroupLayout = t.device.createBindGroupLayout({
-    entries: [{
-      binding: 0,
-      visibility: GPUShaderStage.COMPUTE | GPUShaderStage.FRAGMENT,
-      type: 'uniform-buffer',
-      hasDynamicOffset: true
-    }, {
-      binding: 1,
-      visibility: GPUShaderStage.COMPUTE | GPUShaderStage.FRAGMENT,
-      type: 'storage-buffer',
-      hasDynamicOffset: true
-    }]
-  });
-  const uniformBuffer = t.device.createBuffer({
-    size: 2 * MIN_DYNAMIC_BUFFER_OFFSET_ALIGNMENT + 8,
-    usage: GPUBufferUsage.UNIFORM
-  });
-  const storageBuffer = t.device.createBuffer({
-    size: 2 * MIN_DYNAMIC_BUFFER_OFFSET_ALIGNMENT + 8,
-    usage: GPUBufferUsage.STORAGE
-  });
-  const bindGroup = t.device.createBindGroup({
-    layout: bindGroupLayout,
-    entries: [{
-      binding: 0,
-      resource: {
-        buffer: uniformBuffer,
-        size: BINDING_SIZE
-      }
-    }, {
-      binding: 1,
-      resource: {
-        buffer: storageBuffer,
-        size: BINDING_SIZE
-      }
-    }]
-  });
-  const {
-    type,
-    dynamicOffsets,
-    _success
-  } = t.params;
-  t.expectValidationError(() => {
-    if (type === 'compute') {
-      t.testComputePass(bindGroup, dynamicOffsets);
-    } else if (type === 'renderpass') {
-      t.testRenderPass(bindGroup, dynamicOffsets);
-    } else if (type === 'renderbundle') {
-      t.testRenderBundle(bindGroup, dynamicOffsets);
-    } else {
-      t.fail();
-    }
 
-    t.testComputePass(bindGroup, dynamicOffsets);
-  }, !_success);
-}); // TODO: test error bind group
-//# sourceMappingURL=setBindGroup.spec.js.map
\ No newline at end of file
+g.test('dynamic_offsets_passed_but_not_expected,compute_pass')
+  .params(poptions('type', ['compute', 'renderpass', 'renderbundle']))
+  .fn(async t => {
+    const bindGroupLayout = t.device.createBindGroupLayout({ entries: [] });
+    const bindGroup = t.device.createBindGroup({ layout: bindGroupLayout, entries: [] });
+
+    const { type } = t.params;
+    const dynamicOffsets = [0];
+
+    t.expectValidationError(() => {
+      if (type === 'compute') {
+        const encoder = t.device.createCommandEncoder();
+        const computePass = encoder.beginComputePass();
+        computePass.setBindGroup(0, bindGroup, dynamicOffsets);
+        computePass.endPass();
+        encoder.finish();
+      } else if (type === 'renderpass') {
+        const encoder = t.device.createCommandEncoder();
+        const renderPass = encoder.beginRenderPass({
+          colorAttachments: [
+            {
+              attachment: t.makeAttachmentTexture().createView(),
+              loadValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+            },
+          ],
+        });
+
+        renderPass.setBindGroup(0, bindGroup, dynamicOffsets);
+        renderPass.endPass();
+        encoder.finish();
+      } else if (type === 'renderbundle') {
+        const encoder = t.device.createRenderBundleEncoder({
+          colorFormats: ['rgba8unorm'],
+        });
+
+        encoder.setBindGroup(0, bindGroup, dynamicOffsets);
+        encoder.finish();
+      } else {
+        t.fail();
+      }
+    });
+  });
+
+g.test('dynamic_offsets_match_expectations_in_pass_encoder')
+  .params(
+    params()
+      .combine(poptions('type', ['compute', 'renderpass', 'renderbundle']))
+      .combine([
+        { dynamicOffsets: [256, 0], _success: true }, // Dynamic offsets aligned
+        { dynamicOffsets: [1, 2], _success: false }, // Dynamic offsets not aligned
+
+        // Wrong number of dynamic offsets
+        { dynamicOffsets: [256, 0, 0], _success: false },
+        { dynamicOffsets: [256], _success: false },
+        { dynamicOffsets: [], _success: false },
+
+        // Dynamic uniform buffer out of bounds because of binding size
+        { dynamicOffsets: [512, 0], _success: false },
+        { dynamicOffsets: [1024, 0], _success: false },
+        { dynamicOffsets: [0xffffffff, 0], _success: false },
+
+        // Dynamic storage buffer out of bounds because of binding size
+        { dynamicOffsets: [0, 512], _success: false },
+        { dynamicOffsets: [0, 1024], _success: false },
+        { dynamicOffsets: [0, 0xffffffff], _success: false },
+      ])
+  )
+  .fn(async t => {
+    // Dynamic buffer offsets require offset to be divisible by 256
+    const MIN_DYNAMIC_BUFFER_OFFSET_ALIGNMENT = 256;
+    const BINDING_SIZE = 9;
+
+    const bindGroupLayout = t.device.createBindGroupLayout({
+      entries: [
+        {
+          binding: 0,
+          visibility: GPUShaderStage.COMPUTE | GPUShaderStage.FRAGMENT,
+          type: 'uniform-buffer',
+          hasDynamicOffset: true,
+        },
+
+        {
+          binding: 1,
+          visibility: GPUShaderStage.COMPUTE | GPUShaderStage.FRAGMENT,
+          type: 'storage-buffer',
+          hasDynamicOffset: true,
+        },
+      ],
+    });
+
+    const uniformBuffer = t.device.createBuffer({
+      size: 2 * MIN_DYNAMIC_BUFFER_OFFSET_ALIGNMENT + 8,
+      usage: GPUBufferUsage.UNIFORM,
+    });
+
+    const storageBuffer = t.device.createBuffer({
+      size: 2 * MIN_DYNAMIC_BUFFER_OFFSET_ALIGNMENT + 8,
+      usage: GPUBufferUsage.STORAGE,
+    });
+
+    const bindGroup = t.device.createBindGroup({
+      layout: bindGroupLayout,
+      entries: [
+        {
+          binding: 0,
+          resource: {
+            buffer: uniformBuffer,
+            size: BINDING_SIZE,
+          },
+        },
+
+        {
+          binding: 1,
+          resource: {
+            buffer: storageBuffer,
+            size: BINDING_SIZE,
+          },
+        },
+      ],
+    });
+
+    const { type, dynamicOffsets, _success } = t.params;
+
+    t.expectValidationError(() => {
+      if (type === 'compute') {
+        t.testComputePass(bindGroup, dynamicOffsets);
+      } else if (type === 'renderpass') {
+        t.testRenderPass(bindGroup, dynamicOffsets);
+      } else if (type === 'renderbundle') {
+        t.testRenderBundle(bindGroup, dynamicOffsets);
+      } else {
+        t.fail();
+      }
+      t.testComputePass(bindGroup, dynamicOffsets);
+    }, !_success);
+  });
+
+// TODO: test error bind group
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/setBlendColor.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/setBlendColor.spec.js
index 469641d..8a302ff 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/setBlendColor.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/setBlendColor.spec.js
@@ -1,66 +1,49 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = `
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `
 setBlendColor validation tests.
 `;
 import { makeTestGroup } from '../../../common/framework/test_group.js';
-import { ValidationTest } from './validation_test.js'; // TODO: Move beginRenderPass to a Fixture class.
 
+import { ValidationTest } from './validation_test.js';
+
+// TODO: Move beginRenderPass to a Fixture class.
 class F extends ValidationTest {
   beginRenderPass(commandEncoder) {
     const attachmentTexture = this.device.createTexture({
       format: 'rgba8unorm',
-      size: {
-        width: 16,
-        height: 16,
-        depth: 1
-      },
-      usage: GPUTextureUsage.OUTPUT_ATTACHMENT
+      size: { width: 16, height: 16, depth: 1 },
+      usage: GPUTextureUsage.OUTPUT_ATTACHMENT,
     });
+
     return commandEncoder.beginRenderPass({
-      colorAttachments: [{
-        attachment: attachmentTexture.createView(),
-        loadValue: {
-          r: 1.0,
-          g: 0.0,
-          b: 0.0,
-          a: 1.0
-        }
-      }]
+      colorAttachments: [
+        {
+          attachment: attachmentTexture.createView(),
+          loadValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+        },
+      ],
     });
   }
-
 }
 
 export const g = makeTestGroup(F);
+
 g.test('basic_use_of_setBlendColor').fn(t => {
   const commandEncoder = t.device.createCommandEncoder();
   const renderPass = t.beginRenderPass(commandEncoder);
-  renderPass.setBlendColor({
-    r: 0,
-    g: 0,
-    b: 0,
-    a: 0
-  });
+  renderPass.setBlendColor({ r: 0, g: 0, b: 0, a: 0 });
   renderPass.endPass();
   commandEncoder.finish();
 });
+
 g.test('setBlendColor_allows_any_number_value').fn(t => {
   const values = [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER];
-
   for (const value of values) {
     const commandEncoder = t.device.createCommandEncoder();
     const renderPass = t.beginRenderPass(commandEncoder);
-    renderPass.setBlendColor({
-      r: value,
-      g: value,
-      b: value,
-      a: value
-    });
+    renderPass.setBlendColor({ r: value, g: value, b: value, a: value });
     renderPass.endPass();
     commandEncoder.finish();
   }
 });
-//# sourceMappingURL=setBlendColor.spec.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/setScissorRect.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/setScissorRect.spec.js
index 8381865..7934c00 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/setScissorRect.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/setScissorRect.spec.js
@@ -1,91 +1,54 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = `
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `
 setScissorRect validation tests.
 `;
 import { makeTestGroup } from '../../../common/framework/test_group.js';
-import { ValidationTest } from './validation_test.js';
-const TEXTURE_WIDTH = 16;
-const TEXTURE_HEIGHT = 16; // TODO: Move this fixture class to a common file.
 
+import { ValidationTest } from './validation_test.js';
+
+const TEXTURE_WIDTH = 16;
+const TEXTURE_HEIGHT = 16;
+
+// TODO: Move this fixture class to a common file.
 class F extends ValidationTest {
   beginRenderPass(commandEncoder) {
     const attachmentTexture = this.device.createTexture({
       format: 'rgba8unorm',
-      size: {
-        width: TEXTURE_WIDTH,
-        height: TEXTURE_HEIGHT,
-        depth: 1
-      },
-      usage: GPUTextureUsage.OUTPUT_ATTACHMENT
+      size: { width: TEXTURE_WIDTH, height: TEXTURE_HEIGHT, depth: 1 },
+      usage: GPUTextureUsage.OUTPUT_ATTACHMENT,
     });
+
     return commandEncoder.beginRenderPass({
-      colorAttachments: [{
-        attachment: attachmentTexture.createView(),
-        loadValue: {
-          r: 1.0,
-          g: 0.0,
-          b: 0.0,
-          a: 1.0
-        }
-      }]
+      colorAttachments: [
+        {
+          attachment: attachmentTexture.createView(),
+          loadValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+        },
+      ],
     });
   }
-
 }
 
 export const g = makeTestGroup(F);
-g.test('use_of_setScissorRect').params([{
-  x: 0,
-  y: 0,
-  width: 1,
-  height: 1,
-  _success: true
-}, // Basic use
-{
-  x: 0,
-  y: 0,
-  width: 0,
-  height: 1,
-  _success: false
-}, // Width of zero is not allowed
-{
-  x: 0,
-  y: 0,
-  width: 1,
-  height: 0,
-  _success: false
-}, // Height of zero is not allowed
-{
-  x: 0,
-  y: 0,
-  width: 0,
-  height: 0,
-  _success: false
-}, // Both width and height of zero are not allowed
-{
-  x: 0,
-  y: 0,
-  width: TEXTURE_WIDTH + 1,
-  height: TEXTURE_HEIGHT + 1,
-  _success: true
-} // Scissor larger than the framebuffer is allowed
-]).fn(async t => {
-  const {
-    x,
-    y,
-    width,
-    height,
-    _success
-  } = t.params;
-  const commandEncoder = t.device.createCommandEncoder();
-  const renderPass = t.beginRenderPass(commandEncoder);
-  renderPass.setScissorRect(x, y, width, height);
-  renderPass.endPass();
-  t.expectValidationError(() => {
-    commandEncoder.finish();
-  }, !_success);
-});
-//# sourceMappingURL=setScissorRect.spec.js.map
\ No newline at end of file
+
+g.test('use_of_setScissorRect')
+  .params([
+    { x: 0, y: 0, width: 1, height: 1, _success: true }, // Basic use
+    { x: 0, y: 0, width: 0, height: 1, _success: false }, // Width of zero is not allowed
+    { x: 0, y: 0, width: 1, height: 0, _success: false }, // Height of zero is not allowed
+    { x: 0, y: 0, width: 0, height: 0, _success: false }, // Both width and height of zero are not allowed
+    { x: 0, y: 0, width: TEXTURE_WIDTH + 1, height: TEXTURE_HEIGHT + 1, _success: true }, // Scissor larger than the framebuffer is allowed
+  ])
+  .fn(async t => {
+    const { x, y, width, height, _success } = t.params;
+
+    const commandEncoder = t.device.createCommandEncoder();
+    const renderPass = t.beginRenderPass(commandEncoder);
+    renderPass.setScissorRect(x, y, width, height);
+    renderPass.endPass();
+
+    t.expectValidationError(() => {
+      commandEncoder.finish();
+    }, !_success);
+  });
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/setStencilReference.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/setStencilReference.spec.js
index f205239..f79a24044 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/setStencilReference.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/setStencilReference.spec.js
@@ -1,49 +1,43 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = `
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `
 setStencilReference validation tests.
 `;
 import { poptions } from '../../../common/framework/params_builder.js';
 import { makeTestGroup } from '../../../common/framework/test_group.js';
-import { ValidationTest } from './validation_test.js'; // TODO: Move this fixture class to a common file.
 
+import { ValidationTest } from './validation_test.js';
+
+// TODO: Move this fixture class to a common file.
 class F extends ValidationTest {
   beginRenderPass(commandEncoder) {
     const attachmentTexture = this.device.createTexture({
       format: 'rgba8unorm',
-      size: {
-        width: 16,
-        height: 16,
-        depth: 1
-      },
-      usage: GPUTextureUsage.OUTPUT_ATTACHMENT
+      size: { width: 16, height: 16, depth: 1 },
+      usage: GPUTextureUsage.OUTPUT_ATTACHMENT,
     });
+
     return commandEncoder.beginRenderPass({
-      colorAttachments: [{
-        attachment: attachmentTexture.createView(),
-        loadValue: {
-          r: 1.0,
-          g: 0.0,
-          b: 0.0,
-          a: 1.0
-        }
-      }]
+      colorAttachments: [
+        {
+          attachment: attachmentTexture.createView(),
+          loadValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+        },
+      ],
     });
   }
-
 }
 
 export const g = makeTestGroup(F);
-g.test('use_of_setStencilReference').params(poptions('reference', [0, 0xffffffff])).fn(t => {
-  const {
-    reference
-  } = t.params;
-  const commandEncoder = t.device.createCommandEncoder();
-  const renderPass = t.beginRenderPass(commandEncoder);
-  renderPass.setStencilReference(reference);
-  renderPass.endPass();
-  commandEncoder.finish();
-});
-//# sourceMappingURL=setStencilReference.spec.js.map
\ No newline at end of file
+
+g.test('use_of_setStencilReference')
+  .params(poptions('reference', [0, 0xffffffff]))
+  .fn(t => {
+    const { reference } = t.params;
+
+    const commandEncoder = t.device.createCommandEncoder();
+    const renderPass = t.beginRenderPass(commandEncoder);
+    renderPass.setStencilReference(reference);
+    renderPass.endPass();
+    commandEncoder.finish();
+  });
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/setVertexBuffer.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/setVertexBuffer.spec.js
index bbb45eb..a7a1739 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/setVertexBuffer.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/setVertexBuffer.spec.js
@@ -1,19 +1,18 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = `
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `
 setVertexBuffer validation tests.
 `;
 import { makeTestGroup } from '../../../common/framework/test_group.js';
 import { range } from '../../../common/framework/util/util.js';
+
 import { ValidationTest } from './validation_test.js';
 
 class F extends ValidationTest {
   getVertexBuffer() {
     return this.device.createBuffer({
       size: 256,
-      usage: GPUBufferUsage.VERTEX
+      usage: GPUBufferUsage.VERTEX,
     });
   }
 
@@ -23,20 +22,21 @@
       fragmentStage: this.getFragmentStage(),
       layout: this.getPipelineLayout(),
       primitiveTopology: 'triangle-list',
-      colorStates: [{
-        format: 'rgba8unorm'
-      }],
+      colorStates: [{ format: 'rgba8unorm' }],
       vertexState: {
-        vertexBuffers: [{
-          arrayStride: 3 * 4,
-          attributes: range(bufferCount, i => ({
-            format: 'float3',
-            offset: 0,
-            shaderLocation: i
-          }))
-        }]
-      }
+        vertexBuffers: [
+          {
+            arrayStride: 3 * 4,
+            attributes: range(bufferCount, i => ({
+              format: 'float3',
+              offset: 0,
+              shaderLocation: i,
+            })),
+          },
+        ],
+      },
     };
+
     return this.device.createRenderPipeline(descriptor);
   }
 
@@ -49,10 +49,8 @@
        }
     `;
     return {
-      module: this.makeShaderModule('vertex', {
-        glsl
-      }),
-      entryPoint: 'main'
+      module: this.makeShaderModule('vertex', { glsl }),
+      entryPoint: 'main',
     };
   }
 
@@ -65,50 +63,42 @@
       }
     `;
     return {
-      module: this.makeShaderModule('fragment', {
-        glsl
-      }),
-      entryPoint: 'main'
+      module: this.makeShaderModule('fragment', { glsl }),
+      entryPoint: 'main',
     };
   }
 
   getPipelineLayout() {
-    return this.device.createPipelineLayout({
-      bindGroupLayouts: []
-    });
+    return this.device.createPipelineLayout({ bindGroupLayouts: [] });
   }
 
   beginRenderPass(commandEncoder) {
     const attachmentTexture = this.device.createTexture({
       format: 'rgba8unorm',
-      size: {
-        width: 16,
-        height: 16,
-        depth: 1
-      },
-      usage: GPUTextureUsage.OUTPUT_ATTACHMENT
+      size: { width: 16, height: 16, depth: 1 },
+      usage: GPUTextureUsage.OUTPUT_ATTACHMENT,
     });
+
     return commandEncoder.beginRenderPass({
-      colorAttachments: [{
-        attachment: attachmentTexture.createView(),
-        loadValue: {
-          r: 1.0,
-          g: 0.0,
-          b: 0.0,
-          a: 1.0
-        }
-      }]
+      colorAttachments: [
+        {
+          attachment: attachmentTexture.createView(),
+          loadValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+        },
+      ],
     });
   }
-
 }
 
 export const g = makeTestGroup(F);
+
 g.test('vertex_buffers_inherit_from_previous_pipeline').fn(async t => {
   const pipeline1 = t.createRenderPipeline(1);
   const pipeline2 = t.createRenderPipeline(2);
+
   const vertexBuffer1 = t.getVertexBuffer();
   const vertexBuffer2 = t.getVertexBuffer();
+
   {
     // Check failure when vertex buffer is not set
     const commandEncoder = t.device.createCommandEncoder();
@@ -116,6 +106,7 @@
     renderPass.setPipeline(pipeline1);
     renderPass.draw(3, 1, 0, 0);
     renderPass.endPass();
+
     t.expectValidationError(() => {
       commandEncoder.finish();
     });
@@ -131,14 +122,18 @@
     renderPass.setPipeline(pipeline1);
     renderPass.draw(3, 1, 0, 0);
     renderPass.endPass();
+
     commandEncoder.finish();
   }
 });
+
 g.test('vertex_buffers_do_not_inherit_between_render_passes').fn(async t => {
   const pipeline1 = t.createRenderPipeline(1);
   const pipeline2 = t.createRenderPipeline(2);
+
   const vertexBuffer1 = t.getVertexBuffer();
   const vertexBuffer2 = t.getVertexBuffer();
+
   {
     // Check success when vertex buffer is set for each render pass
     const commandEncoder = t.device.createCommandEncoder();
@@ -176,9 +171,9 @@
       renderPass.draw(3, 1, 0, 0);
       renderPass.endPass();
     }
+
     t.expectValidationError(() => {
       commandEncoder.finish();
     });
   }
 });
-//# sourceMappingURL=setVertexBuffer.spec.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/setViewport.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/setViewport.spec.js
index f1168c11..e2ef691 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/setViewport.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/setViewport.spec.js
@@ -1,193 +1,73 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = `
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `
 setViewport validation tests.
 `;
 import { makeTestGroup } from '../../../common/framework/test_group.js';
-import { ValidationTest } from './validation_test.js';
-const TEXTURE_WIDTH = 16;
-const TEXTURE_HEIGHT = 16; // TODO: Move this fixture class to a common file.
 
+import { ValidationTest } from './validation_test.js';
+
+const TEXTURE_WIDTH = 16;
+const TEXTURE_HEIGHT = 16;
+
+// TODO: Move this fixture class to a common file.
 class F extends ValidationTest {
   beginRenderPass(commandEncoder) {
     const attachmentTexture = this.device.createTexture({
       format: 'rgba8unorm',
-      size: {
-        width: TEXTURE_WIDTH,
-        height: TEXTURE_HEIGHT,
-        depth: 1
-      },
-      usage: GPUTextureUsage.OUTPUT_ATTACHMENT
+      size: { width: TEXTURE_WIDTH, height: TEXTURE_HEIGHT, depth: 1 },
+      usage: GPUTextureUsage.OUTPUT_ATTACHMENT,
     });
+
     return commandEncoder.beginRenderPass({
-      colorAttachments: [{
-        attachment: attachmentTexture.createView(),
-        loadValue: {
-          r: 1.0,
-          g: 0.0,
-          b: 0.0,
-          a: 1.0
-        }
-      }]
+      colorAttachments: [
+        {
+          attachment: attachmentTexture.createView(),
+          loadValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+        },
+      ],
     });
   }
-
 }
 
 export const g = makeTestGroup(F);
-g.test('use_of_setViewport').params([{
-  x: 0,
-  y: 0,
-  width: 1,
-  height: 1,
-  minDepth: 0,
-  maxDepth: 1,
-  _success: true
-}, // Basic use
-{
-  x: 0,
-  y: 0,
-  width: 0,
-  height: 1,
-  minDepth: 0,
-  maxDepth: 1,
-  _success: false
-}, // Width of zero is not allowed
-{
-  x: 0,
-  y: 0,
-  width: 1,
-  height: 0,
-  minDepth: 0,
-  maxDepth: 1,
-  _success: false
-}, // Height of zero is not allowed
-{
-  x: 0,
-  y: 0,
-  width: 0,
-  height: 0,
-  minDepth: 0,
-  maxDepth: 1,
-  _success: false
-}, // Both width and height of zero are not allowed
-{
-  x: -1,
-  y: 0,
-  width: 1,
-  height: 1,
-  minDepth: 0,
-  maxDepth: 1,
-  _success: true
-}, // Negative x is allowed
-{
-  x: 0,
-  y: -1,
-  width: 1,
-  height: 1,
-  minDepth: 0,
-  maxDepth: 1,
-  _success: true
-}, // Negative y is allowed
-{
-  x: 0,
-  y: 0,
-  width: -1,
-  height: 1,
-  minDepth: 0,
-  maxDepth: 1,
-  _success: false
-}, // Negative width is not allowed
-{
-  x: 0,
-  y: 0,
-  width: 1,
-  height: -1,
-  minDepth: 0,
-  maxDepth: 1,
-  _success: false
-}, // Negative height is not allowed
-{
-  x: 0,
-  y: 0,
-  width: 1,
-  height: 1,
-  minDepth: -1,
-  maxDepth: 1,
-  _success: false
-}, // Negative minDepth is not allowed
-{
-  x: 0,
-  y: 0,
-  width: 1,
-  height: 1,
-  minDepth: 0,
-  maxDepth: -1,
-  _success: false
-}, // Negative maxDepth is not allowed
-{
-  x: 0,
-  y: 0,
-  width: 1,
-  height: 1,
-  minDepth: 10,
-  maxDepth: 1,
-  _success: false
-}, // minDepth greater than 1 is not allowed
-{
-  x: 0,
-  y: 0,
-  width: 1,
-  height: 1,
-  minDepth: 0,
-  maxDepth: 10,
-  _success: false
-}, // maxDepth greater than 1 is not allowed
-{
-  x: 0,
-  y: 0,
-  width: 1,
-  height: 1,
-  minDepth: 0.5,
-  maxDepth: 0.5,
-  _success: true
-}, // minDepth equal to maxDepth is allowed
-{
-  x: 0,
-  y: 0,
-  width: 1,
-  height: 1,
-  minDepth: 0.8,
-  maxDepth: 0.5,
-  _success: true
-}, // minDepth greater than maxDepth is allowed
-{
-  x: 0,
-  y: 0,
-  width: TEXTURE_WIDTH + 1,
-  height: TEXTURE_HEIGHT + 1,
-  minDepth: 0,
-  maxDepth: 1,
-  _success: true
-} // Viewport larger than the framebuffer is allowed
-]).fn(async t => {
-  const {
-    x,
-    y,
-    width,
-    height,
-    minDepth,
-    maxDepth,
-    _success
-  } = t.params;
-  const commandEncoder = t.device.createCommandEncoder();
-  const renderPass = t.beginRenderPass(commandEncoder);
-  renderPass.setViewport(x, y, width, height, minDepth, maxDepth);
-  renderPass.endPass();
-  t.expectValidationError(() => {
-    commandEncoder.finish();
-  }, !_success);
-});
-//# sourceMappingURL=setViewport.spec.js.map
\ No newline at end of file
+
+g.test('use_of_setViewport')
+  .params([
+    { x: 0, y: 0, width: 1, height: 1, minDepth: 0, maxDepth: 1, _success: true }, // Basic use
+    { x: 0, y: 0, width: 0, height: 1, minDepth: 0, maxDepth: 1, _success: false }, // Width of zero is not allowed
+    { x: 0, y: 0, width: 1, height: 0, minDepth: 0, maxDepth: 1, _success: false }, // Height of zero is not allowed
+    { x: 0, y: 0, width: 0, height: 0, minDepth: 0, maxDepth: 1, _success: false }, // Both width and height of zero are not allowed
+    { x: -1, y: 0, width: 1, height: 1, minDepth: 0, maxDepth: 1, _success: true }, // Negative x is allowed
+    { x: 0, y: -1, width: 1, height: 1, minDepth: 0, maxDepth: 1, _success: true }, // Negative y is allowed
+    { x: 0, y: 0, width: -1, height: 1, minDepth: 0, maxDepth: 1, _success: false }, // Negative width is not allowed
+    { x: 0, y: 0, width: 1, height: -1, minDepth: 0, maxDepth: 1, _success: false }, // Negative height is not allowed
+    { x: 0, y: 0, width: 1, height: 1, minDepth: -1, maxDepth: 1, _success: false }, // Negative minDepth is not allowed
+    { x: 0, y: 0, width: 1, height: 1, minDepth: 0, maxDepth: -1, _success: false }, // Negative maxDepth is not allowed
+    { x: 0, y: 0, width: 1, height: 1, minDepth: 10, maxDepth: 1, _success: false }, // minDepth greater than 1 is not allowed
+    { x: 0, y: 0, width: 1, height: 1, minDepth: 0, maxDepth: 10, _success: false }, // maxDepth greater than 1 is not allowed
+    { x: 0, y: 0, width: 1, height: 1, minDepth: 0.5, maxDepth: 0.5, _success: true }, // minDepth equal to maxDepth is allowed
+    { x: 0, y: 0, width: 1, height: 1, minDepth: 0.8, maxDepth: 0.5, _success: true }, // minDepth greater than maxDepth is allowed
+    {
+      x: 0,
+      y: 0,
+      width: TEXTURE_WIDTH + 1,
+      height: TEXTURE_HEIGHT + 1,
+      minDepth: 0,
+      maxDepth: 1,
+      _success: true,
+    },
+    // Viewport larger than the framebuffer is allowed
+  ])
+  .fn(async t => {
+    const { x, y, width, height, minDepth, maxDepth, _success } = t.params;
+
+    const commandEncoder = t.device.createCommandEncoder();
+    const renderPass = t.beginRenderPass(commandEncoder);
+    renderPass.setViewport(x, y, width, height, minDepth, maxDepth);
+    renderPass.endPass();
+
+    t.expectValidationError(() => {
+      commandEncoder.finish();
+    }, !_success);
+  });
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/validation_test.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/validation_test.js
index 17eb97b..c6cd147 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/validation_test.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/validation_test.js
@@ -1,30 +1,22 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-import { unreachable } from '../../../common/framework/util/util.js';
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ import { unreachable } from '../../../common/framework/util/util.js';
 import { GPUTest } from '../../gpu_test.js';
+
 export class ValidationTest extends GPUTest {
   getStorageBuffer() {
-    return this.device.createBuffer({
-      size: 1024,
-      usage: GPUBufferUsage.STORAGE
-    });
+    return this.device.createBuffer({ size: 1024, usage: GPUBufferUsage.STORAGE });
   }
 
   getUniformBuffer() {
-    return this.device.createBuffer({
-      size: 1024,
-      usage: GPUBufferUsage.UNIFORM
-    });
+    return this.device.createBuffer({ size: 1024, usage: GPUBufferUsage.UNIFORM });
   }
 
   getErrorBuffer() {
     this.device.pushErrorScope('validation');
     const errorBuffer = this.device.createBuffer({
       size: 1024,
-      usage: 0xffff // Invalid GPUBufferUsage
-
+      usage: 0xffff, // Invalid GPUBufferUsage
     });
     this.device.popErrorScope();
     return errorBuffer;
@@ -35,55 +27,47 @@
   }
 
   getComparisonSampler() {
-    return this.device.createSampler({
-      compare: 'never'
-    });
+    return this.device.createSampler({ compare: 'never' });
   }
 
   getErrorSampler() {
     this.device.pushErrorScope('validation');
-    const sampler = this.device.createSampler({
-      lodMinClamp: -1
-    });
+    const sampler = this.device.createSampler({ lodMinClamp: -1 });
     this.device.popErrorScope();
     return sampler;
   }
 
   getSampledTexture() {
     return this.device.createTexture({
-      size: {
-        width: 16,
-        height: 16,
-        depth: 1
-      },
+      size: { width: 16, height: 16, depth: 1 },
       format: 'rgba8unorm',
-      usage: GPUTextureUsage.SAMPLED
+      usage: GPUTextureUsage.SAMPLED,
     });
   }
 
   getStorageTexture() {
     return this.device.createTexture({
-      size: {
-        width: 16,
-        height: 16,
-        depth: 1
-      },
+      size: { width: 16, height: 16, depth: 1 },
       format: 'rgba8unorm',
-      usage: GPUTextureUsage.STORAGE
+      usage: GPUTextureUsage.STORAGE,
     });
   }
 
+  getErrorTexture() {
+    this.device.pushErrorScope('validation');
+    const texture = this.device.createTexture({
+      size: { width: 0, height: 0, depth: 0 },
+      format: 'rgba8unorm',
+      usage: GPUTextureUsage.SAMPLED,
+    });
+
+    this.device.popErrorScope();
+    return texture;
+  }
+
   getErrorTextureView() {
     this.device.pushErrorScope('validation');
-    const view = this.device.createTexture({
-      size: {
-        width: 0,
-        height: 0,
-        depth: 0
-      },
-      format: 'rgba8unorm',
-      usage: GPUTextureUsage.SAMPLED
-    }).createView();
+    const view = this.getErrorTexture().createView();
     this.device.popErrorScope();
     return view;
   }
@@ -91,38 +75,23 @@
   getBindingResource(bindingType) {
     switch (bindingType) {
       case 'errorBuf':
-        return {
-          buffer: this.getErrorBuffer()
-        };
-
+        return { buffer: this.getErrorBuffer() };
       case 'errorSamp':
         return this.getErrorSampler();
-
       case 'errorTex':
         return this.getErrorTextureView();
-
       case 'uniformBuf':
-        return {
-          buffer: this.getUniformBuffer()
-        };
-
+        return { buffer: this.getUniformBuffer() };
       case 'storageBuf':
-        return {
-          buffer: this.getStorageBuffer()
-        };
-
+        return { buffer: this.getStorageBuffer() };
       case 'plainSamp':
         return this.getSampler();
-
       case 'compareSamp':
         return this.getComparisonSampler();
-
       case 'sampledTex':
         return this.getSampledTexture().createView();
-
       case 'storageTex':
         return this.getStorageTexture().createView();
-
       default:
         unreachable('unknown binding resource type');
     }
@@ -138,9 +107,9 @@
     this.device.pushErrorScope('validation');
     fn();
     const promise = this.device.popErrorScope();
+
     this.eventualAsyncExpectation(async niceStack => {
       const gpuValidationError = await promise;
-
       if (!gpuValidationError) {
         niceStack.message = 'Validation error was expected.';
         this.rec.validationFailed(niceStack);
@@ -150,6 +119,4 @@
       }
     });
   }
-
 }
-//# sourceMappingURL=validation_test.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/vertex_state.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/vertex_state.spec.js
index 4a9ac9c..7a30e3a 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/vertex_state.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/api/validation/vertex_state.spec.js
@@ -1,23 +1,26 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = `
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `
 vertexState validation tests.
 `;
 import { makeTestGroup } from '../../../common/framework/test_group.js';
+
 import { ValidationTest } from './validation_test.js';
+
 const MAX_VERTEX_ATTRIBUTES = 16;
 const MAX_VERTEX_BUFFER_END = 2048;
 const MAX_VERTEX_BUFFER_ARRAY_STRIDE = 2048;
 const MAX_VERTEX_BUFFERS = 16;
+
 const SIZEOF_FLOAT = Float32Array.BYTES_PER_ELEMENT;
+
 const VERTEX_SHADER_CODE_WITH_NO_INPUT = `
   #version 450
   void main() {
     gl_Position = vec4(0.0);
   }
 `;
+
 const FRAGMENT_SHADER_CODE = `
   #version 450
   layout(location = 0) out vec4 fragColor;
@@ -34,108 +37,133 @@
   getDescriptor(vertexState, vertexShaderCode) {
     const descriptor = {
       vertexStage: {
-        module: this.makeShaderModule('vertex', {
-          glsl: vertexShaderCode
-        }),
-        entryPoint: 'main'
+        module: this.makeShaderModule('vertex', { glsl: vertexShaderCode }),
+        entryPoint: 'main',
       },
+
       fragmentStage: {
-        module: this.makeShaderModule('fragment', {
-          glsl: FRAGMENT_SHADER_CODE
-        }),
-        entryPoint: 'main'
+        module: this.makeShaderModule('fragment', { glsl: FRAGMENT_SHADER_CODE }),
+        entryPoint: 'main',
       },
-      layout: this.device.createPipelineLayout({
-        bindGroupLayouts: []
-      }),
+
+      layout: this.device.createPipelineLayout({ bindGroupLayouts: [] }),
       primitiveTopology: 'triangle-list',
-      colorStates: [{
-        format: 'rgba8unorm'
-      }],
-      vertexState
+      colorStates: [{ format: 'rgba8unorm' }],
+      vertexState,
     };
+
     return descriptor;
   }
-
 }
 
 export const g = makeTestGroup(F);
+
 g.test('an_empty_vertex_input_is_valid').fn(t => {
   const vertexState = {};
   const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
   t.device.createRenderPipeline(descriptor);
 });
+
 g.test('a_null_buffer_is_valid').fn(t => {
   {
     // One null buffer is OK
     const vertexState = {
-      vertexBuffers: [{
-        arrayStride: 0,
-        attributes: []
-      }]
+      vertexBuffers: [
+        {
+          arrayStride: 0,
+          attributes: [],
+        },
+      ],
     };
+
     const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
     t.device.createRenderPipeline(descriptor);
   }
   {
     //  One null buffer followed by a buffer is OK
     const vertexState = {
-      vertexBuffers: [{
-        arrayStride: 0,
-        attributes: []
-      }, {
-        arrayStride: 0,
-        attributes: [{
-          format: 'float',
-          offset: 0,
-          shaderLocation: 0
-        }]
-      }]
+      vertexBuffers: [
+        {
+          arrayStride: 0,
+          attributes: [],
+        },
+
+        {
+          arrayStride: 0,
+          attributes: [
+            {
+              format: 'float',
+              offset: 0,
+              shaderLocation: 0,
+            },
+          ],
+        },
+      ],
     };
+
     const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
     t.device.createRenderPipeline(descriptor);
   }
   {
     //  One null buffer sitting between buffers is OK
     const vertexState = {
-      vertexBuffers: [{
-        arrayStride: 0,
-        attributes: [{
-          format: 'float',
-          offset: 0,
-          shaderLocation: 0
-        }]
-      }, {
-        arrayStride: 0,
-        attributes: []
-      }, {
-        arrayStride: 0,
-        attributes: [{
-          format: 'float',
-          offset: 0,
-          shaderLocation: 1
-        }]
-      }]
+      vertexBuffers: [
+        {
+          arrayStride: 0,
+          attributes: [
+            {
+              format: 'float',
+              offset: 0,
+              shaderLocation: 0,
+            },
+          ],
+        },
+
+        {
+          arrayStride: 0,
+          attributes: [],
+        },
+
+        {
+          arrayStride: 0,
+          attributes: [
+            {
+              format: 'float',
+              offset: 0,
+              shaderLocation: 1,
+            },
+          ],
+        },
+      ],
     };
+
     const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
     t.device.createRenderPipeline(descriptor);
   }
 });
+
 g.test('pipeline_vertex_buffers_are_backed_by_attributes_in_vertex_input').fn(async t => {
   const vertexState = {
-    vertexBuffers: [{
-      arrayStride: 2 * SIZEOF_FLOAT,
-      attributes: [{
-        format: 'float',
-        offset: 0,
-        shaderLocation: 0
-      }, {
-        format: 'float',
-        offset: 0,
-        shaderLocation: 1
-      }]
-    }]
+    vertexBuffers: [
+      {
+        arrayStride: 2 * SIZEOF_FLOAT,
+        attributes: [
+          {
+            format: 'float',
+            offset: 0,
+            shaderLocation: 0,
+          },
+
+          {
+            format: 'float',
+            offset: 0,
+            shaderLocation: 1,
+          },
+        ],
+      },
+    ],
   };
+
   {
     // Control case: pipeline with one input per attribute
     const code = `
@@ -171,22 +199,29 @@
       }
     `;
     const descriptor = t.getDescriptor(vertexState, code);
+
     t.expectValidationError(() => {
       t.device.createRenderPipeline(descriptor);
     });
   }
 });
+
 g.test('an_arrayStride_of_0_is_valid').fn(t => {
   const vertexState = {
-    vertexBuffers: [{
-      arrayStride: 0,
-      attributes: [{
-        format: 'float',
-        offset: 0,
-        shaderLocation: 0
-      }]
-    }]
+    vertexBuffers: [
+      {
+        arrayStride: 0,
+        attributes: [
+          {
+            format: 'float',
+            offset: 0,
+            shaderLocation: 0,
+          },
+        ],
+      },
+    ],
   };
+
   {
     // Works ok without attributes
     const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
@@ -199,68 +234,88 @@
     t.device.createRenderPipeline(descriptor);
   }
 });
-g.test('offset_should_be_within_vertex_buffer_arrayStride_if_arrayStride_is_not_zero').fn(async t => {
-  const vertexState = {
-    vertexBuffers: [{
-      arrayStride: 2 * SIZEOF_FLOAT,
-      attributes: [{
-        format: 'float',
-        offset: 0,
-        shaderLocation: 0
-      }, {
-        format: 'float',
-        offset: SIZEOF_FLOAT,
-        shaderLocation: 1
-      }]
-    }]
-  };
-  {
-    // Control case, setting correct arrayStride and offset
-    const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.device.createRenderPipeline(descriptor);
-  }
-  {
-    // Test vertex attribute offset exceed vertex buffer arrayStride range
-    const badVertexState = clone(vertexState);
-    badVertexState.vertexBuffers[0].attributes[1].format = 'float2';
-    const descriptor = t.getDescriptor(badVertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.expectValidationError(() => {
-      t.device.createRenderPipeline(descriptor);
-    });
-  }
-  {
-    // Test vertex attribute offset exceed vertex buffer arrayStride range
-    const badVertexState = clone(vertexState);
-    badVertexState.vertexBuffers[0].arrayStride = SIZEOF_FLOAT;
-    const descriptor = t.getDescriptor(badVertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.expectValidationError(() => {
-      t.device.createRenderPipeline(descriptor);
-    });
-  }
-  {
-    // It's OK if arrayStride is zero
-    const goodVertexState = clone(vertexState);
-    goodVertexState.vertexBuffers[0].arrayStride = 0;
-    const descriptor = t.getDescriptor(goodVertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.device.createRenderPipeline(descriptor);
-  }
-}); // TODO: This should be made into an operation test.
 
+g.test('offset_should_be_within_vertex_buffer_arrayStride_if_arrayStride_is_not_zero').fn(
+  async t => {
+    const vertexState = {
+      vertexBuffers: [
+        {
+          arrayStride: 2 * SIZEOF_FLOAT,
+          attributes: [
+            {
+              format: 'float',
+              offset: 0,
+              shaderLocation: 0,
+            },
+
+            {
+              format: 'float',
+              offset: SIZEOF_FLOAT,
+              shaderLocation: 1,
+            },
+          ],
+        },
+      ],
+    };
+
+    {
+      // Control case, setting correct arrayStride and offset
+      const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
+      t.device.createRenderPipeline(descriptor);
+    }
+    {
+      // Test vertex attribute offset exceed vertex buffer arrayStride range
+      const badVertexState = clone(vertexState);
+      badVertexState.vertexBuffers[0].attributes[1].format = 'float2';
+      const descriptor = t.getDescriptor(badVertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
+
+      t.expectValidationError(() => {
+        t.device.createRenderPipeline(descriptor);
+      });
+    }
+    {
+      // Test vertex attribute offset exceed vertex buffer arrayStride range
+      const badVertexState = clone(vertexState);
+      badVertexState.vertexBuffers[0].arrayStride = SIZEOF_FLOAT;
+      const descriptor = t.getDescriptor(badVertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
+
+      t.expectValidationError(() => {
+        t.device.createRenderPipeline(descriptor);
+      });
+    }
+    {
+      // It's OK if arrayStride is zero
+      const goodVertexState = clone(vertexState);
+      goodVertexState.vertexBuffers[0].arrayStride = 0;
+      const descriptor = t.getDescriptor(goodVertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
+      t.device.createRenderPipeline(descriptor);
+    }
+  }
+);
+
+// TODO: This should be made into an operation test.
 g.test('check_two_attributes_overlapping').fn(async t => {
   const vertexState = {
-    vertexBuffers: [{
-      arrayStride: 2 * SIZEOF_FLOAT,
-      attributes: [{
-        format: 'float',
-        offset: 0,
-        shaderLocation: 0
-      }, {
-        format: 'float',
-        offset: SIZEOF_FLOAT,
-        shaderLocation: 1
-      }]
-    }]
+    vertexBuffers: [
+      {
+        arrayStride: 2 * SIZEOF_FLOAT,
+        attributes: [
+          {
+            format: 'float',
+            offset: 0,
+            shaderLocation: 0,
+          },
+
+          {
+            format: 'float',
+            offset: SIZEOF_FLOAT,
+            shaderLocation: 1,
+          },
+        ],
+      },
+    ],
   };
+
   {
     // Control case, setting correct arrayStride and offset
     const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
@@ -274,105 +329,118 @@
     t.device.createRenderPipeline(descriptor);
   }
 });
+
 g.test('check_out_of_bounds_condition_on_total_number_of_vertex_buffers').fn(async t => {
   const vertexBuffers = [];
 
   for (let i = 0; i < MAX_VERTEX_BUFFERS; i++) {
     vertexBuffers.push({
       arrayStride: 0,
-      attributes: [{
-        format: 'float',
-        offset: 0,
-        shaderLocation: i
-      }]
+      attributes: [
+        {
+          format: 'float',
+          offset: 0,
+          shaderLocation: i,
+        },
+      ],
     });
   }
-
   {
     // Control case, setting max vertex buffer number
-    const vertexState = {
-      vertexBuffers
-    };
+    const vertexState = { vertexBuffers };
     const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
     t.device.createRenderPipeline(descriptor);
   }
   {
     // Test vertex buffer number exceed the limit
     const vertexState = {
-      vertexBuffers: [...vertexBuffers, {
-        arrayStride: 0,
-        attributes: [{
-          format: 'float',
-          offset: 0,
-          shaderLocation: MAX_VERTEX_BUFFERS
-        }]
-      }]
+      vertexBuffers: [
+        ...vertexBuffers,
+        {
+          arrayStride: 0,
+          attributes: [
+            {
+              format: 'float',
+              offset: 0,
+              shaderLocation: MAX_VERTEX_BUFFERS,
+            },
+          ],
+        },
+      ],
     };
+
     const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
+
     t.expectValidationError(() => {
       t.device.createRenderPipeline(descriptor);
     });
   }
 });
-g.test('check_out_of_bounds_on_number_of_vertex_attributes_on_a_single_vertex_buffer').fn(async t => {
-  const vertexAttributes = [];
 
-  for (let i = 0; i < MAX_VERTEX_ATTRIBUTES; i++) {
-    vertexAttributes.push({
-      format: 'float',
-      offset: 0,
-      shaderLocation: i
-    });
-  }
+g.test('check_out_of_bounds_on_number_of_vertex_attributes_on_a_single_vertex_buffer').fn(
+  async t => {
+    const vertexAttributes = [];
 
-  {
-    // Control case, setting max vertex buffer number
-    const vertexState = {
-      vertexBuffers: [{
-        arrayStride: 0,
-        attributes: vertexAttributes
-      }]
-    };
-    const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.device.createRenderPipeline(descriptor);
-  }
-  {
-    // Test vertex attribute number exceed the limit
-    const vertexState = {
-      vertexBuffers: [{
-        arrayStride: 0,
-        attributes: [...vertexAttributes, {
-          format: 'float',
-          offset: 0,
-          shaderLocation: MAX_VERTEX_ATTRIBUTES
-        }]
-      }]
-    };
-    const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
-    t.expectValidationError(() => {
+    for (let i = 0; i < MAX_VERTEX_ATTRIBUTES; i++) {
+      vertexAttributes.push({
+        format: 'float',
+        offset: 0,
+        shaderLocation: i,
+      });
+    }
+    {
+      // Control case, setting max vertex buffer number
+      const vertexState = {
+        vertexBuffers: [
+          {
+            arrayStride: 0,
+            attributes: vertexAttributes,
+          },
+        ],
+      };
+
+      const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
       t.device.createRenderPipeline(descriptor);
-    });
+    }
+    {
+      // Test vertex attribute number exceed the limit
+      const vertexState = {
+        vertexBuffers: [
+          {
+            arrayStride: 0,
+            attributes: [
+              ...vertexAttributes,
+              {
+                format: 'float',
+                offset: 0,
+                shaderLocation: MAX_VERTEX_ATTRIBUTES,
+              },
+            ],
+          },
+        ],
+      };
+
+      const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
+
+      t.expectValidationError(() => {
+        t.device.createRenderPipeline(descriptor);
+      });
+    }
   }
-});
+);
+
 g.test('check_out_of_bounds_on_number_of_vertex_attributes_across_vertex_buffers').fn(async t => {
   const vertexBuffers = [];
-
   for (let i = 0; i < MAX_VERTEX_ATTRIBUTES; i++) {
     vertexBuffers.push({
       arrayStride: 0,
-      attributes: [{
-        format: 'float',
-        offset: 0,
-        shaderLocation: i
-      }]
+      attributes: [{ format: 'float', offset: 0, shaderLocation: i }],
     });
   }
 
   {
     // Control case, setting max vertex buffer number
-    const vertexState = {
-      vertexBuffers
-    };
+    const vertexState = { vertexBuffers };
     const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
     t.device.createRenderPipeline(descriptor);
   }
@@ -381,24 +449,23 @@
     vertexBuffers[MAX_VERTEX_ATTRIBUTES - 1].attributes.push({
       format: 'float',
       offset: 0,
-      shaderLocation: MAX_VERTEX_ATTRIBUTES
+      shaderLocation: MAX_VERTEX_ATTRIBUTES,
     });
-    const vertexState = {
-      vertexBuffers
-    };
+
+    const vertexState = { vertexBuffers };
     const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
+
     t.expectValidationError(() => {
       t.device.createRenderPipeline(descriptor);
     });
   }
 });
+
 g.test('check_out_of_bounds_condition_on_input_strides').fn(async t => {
   const vertexState = {
-    vertexBuffers: [{
-      arrayStride: MAX_VERTEX_BUFFER_ARRAY_STRIDE,
-      attributes: []
-    }]
+    vertexBuffers: [{ arrayStride: MAX_VERTEX_BUFFER_ARRAY_STRIDE, attributes: [] }],
   };
+
   {
     // Control case, setting max input arrayStride
     const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
@@ -408,22 +475,23 @@
     // Test input arrayStride OOB
     vertexState.vertexBuffers[0].arrayStride = MAX_VERTEX_BUFFER_ARRAY_STRIDE + 4;
     const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
+
     t.expectValidationError(() => {
       t.device.createRenderPipeline(descriptor);
     });
   }
 });
+
 g.test('check_multiple_of_4_bytes_constraint_on_input_arrayStride').fn(async t => {
   const vertexState = {
-    vertexBuffers: [{
-      arrayStride: 4,
-      attributes: [{
-        format: 'uchar2',
-        offset: 0,
-        shaderLocation: 0
-      }]
-    }]
+    vertexBuffers: [
+      {
+        arrayStride: 4,
+        attributes: [{ format: 'uchar2', offset: 0, shaderLocation: 0 }],
+      },
+    ],
   };
+
   {
     // Control case, setting input arrayStride 4 bytes
     const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
@@ -433,22 +501,23 @@
     // Test input arrayStride not multiple of 4 bytes
     vertexState.vertexBuffers[0].arrayStride = 2;
     const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
+
     t.expectValidationError(() => {
       t.device.createRenderPipeline(descriptor);
     });
   }
 });
+
 g.test('identical_duplicate_attributes_are_invalid').fn(async t => {
   const vertexState = {
-    vertexBuffers: [{
-      arrayStride: 0,
-      attributes: [{
-        format: 'float',
-        offset: 0,
-        shaderLocation: 0
-      }]
-    }]
+    vertexBuffers: [
+      {
+        arrayStride: 0,
+        attributes: [{ format: 'float', offset: 0, shaderLocation: 0 }],
+      },
+    ],
   };
+
   {
     // Control case, setting attribute 0
     const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
@@ -459,30 +528,31 @@
     vertexState.vertexBuffers[0].attributes.push({
       format: 'float',
       offset: 0,
-      shaderLocation: 0
+      shaderLocation: 0,
     });
+
     const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
+
     t.expectValidationError(() => {
       t.device.createRenderPipeline(descriptor);
     });
   }
 });
+
 g.test('we_cannot_set_same_shader_location').fn(async t => {
   {
     const vertexState = {
-      vertexBuffers: [{
-        arrayStride: 0,
-        attributes: [{
-          format: 'float',
-          offset: 0,
-          shaderLocation: 0
-        }, {
-          format: 'float',
-          offset: SIZEOF_FLOAT,
-          shaderLocation: 1
-        }]
-      }]
+      vertexBuffers: [
+        {
+          arrayStride: 0,
+          attributes: [
+            { format: 'float', offset: 0, shaderLocation: 0 },
+            { format: 'float', offset: SIZEOF_FLOAT, shaderLocation: 1 },
+          ],
+        },
+      ],
     };
+
     {
       // Control case, setting different shader locations in two attributes
       const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
@@ -492,6 +562,7 @@
       // Test same shader location in two attributes in the same buffer
       vertexState.vertexBuffers[0].attributes[1].shaderLocation = 0;
       const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
+
       t.expectValidationError(() => {
         t.device.createRenderPipeline(descriptor);
       });
@@ -499,40 +570,50 @@
   }
   {
     const vertexState = {
-      vertexBuffers: [{
-        arrayStride: 0,
-        attributes: [{
-          format: 'float',
-          offset: 0,
-          shaderLocation: 0
-        }]
-      }, {
-        arrayStride: 0,
-        attributes: [{
-          format: 'float',
-          offset: 0,
-          shaderLocation: 0
-        }]
-      }]
-    }; // Test same shader location in two attributes in different buffers
+      vertexBuffers: [
+        {
+          arrayStride: 0,
+          attributes: [
+            {
+              format: 'float',
+              offset: 0,
+              shaderLocation: 0,
+            },
+          ],
+        },
 
+        {
+          arrayStride: 0,
+          attributes: [
+            {
+              format: 'float',
+              offset: 0,
+              shaderLocation: 0,
+            },
+          ],
+        },
+      ],
+    };
+
+    // Test same shader location in two attributes in different buffers
     const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
+
     t.expectValidationError(() => {
       t.device.createRenderPipeline(descriptor);
     });
   }
 });
+
 g.test('check_out_of_bounds_condition_on_attribute_shader_location').fn(async t => {
   const vertexState = {
-    vertexBuffers: [{
-      arrayStride: 0,
-      attributes: [{
-        format: 'float',
-        offset: 0,
-        shaderLocation: MAX_VERTEX_ATTRIBUTES - 1
-      }]
-    }]
+    vertexBuffers: [
+      {
+        arrayStride: 0,
+        attributes: [{ format: 'float', offset: 0, shaderLocation: MAX_VERTEX_ATTRIBUTES - 1 }],
+      },
+    ],
   };
+
   {
     // Control case, setting last attribute shader location
     const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
@@ -542,22 +623,29 @@
     // Test attribute location OOB
     vertexState.vertexBuffers[0].attributes[0].shaderLocation = MAX_VERTEX_ATTRIBUTES;
     const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
+
     t.expectValidationError(() => {
       t.device.createRenderPipeline(descriptor);
     });
   }
 });
+
 g.test('check_attribute_offset_out_of_bounds').fn(async t => {
   const vertexState = {
-    vertexBuffers: [{
-      arrayStride: 0,
-      attributes: [{
-        format: 'float2',
-        offset: MAX_VERTEX_BUFFER_END - 2 * SIZEOF_FLOAT,
-        shaderLocation: 0
-      }]
-    }]
+    vertexBuffers: [
+      {
+        arrayStride: 0,
+        attributes: [
+          {
+            format: 'float2',
+            offset: MAX_VERTEX_BUFFER_END - 2 * SIZEOF_FLOAT,
+            shaderLocation: 0,
+          },
+        ],
+      },
+    ],
   };
+
   {
     // Control case, setting max attribute offset to MAX_VERTEX_BUFFER_END - 8
     const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
@@ -573,22 +661,23 @@
     // Test attribute offset out of bounds
     vertexState.vertexBuffers[0].attributes[0].offset = MAX_VERTEX_BUFFER_END - 4;
     const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
+
     t.expectValidationError(() => {
       t.device.createRenderPipeline(descriptor);
     });
   }
 });
+
 g.test('check_multiple_of_4_bytes_constraint_on_offset').fn(async t => {
   const vertexState = {
-    vertexBuffers: [{
-      arrayStride: 0,
-      attributes: [{
-        format: 'float',
-        offset: SIZEOF_FLOAT,
-        shaderLocation: 0
-      }]
-    }]
+    vertexBuffers: [
+      {
+        arrayStride: 0,
+        attributes: [{ format: 'float', offset: SIZEOF_FLOAT, shaderLocation: 0 }],
+      },
+    ],
   };
+
   {
     // Control case, setting offset 4 bytes
     const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
@@ -608,25 +697,26 @@
     vertexState.vertexBuffers[0].attributes[0].offset = 2;
     vertexState.vertexBuffers[0].attributes[0].format = 'float';
     const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
+
     t.expectValidationError(() => {
       t.device.createRenderPipeline(descriptor);
     });
   }
 });
+
 g.test('check_attribute_offset_overflow').fn(async t => {
   const vertexState = {
-    vertexBuffers: [{
-      arrayStride: 0,
-      attributes: [{
-        format: 'float',
-        offset: Number.MAX_SAFE_INTEGER,
-        shaderLocation: 0
-      }]
-    }]
+    vertexBuffers: [
+      {
+        arrayStride: 0,
+        attributes: [{ format: 'float', offset: Number.MAX_SAFE_INTEGER, shaderLocation: 0 }],
+      },
+    ],
   };
+
   const descriptor = t.getDescriptor(vertexState, VERTEX_SHADER_CODE_WITH_NO_INPUT);
+
   t.expectValidationError(() => {
     t.device.createRenderPipeline(descriptor);
   });
 });
-//# sourceMappingURL=vertex_state.spec.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/capability_info.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/capability_info.js
index b2d9353b..10a8c5a 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/capability_info.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/capability_info.js
@@ -1,8 +1,6 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-import * as C from '../common/constants.js';
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/
 
 function keysOf(obj) {
   return Object.keys(obj);
@@ -10,214 +8,246 @@
 
 function numericKeysOf(obj) {
   return Object.keys(obj).map(n => Number(n));
-} // Textures
+}
 
+// Buffers
 
-export const kTextureFormatInfo =
-/* prettier-ignore */
-{
-  // Try to keep these manually-formatted in a readable grid.
-  // (Note: this list should always match the one in the spec.)
+export const kBufferUsageInfo = {
+  [GPUBufferUsage.MAP_READ]: {},
+  [GPUBufferUsage.MAP_WRITE]: {},
+  [GPUBufferUsage.COPY_SRC]: {},
+  [GPUBufferUsage.COPY_DST]: {},
+  [GPUBufferUsage.INDEX]: {},
+  [GPUBufferUsage.VERTEX]: {},
+  [GPUBufferUsage.UNIFORM]: {},
+  [GPUBufferUsage.STORAGE]: {},
+  [GPUBufferUsage.INDIRECT]: {},
+  [GPUBufferUsage.QUERY_RESOLVE]: {},
+};
+
+export const kBufferUsages = numericKeysOf(kBufferUsageInfo);
+
+// Textures
+
+export const kRegularTextureFormatInfo = {
   // 8-bit formats
-  'r8unorm': {
+  r8unorm: {
     renderable: true,
     color: true,
     depth: false,
     stencil: false,
     storage: false,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 1,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
-  'r8snorm': {
+  r8snorm: {
     renderable: false,
     color: true,
     depth: false,
     stencil: false,
     storage: false,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 1,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
-  'r8uint': {
+  r8uint: {
     renderable: true,
     color: true,
     depth: false,
     stencil: false,
     storage: false,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 1,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
-  'r8sint': {
+  r8sint: {
     renderable: true,
     color: true,
     depth: false,
     stencil: false,
     storage: false,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 1,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
   // 16-bit formats
-  'r16uint': {
+  r16uint: {
     renderable: true,
     color: true,
     depth: false,
     stencil: false,
     storage: false,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 2,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
-  'r16sint': {
+  r16sint: {
     renderable: true,
     color: true,
     depth: false,
     stencil: false,
     storage: false,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 2,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
-  'r16float': {
+  r16float: {
     renderable: true,
     color: true,
     depth: false,
     stencil: false,
     storage: false,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 2,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
-  'rg8unorm': {
+  rg8unorm: {
     renderable: true,
     color: true,
     depth: false,
     stencil: false,
     storage: false,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 2,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
-  'rg8snorm': {
+  rg8snorm: {
     renderable: false,
     color: true,
     depth: false,
     stencil: false,
     storage: false,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 2,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
-  'rg8uint': {
+  rg8uint: {
     renderable: true,
     color: true,
     depth: false,
     stencil: false,
     storage: false,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 2,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
-  'rg8sint': {
+  rg8sint: {
     renderable: true,
     color: true,
     depth: false,
     stencil: false,
     storage: false,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 2,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
   // 32-bit formats
-  'r32uint': {
+  r32uint: {
     renderable: true,
     color: true,
     depth: false,
     stencil: false,
     storage: true,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 4,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
-  'r32sint': {
+  r32sint: {
     renderable: true,
     color: true,
     depth: false,
     stencil: false,
     storage: true,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 4,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
-  'r32float': {
+  r32float: {
     renderable: true,
     color: true,
     depth: false,
     stencil: false,
     storage: true,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 4,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
-  'rg16uint': {
+  rg16uint: {
     renderable: true,
     color: true,
     depth: false,
     stencil: false,
     storage: false,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 4,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
-  'rg16sint': {
+  rg16sint: {
     renderable: true,
     color: true,
     depth: false,
     stencil: false,
     storage: false,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 4,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
-  'rg16float': {
+  rg16float: {
     renderable: true,
     color: true,
     depth: false,
     stencil: false,
     storage: false,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 4,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
-  'rgba8unorm': {
+  rgba8unorm: {
     renderable: true,
     color: true,
     depth: false,
     stencil: false,
     storage: true,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 4,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
   'rgba8unorm-srgb': {
     renderable: true,
@@ -225,54 +255,59 @@
     depth: false,
     stencil: false,
     storage: false,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 4,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
-  'rgba8snorm': {
+  rgba8snorm: {
     renderable: false,
     color: true,
     depth: false,
     stencil: false,
     storage: true,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 4,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
-  'rgba8uint': {
+  rgba8uint: {
     renderable: true,
     color: true,
     depth: false,
     stencil: false,
     storage: true,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 4,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
-  'rgba8sint': {
+  rgba8sint: {
     renderable: true,
     color: true,
     depth: false,
     stencil: false,
     storage: true,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 4,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
-  'bgra8unorm': {
+  bgra8unorm: {
     renderable: true,
     color: true,
     depth: false,
     stencil: false,
     storage: false,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 4,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
   'bgra8unorm-srgb': {
     renderable: true,
@@ -280,154 +315,179 @@
     depth: false,
     stencil: false,
     storage: false,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 4,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
   // Packed 32-bit formats
-  'rgb10a2unorm': {
+  rgb10a2unorm: {
     renderable: true,
     color: true,
     depth: false,
     stencil: false,
     storage: false,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 4,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
-  'rg11b10float': {
+  rg11b10float: {
     renderable: false,
     color: true,
     depth: false,
     stencil: false,
     storage: false,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 4,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
   // 64-bit formats
-  'rg32uint': {
+  rg32uint: {
     renderable: true,
     color: true,
     depth: false,
     stencil: false,
     storage: true,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 8,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
-  'rg32sint': {
+  rg32sint: {
     renderable: true,
     color: true,
     depth: false,
     stencil: false,
     storage: true,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 8,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
-  'rg32float': {
+  rg32float: {
     renderable: true,
     color: true,
     depth: false,
     stencil: false,
     storage: true,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 8,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
-  'rgba16uint': {
+  rgba16uint: {
     renderable: true,
     color: true,
     depth: false,
     stencil: false,
     storage: true,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 8,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
-  'rgba16sint': {
+  rgba16sint: {
     renderable: true,
     color: true,
     depth: false,
     stencil: false,
     storage: true,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 8,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
-  'rgba16float': {
+  rgba16float: {
     renderable: true,
     color: true,
     depth: false,
     stencil: false,
     storage: true,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 8,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
   // 128-bit formats
-  'rgba32uint': {
+  rgba32uint: {
     renderable: true,
     color: true,
     depth: false,
     stencil: false,
     storage: true,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 16,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
-  'rgba32sint': {
+  rgba32sint: {
     renderable: true,
     color: true,
     depth: false,
     stencil: false,
     storage: true,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 16,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
-  'rgba32float': {
+  rgba32float: {
     renderable: true,
     color: true,
     depth: false,
     stencil: false,
     storage: true,
-    copyable: true,
+    copySrc: true,
+    copyDst: true,
     bytesPerBlock: 16,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
-  // Depth/stencil formats
-  'depth32float': {
+};
+
+export const kRegularTextureFormats = keysOf(kRegularTextureFormatInfo);
+
+export const kSizedDepthStencilFormatInfo = {
+  depth32float: {
     renderable: true,
     color: false,
     depth: true,
     stencil: false,
     storage: false,
-    copyable: true,
+    copySrc: true,
+    copyDst: false,
     bytesPerBlock: 4,
     blockWidth: 1,
-    blockHeight: 1
+    blockHeight: 1,
   },
-  'depth24plus': {
+};
+
+export const kSizedDepthStencilFormats = keysOf(kSizedDepthStencilFormatInfo);
+
+export const kUnsizedDepthStencilFormatInfo = {
+  depth24plus: {
     renderable: true,
     color: false,
     depth: true,
     stencil: false,
     storage: false,
-    copyable: false
+    copySrc: false,
+    copyDst: false,
+    blockWidth: 1,
+    blockHeight: 1,
   },
   'depth24plus-stencil8': {
     renderable: true,
@@ -435,88 +495,317 @@
     depth: true,
     stencil: true,
     storage: false,
-    copyable: false
-  }
+    copySrc: false,
+    copyDst: false,
+    blockWidth: 1,
+    blockHeight: 1,
+  },
 };
-export const kTextureFormats = keysOf(kTextureFormatInfo);
-export const kTextureDimensionInfo =
-/* prettier-ignore */
-{
+
+export const kUnsizedDepthStencilFormats = keysOf(kUnsizedDepthStencilFormatInfo);
+
+export const kCompressedTextureFormatInfo = {
+  // BC formats
+  'bc1-rgba-unorm': {
+    renderable: false,
+    color: true,
+    depth: false,
+    stencil: false,
+    storage: false,
+    copySrc: true,
+    copyDst: true,
+    bytesPerBlock: 8,
+    blockWidth: 4,
+    blockHeight: 4,
+    extension: 'texture-compression-bc',
+  },
+  'bc1-rgba-unorm-srgb': {
+    renderable: false,
+    color: true,
+    depth: false,
+    stencil: false,
+    storage: false,
+    copySrc: true,
+    copyDst: true,
+    bytesPerBlock: 8,
+    blockWidth: 4,
+    blockHeight: 4,
+    extension: 'texture-compression-bc',
+  },
+  'bc2-rgba-unorm': {
+    renderable: false,
+    color: true,
+    depth: false,
+    stencil: false,
+    storage: false,
+    copySrc: true,
+    copyDst: true,
+    bytesPerBlock: 16,
+    blockWidth: 4,
+    blockHeight: 4,
+    extension: 'texture-compression-bc',
+  },
+  'bc2-rgba-unorm-srgb': {
+    renderable: false,
+    color: true,
+    depth: false,
+    stencil: false,
+    storage: false,
+    copySrc: true,
+    copyDst: true,
+    bytesPerBlock: 16,
+    blockWidth: 4,
+    blockHeight: 4,
+    extension: 'texture-compression-bc',
+  },
+  'bc3-rgba-unorm': {
+    renderable: false,
+    color: true,
+    depth: false,
+    stencil: false,
+    storage: false,
+    copySrc: true,
+    copyDst: true,
+    bytesPerBlock: 16,
+    blockWidth: 4,
+    blockHeight: 4,
+    extension: 'texture-compression-bc',
+  },
+  'bc3-rgba-unorm-srgb': {
+    renderable: false,
+    color: true,
+    depth: false,
+    stencil: false,
+    storage: false,
+    copySrc: true,
+    copyDst: true,
+    bytesPerBlock: 16,
+    blockWidth: 4,
+    blockHeight: 4,
+    extension: 'texture-compression-bc',
+  },
+  'bc4-r-unorm': {
+    renderable: false,
+    color: true,
+    depth: false,
+    stencil: false,
+    storage: false,
+    copySrc: true,
+    copyDst: true,
+    bytesPerBlock: 8,
+    blockWidth: 4,
+    blockHeight: 4,
+    extension: 'texture-compression-bc',
+  },
+  'bc4-r-snorm': {
+    renderable: false,
+    color: true,
+    depth: false,
+    stencil: false,
+    storage: false,
+    copySrc: true,
+    copyDst: true,
+    bytesPerBlock: 8,
+    blockWidth: 4,
+    blockHeight: 4,
+    extension: 'texture-compression-bc',
+  },
+  'bc5-rg-unorm': {
+    renderable: false,
+    color: true,
+    depth: false,
+    stencil: false,
+    storage: false,
+    copySrc: true,
+    copyDst: true,
+    bytesPerBlock: 16,
+    blockWidth: 4,
+    blockHeight: 4,
+    extension: 'texture-compression-bc',
+  },
+  'bc5-rg-snorm': {
+    renderable: false,
+    color: true,
+    depth: false,
+    stencil: false,
+    storage: false,
+    copySrc: true,
+    copyDst: true,
+    bytesPerBlock: 16,
+    blockWidth: 4,
+    blockHeight: 4,
+    extension: 'texture-compression-bc',
+  },
+  'bc6h-rgb-ufloat': {
+    renderable: false,
+    color: true,
+    depth: false,
+    stencil: false,
+    storage: false,
+    copySrc: true,
+    copyDst: true,
+    bytesPerBlock: 16,
+    blockWidth: 4,
+    blockHeight: 4,
+    extension: 'texture-compression-bc',
+  },
+  'bc6h-rgb-sfloat': {
+    renderable: false,
+    color: true,
+    depth: false,
+    stencil: false,
+    storage: false,
+    copySrc: true,
+    copyDst: true,
+    bytesPerBlock: 16,
+    blockWidth: 4,
+    blockHeight: 4,
+    extension: 'texture-compression-bc',
+  },
+  'bc7-rgba-unorm': {
+    renderable: false,
+    color: true,
+    depth: false,
+    stencil: false,
+    storage: false,
+    copySrc: true,
+    copyDst: true,
+    bytesPerBlock: 16,
+    blockWidth: 4,
+    blockHeight: 4,
+    extension: 'texture-compression-bc',
+  },
+  'bc7-rgba-unorm-srgb': {
+    renderable: false,
+    color: true,
+    depth: false,
+    stencil: false,
+    storage: false,
+    copySrc: true,
+    copyDst: true,
+    bytesPerBlock: 16,
+    blockWidth: 4,
+    blockHeight: 4,
+    extension: 'texture-compression-bc',
+  },
+};
+
+export const kCompressedTextureFormats = keysOf(kCompressedTextureFormatInfo);
+
+export const kColorTextureFormatInfo = {
+  ...kRegularTextureFormatInfo,
+  ...kCompressedTextureFormatInfo,
+};
+
+export const kColorTextureFormats = keysOf(kColorTextureFormatInfo);
+
+export const kEncodableTextureFormatInfo = {
+  ...kRegularTextureFormatInfo,
+  ...kSizedDepthStencilFormatInfo,
+};
+
+export const kEncodableTextureFormats = keysOf(kEncodableTextureFormatInfo);
+
+export const kSizedTextureFormatInfo = {
+  ...kRegularTextureFormatInfo,
+  ...kSizedDepthStencilFormatInfo,
+  ...kCompressedTextureFormatInfo,
+};
+
+export const kSizedTextureFormats = keysOf(kSizedTextureFormatInfo);
+
+export const kDepthStencilFormatInfo = {
+  ...kSizedDepthStencilFormatInfo,
+  ...kUnsizedDepthStencilFormatInfo,
+};
+
+export const kDepthStencilFormats = keysOf(kDepthStencilFormatInfo);
+
+export const kUncompressedTextureFormatInfo = {
+  ...kRegularTextureFormatInfo,
+  ...kSizedDepthStencilFormatInfo,
+  ...kUnsizedDepthStencilFormatInfo,
+};
+
+export const kUncompressedTextureFormats = keysOf(kUncompressedTextureFormatInfo);
+
+export const kAllTextureFormatInfo = {
+  ...kUncompressedTextureFormatInfo,
+  ...kCompressedTextureFormatInfo,
+};
+
+export const kAllTextureFormats = keysOf(kAllTextureFormatInfo);
+
+export const kTextureDimensionInfo = {
   '1d': {},
   '2d': {},
-  '3d': {}
+  '3d': {},
 };
+
 export const kTextureDimensions = keysOf(kTextureDimensionInfo);
-export const kTextureAspectInfo =
-/* prettier-ignore */
-{
-  'all': {},
+
+export const kTextureAspectInfo = {
+  all: {},
   'depth-only': {},
-  'stencil-only': {}
+  'stencil-only': {},
 };
+
 export const kTextureAspects = keysOf(kTextureAspectInfo);
+
 export const kTextureUsageInfo = {
-  [C.TextureUsage.CopySrc]: {},
-  [C.TextureUsage.CopyDst]: {},
-  [C.TextureUsage.Sampled]: {},
-  [C.TextureUsage.Storage]: {},
-  [C.TextureUsage.OutputAttachment]: {}
+  [GPUTextureUsage.COPY_SRC]: {},
+  [GPUTextureUsage.COPY_DST]: {},
+  [GPUTextureUsage.SAMPLED]: {},
+  [GPUTextureUsage.STORAGE]: {},
+  [GPUTextureUsage.OUTPUT_ATTACHMENT]: {},
 };
-export const kTextureUsages = numericKeysOf(kTextureUsageInfo); // Typedefs for bindings
+
+export const kTextureUsages = numericKeysOf(kTextureUsageInfo);
+
+export const kTextureComponentTypeInfo = {
+  float: {},
+  sint: {},
+  uint: {},
+};
+
+export const kTextureComponentTypes = keysOf(kTextureComponentTypeInfo);
+
+// Texture View
+
+export const kTextureViewDimensionInfo = {
+  '1d': { storage: true },
+  '2d': { storage: true },
+  '2d-array': { storage: true },
+  cube: { storage: false },
+  'cube-array': { storage: false },
+  '3d': { storage: true },
+};
+
+export const kTextureViewDimensions = keysOf(kTextureViewDimensionInfo);
+
+// Typedefs for bindings
 
 // Bindings
+
 export const kMaxBindingsPerBindGroup = 16;
-export const kPerStageBindingLimits =
-/* prettier-ignore */
-{
-  'uniformBuf': {
-    class: 'uniformBuf',
-    max: 12
-  },
-  'storageBuf': {
-    class: 'storageBuf',
-    max: 4
-  },
-  'sampler': {
-    class: 'sampler',
-    max: 16
-  },
-  'sampledTex': {
-    class: 'sampledTex',
-    max: 16
-  },
-  'storageTex': {
-    class: 'storageTex',
-    max: 4
-  }
+
+export const kPerStageBindingLimits = {
+  uniformBuf: { class: 'uniformBuf', max: 12 },
+  storageBuf: { class: 'storageBuf', max: 4 },
+  sampler: { class: 'sampler', max: 16 },
+  sampledTex: { class: 'sampledTex', max: 16 },
+  storageTex: { class: 'storageTex', max: 4 },
 };
-export const kPerPipelineBindingLimits =
-/* prettier-ignore */
-{
-  'uniformBuf': {
-    class: 'uniformBuf',
-    maxDynamic: 8
-  },
-  'storageBuf': {
-    class: 'storageBuf',
-    maxDynamic: 4
-  },
-  'sampler': {
-    class: 'sampler',
-    maxDynamic: 0
-  },
-  'sampledTex': {
-    class: 'sampledTex',
-    maxDynamic: 0
-  },
-  'storageTex': {
-    class: 'storageTex',
-    maxDynamic: 0
-  }
+
+export const kPerPipelineBindingLimits = {
+  uniformBuf: { class: 'uniformBuf', maxDynamic: 8 },
+  storageBuf: { class: 'storageBuf', maxDynamic: 4 },
+  sampler: { class: 'sampler', maxDynamic: 0 },
+  sampledTex: { class: 'sampledTex', maxDynamic: 0 },
+  storageTex: { class: 'storageTex', maxDynamic: 0 },
 };
-const kBindableResource =
-/* prettier-ignore */
-{
+
+const kBindableResource = {
   uniformBuf: {},
   storageBuf: {},
   plainSamp: {},
@@ -525,107 +814,113 @@
   storageTex: {},
   errorBuf: {},
   errorSamp: {},
-  errorTex: {}
+  errorTex: {},
 };
+
 export const kBindableResources = keysOf(kBindableResource);
-const kBindingKind =
-/* prettier-ignore */
-{
+
+const kBindingKind = {
   uniformBuf: {
     resource: 'uniformBuf',
     perStageLimitClass: kPerStageBindingLimits.uniformBuf,
-    perPipelineLimitClass: kPerPipelineBindingLimits.uniformBuf
+    perPipelineLimitClass: kPerPipelineBindingLimits.uniformBuf,
   },
   storageBuf: {
     resource: 'storageBuf',
     perStageLimitClass: kPerStageBindingLimits.storageBuf,
-    perPipelineLimitClass: kPerPipelineBindingLimits.storageBuf
+    perPipelineLimitClass: kPerPipelineBindingLimits.storageBuf,
   },
   plainSamp: {
     resource: 'plainSamp',
     perStageLimitClass: kPerStageBindingLimits.sampler,
-    perPipelineLimitClass: kPerPipelineBindingLimits.sampler
+    perPipelineLimitClass: kPerPipelineBindingLimits.sampler,
   },
   compareSamp: {
     resource: 'compareSamp',
     perStageLimitClass: kPerStageBindingLimits.sampler,
-    perPipelineLimitClass: kPerPipelineBindingLimits.sampler
+    perPipelineLimitClass: kPerPipelineBindingLimits.sampler,
   },
   sampledTex: {
     resource: 'sampledTex',
     perStageLimitClass: kPerStageBindingLimits.sampledTex,
-    perPipelineLimitClass: kPerPipelineBindingLimits.sampledTex
+    perPipelineLimitClass: kPerPipelineBindingLimits.sampledTex,
   },
   storageTex: {
     resource: 'storageTex',
     perStageLimitClass: kPerStageBindingLimits.storageTex,
-    perPipelineLimitClass: kPerPipelineBindingLimits.storageTex
-  }
-}; // Binding type info
+    perPipelineLimitClass: kPerPipelineBindingLimits.storageTex,
+  },
+};
+
+// Binding type info
 
 const kValidStagesAll = {
-  validStages: C.ShaderStage.Vertex | C.ShaderStage.Fragment | C.ShaderStage.Compute
+  validStages: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT | GPUShaderStage.COMPUTE,
 };
-const kValidStagesStorageWrite = {
-  validStages: C.ShaderStage.Fragment | C.ShaderStage.Compute
-};
-export const kBufferBindingTypeInfo =
-/* prettier-ignore */
-{
+
+const kValidStagesStorageWrite = { validStages: GPUShaderStage.FRAGMENT | GPUShaderStage.COMPUTE };
+
+export const kBufferBindingTypeInfo = {
   'uniform-buffer': {
-    usage: C.BufferUsage.Uniform,
+    usage: GPUBufferUsage.UNIFORM,
     ...kBindingKind.uniformBuf,
-    ...kValidStagesAll
+    ...kValidStagesAll,
   },
   'storage-buffer': {
-    usage: C.BufferUsage.Storage,
+    usage: GPUBufferUsage.STORAGE,
     ...kBindingKind.storageBuf,
-    ...kValidStagesStorageWrite
+    ...kValidStagesStorageWrite,
   },
   'readonly-storage-buffer': {
-    usage: C.BufferUsage.Storage,
+    usage: GPUBufferUsage.STORAGE,
     ...kBindingKind.storageBuf,
-    ...kValidStagesAll
-  }
-};
-export const kBufferBindingTypes = keysOf(kBufferBindingTypeInfo);
-export const kSamplerBindingTypeInfo =
-/* prettier-ignore */
-{
-  'sampler': { ...kBindingKind.plainSamp,
-    ...kValidStagesAll
+    ...kValidStagesAll,
   },
-  'comparison-sampler': { ...kBindingKind.compareSamp,
-    ...kValidStagesAll
-  }
 };
+
+export const kBufferBindingTypes = keysOf(kBufferBindingTypeInfo);
+
+export const kSamplerBindingTypeInfo = {
+  sampler: { ...kBindingKind.plainSamp, ...kValidStagesAll },
+  'comparison-sampler': { ...kBindingKind.compareSamp, ...kValidStagesAll },
+};
+
 export const kSamplerBindingTypes = keysOf(kSamplerBindingTypeInfo);
-export const kTextureBindingTypeInfo =
-/* prettier-ignore */
-{
+
+export const kTextureBindingTypeInfo = {
   'sampled-texture': {
-    usage: C.TextureUsage.Sampled,
+    usage: GPUTextureUsage.SAMPLED,
     ...kBindingKind.sampledTex,
-    ...kValidStagesAll
+    ...kValidStagesAll,
   },
   'writeonly-storage-texture': {
-    usage: C.TextureUsage.Storage,
+    usage: GPUTextureUsage.STORAGE,
     ...kBindingKind.storageTex,
-    ...kValidStagesStorageWrite
+    ...kValidStagesStorageWrite,
   },
   'readonly-storage-texture': {
-    usage: C.TextureUsage.Storage,
+    usage: GPUTextureUsage.STORAGE,
     ...kBindingKind.storageTex,
-    ...kValidStagesAll
-  }
+    ...kValidStagesAll,
+  },
 };
-export const kTextureBindingTypes = keysOf(kTextureBindingTypeInfo); // All binding types (merged from above)
 
-export const kBindingTypeInfo = { ...kBufferBindingTypeInfo,
+export const kTextureBindingTypes = keysOf(kTextureBindingTypeInfo);
+
+// All binding types (merged from above)
+
+export const kBindingTypeInfo = {
+  ...kBufferBindingTypeInfo,
   ...kSamplerBindingTypeInfo,
-  ...kTextureBindingTypeInfo
+  ...kTextureBindingTypeInfo,
 };
+
 export const kBindingTypes = keysOf(kBindingTypeInfo);
-export const kShaderStages = [C.ShaderStage.Vertex, C.ShaderStage.Fragment, C.ShaderStage.Compute];
+
+export const kShaderStages = [
+  GPUShaderStage.VERTEX,
+  GPUShaderStage.FRAGMENT,
+  GPUShaderStage.COMPUTE,
+];
+
 export const kShaderStageCombinations = [0, 1, 2, 3, 4, 5, 6, 7];
-//# sourceMappingURL=capability_info.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/examples.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/examples.spec.js
index 28b9ae47..3282536 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/examples.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/examples.spec.js
@@ -1,14 +1,15 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = `
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `
 Examples of writing CTS tests with various features.
 
 Start here when looking for examples of basic framework usage.
 `;
 import { makeTestGroup } from '../common/framework/test_group.js';
-import { GPUTest } from './gpu_test.js'; // To run these tests in the standalone runner, run `grunt build` or `grunt pre` then open:
+
+import { GPUTest } from './gpu_test.js';
+
+// To run these tests in the standalone runner, run `grunt build` or `grunt pre` then open:
 // - http://localhost:8080/?runnow=1&q=webgpu:examples:
 // To run in WPT, copy/symlink the out-wpt/ directory as the webgpu/ directory in WPT, then open:
 // - (wpt server url)/webgpu/cts.html?q=webgpu:examples:
@@ -18,30 +19,50 @@
 // - ?q=webgpu:examples:basic/
 // - ?q=webgpu:examples:
 
-export const g = makeTestGroup(GPUTest); // Note: spaces in test names are replaced with underscores: webgpu:examples:test_name=
+export const g = makeTestGroup(GPUTest);
+
+// Note: spaces in test names are replaced with underscores: webgpu:examples:test_name=
 
 g.test('test_name').fn(t => {});
+
 g.test('basic').fn(t => {
   t.expect(true);
   t.expect(true, 'true should be true');
-  t.shouldThrow( // The expected '.name' of the thrown error.
-  'TypeError', // This function is run inline inside shouldThrow, and is expected to throw.
-  () => {
-    throw new TypeError();
-  }, // Log message.
-  'function should throw Error');
+
+  t.shouldThrow(
+    // The expected '.name' of the thrown error.
+    'TypeError',
+    // This function is run inline inside shouldThrow, and is expected to throw.
+    () => {
+      throw new TypeError();
+    },
+    // Log message.
+    'function should throw Error'
+  );
 });
+
 g.test('basic,async').fn(async t => {
   // shouldReject must be awaited to ensure it can wait for the promise before the test ends.
-  t.shouldReject( // The expected '.name' of the thrown error.
-  'TypeError', // Promise expected to reject.
-  Promise.reject(new TypeError()), // Log message.
-  'Promise.reject should reject'); // Promise can also be an IIFE.
+  t.shouldReject(
+    // The expected '.name' of the thrown error.
+    'TypeError',
+    // Promise expected to reject.
+    Promise.reject(new TypeError()),
+    // Log message.
+    'Promise.reject should reject'
+  );
 
-  t.shouldReject('TypeError', (async () => {
-    throw new TypeError();
-  })(), 'Promise.reject should reject');
-}); // A test can be parameterized with a simple array of objects.
+  // Promise can also be an IIFE.
+  t.shouldReject(
+    'TypeError',
+    (async () => {
+      throw new TypeError();
+    })(),
+    'Promise.reject should reject'
+  );
+});
+
+// A test can be parameterized with a simple array of objects.
 //
 // Parameters can be public (x, y) which means they're part of the case name.
 // They can also be private by starting with an underscore (_result), which passes
@@ -49,19 +70,15 @@
 //
 // - webgpu:examples:basic/params={"x":2,"y":4}    runs with t.params = {x: 2, y: 5, _result: 6}.
 // - webgpu:examples:basic/params={"x":-10,"y":18} runs with t.params = {x: -10, y: 18, _result: 8}.
-
-g.test('basic,params').params([{
-  x: 2,
-  y: 4,
-  _result: 6
-}, //
-{
-  x: -10,
-  y: 18,
-  _result: 8
-}]).fn(t => {
-  t.expect(t.params.x + t.params.y === t.params._result);
-}); // (note the blank comment above to enforce newlines on autoformat)
+g.test('basic,params')
+  .params([
+    { x: 2, y: 4, _result: 6 }, //
+    { x: -10, y: 18, _result: 8 },
+  ])
+  .fn(t => {
+    t.expect(t.params.x + t.params.y === t.params._result);
+  });
+// (note the blank comment above to enforce newlines on autoformat)
 
 g.test('gpu,async').fn(async t => {
   const fence = t.queue.createFence();
@@ -69,16 +86,19 @@
   await fence.onCompletion(1);
   t.expect(fence.getCompletedValue() === 2);
 });
+
 g.test('gpu,buffers').fn(async t => {
   const data = new Uint32Array([0, 1234, 0]);
-  const [src, map] = t.device.createBufferMapped({
+  const src = t.device.createBuffer({
+    mappedAtCreation: true,
     size: 12,
-    usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+    usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
   });
-  new Uint32Array(map).set(data);
-  src.unmap(); // Use the expectContents helper to check the actual contents of a GPUBuffer.
-  // Like shouldReject, it must be awaited.
 
+  new Uint32Array(src.getMappedRange()).set(data);
+  src.unmap();
+
+  // Use the expectContents helper to check the actual contents of a GPUBuffer.
+  // Like shouldReject, it must be awaited.
   t.expectContents(src, data);
 });
-//# sourceMappingURL=examples.spec.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/gpu_test.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/gpu_test.js
index 0d65ef2..a836047 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/gpu_test.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/gpu_test.js
@@ -1,24 +1,34 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
-
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ function _defineProperty(obj, key, value) {
+  if (key in obj) {
+    Object.defineProperty(obj, key, {
+      value: value,
+      enumerable: true,
+      configurable: true,
+      writable: true,
+    });
+  } else {
+    obj[key] = value;
+  }
+  return obj;
+}
 import { Fixture } from '../common/framework/fixture.js';
 import { compileGLSL, initGLSL } from '../common/framework/glsl.js';
 import { DevicePool, TestOOMedShouldAttemptGC } from '../common/framework/gpu/device_pool.js';
 import { attemptGarbageCollection } from '../common/framework/util/collect_garbage.js';
 import { assert } from '../common/framework/util/util.js';
+
 import { fillTextureDataWithTexelValue, getTextureCopyLayout } from './util/texture/layout.js';
 import { getTexelDataRepresentation } from './util/texture/texelData.js';
+
 const devicePool = new DevicePool();
+
 export class GPUTest extends Fixture {
   constructor(...args) {
     super(...args);
-
-    _defineProperty(this, "objects", undefined);
-
-    _defineProperty(this, "initialized", false);
+    _defineProperty(this, 'objects', undefined);
+    _defineProperty(this, 'initialized', false);
   }
 
   get device() {
@@ -34,15 +44,13 @@
   async init() {
     await super.init();
     await initGLSL();
+
     const device = await devicePool.acquire();
     const queue = device.defaultQueue;
-    this.objects = {
-      device,
-      queue
-    };
-  } // Note: finalize is called even if init was unsuccessful.
+    this.objects = { device, queue };
+  }
 
-
+  // Note: finalize is called even if init was unsuccessful.
   async finalize() {
     await super.finalize();
 
@@ -51,20 +59,19 @@
       {
         const objects = this.objects;
         this.objects = undefined;
-
         try {
           await devicePool.release(objects.device);
         } catch (ex) {
           threw = ex;
         }
-      } // The GPUDevice and GPUQueue should now have no outstanding references.
+      }
+      // The GPUDevice and GPUQueue should now have no outstanding references.
 
       if (threw) {
         if (threw instanceof TestOOMedShouldAttemptGC) {
           // Try to clean up, in case there are stray GPU resources in need of collection.
           await attemptGarbageCollection();
         }
-
         throw threw;
       }
     }
@@ -73,52 +80,51 @@
   makeShaderModule(stage, code) {
     // If both are provided, always choose WGSL. (Can change this if needed.)
     if ('wgsl' in code) {
-      return this.device.createShaderModule({
-        code: code.wgsl
-      });
+      return this.device.createShaderModule({ code: code.wgsl });
     } else {
       const spirv = compileGLSL(code.glsl, stage, false);
-      return this.device.createShaderModule({
-        code: spirv
-      });
+      return this.device.createShaderModule({ code: spirv });
     }
   }
 
-  createCopyForMapRead(src, start, size) {
+  createCopyForMapRead(src, srcOffset, size) {
     const dst = this.device.createBuffer({
       size,
-      usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
+      usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
     });
+
     const c = this.device.createCommandEncoder();
-    c.copyBufferToBuffer(src, start, dst, 0, size);
+    c.copyBufferToBuffer(src, srcOffset, dst, 0, size);
+
     this.queue.submit([c.finish()]);
+
     return dst;
-  } // TODO: add an expectContents for textures, which logs data: uris on failure
-
-
-  expectContents(src, expected) {
-    this.expectSubContents(src, 0, expected);
   }
 
-  expectSubContents(src, start, expected) {
-    const dst = this.createCopyForMapRead(src, start, expected.buffer.byteLength);
+  // TODO: add an expectContents for textures, which logs data: uris on failure
+
+  expectContents(src, expected, srcOffset = 0) {
+    this.expectSubContents(src, srcOffset, expected);
+  }
+
+  expectSubContents(src, srcOffset, expected) {
+    const dst = this.createCopyForMapRead(src, srcOffset, expected.buffer.byteLength);
+
     this.eventualAsyncExpectation(async niceStack => {
       const constructor = expected.constructor;
-      const actual = new constructor(await dst.mapReadAsync());
+      await dst.mapAsync(GPUMapMode.READ);
+      const actual = new constructor(dst.getMappedRange());
       const check = this.checkBuffer(actual, expected);
-
       if (check !== undefined) {
         niceStack.message = check;
         this.rec.expectationFailed(niceStack);
       }
-
       dst.destroy();
     });
   }
 
   expectBuffer(actual, exp) {
     const check = this.checkBuffer(actual, exp);
-
     if (check !== undefined) {
       this.rec.expectationFailed(new Error(check));
     }
@@ -126,19 +132,16 @@
 
   checkBuffer(actual, exp, tolerance = 0) {
     assert(actual.constructor === exp.constructor);
-    const size = exp.byteLength;
 
+    const size = exp.byteLength;
     if (actual.byteLength !== size) {
       return 'size mismatch';
     }
-
     const failedByteIndices = [];
     const failedByteExpectedValues = [];
     const failedByteActualValues = [];
-
     for (let i = 0; i < size; ++i) {
       const tol = typeof tolerance === 'function' ? tolerance(i) : tolerance;
-
       if (Math.abs(actual[i] - exp[i]) > tol) {
         if (failedByteIndices.length >= 4) {
           failedByteIndices.push('...');
@@ -146,17 +149,17 @@
           failedByteActualValues.push('...');
           break;
         }
-
         failedByteIndices.push(i.toString());
         failedByteExpectedValues.push(exp[i].toString());
         failedByteActualValues.push(actual[i].toString());
       }
     }
-
     const summary = `at [${failedByteIndices.join(', ')}], \
 expected [${failedByteExpectedValues.join(', ')}], \
 got [${failedByteActualValues.join(', ')}]`;
-    const lines = [summary]; // TODO: Could make a more convenient message, which could look like e.g.:
+    const lines = [summary];
+
+    // TODO: Could make a more convenient message, which could look like e.g.:
     //
     //   Starting at offset 48,
     //              got 22222222 ABCDABCD 99999999
@@ -172,72 +175,104 @@
     // and we should remove them. More important will be logging of texture data in a visual format.
 
     if (size <= 256 && failedByteIndices.length > 0) {
-      const expHex = Array.from(new Uint8Array(exp.buffer, exp.byteOffset, exp.byteLength)).map(x => x.toString(16).padStart(2, '0')).join('');
-      const actHex = Array.from(new Uint8Array(actual.buffer, actual.byteOffset, actual.byteLength)).map(x => x.toString(16).padStart(2, '0')).join('');
+      const expHex = Array.from(new Uint8Array(exp.buffer, exp.byteOffset, exp.byteLength))
+        .map(x => x.toString(16).padStart(2, '0'))
+        .join('');
+      const actHex = Array.from(new Uint8Array(actual.buffer, actual.byteOffset, actual.byteLength))
+        .map(x => x.toString(16).padStart(2, '0'))
+        .join('');
       lines.push('EXPECT:\t  ' + exp.join(' '));
       lines.push('\t0x' + expHex);
       lines.push('ACTUAL:\t  ' + actual.join(' '));
       lines.push('\t0x' + actHex);
     }
-
     if (failedByteIndices.length) {
       return lines.join('\n');
     }
-
     return undefined;
   }
 
-  expectSingleColor(src, format, {
-    size,
-    exp,
-    dimension = '2d',
-    slice = 0,
-    layout
-  }) {
-    const {
-      byteLength,
-      bytesPerRow,
-      rowsPerImage,
-      mipSize
-    } = getTextureCopyLayout(format, dimension, size, layout);
+  expectSingleColor(src, format, { size, exp, dimension = '2d', slice = 0, layout }) {
+    const { byteLength, bytesPerRow, rowsPerImage, mipSize } = getTextureCopyLayout(
+      format,
+      dimension,
+      size,
+      layout
+    );
+
     const expectedTexelData = getTexelDataRepresentation(format).getBytes(exp);
+
     const buffer = this.device.createBuffer({
       size: byteLength,
-      usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+      usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
     });
+
     const commandEncoder = this.device.createCommandEncoder();
-    commandEncoder.copyTextureToBuffer({
-      texture: src,
-      mipLevel: layout?.mipLevel,
-      arrayLayer: slice
-    }, {
-      buffer,
-      bytesPerRow,
-      rowsPerImage
-    }, mipSize);
+    commandEncoder.copyTextureToBuffer(
+      {
+        texture: src,
+        mipLevel: layout === null || layout === void 0 ? void 0 : layout.mipLevel,
+        origin: { x: 0, y: 0, z: slice },
+      },
+      { buffer, bytesPerRow, rowsPerImage },
+      mipSize
+    );
+
     this.queue.submit([commandEncoder.finish()]);
     const arrayBuffer = new ArrayBuffer(byteLength);
     fillTextureDataWithTexelValue(expectedTexelData, format, dimension, arrayBuffer, size, layout);
     this.expectContents(buffer, new Uint8Array(arrayBuffer));
   }
 
-  expectGPUError(filter, fn) {
+  // TODO: Add check for values of depth/stencil, probably through sampling of shader
+  // TODO(natashalee): Can refactor this and expectSingleColor to use a similar base expect
+  expectSinglePixelIn2DTexture(src, format, { x, y }, { exp, slice = 0, layout }) {
+    const { byteLength, bytesPerRow, rowsPerImage, mipSize } = getTextureCopyLayout(
+      format,
+      '2d',
+      [1, 1, 1],
+      layout
+    );
+
+    const buffer = this.device.createBuffer({
+      size: byteLength,
+      usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
+    });
+
+    const commandEncoder = this.device.createCommandEncoder();
+    commandEncoder.copyTextureToBuffer(
+      {
+        texture: src,
+        mipLevel: layout === null || layout === void 0 ? void 0 : layout.mipLevel,
+        origin: { x, y, z: slice },
+      },
+      { buffer, bytesPerRow, rowsPerImage },
+      mipSize
+    );
+
+    this.queue.submit([commandEncoder.finish()]);
+
+    this.expectContents(buffer, exp);
+  }
+
+  expectGPUError(filter, fn, shouldError = true) {
+    // If no error is expected, we let the scope surrounding the test catch it.
+    if (!shouldError) {
+      return fn();
+    }
+
     this.device.pushErrorScope(filter);
     const returnValue = fn();
     const promise = this.device.popErrorScope();
+
     this.eventualAsyncExpectation(async niceStack => {
       const error = await promise;
+
       let failed = false;
-
       switch (filter) {
-        case 'none':
-          failed = error !== null;
-          break;
-
         case 'out-of-memory':
           failed = !(error instanceof GPUOutOfMemoryError);
           break;
-
         case 'validation':
           failed = !(error instanceof GPUValidationError);
           break;
@@ -248,16 +283,13 @@
         this.rec.expectationFailed(niceStack);
       } else {
         niceStack.message = `Captured ${filter} error`;
-
         if (error instanceof GPUValidationError) {
           niceStack.message += ` - ${error.message}`;
         }
-
         this.rec.debug(niceStack);
       }
     });
+
     return returnValue;
   }
-
 }
-//# sourceMappingURL=gpu_test.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/idl/constants/flags.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/idl/constants/flags.spec.js
index 73ea14a3..51b80aa 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/idl/constants/flags.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/idl/constants/flags.spec.js
@@ -1,54 +1,85 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = `
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `
 Test the values of flags interfaces (e.g. GPUTextureUsage).
 `;
-import { BufferUsage, TextureUsage, ColorWrite, ShaderStage } from '../../../common/constants.js';
+import { poptions } from '../../../common/framework/params_builder.js';
 import { makeTestGroup } from '../../../common/framework/test_group.js';
 import { IDLTest } from '../idl_test.js';
+
 export const g = makeTestGroup(IDLTest);
-g.test('BufferUsage').fn(t => {
-  const expected = {
-    MAP_READ: BufferUsage.MapRead,
-    MAP_WRITE: BufferUsage.MapWrite,
-    COPY_SRC: BufferUsage.CopySrc,
-    COPY_DST: BufferUsage.CopyDst,
-    INDEX: BufferUsage.Index,
-    VERTEX: BufferUsage.Vertex,
-    UNIFORM: BufferUsage.Uniform,
-    STORAGE: BufferUsage.Storage,
-    INDIRECT: BufferUsage.Indirect
-  };
-  t.assertMembers(GPUBufferUsage, expected);
+
+const kBufferUsageExp = {
+  MAP_READ: 0x0001,
+  MAP_WRITE: 0x0002,
+  COPY_SRC: 0x0004,
+  COPY_DST: 0x0008,
+  INDEX: 0x0010,
+  VERTEX: 0x0020,
+  UNIFORM: 0x0040,
+  STORAGE: 0x0080,
+  INDIRECT: 0x0100,
+  QUERY_RESOLVE: 0x0200,
+};
+
+g.test('BufferUsage,count').fn(t => {
+  t.assertMemberCount(GPUBufferUsage, kBufferUsageExp);
 });
-g.test('TextureUsage').fn(t => {
-  const expected = {
-    COPY_SRC: TextureUsage.CopySrc,
-    COPY_DST: TextureUsage.CopyDst,
-    SAMPLED: TextureUsage.Sampled,
-    STORAGE: TextureUsage.Storage,
-    OUTPUT_ATTACHMENT: TextureUsage.OutputAttachment
-  };
-  t.assertMembers(GPUTextureUsage, expected);
+g.test('BufferUsage,values')
+  .params(poptions('key', Object.keys(kBufferUsageExp)))
+  .fn(t => {
+    const { key } = t.params;
+    t.assertMember(GPUBufferUsage, kBufferUsageExp, key);
+  });
+
+const kTextureUsageExp = {
+  COPY_SRC: 0x01,
+  COPY_DST: 0x02,
+  SAMPLED: 0x04,
+  STORAGE: 0x08,
+  OUTPUT_ATTACHMENT: 0x10,
+};
+
+g.test('TextureUsage,count').fn(t => {
+  t.assertMemberCount(GPUTextureUsage, kTextureUsageExp);
 });
-g.test('ColorWrite').fn(t => {
-  const expected = {
-    RED: ColorWrite.Red,
-    GREEN: ColorWrite.Green,
-    BLUE: ColorWrite.Blue,
-    ALPHA: ColorWrite.Alpha,
-    ALL: ColorWrite.All
-  };
-  t.assertMembers(GPUColorWrite, expected);
+g.test('TextureUsage,values')
+  .params(poptions('key', Object.keys(kTextureUsageExp)))
+  .fn(t => {
+    const { key } = t.params;
+    t.assertMember(GPUTextureUsage, kTextureUsageExp, key);
+  });
+
+const kColorWriteExp = {
+  RED: 0x1,
+  GREEN: 0x2,
+  BLUE: 0x4,
+  ALPHA: 0x8,
+  ALL: 0xf,
+};
+
+g.test('ColorWrite,count').fn(t => {
+  t.assertMemberCount(GPUColorWrite, kColorWriteExp);
 });
-g.test('ShaderStage').fn(t => {
-  const expected = {
-    VERTEX: ShaderStage.Vertex,
-    FRAGMENT: ShaderStage.Fragment,
-    COMPUTE: ShaderStage.Compute
-  };
-  t.assertMembers(GPUShaderStage, expected);
+g.test('ColorWrite,values')
+  .params(poptions('key', Object.keys(kColorWriteExp)))
+  .fn(t => {
+    const { key } = t.params;
+    t.assertMember(GPUColorWrite, kColorWriteExp, key);
+  });
+
+const kShaderStageExp = {
+  VERTEX: 0x1,
+  FRAGMENT: 0x2,
+  COMPUTE: 0x4,
+};
+
+g.test('ShaderStage,count').fn(t => {
+  t.assertMemberCount(GPUShaderStage, kShaderStageExp);
 });
-//# sourceMappingURL=flags.spec.js.map
\ No newline at end of file
+g.test('ShaderStage,values')
+  .params(poptions('key', Object.keys(kShaderStageExp)))
+  .fn(t => {
+    const { key } = t.params;
+    t.assertMember(GPUShaderStage, kShaderStageExp, key);
+  });
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/idl/idl_test.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/idl/idl_test.js
index 0665ad20..3943563 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/idl/idl_test.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/idl/idl_test.js
@@ -1,27 +1,31 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-import { Fixture } from '../../common/framework/fixture.js';
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ import { Fixture } from '../../common/framework/fixture.js';
 import { assert } from '../../common/framework/util/util.js';
+
 export class IDLTest extends Fixture {
+  // TODO: add a helper to check prototype chains
+
   /**
-   * Asserts that an IDL interface has the expected members.
+   * Asserts that a member of an IDL interface has the expected value.
    */
-  // TODO: exp should allow sentinel markers for unnameable values, such as methods and attributes
-  // TODO: handle extensions
-  // TODO: check prototype chains (maybe as separate method)
-  assertMembers(act, exp) {
-    const expKeys = Object.keys(exp);
-
-    for (const k of expKeys) {
-      assert(k in act, () => `Expected key ${k} missing`);
-      assert(act[k] === exp[k], () => `Value of [${k}] was ${act[k]}, expected ${exp[k]}`);
-    }
-
-    const actKeys = Object.keys(act);
-    assert(actKeys.length === expKeys.length, () => `Had ${actKeys.length} keys, expected ${expKeys.length}`);
+  assertMember(act, exp, key) {
+    assert(key in act, () => `Expected key ${key} missing`);
+    assert(act[key] === exp[key], () => `Value of [${key}] was ${act[key]}, expected ${exp[key]}`);
   }
 
+  /**
+   * Asserts that an IDL interface has the same number of keys as the
+   *
+   * TODO: add a way to check for the types of keys with unknown values, like methods and attributes
+   * TODO: handle extensions
+   */
+  assertMemberCount(act, exp) {
+    const expKeys = Object.keys(exp);
+    const actKeys = Object.keys(act);
+    assert(
+      actKeys.length === expKeys.length,
+      () => `Had ${actKeys.length} keys, expected ${expKeys.length}`
+    );
+  }
 }
-//# sourceMappingURL=idl_test.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/listing.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/listing.js
index 8295e935..f1b621d 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/listing.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/listing.js
@@ -31,15 +31,6 @@
       "api",
       "operation",
       "buffers",
-      "create_mapped"
-    ],
-    "description": ""
-  },
-  {
-    "file": [
-      "api",
-      "operation",
-      "buffers",
       "map"
     ],
     "description": ""
@@ -132,6 +123,24 @@
     "file": [
       "api",
       "operation",
+      "render_pass",
+      "storeOp"
+    ],
+    "description": "API Operation Tests for RenderPass StoreOp.\n\n  Test Coverage:\n\n  - Tests that color and depth-stencil store operations {'clear', 'store'} work correctly for a\n    render pass with both a color attachment and depth-stencil attachment.\n      TODO: use depth24plus-stencil8\n\n  - Tests that store operations {'clear', 'store'} work correctly for a render pass with multiple\n    color attachments.\n      TODO: test with more interesting loadOp values\n\n  - Tests that store operations {'clear', 'store'} work correctly for a render pass with a color\n    attachment for:\n      - All renderable color formats\n      - mip level set to {'0', mip > '0'}\n      - array layer set to {'0', layer > '1'} for 2D textures\n      TODO: depth slice set to {'0', slice > '0'} for 3D textures\n\n  - Tests that store operations {'clear', 'store'} work correctly for a render pass with a\n    depth-stencil attachment for:\n      - All renderable depth-stencil formats\n      - mip level set to {'0', mip > '0'}\n      - array layer set to {'0', layer > '1'} for 2D textures\n      TODO: test depth24plus and depth24plus-stencil8 formats\n      TODO: test that depth and stencil aspects are set seperately\n      TODO: depth slice set to {'0', slice > '0'} for 3D textures\n      TODO: test with more interesting loadOp values"
+  },
+  {
+    "file": [
+      "api",
+      "operation",
+      "render_pipeline",
+      "culling_tests"
+    ],
+    "description": "Test culling and rasterizaion state.\n\nTest coverage:\nTest all culling combinations of GPUFrontFace and GPUCullMode show the correct output.\n\nUse 2 triangles with different winding orders:\n\n- Test that the counter-clock wise triangle has correct output for:\n  - All FrontFaces (ccw, cw)\n  - All CullModes (none, front, back)\n  - All depth stencil attachment types (none, depth24plus, depth32float, depth24plus-stencil8)\n  - Some primitive topologies (triangle-list, TODO: triangle-strip)\n\n- Test that the clock wise triangle has correct output for:\n  - All FrontFaces (ccw, cw)\n  - All CullModes (none, front, back)\n  - All depth stencil attachment types (none, depth24plus, depth32float, depth24plus-stencil8)\n  - Some primitive topologies (triangle-list, TODO: triangle-strip)"
+  },
+  {
+    "file": [
+      "api",
+      "operation",
       "resource_init",
       "copied_texture_clear"
     ],
@@ -173,6 +182,40 @@
     "file": [
       "api",
       "validation",
+      "copyBufferToBuffer"
+    ],
+    "description": "copyBufferToBuffer tests.\n\nTest Plan:\n* Buffer is valid/invalid\n  - the source buffer is invalid\n  - the destination buffer is invalid\n* Buffer usages\n  - the source buffer is created without GPUBufferUsage::COPY_SRC\n  - the destination buffer is created without GPUBufferUsage::COPY_DEST\n* CopySize\n  - copySize is not a multiple of 4\n  - copySize is 0\n* copy offsets\n  - sourceOffset is not a multiple of 4\n  - destinationOffset is not a multiple of 4\n* Arthimetic overflow\n  - (sourceOffset + copySize) is overflow\n  - (destinationOffset + copySize) is overflow\n* Out of bounds\n  - (sourceOffset + copySize) > size of source buffer\n  - (destinationOffset + copySize) > size of destination buffer\n* Source buffer and destination buffer are the same buffer"
+  },
+  {
+    "file": [
+      "api",
+      "validation",
+      "copy_between_linear_data_and_texture"
+    ],
+    "readme": "writeTexture + copyBufferToTexture + copyTextureToBuffer validation tests.\n\nTest coverage:\n* resource usages:\n\t- texture_usage_must_be_valid: for GPUTextureUsage::COPY_SRC, GPUTextureUsage::COPY_DST flags.\n\n* textureCopyView:\n\t- texture_must_be_valid: for valid, destroyed, error textures.\n\t- sample_count_must_be_1: for sample count 1 and 4.\n\t- mip_level_must_be_in_range: for various combinations of mipLevel and mipLevelCount.\n\t- texel_block_alignment_on_origin: for all formats and coordinates.\n\n* linear texture data:\n\t- bound_on_rows_per_image: for various combinations of copyDepth (1, >1), copyHeight, rowsPerImage.\n\t- offset_plus_required_bytes_in_copy_overflow\n\t- required_bytes_in_copy: testing minimal data size and data size too small for various combinations of bytesPerRow, rowsPerImage, copyExtent and offset. for the copy method, bytesPerRow is computed as bytesInACompleteRow aligned to be a multiple of 256 + bytesPerRowPadding * 256.\n\t- texel_block_alignment_on_rows_per_image: for all formats.\n\t- texel_block_alignment_on_offset: for all formats.\n\t- bound_on_bytes_per_row: for all formats and various combinations of bytesPerRow and copyExtent. for writeTexture, bytesPerRow is computed as (blocksPerRow * blockWidth * bytesPerBlock + additionalBytesPerRow) and copyExtent.width is computed as copyWidthInBlocks * blockWidth. for the copy methods, both values are mutliplied by 256.\n\t- bound_on_offset: for various combinations of offset and dataSize.\n\n* texture copy range:\n\t- 1d_texture: copyExtent.height isn't 1, copyExtent.depth isn't 1.\n\t- texel_block_alignment_on_size: for all formats and coordinates.\n\t- texture_range_conditons: for all coordinate and various combinations of origin, copyExtent, textureSize and mipLevel.\n\nTODO: more test coverage for 1D and 3D textures."
+  },
+  {
+    "file": [
+      "api",
+      "validation",
+      "copy_between_linear_data_and_texture",
+      "copyBetweenLinearDataAndTexture_dataRelated"
+    ],
+    "description": ""
+  },
+  {
+    "file": [
+      "api",
+      "validation",
+      "copy_between_linear_data_and_texture",
+      "copyBetweenLinearDataAndTexture_textureRelated"
+    ],
+    "description": ""
+  },
+  {
+    "file": [
+      "api",
+      "validation",
       "createBindGroup"
     ],
     "description": "createBindGroup validation tests."
@@ -221,6 +264,16 @@
     "file": [
       "api",
       "validation",
+      "encoding",
+      "cmds",
+      "index_access"
+    ],
+    "description": "indexed draws validation tests."
+  },
+  {
+    "file": [
+      "api",
+      "validation",
       "error_scope"
     ],
     "description": "error scope validation tests."
@@ -253,6 +306,24 @@
     "file": [
       "api",
       "validation",
+      "render_pass",
+      "resolve"
+    ],
+    "description": "API Validation Tests for RenderPass Resolve.\n\n  Test Coverage:\n    - When resolveTarget is not null:\n      - Test that the colorAttachment is multisampled:\n        - A single sampled colorAttachment should generate an error.\n      - Test that the resolveTarget is single sampled:\n        - A multisampled resolveTarget should generate an error.\n      - Test that the resolveTarget has usage OUTPUT_ATTACHMENT:\n        - A resolveTarget without usage OUTPUT_ATTACHMENT should generate an error.\n      - Test that the resolveTarget's texture view describes a single subresource:\n        - A resolveTarget texture view with base mip {0, base mip > 0} and mip count of 1 should be\n          valid.\n          - An error should be generated when the resolve target view mip count is not 1 and base\n            mip is {0, base mip > 0}.\n        - A resolveTarget texture view with base array layer {0, base array layer > 0} and array\n          layer count of 1 should be valid.\n          - An error should be generated when the resolve target view array layer count is not 1 and\n            base array layer is {0, base array layer > 0}.\n      - Test that the resolveTarget's format is the same as the colorAttachment:\n        - An error should be generated when the resolveTarget's format does not match the\n          colorAttachment's format.\n      - Test that the resolveTarget's size is the same the colorAttachment:\n        - An error should be generated when the resolveTarget's height or width are not equal to\n          the colorAttachment's height or width."
+  },
+  {
+    "file": [
+      "api",
+      "validation",
+      "render_pass",
+      "storeOp"
+    ],
+    "description": "API Validation Tests for RenderPass StoreOp.\n\nTest Coverage:\n  - Tests that when depthReadOnly is true, depthStoreOp must be 'store'.\n    - When depthReadOnly is true and depthStoreOp is 'clear', an error should be generated.\n\n  - Tests that when stencilReadOnly is true, stencilStoreOp must be 'store'.\n    - When stencilReadOnly is true and stencilStoreOp is 'clear', an error should be generated.\n\n  - Tests that the depthReadOnly value matches the stencilReadOnly value.\n    - When depthReadOnly does not match stencilReadOnly, an error should be generated.\n\n  - Tests that depthReadOnly and stencilReadOnly default to false."
+  },
+  {
+    "file": [
+      "api",
+      "validation",
       "render_pass_descriptor"
     ],
     "description": "render pass descriptor validation tests."
@@ -261,6 +332,15 @@
     "file": [
       "api",
       "validation",
+      "resource_usages",
+      "textureUsageInRender"
+    ],
+    "description": "Texture Usages Validation Tests in Render Pass.\n\nTest Coverage:\n - Tests that read and write usages upon the same texture subresource, or different subresources\n   of the same texture. Different subresources of the same texture includes different mip levels,\n   different array layers, and different aspects.\n   - When read and write usages are binding to the same texture subresource, an error should be\n     generated. Otherwise, no error should be generated."
+  },
+  {
+    "file": [
+      "api",
+      "validation",
       "setBindGroup"
     ],
     "description": "setBindGroup validation tests."
@@ -357,6 +437,14 @@
   {
     "file": [
       "shader",
+      "execution",
+      "robust_access_vertex"
+    ],
+    "description": "Test vertex attributes behave correctly (no crash / data leak) when accessed out of bounds\n\nTest coverage:\n\nThe following will be parameterized (all combinations tested):\n\n1) Draw call indexed? (false / true)\n  - Run the draw call using an index buffer\n\n2) Draw call indirect? (false / true)\n  - Run the draw call using an indirect buffer\n\n3) Draw call parameter (vertexCount, firstVertex, indexCount, firstIndex, baseVertex, instanceCount,\n  firstInstance)\n  - The parameter which will go out of bounds. Filtered depending on if the draw call is indexed.\n\n4) Attribute type (float, vec2, vec3, vec4)\n  - The input attribute type in the vertex shader\n\n5) Error scale (1, 4, 10^2, 10^4, 10^6)\n  - Offset to add to the correct draw call parameter\n\n6) Additional vertex buffers (0, +4)\n  - Tests that no OOB occurs if more vertex buffers are used\n\nThe tests will also have another vertex buffer bound for an instanced attribute, to make sure\ninstanceCount / firstInstance are tested.\n\nThe tests will include multiple attributes per vertex buffer.\n\nThe vertex buffers will be filled by repeating a few chosen values until the end of the buffer.\n\nThe test will run a render pipeline which verifies the following:\n1) All vertex attribute values occur in the buffer or are zero\n2) All gl_VertexIndex values are within the index buffer or 0\n\nTODO:\n\nA suppression may be needed for d3d12 on tests that have non-zero baseVertex, since d3d12 counts\nfrom 0 instead of from baseVertex (will fail check for gl_VertexIndex).\n\nVertex buffer contents could be randomized to prevent the case where a previous test creates\na similar buffer to ours and the OOB-read seems valid. This should be deterministic, which adds\nmore complexity that we may not need."
+  },
+  {
+    "file": [
+      "shader",
       "regression"
     ],
     "readme": "One-off tests that reproduce shader bugs found in implementations to prevent the bugs from\nappearing again."
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/shader/execution/robust_access.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/shader/execution/robust_access.spec.js
index 53fc9b6..ecfec06 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/shader/execution/robust_access.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/shader/execution/robust_access.spec.js
@@ -1,16 +1,16 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = `
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `
 Tests to check array clamping in shaders is correctly implemented including vector / matrix indexing
 `;
 import { params, poptions } from '../../../common/framework/params_builder.js';
 import { makeTestGroup } from '../../../common/framework/test_group.js';
 import { assert } from '../../../common/framework/util/util.js';
 import { GPUTest } from '../../gpu_test.js';
-export const g = makeTestGroup(GPUTest); // Utilities that should probably live in some shared place.
 
+export const g = makeTestGroup(GPUTest);
+
+// Utilities that should probably live in some shared place.
 function copyArrayBuffer(src) {
   const dst = new ArrayBuffer(src.byteLength);
   new Uint8Array(dst).set(new Uint8Array(src));
@@ -18,26 +18,32 @@
 }
 
 const kUintMax = 4294967295;
-const kIntMax = 2147483647; // A small utility to test shaders:
+const kIntMax = 2147483647;
+
+// A small utility to test shaders:
 //  - it wraps the source into a small harness that checks the runTest() function returns 0.
 //  - it runs the shader with the testBindings set as bindgroup 0.
 //
 // The shader also has access to a uniform value that's equal to 1u to avoid constant propagation
 // in the shader compiler.
-
 function runShaderTest(t, stage, testSource, testBindings) {
   assert(stage === GPUShaderStage.COMPUTE, 'Only know how to deal with compute for now');
-  const [constantsBuffer, constantsInit] = t.device.createBufferMapped({
+
+  const constantsBuffer = t.device.createBuffer({
+    mappedAtCreation: true,
     size: 4,
-    usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.UNIFORM
+    usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.UNIFORM,
   });
-  const constantsData = new Uint32Array(constantsInit);
+
+  const constantsData = new Uint32Array(constantsBuffer.getMappedRange());
   constantsData[0] = 1;
   constantsBuffer.unmap();
+
   const resultBuffer = t.device.createBuffer({
     size: 4,
-    usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE
+    usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE,
   });
+
   const source = `#version 450
     layout(std140, set = 1, binding = 0) uniform Constants {
       uint one;
@@ -51,32 +57,27 @@
     void main() {
       result = runTest();
     }`;
+
   const pipeline = t.device.createComputePipeline({
     computeStage: {
       entryPoint: 'main',
-      module: t.makeShaderModule('compute', {
-        glsl: source
-      })
-    }
+      module: t.makeShaderModule('compute', { glsl: source }),
+    },
   });
+
   const group = t.device.createBindGroup({
     layout: pipeline.getBindGroupLayout(1),
-    entries: [{
-      binding: 0,
-      resource: {
-        buffer: constantsBuffer
-      }
-    }, {
-      binding: 1,
-      resource: {
-        buffer: resultBuffer
-      }
-    }]
+    entries: [
+      { binding: 0, resource: { buffer: constantsBuffer } },
+      { binding: 1, resource: { buffer: resultBuffer } },
+    ],
   });
+
   const testGroup = t.device.createBindGroup({
     layout: pipeline.getBindGroupLayout(0),
-    entries: testBindings
+    entries: testBindings,
   });
+
   const encoder = t.device.createCommandEncoder();
   const pass = encoder.beginComputePass();
   pass.setPipeline(pipeline);
@@ -84,10 +85,13 @@
   pass.setBindGroup(1, group);
   pass.dispatch(1);
   pass.endPass();
-  t.queue.submit([encoder.finish()]);
-  t.expectContents(resultBuffer, new Uint32Array([0]));
-} // The definition of base types for aggregate types, for example float, uint, etc.
 
+  t.queue.submit([encoder.finish()]);
+
+  t.expectContents(resultBuffer, new Uint32Array([0]));
+}
+
+// The definition of base types for aggregate types, for example float, uint, etc.
 
 const baseTypes = {
   // TODO bools
@@ -96,92 +100,86 @@
     byteSize: 4,
     glslPrefix: 'u',
     glslZero: '0u',
-
     fillBuffer(data, zeroStart, size) {
       const typedData = new Uint32Array(data);
       typedData.fill(42);
-
       for (let i = 0; i < size / 4; i++) {
         typedData[zeroStart / 4 + i] = 0;
       }
-    }
-
+    },
   },
+
   int: {
     name: 'int',
     byteSize: 4,
     glslPrefix: 'i',
     glslZero: '0',
-
     fillBuffer(data, zeroStart, size) {
       const typedData = new Int32Array(data);
       typedData.fill(42);
-
       for (let i = 0; i < size / 4; i++) {
         typedData[zeroStart / 4 + i] = 0;
       }
-    }
-
+    },
   },
+
   float: {
     name: 'float',
     byteSize: 4,
     glslPrefix: '',
     glslZero: '0.0f',
-
     fillBuffer(data, zeroStart, size) {
       const typedData = new Float32Array(data);
       typedData.fill(42);
-
       for (let i = 0; i < size / 4; i++) {
         typedData[zeroStart / 4 + i] = 0;
       }
-    }
-
+    },
   },
+
   bool: {
     name: 'bool',
     byteSize: 4,
     glslPrefix: 'b',
     glslZero: 'false',
-
     fillBuffer(data, zeroStart, size) {
       const typedData = new Uint32Array(data);
       typedData.fill(42);
-
       for (let i = 0; i < size / 4; i++) {
         typedData[zeroStart / 4 + i] = 0;
       }
-    }
+    },
+  },
+};
 
-  }
-}; // The definition of aggregate types.
+// The definition of aggregate types.
 
 const typeParams = (() => {
   const types = {};
-
   for (const baseTypeName of Object.keys(baseTypes)) {
-    const baseType = baseTypes[baseTypeName]; // Arrays
+    const baseType = baseTypes[baseTypeName];
 
+    // Arrays
     types[`${baseTypeName}_sizedArray`] = {
       declaration: `${baseTypeName} data[3]`,
       length: 3,
       std140Length: 2 * 4 + 1,
       std430Length: 3,
       zero: baseType.glslZero,
-      baseType
+      baseType,
     };
+
     types[`${baseTypeName}_unsizedArray`] = {
       declaration: `${baseTypeName} data[]`,
       length: 3,
-      std140Length: 0,
-      // Unused
+      std140Length: 0, // Unused
       std430Length: 3,
       zero: baseType.glslZero,
       baseType,
-      isUnsizedArray: true
-    }; // Vectors
+      isUnsizedArray: true,
+    };
 
+    // Vectors
     for (let dimension = 2; dimension <= 4; dimension++) {
       types[`${baseTypeName}_vector${dimension}`] = {
         declaration: `${baseType.glslPrefix}vec${dimension} data`,
@@ -189,21 +187,22 @@
         std140Length: dimension,
         std430Length: dimension,
         zero: baseType.glslZero,
-        baseType
+        baseType,
       };
     }
-  } // Matrices, there are only float matrics in GLSL.
+  }
 
-
+  // Matrices, there are only float matrics in GLSL.
   for (const transposed of [false, true]) {
     for (let numColumns = 2; numColumns <= 4; numColumns++) {
       for (let numRows = 2; numRows <= 4; numRows++) {
         const majorDim = transposed ? numRows : numColumns;
         const minorDim = transposed ? numColumns : numRows;
+
         const std140SizePerMinorDim = 4;
         const std430SizePerMinorDim = minorDim === 3 ? 4 : minorDim;
-        let typeName = `mat${numColumns}`;
 
+        let typeName = `mat${numColumns}`;
         if (numColumns !== numRows) {
           typeName += `x${numRows}`;
         }
@@ -214,7 +213,7 @@
           std140Length: std140SizePerMinorDim * (majorDim - 1) + minorDim,
           std430Length: std430SizePerMinorDim * (majorDim - 1) + minorDim,
           zero: `vec${numRows}(0.0f)`,
-          baseType: baseTypes['float']
+          baseType: baseTypes['float'],
         };
       }
     }
@@ -223,93 +222,119 @@
   return types;
 })();
 
-g.test('bufferMemory').params(params().combine(poptions('type', Object.keys(typeParams))).combine([{
-  memory: 'storage',
-  access: 'read'
-}, {
-  memory: 'storage',
-  access: 'write'
-}, {
-  memory: 'storage',
-  access: 'atomic'
-}, {
-  memory: 'uniform',
-  access: 'read'
-}]) // Unsized arrays are only supported with SSBOs
-.unless(p => typeParams[p.type].isUnsizedArray === true && p.memory !== 'storage') // Atomics are only supported with integers
-.unless(p => p.access === 'atomic' && !(typeParams[p.type].baseType.name in ['uint', 'int']))).fn(async t => {
-  const type = typeParams[t.params.type];
-  const baseType = type.baseType;
-  const indicesToTest = [// Write to the inside of the type so we can check the size computations were correct.
-  '0', `${type.length} - 1`, // Check exact bounds
-  '-1', `${type.length}`, // Check large offset
-  '-1000000', '1000000', // Check with max uint
-  `${kUintMax}`, `-1 * ${kUintMax}`, // Check with max int
-  `${kIntMax}`, `-1 * ${kIntMax}`];
-  let testSource = '';
-  let byteSize = 0; // Declare the data that will be accessed to check robust access.
+g.test('bufferMemory')
+  .params(
+    params()
+      .combine(poptions('type', Object.keys(typeParams)))
+      .combine([
+        { memory: 'storage', access: 'read' },
+        { memory: 'storage', access: 'write' },
+        { memory: 'storage', access: 'atomic' },
+        { memory: 'uniform', access: 'read' },
+      ])
 
-  if (t.params.memory === 'uniform') {
-    testSource += `
+      // Unsized arrays are only supported with SSBOs
+      .unless(p => typeParams[p.type].isUnsizedArray === true && p.memory !== 'storage')
+      // Atomics are only supported with integers
+      .unless(p => p.access === 'atomic' && !(typeParams[p.type].baseType.name in ['uint', 'int']))
+  )
+  .fn(async t => {
+    const type = typeParams[t.params.type];
+    const baseType = type.baseType;
+
+    const indicesToTest = [
+      // Write to the inside of the type so we can check the size computations were correct.
+      '0',
+      `${type.length} - 1`,
+
+      // Check exact bounds
+      '-1',
+      `${type.length}`,
+
+      // Check large offset
+      '-1000000',
+      '1000000',
+
+      // Check with max uint
+      `${kUintMax}`,
+      `-1 * ${kUintMax}`,
+
+      // Check with max int
+      `${kIntMax}`,
+      `-1 * ${kIntMax}`,
+    ];
+
+    let testSource = '';
+    let byteSize = 0;
+
+    // Declare the data that will be accessed to check robust access.
+    if (t.params.memory === 'uniform') {
+      testSource += `
         layout(std140, set = 0, binding = 0) uniform TestData {
           ${type.declaration};
         };`;
-    byteSize = baseType.byteSize * type.std140Length;
-  } else {
-    testSource += `
+      byteSize = baseType.byteSize * type.std140Length;
+    } else {
+      testSource += `
         layout(std430, set = 0, binding = 0) buffer TestData {
           ${type.declaration};
         };`;
-    byteSize = baseType.byteSize * type.std430Length;
-  } // Build the test function that will do the tests.
+      byteSize = baseType.byteSize * type.std430Length;
+    }
 
-
-  testSource += `
+    // Build the test function that will do the tests.
+    testSource += `
     uint runTest() {
   `;
 
-  for (const indexToTest of indicesToTest) {
-    // TODO check with constants too.
-    const index = `(${indexToTest}) * one`;
+    for (const indexToTest of indicesToTest) {
+      // TODO check with constants too.
+      const index = `(${indexToTest}) * one`;
 
-    if (t.params.access === 'read') {
-      testSource += `
+      if (t.params.access === 'read') {
+        testSource += `
           if(data[${index}] != ${type.zero}) {
             return __LINE__;
           }`;
-    } else if (t.params.access === 'write') {
-      testSource += `data[${index}] = ${type.zero};`;
-    } else {
-      testSource += `atomicAdd(data[${index}], 1);`;
+      } else if (t.params.access === 'write') {
+        testSource += `data[${index}] = ${type.zero};`;
+      } else {
+        testSource += `atomicAdd(data[${index}], 1);`;
+      }
     }
-  }
 
-  testSource += `
+    testSource += `
       return 0;
-    }`; // Create a buffer that contains zeroes in the allowed access area, and 42s everywhere else.
+    }`;
 
-  const [testBuffer, testInit] = t.device.createBufferMapped({
-    size: 512,
-    usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.UNIFORM | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
+    // Create a buffer that contains zeroes in the allowed access area, and 42s everywhere else.
+    const testBuffer = t.device.createBuffer({
+      mappedAtCreation: true,
+      size: 512,
+      usage:
+        GPUBufferUsage.COPY_SRC |
+        GPUBufferUsage.UNIFORM |
+        GPUBufferUsage.STORAGE |
+        GPUBufferUsage.COPY_DST,
+    });
+
+    const testInit = testBuffer.getMappedRange();
+    baseType.fillBuffer(testInit, 256, byteSize);
+    const testInitCopy = copyArrayBuffer(testInit);
+    testBuffer.unmap();
+
+    // Run the shader, accessing the buffer.
+    runShaderTest(t, GPUShaderStage.COMPUTE, testSource, [
+      { binding: 0, resource: { buffer: testBuffer, offset: 256, size: byteSize } },
+    ]);
+
+    // Check that content of the buffer outside of the allowed area didn't change.
+    t.expectSubContents(testBuffer, 0, new Uint8Array(testInitCopy.slice(0, 256)));
+    const dataEnd = 256 + byteSize;
+    t.expectSubContents(testBuffer, dataEnd, new Uint8Array(testInitCopy.slice(dataEnd, 512)));
   });
-  baseType.fillBuffer(testInit, 256, byteSize);
-  const testInitCopy = copyArrayBuffer(testInit);
-  testBuffer.unmap(); // Run the shader, accessing the buffer.
 
-  runShaderTest(t, GPUShaderStage.COMPUTE, testSource, [{
-    binding: 0,
-    resource: {
-      buffer: testBuffer,
-      offset: 256,
-      size: byteSize
-    }
-  }]); // Check that content of the buffer outside of the allowed area didn't change.
-
-  t.expectSubContents(testBuffer, 0, new Uint8Array(testInitCopy.slice(0, 256)));
-  const dataEnd = 256 + byteSize;
-  t.expectSubContents(testBuffer, dataEnd, new Uint8Array(testInitCopy.slice(dataEnd, 512)));
-}); // TODO: also check other shader stages.
+// TODO: also check other shader stages.
 // TODO: also check global, function local, and shared variables.
 // TODO: also check interface variables.
 // TODO: also check storage texture access.
-//# sourceMappingURL=robust_access.spec.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/shader/execution/robust_access_vertex.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/shader/execution/robust_access_vertex.spec.js
new file mode 100644
index 0000000..5ed3848e
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/shader/execution/robust_access_vertex.spec.js
@@ -0,0 +1,455 @@
+/**
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ function _defineProperty(obj, key, value) {
+  if (key in obj) {
+    Object.defineProperty(obj, key, {
+      value: value,
+      enumerable: true,
+      configurable: true,
+      writable: true,
+    });
+  } else {
+    obj[key] = value;
+  }
+  return obj;
+}
+export const description = `
+Test vertex attributes behave correctly (no crash / data leak) when accessed out of bounds
+
+Test coverage:
+
+The following will be parameterized (all combinations tested):
+
+1) Draw call indexed? (false / true)
+  - Run the draw call using an index buffer
+
+2) Draw call indirect? (false / true)
+  - Run the draw call using an indirect buffer
+
+3) Draw call parameter (vertexCount, firstVertex, indexCount, firstIndex, baseVertex, instanceCount,
+  firstInstance)
+  - The parameter which will go out of bounds. Filtered depending on if the draw call is indexed.
+
+4) Attribute type (float, vec2, vec3, vec4)
+  - The input attribute type in the vertex shader
+
+5) Error scale (1, 4, 10^2, 10^4, 10^6)
+  - Offset to add to the correct draw call parameter
+
+6) Additional vertex buffers (0, +4)
+  - Tests that no OOB occurs if more vertex buffers are used
+
+The tests will also have another vertex buffer bound for an instanced attribute, to make sure
+instanceCount / firstInstance are tested.
+
+The tests will include multiple attributes per vertex buffer.
+
+The vertex buffers will be filled by repeating a few chosen values until the end of the buffer.
+
+The test will run a render pipeline which verifies the following:
+1) All vertex attribute values occur in the buffer or are zero
+2) All gl_VertexIndex values are within the index buffer or 0
+
+TODO:
+
+A suppression may be needed for d3d12 on tests that have non-zero baseVertex, since d3d12 counts
+from 0 instead of from baseVertex (will fail check for gl_VertexIndex).
+
+Vertex buffer contents could be randomized to prevent the case where a previous test creates
+a similar buffer to ours and the OOB-read seems valid. This should be deterministic, which adds
+more complexity that we may not need.`;
+import { params, pbool, poptions } from '../../../common/framework/params_builder.js';
+import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { GPUTest } from '../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Encapsulates a draw call (either indexed or non-indexed)
+class DrawCall {
+  // Add a float offset when binding vertex buffer
+
+  // Draw
+
+  // DrawIndexed
+
+  // Both Draw and DrawIndexed
+
+  constructor(device, vertexArrays, vertexCount, partialLastNumber, offsetVertexBuffer) {
+    _defineProperty(this, 'device', void 0);
+    _defineProperty(this, 'vertexBuffers', void 0);
+    _defineProperty(this, 'indexBuffer', void 0);
+    _defineProperty(this, 'offsetVertexBuffer', void 0);
+    _defineProperty(this, 'vertexCount', void 0);
+    _defineProperty(this, 'firstVertex', void 0);
+    _defineProperty(this, 'indexCount', void 0);
+    _defineProperty(this, 'firstIndex', void 0);
+    _defineProperty(this, 'baseVertex', void 0);
+    _defineProperty(this, 'instanceCount', void 0);
+    _defineProperty(this, 'firstInstance', void 0);
+    this.device = device;
+    this.vertexBuffers = vertexArrays.map(v => this.generateVertexBuffer(v, partialLastNumber));
+
+    const indexArray = new Uint16Array(vertexCount).fill(0).map((_, i) => i);
+    this.indexBuffer = this.generateIndexBuffer(indexArray);
+
+    // Default arguments (valid call)
+    this.vertexCount = vertexCount;
+    this.firstVertex = 0;
+    this.indexCount = vertexCount;
+    this.firstIndex = 0;
+    this.baseVertex = 0;
+    this.instanceCount = vertexCount;
+    this.firstInstance = 0;
+
+    this.offsetVertexBuffer = offsetVertexBuffer;
+  }
+
+  // Insert a draw call into |pass| with specified type
+  insertInto(pass, indexed, indirect) {
+    if (indexed) {
+      if (indirect) {
+        this.drawIndexedIndirect(pass);
+      } else {
+        this.drawIndexed(pass);
+      }
+    } else {
+      if (indirect) {
+        this.drawIndirect(pass);
+      } else {
+        this.draw(pass);
+      }
+    }
+  }
+
+  // Insert a draw call into |pass|
+  draw(pass) {
+    this.bindVertexBuffers(pass);
+    pass.draw(this.vertexCount, this.instanceCount, this.firstVertex, this.firstInstance);
+  }
+
+  // Insert an indexed draw call into |pass|
+  drawIndexed(pass) {
+    this.bindVertexBuffers(pass);
+    pass.setIndexBuffer(this.indexBuffer);
+    pass.drawIndexed(
+      this.indexCount,
+      this.instanceCount,
+      this.firstIndex,
+      this.baseVertex,
+      this.firstInstance
+    );
+  }
+
+  // Insert an indirect draw call into |pass|
+  drawIndirect(pass) {
+    this.bindVertexBuffers(pass);
+    pass.drawIndirect(this.generateIndirectBuffer(), 0);
+  }
+
+  // Insert an indexed indirect draw call into |pass|
+  drawIndexedIndirect(pass) {
+    this.bindVertexBuffers(pass);
+    pass.setIndexBuffer(this.indexBuffer);
+    pass.drawIndexedIndirect(this.generateIndexedIndirectBuffer(), 0);
+  }
+
+  // Bind all vertex buffers generated
+  bindVertexBuffers(pass) {
+    let currSlot = 0;
+    for (let i = 0; i < this.vertexBuffers.length; i++) {
+      pass.setVertexBuffer(currSlot++, this.vertexBuffers[i], this.offsetVertexBuffer ? 4 : 0);
+    }
+  }
+
+  // Create a vertex buffer from |vertexArray|
+  // If |partialLastNumber| is true, delete one byte off the end
+  generateVertexBuffer(vertexArray, partialLastNumber) {
+    let size = vertexArray.byteLength;
+    if (partialLastNumber) {
+      size -= 1;
+    }
+    const vertexBuffer = this.device.createBuffer({
+      mappedAtCreation: true,
+      size,
+      usage: GPUBufferUsage.VERTEX,
+    });
+
+    const vertexMapping = vertexBuffer.getMappedRange();
+    if (!partialLastNumber) {
+      new Float32Array(vertexMapping).set(vertexArray);
+    } else {
+      new Uint8Array(vertexMapping).set(new Uint8Array(vertexArray.buffer).slice(0, size));
+    }
+    vertexBuffer.unmap();
+    return vertexBuffer;
+  }
+
+  // Create an index buffer from |indexArray|
+  generateIndexBuffer(indexArray) {
+    const indexBuffer = this.device.createBuffer({
+      size: indexArray.byteLength,
+      usage: GPUBufferUsage.INDEX,
+    });
+
+    new Uint16Array(indexBuffer.getMappedRange()).set(indexArray);
+    indexBuffer.unmap();
+    return indexBuffer;
+  }
+
+  // Create an indirect buffer containing draw call values
+  generateIndirectBuffer() {
+    const indirectArray = new Int32Array([
+      this.vertexCount,
+      this.instanceCount,
+      this.firstVertex,
+      this.firstInstance,
+    ]);
+
+    const indirectBuffer = this.device.createBuffer({
+      mappedAtCreation: true,
+      size: indirectArray.byteLength,
+      usage: GPUBufferUsage.INDIRECT,
+    });
+
+    new Int32Array(indirectBuffer.getMappedRange()).set(indirectArray);
+    indirectBuffer.unmap();
+    return indirectBuffer;
+  }
+
+  // Create an indirect buffer containing indexed draw call values
+  generateIndexedIndirectBuffer() {
+    const indirectArray = new Int32Array([
+      this.indexCount,
+      this.instanceCount,
+      this.firstVertex,
+      this.baseVertex,
+      this.firstInstance,
+    ]);
+
+    const indirectBuffer = this.device.createBuffer({
+      mappedAtCreation: true,
+      size: indirectArray.byteLength,
+      usage: GPUBufferUsage.INDIRECT,
+    });
+
+    new Int32Array(indirectBuffer.getMappedRange()).set(indirectArray);
+    indirectBuffer.unmap();
+    return indirectBuffer;
+  }
+}
+
+// Parameterize different sized types
+
+const typeInfoMap = {
+  float: {
+    format: 'float',
+    size: 4,
+    validationFunc: 'return valid(v);',
+  },
+
+  vec2: {
+    format: 'float2',
+    size: 8,
+    validationFunc: 'return valid(v.x) && valid(v.y);',
+  },
+
+  vec3: {
+    format: 'float3',
+    size: 12,
+    validationFunc: 'return valid(v.x) && valid(v.y) && valid(v.z);',
+  },
+
+  vec4: {
+    format: 'float4',
+    size: 16,
+    validationFunc: `return valid(v.x) && valid(v.y) && valid(v.z) && valid(v.w) ||
+                            v.x == 0 && v.y == 0 && v.z == 0 && (v.w == 0.0 || v.w == 1.0);`,
+  },
+};
+
+g.test('vertexAccess')
+  .params(
+    params()
+      .combine(pbool('indexed'))
+      .combine(pbool('indirect'))
+      .expand(p =>
+        poptions(
+          'drawCallTestParameter',
+          p.indexed
+            ? ['indexCount', 'instanceCount', 'firstIndex', 'baseVertex', 'firstInstance']
+            : ['vertexCount', 'instanceCount', 'firstVertex', 'firstInstance']
+        )
+      )
+      .combine(poptions('type', Object.keys(typeInfoMap)))
+      .combine(poptions('additionalBuffers', [0, 4]))
+      .combine(pbool('partialLastNumber'))
+      .combine(pbool('offsetVertexBuffer'))
+      .combine(poptions('errorScale', [1, 4, 10 ** 2, 10 ** 4, 10 ** 6]))
+  )
+  .fn(async t => {
+    const p = t.params;
+    const typeInfo = typeInfoMap[p.type];
+
+    // Number of vertices to draw
+    const numVertices = 3;
+    // Each buffer will be bound to this many attributes (2 would mean 2 attributes per buffer)
+    const attributesPerBuffer = 2;
+    // Make an array big enough for the vertices, attributes, and size of each element
+    const vertexArray = new Float32Array(numVertices * attributesPerBuffer * (typeInfo.size / 4));
+
+    // Sufficiently unusual values to fill our buffer with to avoid collisions with other tests
+    const arbitraryValues = [759, 329, 908];
+    for (let i = 0; i < vertexArray.length; ++i) {
+      vertexArray[i] = arbitraryValues[i % arbitraryValues.length];
+    }
+    // A valid value is 0 or one in the buffer
+    const validValues = [0, ...arbitraryValues];
+
+    // Instance step mode buffer, vertex step mode buffer
+    const bufferContents = [vertexArray, vertexArray];
+    // Additional buffers (vertex step mode)
+    for (let i = 0; i < p.additionalBuffers; i++) {
+      bufferContents.push(vertexArray);
+    }
+
+    // Mutable draw call
+    const draw = new DrawCall(
+      t.device,
+      bufferContents,
+      numVertices,
+      p.partialLastNumber,
+      p.offsetVertexBuffer
+    );
+
+    // Create attributes listing
+    let layoutStr = '';
+    const attributeNames = [];
+    {
+      let currAttribute = 0;
+      for (let i = 0; i < bufferContents.length; i++) {
+        for (let j = 0; j < attributesPerBuffer; j++) {
+          layoutStr += `layout(location=${currAttribute}) in ${p.type} a_${currAttribute};\n`;
+          attributeNames.push(`a_${currAttribute}`);
+          currAttribute++;
+        }
+      }
+    }
+
+    // Vertex buffer descriptors
+    const vertexBuffers = [];
+    {
+      let currAttribute = 0;
+      for (let i = 0; i < bufferContents.length; i++) {
+        vertexBuffers.push({
+          arrayStride: attributesPerBuffer * typeInfo.size,
+          stepMode: i === 0 ? 'instance' : 'vertex',
+          attributes: Array(attributesPerBuffer)
+            .fill(0)
+            .map((_, i) => ({
+              shaderLocation: currAttribute++,
+              offset: i * typeInfo.size,
+              format: typeInfo.format,
+            })),
+        });
+      }
+    }
+
+    // Offset the range checks for gl_VertexIndex in the shader if we use BaseVertex
+    let vertexIndexOffset = 0;
+    if (p.drawCallTestParameter === 'baseVertex') {
+      vertexIndexOffset += p.errorScale;
+    }
+
+    // Construct pipeline that outputs a red fragment, only if we notice any invalid values
+    const vertexModule = t.makeShaderModule('vertex', {
+      glsl: `
+      #version 450
+      ${layoutStr}
+
+      bool valid(float f) {
+        return ${validValues.map(v => `f == ${v}`).join(' || ')};
+      }
+
+      bool validationFunc(${p.type} v) {
+        ${typeInfo.validationFunc}
+      }
+
+      void main() {
+        bool attributesInBounds = ${attributeNames.map(a => `validationFunc(${a})`).join(' && ')};
+        bool indexInBounds = gl_VertexIndex == 0 || (gl_VertexIndex >= ${vertexIndexOffset} &&
+          gl_VertexIndex < ${vertexIndexOffset + numVertices});
+
+        if (attributesInBounds && (${!p.indexed} || indexInBounds)) {
+          // Success case, move the vertex out of the viewport
+          gl_Position = vec4(-1.0, 0.0, 0.0, 1.0);
+        } else {
+          // Failure case, move the vertex inside the viewport
+          gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
+        }
+      }
+    `,
+    });
+
+    const fragmentModule = t.makeShaderModule('fragment', {
+      glsl: `
+      #version 450
+      precision mediump float;
+
+      layout(location = 0) out vec4 fragColor;
+
+      void main() {
+        fragColor = vec4(1.0, 0.0, 0.0, 1.0);
+      }
+    `,
+    });
+
+    // Pipeline setup, texture setup
+    const colorAttachment = t.device.createTexture({
+      format: 'rgba8unorm',
+      size: { width: 1, height: 1, depth: 1 },
+      usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT,
+    });
+
+    const colorAttachmentView = colorAttachment.createView();
+
+    const pipeline = t.device.createRenderPipeline({
+      vertexStage: { module: vertexModule, entryPoint: 'main' },
+      fragmentStage: { module: fragmentModule, entryPoint: 'main' },
+      primitiveTopology: 'point-list',
+      colorStates: [{ format: 'rgba8unorm', alphaBlend: {}, colorBlend: {} }],
+      vertexState: {
+        indexFormat: 'uint16',
+        vertexBuffers,
+      },
+    });
+
+    // Offset the draw call parameter we are testing by |errorScale|
+    draw[p.drawCallTestParameter] += p.errorScale;
+
+    const encoder = t.device.createCommandEncoder();
+    const pass = encoder.beginRenderPass({
+      colorAttachments: [
+        {
+          attachment: colorAttachmentView,
+          storeOp: 'store',
+          loadValue: { r: 0.0, g: 1.0, b: 0.0, a: 1.0 },
+        },
+      ],
+    });
+
+    pass.setPipeline(pipeline);
+
+    // Run the draw variant
+    draw.insertInto(pass, p.indexed, p.indirect);
+
+    pass.endPass();
+    t.device.defaultQueue.submit([encoder.finish()]);
+
+    // Validate we see green instead of red, meaning no fragment ended up on-screen
+    t.expectSinglePixelIn2DTexture(
+      colorAttachment,
+      'rgba8unorm',
+      { x: 0, y: 0 },
+      { exp: new Uint8Array([0x00, 0xff, 0x00, 0xff]), layout: { mipLevel: 0 } }
+    );
+  });
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/util/conversion.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/util/conversion.js
index 73c04fe..bdba99cf 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/util/conversion.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/util/conversion.js
@@ -1,8 +1,6 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-import { assert } from '../../common/framework/util/util.js';
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ import { assert } from '../../common/framework/util/util.js';
 export function floatAsNormalizedInteger(float, bits, signed) {
   if (signed) {
     assert(float >= -1 && float <= 1);
@@ -13,8 +11,9 @@
     const max = Math.pow(2, bits) - 1;
     return Math.round(float * max);
   }
-} // Does not handle clamping, underflow, overflow, denormalized numbers
+}
 
+// Does not handle clamping, underflow, overflow, denormalized numbers
 export function float32ToFloatBits(n, signBits, exponentBits, fractionBits, bias) {
   assert(exponentBits <= 8);
   assert(fractionBits <= 23);
@@ -30,20 +29,26 @@
 
   const buf = new DataView(new ArrayBuffer(Float32Array.BYTES_PER_ELEMENT));
   buf.setFloat32(0, n, true);
-  const bits = buf.getUint32(0, true); // bits (32): seeeeeeeefffffffffffffffffffffff
+  const bits = buf.getUint32(0, true);
+  // bits (32): seeeeeeeefffffffffffffffffffffff
 
-  const fractionBitsToDiscard = 23 - fractionBits; // 0 or 1
+  const fractionBitsToDiscard = 23 - fractionBits;
 
-  const sign = bits >> 31 & signBits; // >> to remove fraction, & to remove sign, - 127 to remove bias.
+  // 0 or 1
+  const sign = (bits >> 31) & signBits;
 
-  const exp = (bits >> 23 & 0xff) - 127; // Convert to the new biased exponent.
+  // >> to remove fraction, & to remove sign, - 127 to remove bias.
+  const exp = ((bits >> 23) & 0xff) - 127;
 
+  // Convert to the new biased exponent.
   const newBiasedExp = bias + exp;
-  assert(newBiasedExp >= 0 && newBiasedExp < 1 << exponentBits); // Mask only the fraction, and discard the lower bits.
+  assert(newBiasedExp >= 0 && newBiasedExp < 1 << exponentBits);
 
+  // Mask only the fraction, and discard the lower bits.
   const newFraction = (bits & 0x7fffff) >> fractionBitsToDiscard;
-  return sign << exponentBits + fractionBits | newBiasedExp << fractionBits | newFraction;
+  return (sign << (exponentBits + fractionBits)) | (newBiasedExp << fractionBits) | newFraction;
 }
+
 export function assertInIntegerRange(n, bits, signed) {
   if (signed) {
     const min = -Math.pow(2, bits - 1);
@@ -54,8 +59,8 @@
     assert(n >= 0 && n <= max);
   }
 }
+
 export function gammaCompress(n) {
   n = n <= 0.0031308 ? 12.92 * n : 1.055 * Math.pow(n, 1 / 2.4) - 0.055;
   return n < 0 ? 0 : n > 1 ? 1 : n;
 }
-//# sourceMappingURL=conversion.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/util/math.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/util/math.js
index 19720d6..4761467 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/util/math.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/util/math.js
@@ -1,11 +1,11 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export function align(n, alignment) {
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export function align(n, alignment) {
   return Math.ceil(n / alignment) * alignment;
 }
+
 export function isAligned(n, alignment) {
   return n === align(n, alignment);
 }
-//# sourceMappingURL=math.js.map
\ No newline at end of file
+
+export const kMaxSafeMultipleOf8 = Number.MAX_SAFE_INTEGER - 7;
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/util/texture/layout.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/util/texture/layout.js
index 425aa202..c3c610b 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/util/texture/layout.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/util/texture/layout.js
@@ -1,54 +1,40 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-import { assert, unreachable } from '../../../common/framework/util/util.js';
-import { kTextureFormatInfo } from '../../capability_info.js';
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ import { assert, unreachable } from '../../../common/framework/util/util.js';
+import { kSizedTextureFormatInfo } from '../../capability_info.js';
 import { align, isAligned } from '../math.js';
+
 export const kBytesPerRowAlignment = 256;
 export const kBufferCopyAlignment = 4;
-const kDefaultLayoutOptions = {
-  mipLevel: 0,
-  bytesPerRow: undefined,
-  rowsPerImage: undefined
-};
+
+const kDefaultLayoutOptions = { mipLevel: 0, bytesPerRow: undefined, rowsPerImage: undefined };
+
 export function getMipSizePassthroughLayers(dimension, size, mipLevel) {
   const shiftMinOne = n => Math.max(1, n >> mipLevel);
-
   switch (dimension) {
     case '1d':
       assert(size[2] === 1);
       return [shiftMinOne(size[0]), size[1], size[2]];
-
     case '2d':
       return [shiftMinOne(size[0]), shiftMinOne(size[1]), size[2]];
-
     case '3d':
       return [shiftMinOne(size[0]), shiftMinOne(size[1]), shiftMinOne(size[2])];
-
     default:
       unreachable();
   }
 }
-export function getTextureCopyLayout(format, dimension, size, options = kDefaultLayoutOptions) {
-  const {
-    mipLevel
-  } = options;
-  let {
-    bytesPerRow,
-    rowsPerImage
-  } = options;
-  const mipSize = getMipSizePassthroughLayers(dimension, size, mipLevel);
-  const {
-    blockWidth,
-    blockHeight,
-    bytesPerBlock
-  } = kTextureFormatInfo[format];
-  assert(!!bytesPerBlock && !!blockWidth && !!blockHeight);
-  assert(isAligned(mipSize[0], blockWidth));
-  const minBytesPerRow = mipSize[0] / blockWidth * bytesPerBlock;
-  const alignedMinBytesPerRow = align(minBytesPerRow, kBytesPerRowAlignment);
 
+export function getTextureCopyLayout(format, dimension, size, options = kDefaultLayoutOptions) {
+  const { mipLevel } = options;
+  let { bytesPerRow, rowsPerImage } = options;
+
+  const mipSize = getMipSizePassthroughLayers(dimension, size, mipLevel);
+
+  const { blockWidth, blockHeight, bytesPerBlock } = kSizedTextureFormatInfo[format];
+
+  assert(isAligned(mipSize[0], blockWidth));
+  const minBytesPerRow = (mipSize[0] / blockWidth) * bytesPerBlock;
+  const alignedMinBytesPerRow = align(minBytesPerRow, kBytesPerRowAlignment);
   if (bytesPerRow !== undefined) {
     assert(bytesPerRow >= alignedMinBytesPerRow);
     assert(isAligned(bytesPerRow, kBytesPerRowAlignment));
@@ -64,62 +50,85 @@
 
   assert(isAligned(rowsPerImage, blockHeight));
   const bytesPerSlice = bytesPerRow * (rowsPerImage / blockHeight);
-  const sliceSize = bytesPerRow * (mipSize[1] / blockHeight - 1) + bytesPerBlock * (mipSize[0] / blockWidth);
+  const sliceSize =
+    bytesPerRow * (mipSize[1] / blockHeight - 1) + bytesPerBlock * (mipSize[0] / blockWidth);
   const byteLength = bytesPerSlice * (mipSize[2] - 1) + sliceSize;
+
   return {
     bytesPerBlock,
     byteLength: align(byteLength, kBufferCopyAlignment),
     minBytesPerRow,
     bytesPerRow,
     rowsPerImage,
-    mipSize
+    mipSize,
   };
 }
-export function fillTextureDataWithTexelValue(texelValue, format, dimension, outputBuffer, size, options = kDefaultLayoutOptions) {
-  const {
-    blockWidth,
-    blockHeight,
-    bytesPerBlock
-  } = kTextureFormatInfo[format];
-  assert(!!bytesPerBlock && !!blockWidth && !!blockHeight);
+
+export function fillTextureDataWithTexelValue(
+  texelValue,
+  format,
+  dimension,
+  outputBuffer,
+  size,
+  options = kDefaultLayoutOptions
+) {
+  const { blockWidth, blockHeight, bytesPerBlock } = kSizedTextureFormatInfo[format];
   assert(bytesPerBlock === texelValue.byteLength);
-  const {
-    byteLength,
-    rowsPerImage,
-    bytesPerRow
-  } = getTextureCopyLayout(format, dimension, size, options);
+
+  const { byteLength, rowsPerImage, bytesPerRow } = getTextureCopyLayout(
+    format,
+    dimension,
+    size,
+    options
+  );
+
   assert(byteLength <= outputBuffer.byteLength);
+
   const mipSize = getMipSizePassthroughLayers(dimension, size, options.mipLevel);
+
   const texelValueBytes = new Uint8Array(texelValue);
   const outputTexelValueBytes = new Uint8Array(outputBuffer);
-
   for (let slice = 0; slice < mipSize[2]; ++slice) {
     for (let row = 0; row < mipSize[1]; row += blockHeight) {
       for (let col = 0; col < mipSize[0]; col += blockWidth) {
-        const byteOffset = slice * rowsPerImage * bytesPerRow + row * bytesPerRow + col * texelValue.byteLength;
+        const byteOffset =
+          slice * rowsPerImage * bytesPerRow + row * bytesPerRow + col * texelValue.byteLength;
         outputTexelValueBytes.set(texelValueBytes, byteOffset);
       }
     }
   }
 }
-export function createTextureUploadBuffer(texelValue, device, format, dimension, size, options = kDefaultLayoutOptions) {
-  const {
-    byteLength,
-    bytesPerRow,
-    rowsPerImage,
-    bytesPerBlock
-  } = getTextureCopyLayout(format, dimension, size, options);
-  const [buffer, mapping] = device.createBufferMapped({
+
+export function createTextureUploadBuffer(
+  texelValue,
+  device,
+  format,
+  dimension,
+  size,
+  options = kDefaultLayoutOptions
+) {
+  const { byteLength, bytesPerRow, rowsPerImage, bytesPerBlock } = getTextureCopyLayout(
+    format,
+    dimension,
+    size,
+    options
+  );
+
+  const buffer = device.createBuffer({
+    mappedAtCreation: true,
     size: byteLength,
-    usage: GPUBufferUsage.COPY_SRC
+    usage: GPUBufferUsage.COPY_SRC,
   });
+
+  const mapping = buffer.getMappedRange();
+
   assert(texelValue.byteLength === bytesPerBlock);
   fillTextureDataWithTexelValue(texelValue, format, dimension, mapping, size, options);
   buffer.unmap();
+
   return {
     buffer,
     bytesPerRow,
-    rowsPerImage
+    rowsPerImage,
   };
 }
-//# sourceMappingURL=layout.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/util/texture/subresource.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/util/texture/subresource.js
index 04a8e72..889fab1 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/util/texture/subresource.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/util/texture/subresource.js
@@ -1,8 +1,18 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ function _defineProperty(obj, key, value) {
+  if (key in obj) {
+    Object.defineProperty(obj, key, {
+      value: value,
+      enumerable: true,
+      configurable: true,
+      writable: true,
+    });
+  } else {
+    obj[key] = value;
+  }
+  return obj;
+}
 
 function endOfRange(r) {
   return 'count' in r ? r.begin + r.count : r.end;
@@ -16,27 +26,23 @@
 
 export class SubresourceRange {
   constructor(subresources) {
-    _defineProperty(this, "mipRange", void 0);
-
-    _defineProperty(this, "sliceRange", void 0);
-
+    _defineProperty(this, 'mipRange', void 0);
+    _defineProperty(this, 'sliceRange', void 0);
     this.mipRange = {
       begin: subresources.mipRange.begin,
-      end: endOfRange(subresources.mipRange)
+      end: endOfRange(subresources.mipRange),
     };
+
     this.sliceRange = {
       begin: subresources.sliceRange.begin,
-      end: endOfRange(subresources.sliceRange)
+      end: endOfRange(subresources.sliceRange),
     };
   }
 
   *each() {
     for (let level = this.mipRange.begin; level < this.mipRange.end; ++level) {
       for (let slice = this.sliceRange.begin; slice < this.sliceRange.end; ++slice) {
-        yield {
-          level,
-          slice
-        };
+        yield { level, slice };
       }
     }
   }
@@ -45,10 +51,8 @@
     for (let level = this.mipRange.begin; level < this.mipRange.end; ++level) {
       yield {
         level,
-        slices: rangeAsIterator(this.sliceRange)
+        slices: rangeAsIterator(this.sliceRange),
       };
     }
   }
-
 }
-//# sourceMappingURL=subresource.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/util/texture/texelData.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/util/texture/texelData.js
index 2ea7275e..eadaa096 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/util/texture/texelData.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/util/texture/texelData.js
@@ -1,32 +1,46 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
-
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ function _defineProperty(obj, key, value) {
+  if (key in obj) {
+    Object.defineProperty(obj, key, {
+      value: value,
+      enumerable: true,
+      configurable: true,
+      writable: true,
+    });
+  } else {
+    obj[key] = value;
+  }
+  return obj;
+}
 import { assert, unreachable } from '../../../common/framework/util/util.js';
-import { kTextureFormatInfo } from '../../capability_info.js';
-import { assertInIntegerRange, float32ToFloatBits, floatAsNormalizedInteger, gammaCompress } from '../conversion.js';
+import { kUncompressedTextureFormatInfo } from '../../capability_info.js';
+import {
+  assertInIntegerRange,
+  float32ToFloatBits,
+  floatAsNormalizedInteger,
+  gammaCompress,
+} from '../conversion.js';
+
 export let TexelComponent;
-
 (function (TexelComponent) {
-  TexelComponent["R"] = "R";
-  TexelComponent["G"] = "G";
-  TexelComponent["B"] = "B";
-  TexelComponent["A"] = "A";
-  TexelComponent["Depth"] = "Depth";
-  TexelComponent["Stencil"] = "Stencil";
+  TexelComponent['R'] = 'R';
+  TexelComponent['G'] = 'G';
+  TexelComponent['B'] = 'B';
+  TexelComponent['A'] = 'A';
+  TexelComponent['Depth'] = 'Depth';
+  TexelComponent['Stencil'] = 'Stencil';
 })(TexelComponent || (TexelComponent = {}));
+var TexelWriteType;
 
-var TexelWriteType; // Function to convert a value into a texel value. It returns the converted value
+// Function to convert a value into a texel value. It returns the converted value
 // and the type of the converted value. For example, conversion may convert:
 //  - floats to unsigned normalized integers
 //  - floats to half floats, interpreted as uint16 bits
-
 (function (TexelWriteType) {
-  TexelWriteType[TexelWriteType["Sint"] = 0] = "Sint";
-  TexelWriteType[TexelWriteType["Uint"] = 1] = "Uint";
-  TexelWriteType[TexelWriteType["Float"] = 2] = "Float";
+  TexelWriteType[(TexelWriteType['Sint'] = 0)] = 'Sint';
+  TexelWriteType[(TexelWriteType['Uint'] = 1)] = 'Uint';
+  TexelWriteType[(TexelWriteType['Float'] = 2)] = 'Float';
 })(TexelWriteType || (TexelWriteType = {}));
 
 const kR = [TexelComponent.R];
@@ -37,259 +51,160 @@
 
 const unorm = bitLength => n => ({
   value: floatAsNormalizedInteger(n, bitLength, false),
-  type: TexelWriteType.Uint
+  type: TexelWriteType.Uint,
 });
 
 const snorm = bitLength => n => ({
   value: floatAsNormalizedInteger(n, bitLength, true),
-  type: TexelWriteType.Sint
+  type: TexelWriteType.Sint,
 });
 
 const uint = bitLength => n => ({
   value: (assertInIntegerRange(n, bitLength, false), n),
-  type: TexelWriteType.Uint
+  type: TexelWriteType.Uint,
 });
 
 const sint = bitLength => n => ({
   value: (assertInIntegerRange(n, bitLength, true), n),
-  type: TexelWriteType.Sint
+  type: TexelWriteType.Sint,
 });
 
-const unorm2 = {
-  write: unorm(2),
-  bitLength: 2
-};
-const unorm8 = {
-  write: unorm(8),
-  bitLength: 8
-};
-const unorm10 = {
-  write: unorm(10),
-  bitLength: 10
-};
-const snorm8 = {
-  write: snorm(8),
-  bitLength: 8
-};
-const uint8 = {
-  write: uint(8),
-  bitLength: 8
-};
-const uint16 = {
-  write: uint(16),
-  bitLength: 16
-};
-const uint32 = {
-  write: uint(32),
-  bitLength: 32
-};
-const sint8 = {
-  write: sint(8),
-  bitLength: 8
-};
-const sint16 = {
-  write: sint(16),
-  bitLength: 16
-};
-const sint32 = {
-  write: sint(32),
-  bitLength: 32
-};
+const unorm2 = { write: unorm(2), bitLength: 2 };
+const unorm8 = { write: unorm(8), bitLength: 8 };
+const unorm10 = { write: unorm(10), bitLength: 10 };
+
+const snorm8 = { write: snorm(8), bitLength: 8 };
+
+const uint8 = { write: uint(8), bitLength: 8 };
+const uint16 = { write: uint(16), bitLength: 16 };
+const uint32 = { write: uint(32), bitLength: 32 };
+
+const sint8 = { write: sint(8), bitLength: 8 };
+const sint16 = { write: sint(16), bitLength: 16 };
+const sint32 = { write: sint(32), bitLength: 32 };
+
 const float10 = {
   write: n => ({
     value: float32ToFloatBits(n, 0, 5, 5, 15),
-    type: TexelWriteType.Uint
+    type: TexelWriteType.Uint,
   }),
-  bitLength: 10
+
+  bitLength: 10,
 };
+
 const float11 = {
   write: n => ({
     value: float32ToFloatBits(n, 0, 5, 6, 15),
-    type: TexelWriteType.Uint
+    type: TexelWriteType.Uint,
   }),
-  bitLength: 11
+
+  bitLength: 11,
 };
+
 const float16 = {
   write: n => ({
     value: float32ToFloatBits(n, 1, 5, 10, 15),
-    type: TexelWriteType.Uint
+    type: TexelWriteType.Uint,
   }),
-  bitLength: 16
+
+  bitLength: 16,
 };
+
 const float32 = {
   write: n => ({
     value: Math.fround(n),
-    type: TexelWriteType.Float
+    type: TexelWriteType.Float,
   }),
-  bitLength: 32
+
+  bitLength: 32,
 };
 
 const repeatComponents = (componentOrder, perComponentInfo) => {
   const componentInfo = componentOrder.reduce((acc, curr) => {
     return Object.assign(acc, {
-      [curr]: perComponentInfo
+      [curr]: perComponentInfo,
     });
   }, {});
+
   return {
     componentOrder,
-    componentInfo
+    componentInfo,
   };
 };
 
-const kRepresentationInfo =
-/* prettier-ignore */
-{
-  'r8unorm': { ...repeatComponents(kR, unorm8),
-    sRGB: false
-  },
-  'r8snorm': { ...repeatComponents(kR, snorm8),
-    sRGB: false
-  },
-  'r8uint': { ...repeatComponents(kR, uint8),
-    sRGB: false
-  },
-  'r8sint': { ...repeatComponents(kR, sint8),
-    sRGB: false
-  },
-  'r16uint': { ...repeatComponents(kR, uint16),
-    sRGB: false
-  },
-  'r16sint': { ...repeatComponents(kR, sint16),
-    sRGB: false
-  },
-  'r16float': { ...repeatComponents(kR, float16),
-    sRGB: false
-  },
-  'rg8unorm': { ...repeatComponents(kRG, unorm8),
-    sRGB: false
-  },
-  'rg8snorm': { ...repeatComponents(kRG, snorm8),
-    sRGB: false
-  },
-  'rg8uint': { ...repeatComponents(kRG, uint8),
-    sRGB: false
-  },
-  'rg8sint': { ...repeatComponents(kRG, sint8),
-    sRGB: false
-  },
-  'r32uint': { ...repeatComponents(kR, uint32),
-    sRGB: false
-  },
-  'r32sint': { ...repeatComponents(kR, sint32),
-    sRGB: false
-  },
-  'r32float': { ...repeatComponents(kR, float32),
-    sRGB: false
-  },
-  'rg16uint': { ...repeatComponents(kRG, uint16),
-    sRGB: false
-  },
-  'rg16sint': { ...repeatComponents(kRG, sint16),
-    sRGB: false
-  },
-  'rg16float': { ...repeatComponents(kRG, float16),
-    sRGB: false
-  },
-  'rgba8unorm': { ...repeatComponents(kRGBA, unorm8),
-    sRGB: false
-  },
-  'rgba8unorm-srgb': { ...repeatComponents(kRGBA, unorm8),
-    sRGB: true
-  },
-  'rgba8snorm': { ...repeatComponents(kRGBA, snorm8),
-    sRGB: false
-  },
-  'rgba8uint': { ...repeatComponents(kRGBA, uint8),
-    sRGB: false
-  },
-  'rgba8sint': { ...repeatComponents(kRGBA, sint8),
-    sRGB: false
-  },
-  'bgra8unorm': { ...repeatComponents(kBGRA, unorm8),
-    sRGB: false
-  },
-  'bgra8unorm-srgb': { ...repeatComponents(kBGRA, unorm8),
-    sRGB: true
-  },
-  'rg32uint': { ...repeatComponents(kRG, uint32),
-    sRGB: false
-  },
-  'rg32sint': { ...repeatComponents(kRG, sint32),
-    sRGB: false
-  },
-  'rg32float': { ...repeatComponents(kRG, float32),
-    sRGB: false
-  },
-  'rgba16uint': { ...repeatComponents(kRGBA, uint16),
-    sRGB: false
-  },
-  'rgba16sint': { ...repeatComponents(kRGBA, sint16),
-    sRGB: false
-  },
-  'rgba16float': { ...repeatComponents(kRGBA, float16),
-    sRGB: false
-  },
-  'rgba32uint': { ...repeatComponents(kRGBA, uint32),
-    sRGB: false
-  },
-  'rgba32sint': { ...repeatComponents(kRGBA, sint32),
-    sRGB: false
-  },
-  'rgba32float': { ...repeatComponents(kRGBA, float32),
-    sRGB: false
-  },
-  'rgb10a2unorm': {
+const kRepresentationInfo = {
+  r8unorm: { ...repeatComponents(kR, unorm8), sRGB: false },
+  r8snorm: { ...repeatComponents(kR, snorm8), sRGB: false },
+  r8uint: { ...repeatComponents(kR, uint8), sRGB: false },
+  r8sint: { ...repeatComponents(kR, sint8), sRGB: false },
+  r16uint: { ...repeatComponents(kR, uint16), sRGB: false },
+  r16sint: { ...repeatComponents(kR, sint16), sRGB: false },
+  r16float: { ...repeatComponents(kR, float16), sRGB: false },
+  rg8unorm: { ...repeatComponents(kRG, unorm8), sRGB: false },
+  rg8snorm: { ...repeatComponents(kRG, snorm8), sRGB: false },
+  rg8uint: { ...repeatComponents(kRG, uint8), sRGB: false },
+  rg8sint: { ...repeatComponents(kRG, sint8), sRGB: false },
+  r32uint: { ...repeatComponents(kR, uint32), sRGB: false },
+  r32sint: { ...repeatComponents(kR, sint32), sRGB: false },
+  r32float: { ...repeatComponents(kR, float32), sRGB: false },
+  rg16uint: { ...repeatComponents(kRG, uint16), sRGB: false },
+  rg16sint: { ...repeatComponents(kRG, sint16), sRGB: false },
+  rg16float: { ...repeatComponents(kRG, float16), sRGB: false },
+
+  rgba8unorm: { ...repeatComponents(kRGBA, unorm8), sRGB: false },
+  'rgba8unorm-srgb': { ...repeatComponents(kRGBA, unorm8), sRGB: true },
+  rgba8snorm: { ...repeatComponents(kRGBA, snorm8), sRGB: false },
+  rgba8uint: { ...repeatComponents(kRGBA, uint8), sRGB: false },
+  rgba8sint: { ...repeatComponents(kRGBA, sint8), sRGB: false },
+  bgra8unorm: { ...repeatComponents(kBGRA, unorm8), sRGB: false },
+  'bgra8unorm-srgb': { ...repeatComponents(kBGRA, unorm8), sRGB: true },
+  rg32uint: { ...repeatComponents(kRG, uint32), sRGB: false },
+  rg32sint: { ...repeatComponents(kRG, sint32), sRGB: false },
+  rg32float: { ...repeatComponents(kRG, float32), sRGB: false },
+  rgba16uint: { ...repeatComponents(kRGBA, uint16), sRGB: false },
+  rgba16sint: { ...repeatComponents(kRGBA, sint16), sRGB: false },
+  rgba16float: { ...repeatComponents(kRGBA, float16), sRGB: false },
+  rgba32uint: { ...repeatComponents(kRGBA, uint32), sRGB: false },
+  rgba32sint: { ...repeatComponents(kRGBA, sint32), sRGB: false },
+  rgba32float: { ...repeatComponents(kRGBA, float32), sRGB: false },
+
+  rgb10a2unorm: {
     componentOrder: kRGBA,
-    componentInfo: {
-      R: unorm10,
-      G: unorm10,
-      B: unorm10,
-      A: unorm2
-    },
-    sRGB: false
+    componentInfo: { R: unorm10, G: unorm10, B: unorm10, A: unorm2 },
+    sRGB: false,
   },
-  'rg11b10float': {
+  rg11b10float: {
     componentOrder: kRGB,
-    componentInfo: {
-      R: float11,
-      G: float11,
-      B: float10
-    },
-    sRGB: false
+    componentInfo: { R: float11, G: float11, B: float10 },
+    sRGB: false,
   },
-  'depth32float': {
+
+  depth32float: {
     componentOrder: [TexelComponent.Depth],
-    componentInfo: {
-      Depth: float32
-    },
-    sRGB: false
+    componentInfo: { Depth: float32 },
+    sRGB: false,
   },
-  'depth24plus': {
+  depth24plus: {
     componentOrder: [TexelComponent.Depth],
-    componentInfo: {
-      Depth: null
-    },
-    sRGB: false
+    componentInfo: { Depth: null },
+    sRGB: false,
   },
   'depth24plus-stencil8': {
     componentOrder: [TexelComponent.Depth, TexelComponent.Stencil],
-    componentInfo: {
-      Depth: null,
-      Stencil: null
-    },
-    sRGB: false
-  }
+    componentInfo: { Depth: null, Stencil: null },
+    sRGB: false,
+  },
 };
 
 class TexelDataRepresentationImpl {
   // TODO: Determine endianness of the GPU data?
+
   constructor(format, componentOrder, componentInfo, sRGB) {
     this.format = format;
     this.componentOrder = componentOrder;
     this.componentInfo = componentInfo;
     this.sRGB = sRGB;
-
-    _defineProperty(this, "isGPULittleEndian", true);
+    _defineProperty(this, 'isGPULittleEndian', true);
   }
 
   totalBitLength() {
@@ -306,110 +221,100 @@
       assert(!!componentInfo);
       return acc + componentInfo.bitLength;
     }, 0);
+
     const componentInfo = this.componentInfo[component];
     assert(!!componentInfo);
-    const {
-      write,
-      bitLength
-    } = componentInfo;
-    const {
-      value,
-      type
-    } = write(n);
+    const { write, bitLength } = componentInfo;
 
+    const { value, type } = write(n);
     switch (type) {
-      case TexelWriteType.Float:
-        {
-          const byteOffset = Math.floor(bitOffset / 8);
-          const byteLength = Math.ceil(bitLength / 8);
-          assert(byteOffset === bitOffset / 8 && byteLength === bitLength / 8);
-
-          switch (byteLength) {
-            case 4:
-              new DataView(data, byteOffset, byteLength).setFloat32(0, value, this.isGPULittleEndian);
-              break;
-
-            default:
-              unreachable();
-          }
-
-          break;
+      case TexelWriteType.Float: {
+        const byteOffset = Math.floor(bitOffset / 8);
+        const byteLength = Math.ceil(bitLength / 8);
+        assert(byteOffset === bitOffset / 8 && byteLength === bitLength / 8);
+        switch (byteLength) {
+          case 4:
+            new DataView(data, byteOffset, byteLength).setFloat32(0, value, this.isGPULittleEndian);
+            break;
+          default:
+            unreachable();
         }
 
-      case TexelWriteType.Sint:
-        {
-          const byteOffset = Math.floor(bitOffset / 8);
-          const byteLength = Math.ceil(bitLength / 8);
-          assert(byteOffset === bitOffset / 8 && byteLength === bitLength / 8);
+        break;
+      }
+      case TexelWriteType.Sint: {
+        const byteOffset = Math.floor(bitOffset / 8);
+        const byteLength = Math.ceil(bitLength / 8);
+        assert(byteOffset === bitOffset / 8 && byteLength === bitLength / 8);
+        switch (byteLength) {
+          case 1:
+            new DataView(data, byteOffset, byteLength).setInt8(0, value);
+            break;
+          case 2:
+            new DataView(data, byteOffset, byteLength).setInt16(0, value, this.isGPULittleEndian);
+            break;
+          case 4:
+            new DataView(data, byteOffset, byteLength).setInt32(0, value, this.isGPULittleEndian);
+            break;
+          default:
+            unreachable();
+        }
 
+        break;
+      }
+      case TexelWriteType.Uint: {
+        const byteOffset = Math.floor(bitOffset / 8);
+        const byteLength = Math.ceil(bitLength / 8);
+        if (byteOffset === bitOffset / 8 && byteLength === bitLength / 8) {
           switch (byteLength) {
             case 1:
-              new DataView(data, byteOffset, byteLength).setInt8(0, value);
+              new DataView(data, byteOffset, byteLength).setUint8(0, value);
               break;
-
             case 2:
-              new DataView(data, byteOffset, byteLength).setInt16(0, value, this.isGPULittleEndian);
-              break;
+              new DataView(data, byteOffset, byteLength).setUint16(
+                0,
+                value,
+                this.isGPULittleEndian
+              );
 
+              break;
             case 4:
-              new DataView(data, byteOffset, byteLength).setInt32(0, value, this.isGPULittleEndian);
-              break;
+              new DataView(data, byteOffset, byteLength).setUint32(
+                0,
+                value,
+                this.isGPULittleEndian
+              );
 
+              break;
             default:
               unreachable();
           }
+        } else {
+          // Packed representations are all 32-bit and use Uint as the data type.
+          // ex.) rg10b11float, rgb10a2unorm
+          switch (this.totalBitLength()) {
+            case 32: {
+              const view = new DataView(data);
+              const currentValue = view.getUint32(0, this.isGPULittleEndian);
 
-          break;
-        }
+              let mask = 0xffffffff;
+              const bitsToClearRight = bitOffset;
+              const bitsToClearLeft = 32 - (bitLength + bitOffset);
 
-      case TexelWriteType.Uint:
-        {
-          const byteOffset = Math.floor(bitOffset / 8);
-          const byteLength = Math.ceil(bitLength / 8);
+              mask = (mask >>> bitsToClearRight) << bitsToClearRight;
+              mask = (mask << bitsToClearLeft) >>> bitsToClearLeft;
 
-          if (byteOffset === bitOffset / 8 && byteLength === bitLength / 8) {
-            switch (byteLength) {
-              case 1:
-                new DataView(data, byteOffset, byteLength).setUint8(0, value);
-                break;
+              const newValue = (currentValue & ~mask) | (value << bitOffset);
 
-              case 2:
-                new DataView(data, byteOffset, byteLength).setUint16(0, value, this.isGPULittleEndian);
-                break;
-
-              case 4:
-                new DataView(data, byteOffset, byteLength).setUint32(0, value, this.isGPULittleEndian);
-                break;
-
-              default:
-                unreachable();
+              view.setUint32(0, newValue, this.isGPULittleEndian);
+              break;
             }
-          } else {
-            // Packed representations are all 32-bit and use Uint as the data type.
-            // ex.) rg10b11float, rgb10a2unorm
-            switch (this.totalBitLength()) {
-              case 32:
-                {
-                  const view = new DataView(data);
-                  const currentValue = view.getUint32(0, this.isGPULittleEndian);
-                  let mask = 0xffffffff;
-                  const bitsToClearRight = bitOffset;
-                  const bitsToClearLeft = 32 - (bitLength + bitOffset);
-                  mask = mask >>> bitsToClearRight << bitsToClearRight;
-                  mask = mask << bitsToClearLeft >>> bitsToClearLeft;
-                  const newValue = currentValue & ~mask | value << bitOffset;
-                  view.setUint32(0, newValue, this.isGPULittleEndian);
-                  break;
-                }
-
-              default:
-                unreachable();
-            }
+            default:
+              unreachable();
           }
-
-          break;
         }
-
+        break;
+      }
       default:
         unreachable();
     }
@@ -421,35 +326,34 @@
       assert(components.R !== undefined);
       assert(components.G !== undefined);
       assert(components.B !== undefined);
-      [components.R, components.G, components.B] = [gammaCompress(components.R), gammaCompress(components.G), gammaCompress(components.B)];
+      [components.R, components.G, components.B] = [
+        gammaCompress(components.R),
+        gammaCompress(components.G),
+        gammaCompress(components.B),
+      ];
     }
 
-    const bytesPerBlock = kTextureFormatInfo[this.format].bytesPerBlock;
+    const bytesPerBlock = kUncompressedTextureFormatInfo[this.format].bytesPerBlock;
     assert(!!bytesPerBlock);
-    const data = new ArrayBuffer(bytesPerBlock);
 
+    const data = new ArrayBuffer(bytesPerBlock);
     for (const c of this.componentOrder) {
       const componentValue = components[c];
       assert(componentValue !== undefined);
       this.setComponent(data, c, componentValue);
     }
-
     return data;
   }
-
 }
 
 const kRepresentationCache = new Map();
 export function getTexelDataRepresentation(format) {
   if (!kRepresentationCache.has(format)) {
-    const {
-      componentOrder,
-      componentInfo,
-      sRGB
-    } = kRepresentationInfo[format];
-    kRepresentationCache.set(format, new TexelDataRepresentationImpl(format, componentOrder, componentInfo, sRGB));
+    const { componentOrder, componentInfo, sRGB } = kRepresentationInfo[format];
+    kRepresentationCache.set(
+      format,
+      new TexelDataRepresentationImpl(format, componentOrder, componentInfo, sRGB)
+    );
   }
-
   return kRepresentationCache.get(format);
 }
-//# sourceMappingURL=texelData.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/web-platform/canvas/context_creation.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/web-platform/canvas/context_creation.spec.js
index 427b15fce..26dc5d8 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/web-platform/canvas/context_creation.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/web-platform/canvas/context_creation.spec.js
@@ -1,11 +1,11 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = '';
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = '';
 import { Fixture } from '../../../common/framework/fixture.js';
 import { makeTestGroup } from '../../../common/framework/test_group.js';
+
 export const g = makeTestGroup(Fixture);
+
 g.test('canvas_element_getContext_returns_GPUCanvasContext').fn(async t => {
   if (typeof document === 'undefined') {
     // Skip if there is no document (Workers, Node)
@@ -14,9 +14,11 @@
 
   const canvas = document.createElement('canvas');
   canvas.width = 10;
-  canvas.height = 10; // TODO: fix types so these aren't necessary
+  canvas.height = 10;
+
+  // TODO: fix types so these aren't necessary
 
   const ctx = canvas.getContext('gpupresent');
+
   t.expect(ctx instanceof window.GPUCanvasContext);
 });
-//# sourceMappingURL=context_creation.spec.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/web-platform/copyImageBitmapToTexture.spec.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/web-platform/copyImageBitmapToTexture.spec.js
index 411ee2d..9df5ff94 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/web-platform/copyImageBitmapToTexture.spec.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/web-platform/copyImageBitmapToTexture.spec.js
@@ -1,34 +1,55 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-export const description = `
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `
 copy imageBitmap To texture tests.
 `;
 import { poptions, params } from '../../common/framework/params_builder.js';
 import { makeTestGroup } from '../../common/framework/test_group.js';
+import { unreachable } from '../../common/framework/util/util.js';
+import { kUncompressedTextureFormatInfo } from '../capability_info.js';
 import { GPUTest } from '../gpu_test.js';
+import { getTexelDataRepresentation } from '../util/texture/texelData.js';
 
 function calculateRowPitch(width, bytesPerPixel) {
-  const bytesPerRow = width * bytesPerPixel; // Rounds up to a multiple of 256 according to WebGPU requirements.
-
-  return (bytesPerRow - 1 >> 8) + 1 << 8;
+  const bytesPerRow = width * bytesPerPixel;
+  // Rounds up to a multiple of 256 according to WebGPU requirements.
+  return (((bytesPerRow - 1) >> 8) + 1) << 8;
 }
+var Color;
+
+// Cache for generated pixels.
+(function (Color) {
+  Color[(Color['Red'] = 0)] = 'Red';
+  Color[(Color['Green'] = 1)] = 'Green';
+  Color[(Color['Blue'] = 2)] = 'Blue';
+  Color[(Color['White'] = 3)] = 'White';
+  Color[(Color['OpaqueBlack'] = 4)] = 'OpaqueBlack';
+  Color[(Color['TransparentBlack'] = 5)] = 'TransparentBlack';
+})(Color || (Color = {}));
+const generatedPixelCache = new Map();
 
 class F extends GPUTest {
   checkCopyImageBitmapResult(src, expected, width, height, bytesPerPixel) {
     const exp = new Uint8Array(expected.buffer, expected.byteOffset, expected.byteLength);
     const rowPitch = calculateRowPitch(width, bytesPerPixel);
     const dst = this.createCopyForMapRead(src, 0, rowPitch * height);
+
     this.eventualAsyncExpectation(async niceStack => {
-      const actual = new Uint8Array(await dst.mapReadAsync());
-      const check = this.checkBufferWithRowPitch(actual, exp, width, height, rowPitch, bytesPerPixel);
+      await dst.mapAsync(GPUMapMode.READ);
+      const actual = new Uint8Array(dst.getMappedRange());
+      const check = this.checkBufferWithRowPitch(
+        actual,
+        exp,
+        width,
+        height,
+        rowPitch,
+        bytesPerPixel
+      );
 
       if (check !== undefined) {
         niceStack.message = check;
         this.rec.expectationFailed(niceStack);
       }
-
       dst.destroy();
     });
   }
@@ -37,14 +58,11 @@
     const failedByteIndices = [];
     const failedByteExpectedValues = [];
     const failedByteActualValues = [];
-
     iLoop: for (let i = 0; i < height; ++i) {
       const bytesPerRow = width * bytesPerPixel;
-
       for (let j = 0; j < bytesPerRow; ++j) {
         const indexExp = j + i * bytesPerRow;
         const indexActual = j + rowPitch * i;
-
         if (actual[indexActual] !== exp[indexExp]) {
           if (failedByteIndices.length >= 4) {
             failedByteIndices.push('...');
@@ -52,201 +70,305 @@
             failedByteActualValues.push('...');
             break iLoop;
           }
-
           failedByteIndices.push(`(${i},${j})`);
           failedByteExpectedValues.push(exp[indexExp].toString());
           failedByteActualValues.push(actual[indexActual].toString());
         }
       }
     }
-
     if (failedByteIndices.length > 0) {
       return `at [${failedByteIndices.join(', ')}], \
 expected [${failedByteExpectedValues.join(', ')}], \
 got [${failedByteActualValues.join(', ')}]`;
     }
-
     return undefined;
   }
 
-  doTestAndCheckResult(imageBitmapCopyView, dstTextureCopyView, copySize, bytesPerPixel, expectedData) {
-    this.device.defaultQueue.copyImageBitmapToTexture(imageBitmapCopyView, dstTextureCopyView, copySize);
+  doTestAndCheckResult(
+    imageBitmapCopyView,
+    dstTextureCopyView,
+    copySize,
+    bytesPerPixel,
+    expectedData
+  ) {
+    this.device.defaultQueue.copyImageBitmapToTexture(
+      imageBitmapCopyView,
+      dstTextureCopyView,
+      copySize
+    );
+
     const imageBitmap = imageBitmapCopyView.imageBitmap;
     const dstTexture = dstTextureCopyView.texture;
+
     const bytesPerRow = calculateRowPitch(imageBitmap.width, bytesPerPixel);
     const testBuffer = this.device.createBuffer({
       size: bytesPerRow * imageBitmap.height,
-      usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+      usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
     });
+
     const encoder = this.device.createCommandEncoder();
-    encoder.copyTextureToBuffer({
-      texture: dstTexture,
-      mipLevel: 0,
-      origin: {
-        x: 0,
-        y: 0,
-        z: 0
-      }
-    }, {
-      buffer: testBuffer,
-      bytesPerRow
-    }, {
-      width: imageBitmap.width,
-      height: imageBitmap.height,
-      depth: 1
-    });
+
+    encoder.copyTextureToBuffer(
+      { texture: dstTexture, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
+      { buffer: testBuffer, bytesPerRow },
+      { width: imageBitmap.width, height: imageBitmap.height, depth: 1 }
+    );
+
     this.device.defaultQueue.submit([encoder.finish()]);
-    this.checkCopyImageBitmapResult(testBuffer, expectedData, imageBitmap.width, imageBitmap.height, bytesPerPixel);
+
+    this.checkCopyImageBitmapResult(
+      testBuffer,
+      expectedData,
+      imageBitmap.width,
+      imageBitmap.height,
+      bytesPerPixel
+    );
   }
 
+  generatePixel(color, format) {
+    var _generatedPixelCache$, _generatedPixelCache$3;
+    if (!generatedPixelCache.get(format)) {
+      generatedPixelCache.set(format, new Map());
+    }
+
+    // None of the dst texture format is 'uint' or 'sint', so we can always use float value.
+    if (
+      !((_generatedPixelCache$ = generatedPixelCache.get(format)) === null ||
+      _generatedPixelCache$ === void 0
+        ? void 0
+        : _generatedPixelCache$.has(color))
+    ) {
+      var _generatedPixelCache$2;
+      let pixels;
+      switch (color) {
+        case Color.Red:
+          pixels = new Uint8Array(
+            getTexelDataRepresentation(format).getBytes({ R: 1.0, G: 0, B: 0, A: 1.0 })
+          );
+
+          break;
+        case Color.Green:
+          pixels = new Uint8Array(
+            getTexelDataRepresentation(format).getBytes({ R: 0, G: 1.0, B: 0, A: 1.0 })
+          );
+
+          break;
+        case Color.Blue:
+          pixels = new Uint8Array(
+            getTexelDataRepresentation(format).getBytes({ R: 0, G: 0, B: 1.0, A: 1.0 })
+          );
+
+          break;
+        case Color.White:
+          pixels = new Uint8Array(
+            getTexelDataRepresentation(format).getBytes({ R: 0, G: 0, B: 0, A: 1.0 })
+          );
+
+          break;
+        case Color.OpaqueBlack:
+          pixels = new Uint8Array(
+            getTexelDataRepresentation(format).getBytes({ R: 1.0, G: 1.0, B: 1.0, A: 1.0 })
+          );
+
+          break;
+        case Color.TransparentBlack:
+          pixels = new Uint8Array(
+            getTexelDataRepresentation(format).getBytes({ R: 1.0, G: 1.0, B: 1.0, A: 0 })
+          );
+
+          break;
+        default:
+          unreachable();
+      }
+
+      (_generatedPixelCache$2 = generatedPixelCache.get(format)) === null ||
+      _generatedPixelCache$2 === void 0
+        ? void 0
+        : _generatedPixelCache$2.set(color, pixels);
+    }
+
+    return (_generatedPixelCache$3 = generatedPixelCache.get(format)) === null ||
+      _generatedPixelCache$3 === void 0
+      ? void 0
+      : _generatedPixelCache$3.get(color);
+  }
 }
 
 export const g = makeTestGroup(F);
-g.test('from_ImageData').params(params().combine(poptions('width', [1, 2, 4, 15, 255, 256])).combine(poptions('height', [1, 2, 4, 15, 255, 256])).combine(poptions('alpha', ['none', 'premultiply'])).combine(poptions('orientation', ['none', 'flipY']))).fn(async t => {
-  const {
-    width,
-    height,
-    alpha,
-    orientation
-  } = t.params; // The texture format is rgba8unorm, so the bytes per pixel is 4.
 
-  const bytesPerPixel = 4;
-  const imagePixels = new Uint8ClampedArray(bytesPerPixel * width * height);
+g.test('from_ImageData')
+  .params(
+    params()
+      .combine(poptions('width', [1, 2, 4, 15, 255, 256]))
+      .combine(poptions('height', [1, 2, 4, 15, 255, 256]))
+      .combine(poptions('alpha', ['none', 'premultiply']))
+      .combine(poptions('orientation', ['none', 'flipY']))
+      .combine(
+        poptions('dstColorFormat', [
+          'rgba8unorm',
+          'bgra8unorm',
+          'rgba8unorm-srgb',
+          'bgra8unorm-srgb',
+          'rgb10a2unorm',
+          'rgba16float',
+          'rgba32float',
+          'rg8unorm',
+          'rg16float',
+        ])
+      )
+  )
+  .fn(async t => {
+    const { width, height, alpha, orientation, dstColorFormat } = t.params;
 
-  if (alpha === 'premultiply') {
-    // Make expected value simple to construct:
-    // Input is (255, 255, 255, a), which will be stored into the ImageBitmap
-    // as (a, a, a, a).
-    for (let i = 0; i < width * height * bytesPerPixel; ++i) {
-      imagePixels[i] = i % 4 !== 3 ? 255 : i % 256;
+    const format = 'rgba8unorm';
+    const srcBytesPerPixel = kUncompressedTextureFormatInfo[format].bytesPerBlock;
+
+    // Generate input contents by iterating 'Color' enum
+    const imagePixels = new Uint8ClampedArray(srcBytesPerPixel * width * height);
+    const startPixel = Color.Red;
+    for (let i = 0, currentPixel = startPixel; i < width * height; ++i) {
+      const pixels = t.generatePixel(currentPixel, format);
+      if (currentPixel === Color.TransparentBlack) {
+        currentPixel = Color.Red;
+      } else {
+        ++currentPixel;
+      }
+      for (let j = 0; j < srcBytesPerPixel; ++j) {
+        imagePixels[i * srcBytesPerPixel + j] = pixels[j];
+      }
     }
-  } else {
+
+    // Generate correct expected values
+    const imageData = new ImageData(imagePixels, width, height);
+
+    const imageBitmap = await createImageBitmap(imageData, {
+      premultiplyAlpha: alpha,
+      imageOrientation: orientation,
+    });
+
+    const dst = t.device.createTexture({
+      size: {
+        width: imageBitmap.width,
+        height: imageBitmap.height,
+        depth: 1,
+      },
+
+      format: dstColorFormat,
+      usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC,
+    });
+
+    // Construct expected value for different dst color format
+    const dstBytesPerPixel = kUncompressedTextureFormatInfo[dstColorFormat].bytesPerBlock;
+    const dstPixels = new Uint8ClampedArray(dstBytesPerPixel * width * height);
+    let expectedPixels = new Uint8ClampedArray(dstBytesPerPixel * width * height);
+    for (let i = 0, currentPixel = startPixel; i < width * height; ++i) {
+      const pixels = t.generatePixel(currentPixel, dstColorFormat);
+      for (let j = 0; j < dstBytesPerPixel; ++j) {
+        // All pixels are 0 due to premultiply alpha
+        if (alpha === 'premultiply' && currentPixel === Color.TransparentBlack) {
+          dstPixels[i * dstBytesPerPixel + j] = 0;
+        } else {
+          dstPixels[i * dstBytesPerPixel + j] = pixels[j];
+        }
+      }
+
+      if (currentPixel === Color.TransparentBlack) {
+        currentPixel = Color.Red;
+      } else {
+        ++currentPixel;
+      }
+    }
+
+    if (orientation === 'flipY') {
+      for (let i = 0; i < height; ++i) {
+        for (let j = 0; j < width * dstBytesPerPixel; ++j) {
+          const posImagePixel = (height - i - 1) * width * dstBytesPerPixel + j;
+          const posExpectedValue = i * width * dstBytesPerPixel + j;
+          expectedPixels[posExpectedValue] = dstPixels[posImagePixel];
+        }
+      }
+    } else {
+      expectedPixels = dstPixels;
+    }
+
+    t.doTestAndCheckResult(
+      { imageBitmap, origin: { x: 0, y: 0 } },
+      { texture: dst },
+      { width: imageBitmap.width, height: imageBitmap.height, depth: 1 },
+      dstBytesPerPixel,
+      expectedPixels
+    );
+  });
+
+g.test('from_canvas')
+  .params(
+    params()
+      .combine(poptions('width', [1, 2, 4, 15, 255, 256]))
+      .combine(poptions('height', [1, 2, 4, 15, 255, 256]))
+  )
+  .fn(async t => {
+    const { width, height } = t.params;
+
+    // CTS sometimes runs on worker threads, where document is not available.
+    // In this case, OffscreenCanvas can be used instead of <canvas>.
+    // But some browsers don't support OffscreenCanvas, and some don't
+    // support '2d' contexts on OffscreenCanvas.
+    // In this situation, the case will be skipped.
+    let imageCanvas;
+    if (typeof document !== 'undefined') {
+      imageCanvas = document.createElement('canvas');
+      imageCanvas.width = width;
+      imageCanvas.height = height;
+    } else if (typeof OffscreenCanvas === 'undefined') {
+      t.skip('OffscreenCanvas is not supported');
+      return;
+    } else {
+      imageCanvas = new OffscreenCanvas(width, height);
+    }
+    const imageCanvasContext = imageCanvas.getContext('2d');
+    if (imageCanvasContext === null) {
+      t.skip('OffscreenCanvas "2d" context not available');
+      return;
+    }
+
+    // The texture format is rgba8unorm, so the bytes per pixel is 4.
+    const bytesPerPixel = 4;
+
+    // Generate original data.
+    const imagePixels = new Uint8ClampedArray(bytesPerPixel * width * height);
     for (let i = 0; i < width * height * bytesPerPixel; ++i) {
       imagePixels[i] = i % 4 === 3 ? 255 : i % 256;
     }
-  }
 
-  const imageData = new ImageData(imagePixels, width, height);
-  const imageBitmap = await createImageBitmap(imageData, {
-    premultiplyAlpha: alpha,
-    imageOrientation: orientation
+    const imageData = new ImageData(imagePixels, width, height);
+    imageCanvasContext.putImageData(imageData, 0, 0);
+
+    const imageBitmap = await createImageBitmap(imageCanvas);
+
+    const dst = t.device.createTexture({
+      size: {
+        width: imageBitmap.width,
+        height: imageBitmap.height,
+        depth: 1,
+      },
+
+      format: 'rgba8unorm',
+      usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC,
+    });
+
+    // This will get origin data and even it has premultiplied-alpha
+    const expectedData = imageCanvasContext.getImageData(
+      0,
+      0,
+      imageBitmap.width,
+      imageBitmap.height
+    ).data;
+
+    t.doTestAndCheckResult(
+      { imageBitmap, origin: { x: 0, y: 0 } },
+      { texture: dst },
+      { width: imageBitmap.width, height: imageBitmap.height, depth: 1 },
+      bytesPerPixel,
+      expectedData
+    );
   });
-  const dst = t.device.createTexture({
-    size: {
-      width: imageBitmap.width,
-      height: imageBitmap.height,
-      depth: 1
-    },
-    format: 'rgba8unorm',
-    usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC
-  }); // Construct expected value
-
-  const expectedPixels = new Uint8ClampedArray(bytesPerPixel * width * height);
-
-  for (let i = 0; i < width * height * bytesPerPixel; ++i) {
-    expectedPixels[i] = imagePixels[i];
-  }
-
-  if (orientation === 'flipY') {
-    for (let i = 0; i < height; ++i) {
-      for (let j = 0; j < width * bytesPerPixel; ++j) {
-        const pos_image_pixel = (height - i - 1) * width * bytesPerPixel + j;
-        const pos_expected_value = i * width * bytesPerPixel + j;
-        expectedPixels[pos_expected_value] = imagePixels[pos_image_pixel];
-      }
-    }
-  }
-
-  if (alpha === 'premultiply') {
-    for (let i = 0; i < width * height * bytesPerPixel; ++i) {
-      const alpha_value_position = 3 - i % 4 + i;
-
-      if (i % 4 !== 3) {
-        // Expected value is (a, a, a, a)
-        expectedPixels[i] = expectedPixels[alpha_value_position];
-      }
-    }
-  }
-
-  t.doTestAndCheckResult({
-    imageBitmap,
-    origin: {
-      x: 0,
-      y: 0
-    }
-  }, {
-    texture: dst
-  }, {
-    width: imageBitmap.width,
-    height: imageBitmap.height,
-    depth: 1
-  }, bytesPerPixel, expectedPixels);
-});
-g.test('from_canvas').params(params().combine(poptions('width', [1, 2, 4, 15, 255, 256])).combine(poptions('height', [1, 2, 4, 15, 255, 256]))).fn(async t => {
-  const {
-    width,
-    height
-  } = t.params; // CTS sometimes runs on worker threads, where document is not available.
-  // In this case, OffscreenCanvas can be used instead of <canvas>.
-  // But some browsers don't support OffscreenCanvas, and some don't
-  // support '2d' contexts on OffscreenCanvas.
-  // In this situation, the case will be skipped.
-
-  let imageCanvas;
-
-  if (typeof document !== 'undefined') {
-    imageCanvas = document.createElement('canvas');
-    imageCanvas.width = width;
-    imageCanvas.height = height;
-  } else if (typeof OffscreenCanvas === 'undefined') {
-    t.skip('OffscreenCanvas is not supported');
-    return;
-  } else {
-    imageCanvas = new OffscreenCanvas(width, height);
-  }
-
-  const imageCanvasContext = imageCanvas.getContext('2d');
-
-  if (imageCanvasContext === null) {
-    t.skip('OffscreenCanvas "2d" context not available');
-    return;
-  } // The texture format is rgba8unorm, so the bytes per pixel is 4.
-
-
-  const bytesPerPixel = 4; // Generate original data.
-
-  const imagePixels = new Uint8ClampedArray(bytesPerPixel * width * height);
-
-  for (let i = 0; i < width * height * bytesPerPixel; ++i) {
-    imagePixels[i] = i % 4 === 3 ? 255 : i % 256;
-  }
-
-  const imageData = new ImageData(imagePixels, width, height);
-  imageCanvasContext.putImageData(imageData, 0, 0);
-  const imageBitmap = await createImageBitmap(imageCanvas);
-  const dst = t.device.createTexture({
-    size: {
-      width: imageBitmap.width,
-      height: imageBitmap.height,
-      depth: 1
-    },
-    format: 'rgba8unorm',
-    usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC
-  }); // This will get origin data and even it has premultiplied-alpha
-
-  const expectedData = imageCanvasContext.getImageData(0, 0, imageBitmap.width, imageBitmap.height).data;
-  t.doTestAndCheckResult({
-    imageBitmap,
-    origin: {
-      x: 0,
-      y: 0
-    }
-  }, {
-    texture: dst
-  }, {
-    width: imageBitmap.width,
-    height: imageBitmap.height,
-    depth: 1
-  }, bytesPerPixel, expectedData);
-});
-//# sourceMappingURL=copyImageBitmapToTexture.spec.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/web-platform/reftests/canvas_clear.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/web-platform/reftests/canvas_clear.js
index 52ffaed..a9149f0 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/web-platform/reftests/canvas_clear.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/web-platform/reftests/canvas_clear.js
@@ -1,31 +1,29 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
-
-import { runRefTest } from './gpu_ref_test.js';
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ import { runRefTest } from './gpu_ref_test.js';
 runRefTest(async t => {
   const canvas = document.getElementById('gpucanvas');
+
   const ctx = canvas.getContext('gpupresent');
   const swapChain = ctx.configureSwapChain({
     device: t.device,
-    format: 'bgra8unorm'
+    format: 'bgra8unorm',
   });
+
   const colorAttachment = swapChain.getCurrentTexture();
   const colorAttachmentView = colorAttachment.createView();
+
   const encoder = t.device.createCommandEncoder();
   const pass = encoder.beginRenderPass({
-    colorAttachments: [{
-      attachment: colorAttachmentView,
-      loadValue: {
-        r: 0.0,
-        g: 1.0,
-        b: 0.0,
-        a: 1.0
+    colorAttachments: [
+      {
+        attachment: colorAttachmentView,
+        loadValue: { r: 0.0, g: 1.0, b: 0.0, a: 1.0 },
+        storeOp: 'store',
       },
-      storeOp: 'store'
-    }]
+    ],
   });
+
   pass.endPass();
   t.device.defaultQueue.submit([encoder.finish()]);
 });
-//# sourceMappingURL=canvas_clear.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/web-platform/reftests/canvas_complex.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/web-platform/reftests/canvas_complex.js
index 1e63a0a..f2271d0 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/web-platform/reftests/canvas_complex.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/web-platform/reftests/canvas_complex.js
@@ -1,9 +1,9 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ import { unreachable } from '../../../common/framework/util/util.js';
+import { runRefTest } from './gpu_ref_test.js';
 
-import { unreachable } from '../../../common/framework/util/util.js';
-import { runRefTest } from './gpu_ref_test.js'; // <canvas> element from html page
+// <canvas> element from html page
 
 export function run(format) {
   runRefTest(async t => {
@@ -13,7 +13,6 @@
       case 'bgra8unorm':
       case 'rgba16float':
         break;
-
       default:
         unreachable();
     }
@@ -21,29 +20,28 @@
     const swapChain = ctx.configureSwapChain({
       device: t.device,
       format,
-      usage: GPUTextureUsage.COPY_DST
-    });
-    const rows = 2;
-    const bytesPerRow = 256;
-    const [buffer, mapping] = t.device.createBufferMapped({
-      size: rows * bytesPerRow,
-      usage: GPUBufferUsage.COPY_SRC
+      usage: GPUTextureUsage.COPY_DST,
     });
 
+    const rows = 2;
+    const bytesPerRow = 256;
+    const buffer = t.device.createBuffer({
+      mappedAtCreation: true,
+      size: rows * bytesPerRow,
+      usage: GPUBufferUsage.COPY_SRC,
+    });
+
+    const mapping = buffer.getMappedRange();
     switch (format) {
       case 'bgra8unorm':
         {
           const data = new Uint8Array(mapping);
           data.set(new Uint8Array([0x00, 0x00, 0x7f, 0xff]), 0); // red
-
           data.set(new Uint8Array([0x00, 0x7f, 0x00, 0xff]), 4); // green
-
           data.set(new Uint8Array([0x7f, 0x00, 0x00, 0xff]), 256 + 0); // blue
-
           data.set(new Uint8Array([0x00, 0x7f, 0x7f, 0xff]), 256 + 4); // yellow
         }
         break;
-
       case 'rgba16float':
         {
           // Untested!
@@ -52,22 +50,18 @@
           const one = 0x3c00;
           const data = new DataView(mapping);
           data.setUint16(0x000, half, false); // red
-
           data.setUint16(0x002, zero, false);
           data.setUint16(0x004, zero, false);
           data.setUint16(0x008, one, false);
           data.setUint16(0x010, zero, false); // green
-
           data.setUint16(0x020, half, false);
           data.setUint16(0x040, zero, false);
           data.setUint16(0x080, one, false);
           data.setUint16(0x100, zero, false); // blue
-
           data.setUint16(0x102, zero, false);
           data.setUint16(0x104, half, false);
           data.setUint16(0x108, one, false);
           data.setUint16(0x110, half, false); // yellow
-
           data.setUint16(0x120, half, false);
           data.setUint16(0x140, zero, false);
           data.setUint16(0x180, one, false);
@@ -76,15 +70,11 @@
     }
 
     buffer.unmap();
+
     const texture = swapChain.getCurrentTexture();
+
     const encoder = t.device.createCommandEncoder();
-    encoder.copyBufferToTexture({
-      buffer,
-      bytesPerRow
-    }, {
-      texture
-    }, [2, 2, 1]);
+    encoder.copyBufferToTexture({ buffer, bytesPerRow }, { texture }, [2, 2, 1]);
     t.device.defaultQueue.submit([encoder.finish()]);
   });
 }
-//# sourceMappingURL=canvas_complex.js.map
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/web-platform/reftests/gpu_ref_test.js b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/web-platform/reftests/gpu_ref_test.js
index f45f232..a4500283 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/web-platform/reftests/gpu_ref_test.js
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/webgpu/web-platform/reftests/gpu_ref_test.js
@@ -1,17 +1,20 @@
 /**
-* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
-**/
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ import { assert } from '../../../common/framework/util/util.js';
 
-import { assert } from '../../../common/framework/util/util.js';
 export async function runRefTest(fn) {
-  assert(typeof navigator !== 'undefined' && navigator.gpu !== undefined, 'No WebGPU implementation found');
+  assert(
+    typeof navigator !== 'undefined' && navigator.gpu !== undefined,
+    'No WebGPU implementation found'
+  );
+
   const adapter = await navigator.gpu.requestAdapter();
+  assert(adapter !== null);
   const device = await adapter.requestDevice();
+  assert(device !== null);
   const queue = device.defaultQueue;
-  await fn({
-    device,
-    queue
-  });
+
+  await fn({ device, queue });
+
   takeScreenshotDelayed(50);
 }
-//# sourceMappingURL=gpu_ref_test.js.map
\ No newline at end of file
diff --git a/tools/accessibility/inspect/ax_tree_server.cc b/tools/accessibility/inspect/ax_tree_server.cc
index 334e4ab..aeb05da 100644
--- a/tools/accessibility/inspect/ax_tree_server.cc
+++ b/tools/accessibility/inspect/ax_tree_server.cc
@@ -75,27 +75,26 @@
                              base::SPLIT_WANT_ALL)) {
         if (base::StartsWith(line, kAllowOptEmptyStr,
                              base::CompareCase::SENSITIVE)) {
-          filters.push_back(AccessibilityTreeFormatter::PropertyFilter(
-              base::UTF8ToUTF16(line.substr(strlen(kAllowOptEmptyStr))),
-              AccessibilityTreeFormatter::PropertyFilter::ALLOW_EMPTY));
+          filters.emplace_back(
+              line.substr(strlen(kAllowOptEmptyStr)),
+              AccessibilityTreeFormatter::PropertyFilter::ALLOW_EMPTY);
         } else if (base::StartsWith(line, kAllowOptStr,
                                     base::CompareCase::SENSITIVE)) {
-          filters.push_back(AccessibilityTreeFormatter::PropertyFilter(
-              base::UTF8ToUTF16(line.substr(strlen(kAllowOptStr))),
-              AccessibilityTreeFormatter::PropertyFilter::ALLOW));
+          filters.emplace_back(
+              line.substr(strlen(kAllowOptStr)),
+              AccessibilityTreeFormatter::PropertyFilter::ALLOW);
         } else if (base::StartsWith(line, kDenyOptStr,
                                     base::CompareCase::SENSITIVE)) {
-          filters.push_back(AccessibilityTreeFormatter::PropertyFilter(
-              base::UTF8ToUTF16(line.substr(strlen(kDenyOptStr))),
-              AccessibilityTreeFormatter::PropertyFilter::DENY));
+          filters.emplace_back(
+              line.substr(strlen(kDenyOptStr)),
+              AccessibilityTreeFormatter::PropertyFilter::DENY);
         }
       }
     }
   }
   if (filters.empty()) {
     filters = {AccessibilityTreeFormatter::PropertyFilter(
-        base::ASCIIToUTF16("*"),
-        AccessibilityTreeFormatter::PropertyFilter::ALLOW)};
+        "*", AccessibilityTreeFormatter::PropertyFilter::ALLOW)};
   }
 
   return filters;
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 82335fc4..72ac0d0 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -21447,6 +21447,7 @@
   <int value="776" label="FileSystemReadBlockedForUrls"/>
   <int value="777" label="FileSystemWriteAskForUrls"/>
   <int value="778" label="FileSystemWriteBlockedForUrls"/>
+  <int value="779" label="PrintersBulkBlocklist"/>
 </enum>
 
 <enum name="EnterprisePolicyDeviceIdValidity">
@@ -56171,6 +56172,7 @@
   <int value="22" label="DLC_NEED_SPACE"/>
   <int value="23" label="INSUFFICIENT_DISK_SPACE"/>
   <int value="24" label="INVALID_LICENSE"/>
+  <int value="25" label="OFFLINE"/>
 </enum>
 
 <enum name="PluginVmSetupResult">
@@ -64524,6 +64526,9 @@
 </enum>
 
 <enum name="ShowAllSavedPasswordsContext">
+  <obsolete>
+    Deprecated 08/2020.
+  </obsolete>
   <int value="0" label="None"/>
   <int value="1" label="Under password suggestions"/>
   <int value="2" label="Standalone suggestion"/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 5223708..e4851003 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -121910,6 +121910,9 @@
 <histogram
     name="PageLoad.Clients.FontPreload.LayoutInstability.CumulativeShiftScore"
     units="scorex10" expires_after="2020-08-31">
+  <obsolete>
+    Removed in August 2020 as the behavior is launched
+  </obsolete>
   <owner>xiaochengh@chromium.org</owner>
   <owner>rendering-core-dev@chromium.org</owner>
   <summary>
@@ -121921,6 +121924,9 @@
 <histogram
     name="PageLoad.Clients.FontPreload.PaintTiming.NavigationToLargestImagePaint"
     units="ms" expires_after="2020-08-31">
+  <obsolete>
+    Removed in August 2020 as the behavior is launched
+  </obsolete>
   <owner>xiaochengh@chromium.org</owner>
   <owner>rendering-core-dev@chromium.org</owner>
   <summary>
@@ -121933,6 +121939,9 @@
 <histogram
     name="PageLoad.Clients.FontPreload.PaintTiming.NavigationToLargestTextPaint"
     units="ms" expires_after="2020-08-31">
+  <obsolete>
+    Removed in August 2020 as the behavior is launched
+  </obsolete>
   <owner>xiaochengh@chromium.org</owner>
   <owner>rendering-core-dev@chromium.org</owner>
   <summary>
@@ -128563,6 +128572,9 @@
 
 <histogram name="PasswordManager.ShowAllSavedPasswordsAcceptedContext"
     enum="ShowAllSavedPasswordsContext" expires_after="2020-09-30">
+  <obsolete>
+    Removed 08/2020.
+  </obsolete>
   <owner>vasilii@chromium.org</owner>
   <owner>fhorschig@chromium.org</owner>
   <summary>
@@ -128572,6 +128584,9 @@
 
 <histogram name="PasswordManager.ShowAllSavedPasswordsShownContext"
     enum="ShowAllSavedPasswordsContext" expires_after="2020-09-30">
+  <obsolete>
+    Removed 08/2020.
+  </obsolete>
   <owner>vasilii@chromium.org</owner>
   <owner>fhorschig@chromium.org</owner>
   <summary>
@@ -210288,7 +210303,11 @@
     ordering="prefix">
   <suffix name="Clients.FontPreload"
       label="PageLoadMetrics when the first rendering cycle has been delayed
-             in favor of font preloading"/>
+             in favor of font preloading">
+    <obsolete>
+      Removed in August 2020 as the behavior is launched
+    </obsolete>
+  </suffix>
   <affected-histogram
       name="PageLoad.PaintTiming.NavigationToFirstContentfulPaint"/>
   <affected-histogram name="PageLoad.PaintTiming.NavigationToFirstPaint"/>
diff --git a/ui/file_manager/file_manager/test/scripts/create_test_main.py b/ui/file_manager/file_manager/test/scripts/create_test_main.py
index 3c9cd40..b38d213 100755
--- a/ui/file_manager/file_manager/test/scripts/create_test_main.py
+++ b/ui/file_manager/file_manager/test/scripts/create_test_main.py
@@ -212,6 +212,7 @@
     ('foreground/js/elements_importer.js', (
         ("= 'foreground", "= 'test/gen/foreground"),
     )),
+    ('foreground/elements/files_password_dialog.html', ()),
     ('foreground/elements/files_format_dialog.html', ()),
     ('foreground/elements/files_icon_button.html', ()),
     ('foreground/elements/files_message.html', ()),
diff --git a/ui/ozone/platform/wayland/BUILD.gn b/ui/ozone/platform/wayland/BUILD.gn
index c6930f2..6b87d5c 100644
--- a/ui/ozone/platform/wayland/BUILD.gn
+++ b/ui/ozone/platform/wayland/BUILD.gn
@@ -174,7 +174,6 @@
     "//ui/ozone/common",
     "//ui/ozone/public/mojom/wayland:wayland_mojom",
     "//ui/platform_window",
-    "//ui/platform_window/extensions",
     "//ui/platform_window/wm",
   ]
 
@@ -358,7 +357,6 @@
     "//ui/gfx/linux:test_support",
     "//ui/ozone:platform",
     "//ui/ozone:test_support",
-    "//ui/platform_window/extensions",
     "//ui/platform_window/wm",
   ]
 
diff --git a/ui/ozone/platform/wayland/host/wayland_data_device.cc b/ui/ozone/platform/wayland/host/wayland_data_device.cc
index 932b1e4..f80738e5 100644
--- a/ui/ozone/platform/wayland/host/wayland_data_device.cc
+++ b/ui/ozone/platform/wayland/host/wayland_data_device.cc
@@ -149,15 +149,15 @@
 
 void WaylandDataDevice::OnLeave(void* data, wl_data_device* data_device) {
   auto* self = static_cast<WaylandDataDevice*>(data);
-  if (self->drag_delegate_) {
+  if (self->drag_delegate_)
     self->drag_delegate_->OnDragLeave();
 
-    // When in a DND session initiated by an external application,
-    // |drag_delegate_| is set at OnEnter, and must be reset here to avoid
-    // potential use-after-free.
-    if (!self->drag_delegate_->IsDragSource())
-      self->drag_delegate_ = nullptr;
-  }
+  // When in a DND session initiated by an external application,
+  // |drag_delegate_| is set at OnEnter, and must be reset here to avoid
+  // potential use-after-free. Above call to OnDragLeave() may result in
+  // |drag_delegate_| being reset, so it must be checked here as well.
+  if (self->drag_delegate_ && !self->drag_delegate_->IsDragSource())
+    self->drag_delegate_ = nullptr;
 }
 
 void WaylandDataDevice::OnSelection(void* data,
diff --git a/ui/ozone/platform/wayland/host/wayland_window_drag_controller.cc b/ui/ozone/platform/wayland/host/wayland_window_drag_controller.cc
index 6f809eb..64ffd8b4 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_drag_controller.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window_drag_controller.cc
@@ -199,9 +199,20 @@
   // which would require hacky workarounds in HandleDropAndResetState function
   // to properly detect and handle such cases.
 
-  VLOG(1) << "OnLeave";
+  if (!data_offer_)
+    return;
 
+  VLOG(1) << "OnLeave";
   data_offer_.reset();
+
+  // As Wayland clients are only aware of surface-local coordinates and there is
+  // no implicit grab during DND sessions, a fake motion event with negative
+  // coordinates must be used here to make it possible for higher level UI
+  // components to detect when a window should be detached. E.g: On Chrome,
+  // dragging a tab all the way up to the top edge of the window won't work
+  // without this fake motion event upon wl_data_device::leave events.
+  if (state_ == State::kAttached)
+    pointer_delegate_->OnPointerMotionEvent({-1, -1});
 }
 
 void WaylandWindowDragController::OnDragDrop() {
diff --git a/ui/ozone/platform/wayland/host/wayland_window_drag_controller_unittest.cc b/ui/ozone/platform/wayland/host/wayland_window_drag_controller_unittest.cc
index 97ec3766..4f24aad 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_drag_controller_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window_drag_controller_unittest.cc
@@ -299,6 +299,7 @@
   SendPointerEnter(window_.get(), &delegate_);
   SendPointerPress(window_.get(), &delegate_, BTN_LEFT);
   SendPointerMotion(window_.get(), &delegate_, {10, 10});
+  Sync();
 
   // Sets up an "interaction flow", start the drag session and run move loop:
   //  - Event dispatching and bounds changes are monitored
@@ -311,13 +312,7 @@
   auto* move_loop_handler = GetWmMoveLoopHandler(*window_);
   DCHECK(move_loop_handler);
 
-  enum {
-    kStarted,
-    kDragging,
-    kExitedWindow,
-    kDropping,
-    kDone
-  } test_step = kStarted;
+  enum { kStarted, kDragging, kExitedDropping, kDone } test_step = kStarted;
 
   EXPECT_CALL(delegate_, DispatchEvent(_)).WillRepeatedly([&](Event* event) {
     EXPECT_TRUE(event->IsMouseEvent());
@@ -332,13 +327,7 @@
         SendDndMotion({20, 20});
         test_step = kDragging;
         break;
-      case kExitedWindow:
-        EXPECT_EQ(ET_MOUSE_EXITED, event->type());
-        // Release mouse button with no window foucsed.
-        SendDndDrop();
-        test_step = kDropping;
-        break;
-      case kDropping:
+      case kExitedDropping:
         EXPECT_EQ(ET_MOUSE_RELEASED, event->type());
         EXPECT_EQ(State::kDropped, drag_controller()->state());
         // Ensure PlatformScreen keeps consistent.
@@ -365,8 +354,9 @@
         EXPECT_EQ(kDragging, test_step);
         EXPECT_EQ(gfx::Point(20, 20), bounds.origin());
 
+        SendDndLeave();
         SendDndDrop();
-        test_step = kDropping;
+        test_step = kExitedDropping;
       });
 
   // RunMoveLoop() blocks until the dragging sessions ends, so resume test
@@ -536,6 +526,50 @@
             screen_->GetLocalProcessWidgetAtPoint({20, 20}, {}));
 }
 
+// Verifies wl_data_device::leave events are properly handled and propagated
+// while in window dragging "attached" mode.
+TEST_P(WaylandWindowDragControllerTest, DragExitAttached) {
+  // Ensure there is no window currently focused
+  EXPECT_FALSE(window_manager()->GetCurrentFocusedWindow());
+  EXPECT_EQ(gfx::kNullAcceleratedWidget,
+            screen_->GetLocalProcessWidgetAtPoint({10, 10}, {}));
+
+  SendPointerEnter(window_.get(), &delegate_);
+  SendPointerPress(window_.get(), &delegate_, BTN_LEFT);
+  SendPointerMotion(window_.get(), &delegate_, {10, 10});
+  Sync();
+  EXPECT_EQ(window_->GetWidget(),
+            screen_->GetLocalProcessWidgetAtPoint({10, 10}, {}));
+
+  auto* wayland_extension = GetWaylandExtension(*window_);
+  wayland_extension->StartWindowDraggingSessionIfNeeded();
+  EXPECT_CALL(delegate_, DispatchEvent(_)).Times(1);
+  Sync();
+  Sync();
+  EXPECT_EQ(State::kAttached, drag_controller()->state());
+
+  // Emulate a wl_data_device::leave and make sure a motion event is dispatched
+  // in response.
+  SendDndLeave();
+  EXPECT_CALL(delegate_, DispatchEvent(_)).WillOnce([&](Event* event) {
+    EXPECT_EQ(ET_MOUSE_DRAGGED, event->type());
+    EXPECT_EQ(gfx::Point(-1, -1).ToString(),
+              event->AsMouseEvent()->location().ToString());
+  });
+  Sync();
+
+  SendDndDrop();
+  EXPECT_CALL(delegate_, DispatchEvent(_)).Times(1);
+  Sync();
+
+  SendPointerEnter(window_.get(), &delegate_);
+  Sync();
+
+  EXPECT_EQ(window_.get(), window_manager()->GetCurrentFocusedWindow());
+  EXPECT_EQ(window_->GetWidget(),
+            screen_->GetLocalProcessWidgetAtPoint({20, 20}, {}));
+}
+
 INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
                          WaylandWindowDragControllerTest,
                          ::testing::Values(kXdgShellStable));
diff --git a/ui/platform_window/BUILD.gn b/ui/platform_window/BUILD.gn
index f06af0f..a2d20c96 100644
--- a/ui/platform_window/BUILD.gn
+++ b/ui/platform_window/BUILD.gn
@@ -6,6 +6,9 @@
 
 component("platform_window") {
   sources = [
+    "extensions/workspace_extension.cc",
+    "extensions/workspace_extension.h",
+    "extensions/workspace_extension_delegate.h",
     "platform_window.cc",
     "platform_window.h",
     "platform_window_delegate.cc",
@@ -37,6 +40,16 @@
       "//third_party/fuchsia-sdk/sdk/pkg/sys_cpp",
     ]
   }
+
+  if (is_linux || is_chromeos) {
+    sources += [
+      "extensions/wayland_extension.cc",
+      "extensions/wayland_extension.h",
+      "extensions/x11_extension.cc",
+      "extensions/x11_extension.h",
+      "extensions/x11_extension_delegate.h",
+    ]
+  }
 }
 
 group("platform_impls") {
diff --git a/ui/platform_window/extensions/BUILD.gn b/ui/platform_window/extensions/BUILD.gn
deleted file mode 100644
index 390ee4c..0000000
--- a/ui/platform_window/extensions/BUILD.gn
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright 2019 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//build/config/ui.gni")
-
-source_set("extensions") {
-  sources = [
-    "workspace_extension.cc",
-    "workspace_extension.h",
-    "workspace_extension_delegate.h",
-  ]
-
-  defines = [ "IS_EXTENSIONS_IMPL" ]
-
-  deps = [
-    "//base",
-    "//ui/base",
-    "//ui/platform_window",
-  ]
-
-  if (is_linux || is_chromeos) {
-    sources += [
-      "wayland_extension.cc",
-      "wayland_extension.h",
-      "x11_extension.cc",
-      "x11_extension.h",
-      "x11_extension_delegate.h",
-    ]
-  }
-}
diff --git a/ui/platform_window/extensions/wayland_extension.h b/ui/platform_window/extensions/wayland_extension.h
index 6b288dd..36e34420 100644
--- a/ui/platform_window/extensions/wayland_extension.h
+++ b/ui/platform_window/extensions/wayland_extension.h
@@ -11,7 +11,7 @@
 
 class PlatformWindow;
 
-class COMPONENT_EXPORT(EXTENSIONS) WaylandExtension {
+class COMPONENT_EXPORT(PLATFORM_WINDOW) WaylandExtension {
  public:
   // Starts a window dragging session for the owning platform window, if
   // it is not running yet. Under Wayland, window dragging is backed by a
@@ -25,7 +25,7 @@
   void SetWaylandExtension(PlatformWindow* window, WaylandExtension* extension);
 };
 
-COMPONENT_EXPORT(EXTENSIONS)
+COMPONENT_EXPORT(PLATFORM_WINDOW)
 WaylandExtension* GetWaylandExtension(const PlatformWindow& window);
 
 }  // namespace ui
diff --git a/ui/platform_window/extensions/workspace_extension.h b/ui/platform_window/extensions/workspace_extension.h
index e36b1464..83c806a 100644
--- a/ui/platform_window/extensions/workspace_extension.h
+++ b/ui/platform_window/extensions/workspace_extension.h
@@ -19,7 +19,7 @@
 // owned by a PlatformWindow owner. To avoid casts from the PlatformWindow to
 // the WorkspaceExtension, a pointer to this interface must be set through
 // "SetWorkspaceExtension".
-class COMPONENT_EXPORT(EXTENSIONS) WorkspaceExtension {
+class COMPONENT_EXPORT(PLATFORM_WINDOW) WorkspaceExtension {
  public:
   // Returns the workspace the PlatformWindow is located in.
   virtual std::string GetWorkspace() const = 0;
@@ -44,7 +44,7 @@
                              WorkspaceExtension* workspace_extension);
 };
 
-COMPONENT_EXPORT(EXTENSIONS)
+COMPONENT_EXPORT(PLATFORM_WINDOW)
 WorkspaceExtension* GetWorkspaceExtension(
     const PlatformWindow& platform_window);
 
diff --git a/ui/platform_window/extensions/workspace_extension_delegate.h b/ui/platform_window/extensions/workspace_extension_delegate.h
index 8b1688e..2c210738 100644
--- a/ui/platform_window/extensions/workspace_extension_delegate.h
+++ b/ui/platform_window/extensions/workspace_extension_delegate.h
@@ -11,7 +11,7 @@
 
 // Notifies the delegate about changed workspace. The delegate must be set in
 // WorkspaceExtension to be able to receive these changes.
-class COMPONENT_EXPORT(EXTENSIONS) WorkspaceExtensionDelegate {
+class COMPONENT_EXPORT(PLATFORM_WINDOW) WorkspaceExtensionDelegate {
  public:
   // Notifies the delegate if the window has changed the workspace it is
   // located in.
diff --git a/ui/platform_window/extensions/x11_extension.h b/ui/platform_window/extensions/x11_extension.h
index d1525ba49..b7bb72d89 100644
--- a/ui/platform_window/extensions/x11_extension.h
+++ b/ui/platform_window/extensions/x11_extension.h
@@ -17,7 +17,7 @@
 // APIs. Please refer to README for more details.
 //
 // The extension mustn't be called until PlatformWindow is initialized.
-class COMPONENT_EXPORT(EXTENSIONS) X11Extension {
+class COMPONENT_EXPORT(PLATFORM_WINDOW) X11Extension {
  public:
   // Returns whether an XSync extension is available at the current platform.
   virtual bool IsSyncExtensionAvailable() const = 0;
@@ -55,7 +55,7 @@
                        X11Extension* x11_extensions);
 };
 
-COMPONENT_EXPORT(EXTENSIONS)
+COMPONENT_EXPORT(PLATFORM_WINDOW)
 X11Extension* GetX11Extension(const PlatformWindow& platform_window);
 
 }  // namespace ui
diff --git a/ui/platform_window/extensions/x11_extension_delegate.h b/ui/platform_window/extensions/x11_extension_delegate.h
index 4e7cf8e..86ec59d2 100644
--- a/ui/platform_window/extensions/x11_extension_delegate.h
+++ b/ui/platform_window/extensions/x11_extension_delegate.h
@@ -20,7 +20,7 @@
 
 namespace ui {
 
-class COMPONENT_EXPORT(EXTENSIONS) X11ExtensionDelegate {
+class COMPONENT_EXPORT(PLATFORM_WINDOW) X11ExtensionDelegate {
  public:
   // Notifies if the PlatformWindow looses a mouse grab. This can be useful
   // for Wayland or X11. Both of them provide pointer enter and leave
diff --git a/ui/platform_window/x11/BUILD.gn b/ui/platform_window/x11/BUILD.gn
index 47eae58..f222eb7 100644
--- a/ui/platform_window/x11/BUILD.gn
+++ b/ui/platform_window/x11/BUILD.gn
@@ -24,7 +24,6 @@
     "//ui/gfx/x",
     "//ui/platform_window",
     "//ui/platform_window/common",
-    "//ui/platform_window/extensions",
     "//ui/platform_window/wm",
   ]
 
@@ -73,7 +72,6 @@
     "//ui/gfx:test_support",
     "//ui/gfx/x",
     "//ui/platform_window",
-    "//ui/platform_window/extensions",
   ]
   configs += [ "//build/config:precompiled_headers" ]
 
diff --git a/ui/views/BUILD.gn b/ui/views/BUILD.gn
index 83337bb..13fbfeea 100644
--- a/ui/views/BUILD.gn
+++ b/ui/views/BUILD.gn
@@ -789,10 +789,7 @@
           "widget/desktop_aura/window_event_filter_linux.cc",
           "widget/desktop_aura/window_event_filter_linux.h",
         ]
-        deps += [
-          "//ui/base:hit_test",
-          "//ui/platform_window/extensions",
-        ]
+        deps += [ "//ui/base:hit_test" ]
       }
       if (is_linux || is_chromeos || is_fuchsia) {
         public += [
@@ -805,7 +802,6 @@
           "widget/desktop_aura/desktop_window_tree_host_platform.cc",
           "widget/desktop_aura/window_move_client_platform.cc",
         ]
-        deps += [ "//ui/platform_window/extensions" ]
       }
       if (use_atk) {
         sources += [
diff --git a/weblayer/public/java/org/chromium/weblayer/Profile.java b/weblayer/public/java/org/chromium/weblayer/Profile.java
index 980af58..0a1d2a856 100644
--- a/weblayer/public/java/org/chromium/weblayer/Profile.java
+++ b/weblayer/public/java/org/chromium/weblayer/Profile.java
@@ -315,13 +315,13 @@
     }
 
     /**
-     * If an embedder knows that a cross-origin navigation is likely starting soon they can call
-     * this method to start a spare renderer process. A subsequent navigation may use this
-     * preinitialized process, improving performance.
-     *
-     * It is safe to call this multiple times or when it is not certain that the spare renderer will
-     * be used, although calling this too eagerly may reduce performance as unnecessary processes
-     * are created.
+     * For cross-origin navigations, the implementation may leverage a separate OS process for
+     * stronger isolation. If an embedder knows that a cross-origin navigation is likely starting
+     * soon, they can call this method as a hint to the implementation to start a fresh OS process.
+     * A subsequent navigation may use this preinitialized process, improving performance. It is
+     * safe to call this multiple times or when it is not certain that the spare renderer will be
+     * used, although calling this too eagerly may reduce performance as unnecessary processes are
+     * created.
      *
      * @since 85
      */
diff --git a/weblayer/public/java/org/chromium/weblayer/WebLayer.java b/weblayer/public/java/org/chromium/weblayer/WebLayer.java
index 6b1d7798..fb9b523 100644
--- a/weblayer/public/java/org/chromium/weblayer/WebLayer.java
+++ b/weblayer/public/java/org/chromium/weblayer/WebLayer.java
@@ -91,7 +91,6 @@
      */
     public static boolean isAvailable(Context context) {
         ThreadCheck.ensureOnUiThread();
-        context = context.getApplicationContext();
         return getWebLayerLoader(context).isAvailable();
     }
 
@@ -127,8 +126,7 @@
             throws UnsupportedVersionException {
         ThreadCheck.ensureOnUiThread();
         checkAvailable(appContext);
-        appContext = appContext.getApplicationContext();
-        getWebLayerLoader(appContext).loadAsync(appContext, callback);
+        getWebLayerLoader(appContext).loadAsync(callback);
     }
 
     /**
@@ -150,13 +148,12 @@
     public static WebLayer loadSync(@NonNull Context appContext)
             throws UnsupportedVersionException {
         ThreadCheck.ensureOnUiThread();
-        appContext = appContext.getApplicationContext();
         checkAvailable(appContext);
-        return getWebLayerLoader(appContext).loadSync(appContext);
+        return getWebLayerLoader(appContext).loadSync();
     }
 
-    private static WebLayerLoader getWebLayerLoader(Context appContext) {
-        if (sLoader == null) sLoader = new WebLayerLoader(appContext);
+    private static WebLayerLoader getWebLayerLoader(Context context) {
+        if (sLoader == null) sLoader = new WebLayerLoader(context);
         return sLoader;
     }
 
@@ -167,7 +164,6 @@
     static WebLayer getLoadedWebLayer(@NonNull Context appContext)
             throws UnsupportedVersionException {
         ThreadCheck.ensureOnUiThread();
-        appContext = appContext.getApplicationContext();
         checkAvailable(appContext);
         return getWebLayerLoader(appContext).getLoadedWebLayer();
     }
@@ -186,7 +182,6 @@
      */
     public static int getSupportedMajorVersion(@NonNull Context context) {
         ThreadCheck.ensureOnUiThread();
-        context = context.getApplicationContext();
         return getWebLayerLoader(context).getMajorVersion();
     }
 
@@ -214,7 +209,6 @@
     @NonNull
     public static String getSupportedFullVersion(@NonNull Context context) {
         ThreadCheck.ensureOnUiThread();
-        context = context.getApplicationContext();
         return getWebLayerLoader(context).getVersion();
     }
 
@@ -245,18 +239,27 @@
         private final int mMajorVersion;
         private final String mVersion;
         private boolean mIsLoadingAsync;
+        private Context mContext;
 
         /**
          * Creates WebLayerLoader. This does a minimal amount of loading
          */
-        public WebLayerLoader(@NonNull Context appContext) {
+        public WebLayerLoader(@NonNull Context context) {
             boolean available = false;
             int majorVersion = -1;
             String version = "<unavailable>";
+            // Use the application context as the supplied context may have a shorter lifetime.
+            mContext = context.getApplicationContext();
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
+                    && context.getAttributionTag() != null) {
+                // Getting the application context means we lose any attribution. Use the
+                // attribution tag from the supplied context so that embedders have a way to set an
+                // attribution tag.
+                mContext = mContext.createAttributionContext(context.getAttributionTag());
+            }
             try {
-                Class factoryClass =
-                        getOrCreateRemoteClassLoader(appContext)
-                                .loadClass("org.chromium.weblayer_private.WebLayerFactoryImpl");
+                Class factoryClass = getOrCreateRemoteClassLoader(mContext).loadClass(
+                        "org.chromium.weblayer_private.WebLayerFactoryImpl");
                 mFactory = IWebLayerFactory.Stub.asInterface(
                         (IBinder) factoryClass
                                 .getMethod("create", String.class, int.class, int.class)
@@ -291,7 +294,7 @@
             return mVersion;
         }
 
-        public void loadAsync(@NonNull Context appContext, @NonNull Callback<WebLayer> callback) {
+        public void loadAsync(@NonNull Callback<WebLayer> callback) {
             if (mWebLayer != null) {
                 callback.onResult(mWebLayer);
                 return;
@@ -301,35 +304,33 @@
                 return; // Already loading.
             }
             mIsLoadingAsync = true;
-            if (getIWebLayer(appContext) == null) {
+            if (getIWebLayer() == null) {
                 // Unable to create WebLayer. This generally shouldn't happen.
                 onWebLayerReady();
                 return;
             }
             try {
-                getIWebLayer(appContext)
-                        .loadAsync(ObjectWrapper.wrap(appContext),
-                                ObjectWrapper.wrap(getOrCreateRemoteContext(appContext)),
-                                ObjectWrapper.wrap(
-                                        (ValueCallback<Boolean>) result -> { onWebLayerReady(); }));
+                getIWebLayer().loadAsync(ObjectWrapper.wrap(mContext),
+                        ObjectWrapper.wrap(getOrCreateRemoteContext(mContext)),
+                        ObjectWrapper.wrap(
+                                (ValueCallback<Boolean>) result -> { onWebLayerReady(); }));
             } catch (Exception e) {
                 throw new APICallException(e);
             }
         }
 
-        public WebLayer loadSync(@NonNull Context appContext) {
+        public WebLayer loadSync() {
             if (mWebLayer != null) {
                 return mWebLayer;
             }
-            if (getIWebLayer(appContext) == null) {
+            if (getIWebLayer() == null) {
                 // Error in creating WebLayer. This generally shouldn't happen.
                 onWebLayerReady();
                 return null;
             }
             try {
-                getIWebLayer(appContext)
-                        .loadSync(ObjectWrapper.wrap(appContext),
-                                ObjectWrapper.wrap(getOrCreateRemoteContext(appContext)));
+                getIWebLayer().loadSync(ObjectWrapper.wrap(mContext),
+                        ObjectWrapper.wrap(getOrCreateRemoteContext(mContext)));
                 onWebLayerReady();
                 return mWebLayer;
             } catch (Exception e) {
@@ -342,7 +343,7 @@
         }
 
         @Nullable
-        private IWebLayer getIWebLayer(@NonNull Context appContext) {
+        private IWebLayer getIWebLayer() {
             if (mIWebLayer != null) return mIWebLayer;
             if (!mAvailable) return null;
             try {
@@ -560,8 +561,8 @@
         }
     }
 
-    /* package */ static IWebLayer getIWebLayer(Context appContext) {
-        return getWebLayerLoader(appContext).getIWebLayer(appContext);
+    /* package */ static IWebLayer getIWebLayer(Context context) {
+        return getWebLayerLoader(context).getIWebLayer();
     }
 
     /**
diff --git a/weblayer/public/profile.h b/weblayer/public/profile.h
index 33273263..5b09572 100644
--- a/weblayer/public/profile.h
+++ b/weblayer/public/profile.h
@@ -103,13 +103,14 @@
       const GURL& page_url,
       base::OnceCallback<void(gfx::Image)> callback) = 0;
 
-  // If an embedder knows that a cross-origin navigation is likely starting soon
-  // they can call this method to start a spare renderer process. A subsequent
-  // navigation may use this preinitialized process, improving performance.
-
-  // It is safe to call this multiple times or when it is not certain that the
-  // spare renderer will be used, although calling this too eagerly may reduce
-  // performance as unnecessary processes are created.
+  // For cross-origin navigations, the implementation may leverage a separate OS
+  // process for stronger isolation. If an embedder knows that a cross-origin
+  // navigation is likely starting soon, they can call this method as a hint to
+  // the implementation to start a fresh OS process. A subsequent navigation may
+  // use this preinitialized process, improving performance. It is safe to call
+  // this multiple times or when it is not certain that the spare renderer will
+  // be used, although calling this too eagerly may reduce performance as
+  // unnecessary processes are created.
   virtual void PrepareForPossibleCrossOriginNavigation() = 0;
 };
 
diff --git a/weblayer/shell/android/shell_apk/src/org/chromium/weblayer/shell/WebLayerShellActivity.java b/weblayer/shell/android/shell_apk/src/org/chromium/weblayer/shell/WebLayerShellActivity.java
index 6bd42e5..6156f9d 100644
--- a/weblayer/shell/android/shell_apk/src/org/chromium/weblayer/shell/WebLayerShellActivity.java
+++ b/weblayer/shell/android/shell_apk/src/org/chromium/weblayer/shell/WebLayerShellActivity.java
@@ -382,11 +382,6 @@
                         .setTextSizeSP(DEFAULT_TEXT_SIZE)
                         .setTextColor(android.R.color.black)
                         .setIconColor(android.R.color.black)
-                        .setTextClickListener(v -> {
-                            mEditUrlView.setText("");
-                            mUrlViewContainer.setDisplayedChild(EDITABLE_URL_TEXT_VIEW);
-                            mEditUrlView.requestFocus();
-                        })
                         .setTextLongClickListener(v -> {
                             ClipboardManager clipboard =
                                     (ClipboardManager) v.getContext().getSystemService(
@@ -396,6 +391,11 @@
                             return true;
                         })
                         .build());
+        nonEditUrlView.setOnClickListener(v -> {
+            mEditUrlView.setText("");
+            mUrlViewContainer.setDisplayedChild(EDITABLE_URL_TEXT_VIEW);
+            mEditUrlView.requestFocus();
+        });
         RelativeLayout nonEditUrlViewContainer =
                 mTopContentsContainer.findViewById(R.id.noneditable_url_view_container);
         nonEditUrlViewContainer.addView(nonEditUrlView,