diff --git a/DEPS b/DEPS
index 958a953..18fa36d 100644
--- a/DEPS
+++ b/DEPS
@@ -245,15 +245,15 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '30fdea3d8fd5ed39de28acae75586d7c4f08ad11',
+  'skia_revision': '0e845dc8b05cb2d40d1c880184e33dd76081283a',
   # 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': '5da4293254a42479d73853b54bc9a54b1f17fd84',
+  'v8_revision': '4807b5a15583ca12874e810a2dfcff1415441657',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '96fd9b72d07c54398af2fec01bcd697ad334e6b2',
+  'angle_revision': 'd4c8209b3ec9e81339dbb0ba4b62476d61bcbc6b',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -261,7 +261,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': 'd8851a7fd3c12685297ff4690e3dae842be85777',
+  'pdfium_revision': '81683d4924b14869564198f832a20f46926cbad1',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -276,7 +276,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling googletest
   # and whatever else without interference from each other.
-  'googletest_revision': '1d9f7c5fb2f56b4321b9eec0731b874eb6eef466',
+  'googletest_revision': 'aea0874c4252d47d1da096ad763b9c04b42c8514',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling lighttpd
   # and whatever else without interference from each other.
@@ -320,7 +320,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': '6bcbe0f26d957c72a4a1aab42b4ed3c8079915c8',
+  'devtools_frontend_revision': 'be87120d5ca8b28896f4cf2a19da255437ea6fd6',
   # 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.
@@ -360,7 +360,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': 'a62f8e402c081cd38307863d813cf7712a492a3b',
+  'dawn_revision': 'cdc9bb4f313be35ef99490c2c2ad7d6e5472c948',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -805,7 +805,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'B9rhutyqLDhWWNhiyH6A1gC63mYwYyQfnP4O5533VnwC',
+          'version': 'yFvftcNOlZZJaFm5vRn6OVKPs5whEChqCg3ESP47ycIC',
       },
     ],
     'condition': 'checkout_android',
@@ -1044,7 +1044,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'c3e25c828dd3aac5f03cab26bf12cc788d111532',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '9fa0cb7230db34d3df4434523e5352b35bde7136',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1648,7 +1648,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'fca7b339442bd70c5dc49bb33ee7f9466b560a97',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'cb87ec9557b69c2c4e036673c39bb013f28329b9',
+    Var('webrtc_git') + '/src.git' + '@' + 'f490d6f818fb7ade9f2c24b83bd51054943cc4a6',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1718,7 +1718,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@b91bc92c92c7c822fe546935d41239d6143f1ea6',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@6b7d7f99ba3e662467b03fa7ed22b98d024b0ef0',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 3eb2598..3705c3e 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -1076,7 +1076,6 @@
     'OS_MAC',
     'OS_NACL',
     'OS_NACL_NONSFI',
-    'OS_NACL_SFI',
     'OS_NETBSD',
     'OS_OPENBSD',
     'OS_POSIX',
diff --git a/WATCHLISTS b/WATCHLISTS
index 54daad8..0d0d802 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -2508,7 +2508,8 @@
     'eme': ['eme-reviews@chromium.org'],
     'enterprise_reporting_private': ['domfc+watch@chromium.org',
                                      'anthonyvd+watch@chormium.org'],
-    'exo': ['crostini-ui+exo@chromium.org'],
+    'exo': ['crostini-ui+exo@chromium.org',
+            'yhanada+watchexo@chromium.org'],
     'explore_sites': ['chili+watch@chromium.org',
                       'dewittj+watch@chromium.org',
                       'dimich+watch@chromium.org',
diff --git a/ash/components/arc/arc_features.cc b/ash/components/arc/arc_features.cc
index 2859021..a5c39f31 100644
--- a/ash/components/arc/arc_features.cc
+++ b/ash/components/arc/arc_features.cc
@@ -63,6 +63,11 @@
 const base::Feature kFilePickerExperimentFeature{
     "ArcFilePickerExperiment", base::FEATURE_ENABLED_BY_DEFAULT};
 
+// Controls experimental key GMS Core and related services protection against to
+// be killed by low memory killer.
+const base::Feature kGmsCoreLowMemoryKillerProtection{
+    "ArcGmsCoreLowMemoryKillerProtection", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Controls whether the guest zram is enabled. This is only for ARCVM.
 const base::Feature kGuestZram{"ArcGuestZram",
                                base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/ash/components/arc/arc_features.h b/ash/components/arc/arc_features.h
index f44976c..bae6ee4 100644
--- a/ash/components/arc/arc_features.h
+++ b/ash/components/arc/arc_features.h
@@ -22,6 +22,7 @@
 extern const base::Feature kEnableUnmanagedToManagedTransitionFeature;
 extern const base::Feature kEnableUsap;
 extern const base::Feature kFilePickerExperimentFeature;
+extern const base::Feature kGmsCoreLowMemoryKillerProtection;
 extern const base::Feature kGuestZram;
 extern const base::FeatureParam<int> kGuestZramSize;
 extern const base::Feature kLogdConfig;
diff --git a/ash/components/arc/mojom/BUILD.gn b/ash/components/arc/mojom/BUILD.gn
index 14cf13f..6339249 100644
--- a/ash/components/arc/mojom/BUILD.gn
+++ b/ash/components/arc/mojom/BUILD.gn
@@ -325,12 +325,8 @@
           cpp = "media::VideoPixelFormat"
         },
         {
-          mojom = "arc.mojom.DecodeStatus"
-          cpp = "media::DecodeStatus"
-        },
-        {
-          mojom = "arc.mojom.Status"
-          cpp = "media::Status"
+          mojom = "arc.mojom.DecoderStatus"
+          cpp = "media::DecoderStatus"
         },
       ]
       traits_headers = [ "video_accelerator_mojom_traits.h" ]
diff --git a/ash/components/arc/mojom/video_accelerator_mojom_traits.cc b/ash/components/arc/mojom/video_accelerator_mojom_traits.cc
index 6a0865f..9222684 100644
--- a/ash/components/arc/mojom/video_accelerator_mojom_traits.cc
+++ b/ash/components/arc/mojom/video_accelerator_mojom_traits.cc
@@ -228,85 +228,45 @@
 }
 
 // static
-arc::mojom::DecodeStatus
-EnumTraits<arc::mojom::DecodeStatus, media::DecodeStatus>::ToMojom(
-    media::DecodeStatus input) {
-  switch (input) {
-    case media::DecodeStatus::kOk:
-      return arc::mojom::DecodeStatus::OK;
-    case media::DecodeStatus::kAborted:
-      return arc::mojom::DecodeStatus::ABORTED;
-    case media::DecodeStatus::kDecoderFailedDecode:
-      return arc::mojom::DecodeStatus::DECODE_FAILED;
-    default:
-      NOTREACHED() << "unknown decode status: " << static_cast<int>(input);
-      return arc::mojom::DecodeStatus::DECODE_FAILED;
-  }
-}
-
-// static
-bool EnumTraits<arc::mojom::DecodeStatus, media::DecodeStatus>::FromMojom(
-    arc::mojom::DecodeStatus input,
-    media::DecodeStatus* output) {
-  switch (input) {
-    case arc::mojom::DecodeStatus::OK:
-      *output = media::DecodeStatus::kOk;
-      return true;
-    case arc::mojom::DecodeStatus::ABORTED:
-      *output = media::DecodeStatus::kAborted;
-      return true;
-    case arc::mojom::DecodeStatus::DECODE_FAILED:
-      *output = media::DecodeStatus::kDecoderFailedDecode;
-      return true;
-  }
-  NOTREACHED() << "unknown decode status: " << static_cast<int>(input);
-  return false;
-}
-
-// static
-arc::mojom::Status EnumTraits<arc::mojom::Status, media::Status>::ToMojom(
-    media::Status input) {
+arc::mojom::DecoderStatus
+EnumTraits<arc::mojom::DecoderStatus, media::DecoderStatus>::ToMojom(
+    media::DecoderStatus input) {
   switch (input.code()) {
-    case media::StatusCode::kOk:
-      return arc::mojom::Status::OK;
-    case media::StatusCode::kAborted:
-      return arc::mojom::Status::ABORTED;
-    case media::StatusCode::kInvalidArgument:
-      return arc::mojom::Status::INVALID_ARGUMENT;
-    case media::StatusCode::kDecoderFailedDecode:
-      return arc::mojom::Status::DECODER_DECODE_FAILED;
-    case media::StatusCode::kDecoderInitializationFailed:
-      return arc::mojom::Status::DECODER_INITIALIZATION_FAILED;
-    case media::StatusCode::kDecoderCreationFailed:
-      return arc::mojom::Status::DECODER_CREATION_FAILED;
+    case media::DecoderStatus::Codes::kOk:
+      return arc::mojom::DecoderStatus::OK;
+    case media::DecoderStatus::Codes::kAborted:
+      return arc::mojom::DecoderStatus::ABORTED;
+    case media::DecoderStatus::Codes::kFailed:
+      return arc::mojom::DecoderStatus::FAILED;
+    case media::DecoderStatus::Codes::kFailedToCreateDecoder:
+      return arc::mojom::DecoderStatus::CREATION_FAILED;
+    case media::DecoderStatus::Codes::kInvalidArgument:
+      return arc::mojom::DecoderStatus::INVALID_ARGUMENT;
     default:
       NOTREACHED() << "unknown status: " << static_cast<int>(input.code());
-      return arc::mojom::Status::INVALID_ARGUMENT;
+      return arc::mojom::DecoderStatus::INVALID_ARGUMENT;
   }
 }
 
 // static
-bool EnumTraits<arc::mojom::Status, media::Status>::FromMojom(
-    arc::mojom::Status input,
-    media::Status* output) {
+bool EnumTraits<arc::mojom::DecoderStatus, media::DecoderStatus>::FromMojom(
+    arc::mojom::DecoderStatus input,
+    media::DecoderStatus* output) {
   switch (input) {
-    case arc::mojom::Status::OK:
-      *output = media::StatusCode::kOk;
+    case arc::mojom::DecoderStatus::OK:
+      *output = media::DecoderStatus::Codes::kOk;
       return true;
-    case arc::mojom::Status::ABORTED:
-      *output = media::StatusCode::kAborted;
+    case arc::mojom::DecoderStatus::ABORTED:
+      *output = media::DecoderStatus::Codes::kAborted;
       return true;
-    case arc::mojom::Status::INVALID_ARGUMENT:
-      *output = media::StatusCode::kInvalidArgument;
+    case arc::mojom::DecoderStatus::FAILED:
+      *output = media::DecoderStatus::Codes::kFailed;
       return true;
-    case arc::mojom::Status::DECODER_DECODE_FAILED:
-      *output = media::StatusCode::kDecoderFailedDecode;
+    case arc::mojom::DecoderStatus::CREATION_FAILED:
+      *output = media::DecoderStatus::Codes::kFailedToCreateDecoder;
       return true;
-    case arc::mojom::Status::DECODER_INITIALIZATION_FAILED:
-      *output = media::StatusCode::kDecoderInitializationFailed;
-      return true;
-    case arc::mojom::Status::DECODER_CREATION_FAILED:
-      *output = media::StatusCode::kDecoderCreationFailed;
+    case arc::mojom::DecoderStatus::INVALID_ARGUMENT:
+      *output = media::DecoderStatus::Codes::kInvalidArgument;
       return true;
   }
   NOTREACHED() << "unknown status: " << static_cast<int>(input);
diff --git a/ash/components/arc/mojom/video_accelerator_mojom_traits.h b/ash/components/arc/mojom/video_accelerator_mojom_traits.h
index 334cc49..f047c84b 100644
--- a/ash/components/arc/mojom/video_accelerator_mojom_traits.h
+++ b/ash/components/arc/mojom/video_accelerator_mojom_traits.h
@@ -11,7 +11,7 @@
 #include "ash/components/arc/mojom/video_decoder.mojom-shared.h"
 #include "ash/components/arc/video_accelerator/video_frame_plane.h"
 #include "media/base/color_plane_layout.h"
-#include "media/base/decode_status.h"
+#include "media/base/decoder_status.h"
 #include "media/base/status.h"
 #include "media/base/video_codecs.h"
 #include "media/base/video_frame_layout.h"
@@ -135,18 +135,11 @@
 };
 
 template <>
-struct EnumTraits<arc::mojom::DecodeStatus, media::DecodeStatus> {
-  static arc::mojom::DecodeStatus ToMojom(media::DecodeStatus input);
+struct EnumTraits<arc::mojom::DecoderStatus, media::DecoderStatus> {
+  static arc::mojom::DecoderStatus ToMojom(media::DecoderStatus input);
 
-  static bool FromMojom(arc::mojom::DecodeStatus input,
-                        media::DecodeStatus* output);
-};
-
-template <>
-struct EnumTraits<arc::mojom::Status, media::Status> {
-  static arc::mojom::Status ToMojom(media::Status input);
-
-  static bool FromMojom(arc::mojom::Status input, media::Status* output);
+  static bool FromMojom(arc::mojom::DecoderStatus input,
+                        media::DecoderStatus* output);
 };
 
 }  // namespace mojo
diff --git a/ash/components/arc/mojom/video_decoder.mojom b/ash/components/arc/mojom/video_decoder.mojom
index f10833b..1e9b46ca 100644
--- a/ash/components/arc/mojom/video_decoder.mojom
+++ b/ash/components/arc/mojom/video_decoder.mojom
@@ -9,24 +9,17 @@
 import "ash/components/arc/mojom/video_frame_pool.mojom";
 import "sandbox/policy/mojom/sandbox.mojom";
 
-// Based on media::Status, only a limited set of relevant status codes are
-// currently added here.
+// Based on media::DecoderStatus, only a limited set of relevant status codes
+// are currently added here.
+// TODO(b:189278506): ensure that the ARCVM side of the video decoder uses the
+// new entries listed here.
 [Extensible]
-enum Status {
+enum DecoderStatus {
   OK = 0,
   ABORTED = 1,
-  INVALID_ARGUMENT = 2,
-  DECODER_DECODE_FAILED = 3,
-  DECODER_INITIALIZATION_FAILED = 4,
-  DECODER_CREATION_FAILED = 5,
-};
-
-// Based on media::DecodeStatus
-[Extensible]
-enum DecodeStatus {
-  OK = 0,
-  ABORTED = 1,
-  DECODE_FAILED = 2,
+  FAILED = 2,
+  INVALID_ARGUMENT = 3,
+  CREATION_FAILED = 4,
 };
 
 // Based on media::DecoderBuffer
@@ -72,10 +65,10 @@
   Initialize@0(VideoDecoderConfig config,
                pending_remote<VideoDecoderClient> client,
                pending_associated_receiver<VideoFramePool> video_frame_pool)
-               => (Status status);
+               => (DecoderStatus status);
 
   // Decode the specified frame, flushes if an EOS buffer is passed
-  Decode@1(DecoderBuffer buffer) => (DecodeStatus status);
+  Decode@1(DecoderBuffer buffer) => (DecoderStatus status);
 
   // Reset the decoder. All pending Decode requests will be finished or aborted
   // before the callback is called.
@@ -95,5 +88,5 @@
                         int64 timestamp);
 
   // Notify the client an error happened while decoding the stream.
-  OnError@1(Status status);
+  OnError@1(DecoderStatus status);
 };
diff --git a/ash/components/arc/video_accelerator/gpu_arc_video_decode_accelerator.cc b/ash/components/arc/video_accelerator/gpu_arc_video_decode_accelerator.cc
index 1e74524..ecc5053 100644
--- a/ash/components/arc/video_accelerator/gpu_arc_video_decode_accelerator.cc
+++ b/ash/components/arc/video_accelerator/gpu_arc_video_decode_accelerator.cc
@@ -421,8 +421,8 @@
 }
 
 void GpuArcVideoDecodeAccelerator::NotifyInitializationComplete(
-    media::Status status) {
-  DVLOGF(4) << "status: " << status.code();
+    media::DecoderStatus status) {
+  DVLOGF(4) << "status: " << static_cast<int>(status.code());
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
   OnInitializeDone(
diff --git a/ash/components/arc/video_accelerator/gpu_arc_video_decode_accelerator.h b/ash/components/arc/video_accelerator/gpu_arc_video_decode_accelerator.h
index abc2de2..8d66e81 100644
--- a/ash/components/arc/video_accelerator/gpu_arc_video_decode_accelerator.h
+++ b/ash/components/arc/video_accelerator/gpu_arc_video_decode_accelerator.h
@@ -51,7 +51,7 @@
   ~GpuArcVideoDecodeAccelerator() override;
 
   // Implementation of media::VideoDecodeAccelerator::Client interface.
-  void NotifyInitializationComplete(media::Status status) override;
+  void NotifyInitializationComplete(media::DecoderStatus status) override;
   void ProvidePictureBuffers(uint32_t requested_num_of_buffers,
                              media::VideoPixelFormat format,
                              uint32_t textures_per_buffer,
diff --git a/ash/components/arc/video_accelerator/gpu_arc_video_decoder.cc b/ash/components/arc/video_accelerator/gpu_arc_video_decoder.cc
index bcbb52e..3c08883 100644
--- a/ash/components/arc/video_accelerator/gpu_arc_video_decoder.cc
+++ b/ash/components/arc/video_accelerator/gpu_arc_video_decoder.cc
@@ -17,7 +17,7 @@
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
-#include "media/base/decode_status.h"
+#include "media/base/decoder_status.h"
 #include "media/base/media_util.h"
 #include "media/base/video_codecs.h"
 #include "media/base/video_frame.h"
@@ -90,14 +90,13 @@
 
   if (decoder_) {
     VLOGF(1) << "Re-initialization is not allowed";
-    OnInitializeDone(
-        media::Status(media::StatusCode::kDecoderInitializationFailed));
+    OnInitializeDone(media::DecoderStatus::Codes::kFailed);
     return;
   }
 
   if (num_instances_ >= kMaxConcurrentInstances) {
     VLOGF(1) << "Maximum concurrent instances reached: " << num_instances_;
-    OnInitializeDone(media::Status(media::StatusCode::kDecoderCreationFailed));
+    OnInitializeDone(media::DecoderStatus::Codes::kFailedToCreateDecoder);
     return;
   }
 
@@ -110,7 +109,7 @@
 
   if (!decoder_) {
     VLOGF(1) << "Failed to create video decoder";
-    OnInitializeDone(media::Status(media::StatusCode::kDecoderCreationFailed));
+    OnInitializeDone(media::DecoderStatus::Codes::kFailedToCreateDecoder);
     return;
   }
   num_instances_++;
@@ -161,7 +160,7 @@
   mojom::BufferPtr& buffer_ptr = buffer->get_buffer();
   base::ScopedFD fd = buffer_ptr->handle_fd.TakeFD();
   if (!fd.is_valid()) {
-    OnError(FROM_HERE, media::Status(media::StatusCode::kInvalidArgument));
+    OnError(media::DecoderStatus::Codes::kInvalidArgument);
     return;
   }
   DVLOGF(4) << "timestamp: " << buffer_ptr->timestamp << ", fd: " << fd.get();
@@ -183,7 +182,7 @@
       CreateDecoderBuffer(std::move(fd), buffer_ptr->offset, buffer_ptr->size);
   if (!decoder_buffer) {
     VLOGF(1) << "Failed to create decoder buffer from fd";
-    OnError(FROM_HERE, media::Status(media::StatusCode::kInvalidArgument));
+    OnError(media::DecoderStatus::Codes::kInvalidArgument);
     return;
   }
 
@@ -234,28 +233,24 @@
                                base::Unretained(this), std::move(callback)));
 }
 
-void GpuArcVideoDecoder::OnInitializeDone(media::Status status) {
-  DVLOGF(4) << "status: " << status.code();
+void GpuArcVideoDecoder::OnInitializeDone(media::DecoderStatus status) {
+  DVLOGF(4) << "status: " << static_cast<int>(status.code());
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  // Report initialization status to UMA.
-  base::UmaHistogramEnumeration("Media.GpuArcVideoDecoder.InitializeResult",
-                                status.code());
-
   std::move(init_callback_).Run(status);
 }
 
 void GpuArcVideoDecoder::OnDecodeDone(DecodeCallback callback,
-                                      media::Status status) {
-  DVLOGF(4) << "status: " << status.code();
+                                      media::DecoderStatus status) {
+  DVLOGF(4) << "status: " << static_cast<int>(status.code());
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  if (!status.is_ok() && (status.code() != media::StatusCode::kAborted)) {
+  if (!status.is_ok() && status != media::DecoderStatus::Codes::kAborted) {
     std::move(callback).Run(status.code());
     return;
   }
 
-  std::move(callback).Run(media::DecodeStatus::OK);
+  std::move(callback).Run(media::DecoderStatus::Codes::kOk);
 }
 
 void GpuArcVideoDecoder::OnFrameReady(scoped_refptr<media::VideoFrame> frame) {
@@ -267,7 +262,7 @@
       video_frame_pool_->GetVideoFrameId(frame.get());
   if (!video_frame_id) {
     VLOGF(1) << "Failed to get video frame id.";
-    OnError(FROM_HERE, media::Status(media::StatusCode::kInvalidArgument));
+    OnError(media::DecoderStatus::Codes::kInvalidArgument);
     return;
   }
 
@@ -295,7 +290,7 @@
 
   if (!reset_callback_) {
     VLOGF(1) << "Unexpected OnResetDone() callback received from VD";
-    OnError(FROM_HERE, media::Status(media::StatusCode::kInvalidArgument));
+    OnError(media::DecoderStatus::Codes::kInvalidArgument);
     return;
   }
 
@@ -312,8 +307,7 @@
   client_video_frames_.clear();
 }
 
-void GpuArcVideoDecoder::OnError(base::Location location,
-                                 const media::Status& status) {
+void GpuArcVideoDecoder::OnError(media::DecoderStatus status) {
   VLOGF(1) << "error: " << static_cast<int>(status.code());
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
@@ -327,7 +321,7 @@
 
   error_state_ = true;
   if (client_) {
-    client_->OnError(status);
+    client_->OnError(std::move(status));
   }
 
   // Abort all pending requests.
@@ -364,7 +358,7 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (error_state_) {
-    std::move(callback).Run(media::DecodeStatus::DECODE_ERROR);
+    std::move(callback).Run(media::DecoderStatus::Codes::kFailed);
     return;
   }
   if (!decoder_) {
@@ -387,7 +381,7 @@
 
   if (!decoder_) {
     VLOGF(1) << "VD not initialized";
-    OnError(FROM_HERE, media::Status(media::StatusCode::kInvalidArgument));
+    OnError(media::DecoderStatus::Codes::kInvalidArgument);
     return;
   }
 
diff --git a/ash/components/arc/video_accelerator/gpu_arc_video_decoder.h b/ash/components/arc/video_accelerator/gpu_arc_video_decoder.h
index 07646c8..f586a25d 100644
--- a/ash/components/arc/video_accelerator/gpu_arc_video_decoder.h
+++ b/ash/components/arc/video_accelerator/gpu_arc_video_decoder.h
@@ -56,9 +56,9 @@
   using Request = base::OnceClosure;
 
   // Called by the decoder when initialization is done.
-  void OnInitializeDone(media::Status status);
+  void OnInitializeDone(media::DecoderStatus status);
   // Called by the decoder when the specified buffer has been decoded.
-  void OnDecodeDone(DecodeCallback callback, media::Status status);
+  void OnDecodeDone(DecodeCallback callback, media::DecoderStatus status);
   // Called by the decoder when a decoded frame is ready.
   void OnFrameReady(scoped_refptr<media::VideoFrame> frame);
   // Called by the decoder when a reset has been completed.
@@ -66,7 +66,7 @@
   // Called by the video frame pool when new frames have been requested.
   void OnRequestVideoFrames();
   // Called when an error occurred.
-  void OnError(base::Location location, const media::Status& status);
+  void OnError(media::DecoderStatus status);
 
   // Handle all requests that are currently in the |requests_| queue.
   void HandleRequests();
diff --git a/ash/components/arc/video_accelerator/gpu_arc_video_frame_pool.cc b/ash/components/arc/video_accelerator/gpu_arc_video_frame_pool.cc
index 77532ad..a177295 100644
--- a/ash/components/arc/video_accelerator/gpu_arc_video_frame_pool.cc
+++ b/ash/components/arc/video_accelerator/gpu_arc_video_frame_pool.cc
@@ -12,7 +12,7 @@
 #include "base/posix/eintr_wrapper.h"
 #include "build/build_config.h"
 #include "gpu/ipc/common/gpu_memory_buffer_support.h"
-#include "media/base/decode_status.h"
+#include "media/base/decoder_status.h"
 #include "media/base/format_utils.h"
 #include "media/base/video_types.h"
 #include "media/gpu/buffer_validation.h"
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index 813e83fe..eddf2c9 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -549,6 +549,11 @@
 const base::Feature kEnableWireGuard{"EnableWireGuard",
                                      base::FEATURE_ENABLED_BY_DEFAULT};
 
+// Enforces Ash extension keep-list. Only the extensions/Chrome apps in the
+// keep-list are enabled in Ash.
+const base::Feature kEnforceAshExtensionKeeplist{
+    "EnforceAshExtensionKeeplist", base::FEATURE_ENABLED_BY_DEFAULT};
+
 // Enables Device End Of Lifetime warning notifications.
 const base::Feature kEolWarningNotifications{"EolWarningNotifications",
                                              base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index e38b43b6d..d3e06b57 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -218,6 +218,8 @@
 extern const base::Feature kEnableSamlReauthenticationOnLockscreen;
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::Feature kEnableWireGuard;
 COMPONENT_EXPORT(ASH_CONSTANTS)
+extern const base::Feature kEnforceAshExtensionKeeplist;
+COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kEolWarningNotifications;
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::Feature kESimPolicy;
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::Feature kExoLockNotification;
diff --git a/ash/quick_pair/ui/fast_pair/fast_pair_notification_controller.cc b/ash/quick_pair/ui/fast_pair/fast_pair_notification_controller.cc
index df843a318d..1ecda79 100644
--- a/ash/quick_pair/ui/fast_pair/fast_pair_notification_controller.cc
+++ b/ash/quick_pair/ui/fast_pair/fast_pair_notification_controller.cc
@@ -9,7 +9,6 @@
 #include "ash/strings/grit/ash_strings.h"
 #include "base/bind.h"
 #include "base/callback_helpers.h"
-#include "base/time/time.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/message_center/message_center.h"
 #include "ui/message_center/public/cpp/notification.h"
@@ -36,8 +35,6 @@
 // progress bar.
 const int kInfiniteLoadingProgressValue = -1;
 
-constexpr base::TimeDelta kNotificationTimeout = base::Seconds(30);
-
 // Creates an empty Fast Pair notification with the given id and uses the
 // Bluetooth icon and FastPair notifierID.
 std::unique_ptr<message_center::Notification> CreateNotification(
@@ -59,6 +56,7 @@
           /*delegate=*/nullptr,
           /*small_image=*/ash::kNotificationBluetoothIcon,
           /*warning_level=*/warning_level);
+
   notification->set_never_timeout(true);
   notification->set_priority(
       message_center::NotificationPriority::MAX_PRIORITY);
@@ -78,12 +76,10 @@
   explicit NotificationDelegate(
       base::RepeatingClosure on_primary_click,
       base::OnceCallback<void(bool)> on_close,
-      base::RepeatingClosure on_secondary_click = base::DoNothing(),
-      base::OneShotTimer* expire_notification_timer = nullptr) {
+      base::RepeatingClosure on_secondary_click = base::DoNothing()) {
     on_primary_click_ = on_primary_click;
     on_secondary_click_ = on_secondary_click;
     on_close_ = std::move(on_close);
-    expire_notification_timer_ = expire_notification_timer;
   }
 
  protected:
@@ -107,22 +103,13 @@
   }
 
   // message_center::NotificationDelegate override:
-  void Close(bool by_user) override {
-    // If there is an expire notification timer, stop the timer if the user
-    // dismisses the notification to prevent the timer firing and removing
-    // notifications that might come up later.
-    if (expire_notification_timer_)
-      expire_notification_timer_->Stop();
-
-    std::move(on_close_).Run(by_user);
-  }
+  void Close(bool by_user) override { std::move(on_close_).Run(by_user); }
 
  private:
   enum class Button { kPrimaryButton, kSecondaryButton };
   base::RepeatingClosure on_primary_click_;
   base::RepeatingClosure on_secondary_click_;
   base::OnceCallback<void(bool)> on_close_;
-  base::OneShotTimer* expire_notification_timer_;
 };
 
 FastPairNotificationController::FastPairNotificationController() = default;
@@ -176,19 +163,9 @@
   discovery_notification->set_delegate(
       base::MakeRefCounted<NotificationDelegate>(
           /*on_primary_click=*/on_connect_clicked,
-          /*on_close=*/std::move(on_close),
-          /*on_secondary_click=*/base::DoNothing(),
-          /*expire_notification_timer=*/&expire_notification_timer_));
+          /*on_close=*/std::move(on_close)));
   discovery_notification->set_image(device_image);
 
-  // Start timer for how long to show the notification before removing the
-  // notification. After the timeout period, we will remove the notification
-  // from the Message Center.
-  expire_notification_timer_.Start(
-      FROM_HERE, kNotificationTimeout,
-      base::BindOnce(&FastPairNotificationController::RemoveNotifications,
-                     weak_ptr_factory_.GetWeakPtr()));
-
   MessageCenter::Get()->AddNotification(std::move(discovery_notification));
 }
 
@@ -196,14 +173,6 @@
     const std::u16string& device_name,
     gfx::Image device_image,
     base::OnceCallback<void(bool)> on_close) {
-  // If we get to this point in the pairing flow where we are showing the
-  // Pairing notification, then the user has elected to begin pairing and we
-  // can stop the timer that was waiting for user interaction on the
-  // Discovery notification. We do not need the timer for the Pairing
-  // notification since it will be removed when pairing succeeds or fails by
-  // the system.
-  expire_notification_timer_.Stop();
-
   std::unique_ptr<message_center::Notification> pairing_notification =
       CreateNotification(
           kFastPairPairingNotificationId,
@@ -249,18 +218,9 @@
       base::MakeRefCounted<NotificationDelegate>(
           /*on_primary_click=*/on_save_clicked,
           /*on_close=*/std::move(on_close),
-          /*on_secondary_click=*/on_learn_more_clicked,
-          /*expire_notification_timer=*/&expire_notification_timer_));
+          /*on_secondary_click=*/on_learn_more_clicked));
   associate_account_notification->set_image(device_image);
 
-  // Start timer for how long to show the notification before removing the
-  // notification. After the timeout period, we will remove the notification
-  // from the Message Center.
-  expire_notification_timer_.Start(
-      FROM_HERE, kNotificationTimeout,
-      base::BindOnce(&FastPairNotificationController::RemoveNotifications,
-                     weak_ptr_factory_.GetWeakPtr()));
-
   MessageCenter::Get()->AddNotification(
       std::move(associate_account_notification));
 }
diff --git a/ash/quick_pair/ui/fast_pair/fast_pair_notification_controller.h b/ash/quick_pair/ui/fast_pair/fast_pair_notification_controller.h
index 315c7796..9022153 100644
--- a/ash/quick_pair/ui/fast_pair/fast_pair_notification_controller.h
+++ b/ash/quick_pair/ui/fast_pair/fast_pair_notification_controller.h
@@ -7,8 +7,6 @@
 
 #include "base/callback.h"
 #include "base/component_export.h"
-#include "base/memory/weak_ptr.h"
-#include "base/timer/timer.h"
 #include "ui/gfx/image/image.h"
 
 namespace ash {
@@ -44,14 +42,6 @@
                             base::RepeatingClosure on_learn_more_clicked,
                             base::OnceCallback<void(bool)> on_close);
   void RemoveNotifications();
-
- private:
-  // This timer is used for Discovery and Associate Account notifications to
-  // remove them from the Message Center if the user does not elect to begin
-  // pairing/saving to their account.
-  base::OneShotTimer expire_notification_timer_;
-
-  base::WeakPtrFactory<FastPairNotificationController> weak_ptr_factory_{this};
 };
 
 }  // namespace quick_pair
diff --git a/ash/webui/camera_app_ui/resources/js/js.gni b/ash/webui/camera_app_ui/resources/js/js.gni
index ab5c167..1bf4085 100644
--- a/ash/webui/camera_app_ui/resources/js/js.gni
+++ b/ash/webui/camera_app_ui/resources/js/js.gni
@@ -68,9 +68,9 @@
   "untrusted_script_loader.ts",
   "untrusted_video_processor_helper.ts",
   "util.ts",
-  "views/camera_intent.js",
+  "views/camera_intent.ts",
   "views/camera.ts",
-  "views/camera/layout.js",
+  "views/camera/layout.ts",
   "views/camera/mode/index.ts",
   "views/camera/mode/mode_base.ts",
   "views/camera/mode/photo.ts",
@@ -80,18 +80,18 @@
   "views/camera/mode/square.ts",
   "views/camera/mode/video.ts",
   "views/camera/document_corner_overlay.js",
-  "views/camera/options.js",
+  "views/camera/options.ts",
   "views/camera/preview.js",
-  "views/camera/scan_options.js",
-  "views/camera/timertick.js",
-  "views/camera/video_encoder_options.js",
+  "views/camera/scan_options.ts",
+  "views/camera/timertick.ts",
+  "views/camera/video_encoder_options.ts",
   "views/crop_document.ts",
-  "views/dialog.js",
+  "views/dialog.ts",
   "views/ptz_panel.js",
   "views/review.ts",
   "views/settings.js",
   "views/view.ts",
-  "views/warning.js",
+  "views/warning.ts",
   "waitable_event.ts",
   "window_controller.ts",
 ]
diff --git a/ash/webui/camera_app_ui/resources/js/views/camera/layout.js b/ash/webui/camera_app_ui/resources/js/views/camera/layout.js
deleted file mode 100644
index 0ead234..0000000
--- a/ash/webui/camera_app_ui/resources/js/views/camera/layout.js
+++ /dev/null
@@ -1,110 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import {cssStyle} from '../../css.js';
-import * as dom from '../../dom.js';
-import * as state from '../../state.js';
-import {Mode} from '../../type.js';
-import {windowController} from '../../window_controller.js';
-
-/**
- * Creates a controller to handle layouts of Camera view.
- */
-export class Layout {
-  /**
-   * @public
-   */
-  constructor() {
-    /**
-     * @const {!HTMLDivElement}
-     * @private
-     */
-    this.previewBox_ = dom.get('#preview-box', HTMLDivElement);
-
-    /**
-     * @const {!HTMLCanvasElement}
-     * @private
-     */
-    this.faceOverlay_ = dom.get('#preview-face-overlay', HTMLCanvasElement);
-
-    /**
-     * @const {!CSSStyleDeclaration}
-     * @private
-     */
-    this.viewportRule_ = cssStyle('#preview-viewport');
-
-    /**
-     * @const {!CSSStyleDeclaration}
-     * @private
-     */
-    this.contentRule_ = cssStyle('.preview-content');
-  }
-
-  /**
-   * @param {number} width
-   * @param {number} height
-   * @private
-   */
-  setContentSize_(width, height) {
-    this.contentRule_.setProperty('width', `${width}px`);
-    this.contentRule_.setProperty('height', `${height}px`);
-    this.faceOverlay_.width = width;
-    this.faceOverlay_.height = height;
-  }
-
-  /**
-   * @param {number} width
-   * @param {number} height
-   * @private
-   */
-  setViewportSize_(width, height) {
-    this.viewportRule_.setProperty('width', `${width}px`);
-    this.viewportRule_.setProperty('height', `${height}px`);
-  }
-
-  /**
-   * Sets the offset between video content and viewport.
-   * @param {number} dx
-   * @param {number} dy
-   * @private
-   */
-  setContentOffset_(dx, dy) {
-    this.contentRule_.setProperty('left', `${dx}px`);
-    this.contentRule_.setProperty('top', `${dy}px`);
-  }
-
-  /**
-   * Updates the layout for video-size or window-size changes.
-   */
-  update() {
-    const fullWindow = windowController.isFullscreenOrMaximized();
-    const tall = window.innerHeight > window.innerWidth;
-    state.set(state.State.TABLET_LANDSCAPE, fullWindow && !tall);
-    state.set(state.State.MAX_WND, fullWindow);
-    state.set(state.State.TALL, tall);
-
-    const {width: boxW, height: boxH} =
-        this.previewBox_.getBoundingClientRect();
-    const video = dom.get('#preview-video', HTMLVideoElement);
-
-    if (state.get(Mode.SQUARE)) {
-      const viewportSize = Math.min(boxW, boxH);
-      this.setViewportSize_(viewportSize, viewportSize);
-      const scale =
-          viewportSize / Math.min(video.videoHeight, video.videoWidth);
-      const contentW = scale * video.videoWidth;
-      const contentH = scale * video.videoHeight;
-      this.setContentSize_(contentW, contentH);
-      this.setContentOffset_(
-          (viewportSize - contentW) / 2, (viewportSize - contentH) / 2);
-    } else {
-      const scale = Math.min(boxH / video.videoHeight, boxW / video.videoWidth);
-      const contentW = scale * video.videoWidth;
-      const contentH = scale * video.videoHeight;
-      this.setViewportSize_(contentW, contentH);
-      this.setContentSize_(contentW, contentH);
-      this.setContentOffset_(0, 0);
-    }
-  }
-}
diff --git a/ash/webui/camera_app_ui/resources/js/views/camera/layout.ts b/ash/webui/camera_app_ui/resources/js/views/camera/layout.ts
new file mode 100644
index 0000000..82dc3b60
--- /dev/null
+++ b/ash/webui/camera_app_ui/resources/js/views/camera/layout.ts
@@ -0,0 +1,73 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {cssStyle} from '../../css.js';
+import * as dom from '../../dom.js';
+import * as state from '../../state.js';
+import {Mode} from '../../type.js';
+import {windowController} from '../../window_controller.js';
+
+/**
+ * Creates a controller to handle layouts of Camera view.
+ */
+export class Layout {
+  private readonly previewBox = dom.get('#preview-box', HTMLDivElement);
+  private readonly faceOverlay =
+      dom.get('#preview-face-overlay', HTMLCanvasElement);
+  private readonly viewportRule = cssStyle('#preview-viewport');
+  private readonly contentRule = cssStyle('.preview-content');
+
+  private setContentSize(width: number, height: number) {
+    this.contentRule.setProperty('width', `${width}px`);
+    this.contentRule.setProperty('height', `${height}px`);
+    this.faceOverlay.width = width;
+    this.faceOverlay.height = height;
+  }
+
+  private setViewportSize(width: number, height: number) {
+    this.viewportRule.setProperty('width', `${width}px`);
+    this.viewportRule.setProperty('height', `${height}px`);
+  }
+
+  /**
+   * Sets the offset between video content and viewport.
+   */
+  private setContentOffset(dx: number, dy: number) {
+    this.contentRule.setProperty('left', `${dx}px`);
+    this.contentRule.setProperty('top', `${dy}px`);
+  }
+
+  /**
+   * Updates the layout for video-size or window-size changes.
+   */
+  update(): void {
+    const fullWindow = windowController.isFullscreenOrMaximized();
+    const tall = window.innerHeight > window.innerWidth;
+    state.set(state.State.TABLET_LANDSCAPE, fullWindow && !tall);
+    state.set(state.State.MAX_WND, fullWindow);
+    state.set(state.State.TALL, tall);
+
+    const {width: boxW, height: boxH} = this.previewBox.getBoundingClientRect();
+    const video = dom.get('#preview-video', HTMLVideoElement);
+
+    if (state.get(Mode.SQUARE)) {
+      const viewportSize = Math.min(boxW, boxH);
+      this.setViewportSize(viewportSize, viewportSize);
+      const scale =
+          viewportSize / Math.min(video.videoHeight, video.videoWidth);
+      const contentW = scale * video.videoWidth;
+      const contentH = scale * video.videoHeight;
+      this.setContentSize(contentW, contentH);
+      this.setContentOffset(
+          (viewportSize - contentW) / 2, (viewportSize - contentH) / 2);
+    } else {
+      const scale = Math.min(boxH / video.videoHeight, boxW / video.videoWidth);
+      const contentW = scale * video.videoWidth;
+      const contentH = scale * video.videoHeight;
+      this.setViewportSize(contentW, contentH);
+      this.setContentSize(contentW, contentH);
+      this.setContentOffset(0, 0);
+    }
+  }
+}
diff --git a/ash/webui/camera_app_ui/resources/js/views/camera/options.js b/ash/webui/camera_app_ui/resources/js/views/camera/options.js
deleted file mode 100644
index 07eded5..0000000
--- a/ash/webui/camera_app_ui/resources/js/views/camera/options.js
+++ /dev/null
@@ -1,233 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import * as animate from '../../animation.js';
-// eslint-disable-next-line no-unused-vars
-import {Camera3DeviceInfo} from '../../device/camera3_device_info.js';
-// eslint-disable-next-line no-unused-vars
-import {DeviceInfoUpdater} from '../../device/device_info_updater.js';
-import * as dom from '../../dom.js';
-import {I18nString} from '../../i18n_string.js';
-import * as localStorage from '../../models/local_storage.js';
-import * as nav from '../../nav.js';
-import * as state from '../../state.js';
-import {Facing, PerfEvent, ViewName} from '../../type.js';
-import * as util from '../../util.js';
-
-/**
- * Creates a controller for the options of Camera view.
- */
-export class Options {
-  /**
-   * @param {!DeviceInfoUpdater} infoUpdater
-   * @param {function(): !Promise} doSwitchDevice Callback to trigger device
-   *     switching.
-   */
-  constructor(infoUpdater, doSwitchDevice) {
-    /**
-     * @type {!DeviceInfoUpdater}
-     * @private
-     * @const
-     */
-    this.infoUpdater_ = infoUpdater;
-
-    /**
-     * @type {function(): !Promise}
-     * @private
-     * @const
-     */
-    this.doSwitchDevice_ = doSwitchDevice;
-
-    /**
-     * @type {!HTMLInputElement}
-     * @private
-     * @const
-     */
-    this.toggleMic_ = dom.get('#toggle-mic', HTMLInputElement);
-
-    /**
-     * @type {!HTMLInputElement}
-     * @private
-     * @const
-     */
-    this.toggleMirror_ = dom.get('#toggle-mirror', HTMLInputElement);
-
-    /**
-     * Device id of the camera device currently used or selected.
-     * @type {?string}
-     * @private
-     */
-    this.videoDeviceId_ = null;
-
-    /**
-     * Whether list of video devices is being refreshed now.
-     * @type {boolean}
-     * @private
-     */
-    this.refreshingVideoDeviceIds_ = false;
-
-    /**
-     * Mirroring set per device.
-     * @type {!Object}
-     * @private
-     */
-    this.mirroringToggles_ = {};
-
-    /**
-     * Current audio track in use.
-     * @type {?MediaStreamTrack}
-     * @private
-     */
-    this.audioTrack_ = null;
-
-    dom.get('#switch-device', HTMLButtonElement)
-        .addEventListener('click', () => this.switchDevice_());
-    dom.get('#open-settings', HTMLButtonElement)
-        .addEventListener('click', () => nav.open(ViewName.SETTINGS));
-
-    this.toggleMic_.addEventListener('click', () => this.updateAudioByMic_());
-    this.toggleMirror_.addEventListener('click', () => this.saveMirroring_());
-
-    util.bindElementAriaLabelWithState({
-      element: dom.get('#toggle-timer', Element),
-      state: state.State.TIMER_3SEC,
-      onLabel: I18nString.TOGGLE_TIMER_3S_BUTTON,
-      offLabel: I18nString.TOGGLE_TIMER_10S_BUTTON,
-    });
-
-    // Restore saved mirroring states per video device.
-    this.mirroringToggles_ = localStorage.getObject('mirroringToggles');
-    // Remove the deprecated values.
-    localStorage.remove('effectIndex', 'toggleMulti', 'toggleMirror');
-
-    this.infoUpdater_.addDeviceChangeListener((updater) => {
-      state.set(state.State.MULTI_CAMERA, updater.getDevicesInfo().length >= 2);
-    });
-  }
-
-  /**
-   * Device id of the camera device currently used or selected.
-   * @return {?string}
-   */
-  get currentDeviceId() {
-    return this.videoDeviceId_;
-  }
-
-  /**
-   * Switches to the next available camera device.
-   * @private
-   */
-  async switchDevice_() {
-    if (!state.get(state.State.STREAMING) || state.get(state.State.TAKING)) {
-      return;
-    }
-    state.set(PerfEvent.CAMERA_SWITCHING, true);
-    const devices = await this.infoUpdater_.getDevicesInfo();
-    animate.play(dom.get('#switch-device', HTMLElement));
-    let index =
-        devices.findIndex((entry) => entry.deviceId === this.videoDeviceId_);
-    if (index === -1) {
-      index = 0;
-    }
-    if (devices.length > 0) {
-      index = (index + 1) % devices.length;
-      this.videoDeviceId_ = devices[index].deviceId;
-    }
-    const isSuccess = await this.doSwitchDevice_();
-    state.set(PerfEvent.CAMERA_SWITCHING, false, {hasError: !isSuccess});
-  }
-
-  /**
-   * Updates the options' values for the current constraints and stream.
-   * @param {!MediaStream} stream Current Stream in use.
-   * @param {!Facing} facing
-   */
-  updateValues(stream, facing) {
-    const track = stream.getVideoTracks()[0];
-    const trackSettings = track.getSettings && track.getSettings();
-    this.videoDeviceId_ = trackSettings && trackSettings.deviceId || null;
-    this.updateMirroring_(facing);
-    this.audioTrack_ = stream.getAudioTracks()[0];
-    this.updateAudioByMic_();
-  }
-
-  /**
-   * Updates mirroring for a new stream.
-   * @param {!Facing} facing Facing of the stream.
-   * @private
-   */
-  updateMirroring_(facing) {
-    // Update mirroring by detected facing-mode. Enable mirroring by default if
-    // facing-mode isn't available.
-    let enabled = facing !== Facing.ENVIRONMENT;
-
-    // Override mirroring only if mirroring was toggled manually.
-    if (this.videoDeviceId_ in this.mirroringToggles_) {
-      enabled = this.mirroringToggles_[this.videoDeviceId_];
-    }
-
-    util.toggleChecked(this.toggleMirror_, enabled);
-  }
-
-  /**
-   * Saves the toggled mirror state for the current video device.
-   * @private
-   */
-  saveMirroring_() {
-    this.mirroringToggles_[this.videoDeviceId_] = this.toggleMirror_.checked;
-    localStorage.set('mirroringToggles', this.mirroringToggles_);
-  }
-
-  /**
-   * Enables/disables the current audio track according to the microphone
-   * option.
-   * @private
-   */
-  updateAudioByMic_() {
-    if (this.audioTrack_) {
-      this.audioTrack_.enabled = this.toggleMic_.checked;
-    }
-  }
-
-  /**
-   * Gets the video device ids sorted by preference.
-   * @param {!Facing} facing Preferred facing to use
-   * @return {!Array<string>}
-   */
-  videoDeviceIds(facing) {
-    /** @type {!Array<(!Camera3DeviceInfo|!MediaDeviceInfo)>} */
-    let devices;
-    /**
-     * Object mapping from device id to facing. Set to null for fake cameras.
-     * @type {?Object<string, !Facing>}
-     */
-    let facings = null;
-
-    const camera3Info = this.infoUpdater_.getCamera3DevicesInfo();
-    if (camera3Info) {
-      devices = camera3Info;
-      facings = {};
-      for (const {deviceId, facing} of camera3Info) {
-        facings[deviceId] = facing;
-      }
-    } else {
-      devices = this.infoUpdater_.getDevicesInfo();
-    }
-
-    const preferredFacing =
-        facing === Facing.NOT_SET ? util.getDefaultFacing() : facing;
-    // Put the selected video device id first.
-    const sorted = devices.map((device) => device.deviceId).sort((a, b) => {
-      if (a === b) {
-        return 0;
-      }
-      if (this.videoDeviceId_ ? a === this.videoDeviceId_ :
-                                (facings && facings[a] === preferredFacing)) {
-        return -1;
-      }
-      return 1;
-    });
-    return sorted;
-  }
-}
diff --git a/ash/webui/camera_app_ui/resources/js/views/camera/options.ts b/ash/webui/camera_app_ui/resources/js/views/camera/options.ts
new file mode 100644
index 0000000..1838bcf
--- /dev/null
+++ b/ash/webui/camera_app_ui/resources/js/views/camera/options.ts
@@ -0,0 +1,185 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import * as animate from '../../animation.js';
+import {Camera3DeviceInfo} from '../../device/camera3_device_info.js';
+import {DeviceInfoUpdater} from '../../device/device_info_updater.js';
+import * as dom from '../../dom.js';
+import {I18nString} from '../../i18n_string.js';
+import * as localStorage from '../../models/local_storage.js';
+import * as nav from '../../nav.js';
+import * as state from '../../state.js';
+import {Facing, PerfEvent, ViewName} from '../../type.js';
+import * as util from '../../util.js';
+
+/**
+ * Creates a controller for the options of Camera view.
+ */
+export class Options {
+  private readonly toggleMic = dom.get('#toggle-mic', HTMLInputElement);
+  private readonly toggleMirror = dom.get('#toggle-mirror', HTMLInputElement);
+
+  /**
+   * Device id of the camera device currently used or selected.
+   */
+  private videoDeviceId: string|null = null;
+
+  /**
+   * Mirroring set per device.
+   */
+  private mirroringToggles: Record<string, boolean> = {};
+
+  /**
+   * Current audio track in use.
+   */
+  private audioTrack: MediaStreamTrack|null = null;
+
+  /**
+   * @param doSwitchDevice Callback to trigger device switching.
+   */
+  constructor(
+      private readonly infoUpdater: DeviceInfoUpdater,
+      private readonly doSwitchDevice: () => Promise<boolean>,
+  ) {
+    dom.get('#switch-device', HTMLButtonElement)
+        .addEventListener('click', () => this.switchDevice());
+    dom.get('#open-settings', HTMLButtonElement)
+        .addEventListener('click', () => nav.open(ViewName.SETTINGS));
+
+    this.toggleMic.addEventListener('click', () => this.updateAudioByMic());
+    this.toggleMirror.addEventListener('click', () => this.saveMirroring());
+
+    util.bindElementAriaLabelWithState({
+      element: dom.get('#toggle-timer', Element),
+      state: state.State.TIMER_3SEC,
+      onLabel: I18nString.TOGGLE_TIMER_3S_BUTTON,
+      offLabel: I18nString.TOGGLE_TIMER_10S_BUTTON,
+    });
+
+    // Restore saved mirroring states per video device.
+    this.mirroringToggles = localStorage.getObject('mirroringToggles');
+    // Remove the deprecated values.
+    localStorage.remove('effectIndex', 'toggleMulti', 'toggleMirror');
+
+    this.infoUpdater.addDeviceChangeListener((updater) => {
+      state.set(state.State.MULTI_CAMERA, updater.getDevicesInfo().length >= 2);
+    });
+  }
+
+  /**
+   * Device id of the camera device currently used or selected.
+   */
+  get currentDeviceId(): string|null {
+    return this.videoDeviceId;
+  }
+
+  /**
+   * Switches to the next available camera device.
+   */
+  private async switchDevice() {
+    if (!state.get(state.State.STREAMING) || state.get(state.State.TAKING)) {
+      return;
+    }
+    state.set(PerfEvent.CAMERA_SWITCHING, true);
+    const devices = this.infoUpdater.getDevicesInfo();
+    animate.play(dom.get('#switch-device', HTMLElement));
+    let index =
+        devices.findIndex((entry) => entry.deviceId === this.videoDeviceId);
+    if (index === -1) {
+      index = 0;
+    }
+    if (devices.length > 0) {
+      index = (index + 1) % devices.length;
+      this.videoDeviceId = devices[index].deviceId;
+    }
+    const isSuccess = await this.doSwitchDevice();
+    state.set(PerfEvent.CAMERA_SWITCHING, false, {hasError: !isSuccess});
+  }
+
+  /**
+   * Updates the options' values for the current constraints and stream.
+   * @param stream Current Stream in use.
+   */
+  updateValues(stream: MediaStream, facing: Facing): void {
+    const track = stream.getVideoTracks()[0];
+    const trackSettings = track.getSettings && track.getSettings();
+    this.videoDeviceId = trackSettings && trackSettings.deviceId || null;
+    this.updateMirroring(facing);
+    this.audioTrack = stream.getAudioTracks()[0];
+    this.updateAudioByMic();
+  }
+
+  /**
+   * Updates mirroring for a new stream.
+   * @param facing Facing of the stream.
+   */
+  private updateMirroring(facing: Facing) {
+    // Update mirroring by detected facing-mode. Enable mirroring by default if
+    // facing-mode isn't available.
+    let enabled = facing !== Facing.ENVIRONMENT;
+
+    // Override mirroring only if mirroring was toggled manually.
+    if (this.videoDeviceId in this.mirroringToggles) {
+      enabled = this.mirroringToggles[this.videoDeviceId];
+    }
+
+    util.toggleChecked(this.toggleMirror, enabled);
+  }
+
+  /**
+   * Saves the toggled mirror state for the current video device.
+   */
+  private saveMirroring() {
+    this.mirroringToggles[this.videoDeviceId] = this.toggleMirror.checked;
+    localStorage.set('mirroringToggles', this.mirroringToggles);
+  }
+
+  /**
+   * Enables/disables the current audio track according to the microphone
+   * option.
+   */
+  private updateAudioByMic() {
+    if (this.audioTrack) {
+      this.audioTrack.enabled = this.toggleMic.checked;
+    }
+  }
+
+  /**
+   * Gets the video device ids sorted by preference.
+   * @param facing Preferred facing to use
+   */
+  videoDeviceIds(facing: Facing): string[] {
+    let devices: Array<Camera3DeviceInfo|MediaDeviceInfo>;
+    /**
+     * Object mapping from device id to facing. Set to null for fake cameras.
+     */
+    let facings: Record<string, Facing>|null = null;
+
+    const camera3Info = this.infoUpdater.getCamera3DevicesInfo();
+    if (camera3Info) {
+      devices = camera3Info;
+      facings = {};
+      for (const {deviceId, facing} of camera3Info) {
+        facings[deviceId] = facing;
+      }
+    } else {
+      devices = this.infoUpdater.getDevicesInfo();
+    }
+
+    const preferredFacing =
+        facing === Facing.NOT_SET ? util.getDefaultFacing() : facing;
+    // Put the selected video device id first.
+    const sorted = devices.map((device) => device.deviceId).sort((a, b) => {
+      if (a === b) {
+        return 0;
+      }
+      if (this.videoDeviceId ? a === this.videoDeviceId :
+                               (facings && facings[a] === preferredFacing)) {
+        return -1;
+      }
+      return 1;
+    });
+    return sorted;
+  }
+}
diff --git a/ash/webui/camera_app_ui/resources/js/views/camera/scan_options.js b/ash/webui/camera_app_ui/resources/js/views/camera/scan_options.js
deleted file mode 100644
index 8b319ec8..0000000
--- a/ash/webui/camera_app_ui/resources/js/views/camera/scan_options.js
+++ /dev/null
@@ -1,223 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import {assert, assertInstanceof} from '../../assert.js';
-import * as barcodeChip from '../../barcode_chip.js';
-// eslint-disable-next-line no-unused-vars
-import {DeviceInfoUpdater} from '../../device/device_info_updater.js';
-import * as dom from '../../dom.js';
-// eslint-disable-next-line no-unused-vars
-import {Point} from '../../geometry.js';
-import {sendBarcodeEnabledEvent} from '../../metrics.js';
-import {BarcodeScanner} from '../../models/barcode.js';
-import * as state from '../../state.js';
-import {Mode} from '../../type.js';
-
-import {DocumentCornerOverlay} from './document_corner_overlay.js';
-
-/**
- * @enum {string}
- */
-const ScanType = {
-  BARCODE: 'barcode',
-  DOCUMENT: 'document',
-};
-
-const scanTypeValues = new Set(Object.values(ScanType));
-
-/**
- * @param {!HTMLInputElement} el
- * @return {!ScanType}
- */
-function getScanTypeFromElement(el) {
-  const s = el.dataset['scantype'];
-  assert(scanTypeValues.has(s), `No such scantype: ${s}`);
-  return /** @type {!ScanType} */ (s);
-}
-
-/**
- * @param {!ScanType} type
- * @return {!HTMLInputElement}
- */
-function getElemetFromScanType(type) {
-  return dom.get(`input[data-scantype=${type}]`, HTMLInputElement);
-}
-
-const DEFAULT_SCAN_TYPE = ScanType.DOCUMENT;
-
-/**
- * Controller for the scan options of Camera view.
- */
-export class ScanOptions {
-  /**
-   * @param {!function(!Point): !Promise<void>} updatePointOfInterest function
-   *     to update point of interest on the stream.
-   * @public
-   */
-  constructor(updatePointOfInterest) {
-    /**
-     * Togglable barcode option in photo mode.
-     * @type {!HTMLInputElement}
-     * @private
-     * @const
-     */
-    this.photoBarcodeOption_ = dom.get('#toggle-barcode', HTMLInputElement);
-
-    /**
-     * @type {!Array<!HTMLInputElement>}
-     * @private
-     * @const
-     */
-    this.scanOptions_ =
-        [...dom.getAll('#scan-modes-group [data-scantype]', HTMLInputElement)];
-
-    /**
-     * Whether preview have attached as scan frame source.
-     * @type {boolean}
-     * @private
-     */
-    this.previewAttached_ = false;
-
-    /**
-     * May be null if preview is not ready.
-     * @type {?BarcodeScanner}
-     * @private
-     */
-    this.barcodeScanner_ = null;
-
-    /**
-     * @const {!DocumentCornerOverlay}
-     * @private
-     */
-    this.documentCornerOverylay_ =
-        new DocumentCornerOverlay(updatePointOfInterest);
-
-    /**
-     * Called when scan option changed.
-     * @type {function(): void}
-     * @public
-     */
-    this.onChange = () => {};
-
-    [this.photoBarcodeOption_, ...this.scanOptions_].forEach((opt) => {
-      opt.addEventListener('click', (evt) => {
-        if (state.get(state.State.CAMERA_CONFIGURING)) {
-          evt.preventDefault();
-        }
-      });
-    });
-    this.photoBarcodeOption_.addEventListener('change', () => {
-      this.updateOption_(
-          this.photoBarcodeOption_.checked ? ScanType.BARCODE : null);
-    });
-    this.scanOptions_.forEach((opt) => {
-      opt.addEventListener('change', (evt) => {
-        if (opt.checked) {
-          this.updateOption_(this.getToggledScanOption_());
-        }
-      });
-    });
-  }
-
-  /**
-   * @return {!ScanType} Returns scan type of checked radio buttons in scan type
-   *     option groups.
-   */
-  getToggledScanOption_() {
-    const checkedEl = this.scanOptions_.find(({checked}) => checked);
-    return checkedEl === undefined ? DEFAULT_SCAN_TYPE :
-                                     getScanTypeFromElement(checkedEl);
-  }
-
-  /**
-   * Attaches to preview video as source of frames to be scanned.
-   * @param {!HTMLVideoElement} video
-   * @return {!Promise}
-   */
-  async attachPreview(video) {
-    assert(!this.previewAttached_);
-    this.barcodeScanner_ = new BarcodeScanner(video, (value) => {
-      barcodeChip.show(value);
-    });
-    const {deviceId} = assertInstanceof(video.srcObject, MediaStream)
-                           .getVideoTracks()[0]
-                           .getSettings();
-    this.documentCornerOverylay_.attach(deviceId);
-    this.previewAttached_ = true;
-    const scanType = state.get(Mode.SCAN) ? this.getToggledScanOption_() : null;
-    await this.updateOption_(scanType);
-  }
-
-  /**
-   * @return {boolean}
-   */
-  isDocumentModeEanbled() {
-    return this.documentCornerOverylay_.isEnabled();
-  }
-
-  /**
-   * @param {?ScanType} scanType Scan type to be enabled, null for no type is
-   *     enabled.
-   * @private
-   */
-  async updateOption_(scanType) {
-    if (!this.previewAttached_) {
-      return;
-    }
-    assert(this.barcodeScanner_ !== null);
-
-    this.updateOptionsUI_(scanType);
-    const mode = state.get(state.State.SHOW_SCAN_MODE) ? Mode.SCAN : Mode.PHOTO;
-    if (state.get(mode) && scanType === ScanType.BARCODE) {
-      sendBarcodeEnabledEvent();
-      this.barcodeScanner_.start();
-      state.set(state.State.ENABLE_SCAN_BARCODE, true);
-    } else {
-      this.stopBarcodeScanner_();
-    }
-
-    if (state.get(Mode.SCAN) && scanType === ScanType.DOCUMENT) {
-      await this.documentCornerOverylay_.start();
-    } else {
-      await this.documentCornerOverylay_.stop();
-    }
-
-    this.onChange();
-  }
-
-  /**
-   * @private
-   */
-  stopBarcodeScanner_() {
-    this.barcodeScanner_.stop();
-    barcodeChip.dismiss();
-    state.set(state.State.ENABLE_SCAN_BARCODE, false);
-  }
-
-  /**
-   * @param {?ScanType} scanType
-   * @private
-   */
-  updateOptionsUI_(scanType) {
-    if (state.get(Mode.SCAN)) {
-      assert(scanType !== null);
-      getElemetFromScanType(scanType).checked = true;
-    } else if (state.get(Mode.PHOTO)) {
-      this.photoBarcodeOption_.checked = scanType === ScanType.BARCODE;
-    }
-  }
-
-  /**
-   * Stops all scanner and detach from current preview.
-   * @return {!Promise}
-   */
-  async detachPreview() {
-    if (this.barcodeScanner_ !== null) {
-      this.stopBarcodeScanner_();
-      this.barcodeScanner_ = null;
-    }
-    await this.documentCornerOverylay_.detach();
-    this.previewAttached_ = false;
-  }
-}
diff --git a/ash/webui/camera_app_ui/resources/js/views/camera/scan_options.ts b/ash/webui/camera_app_ui/resources/js/views/camera/scan_options.ts
new file mode 100644
index 0000000..8be6eae
--- /dev/null
+++ b/ash/webui/camera_app_ui/resources/js/views/camera/scan_options.ts
@@ -0,0 +1,180 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {assert, assertInstanceof} from '../../assert.js';
+import * as barcodeChip from '../../barcode_chip.js';
+import * as dom from '../../dom.js';
+import {Point} from '../../geometry.js';
+import {sendBarcodeEnabledEvent} from '../../metrics.js';
+import {BarcodeScanner} from '../../models/barcode.js';
+import * as state from '../../state.js';
+import {Mode} from '../../type.js';
+import {assertEnumVariant} from '../../util.js';
+
+import {DocumentCornerOverlay} from './document_corner_overlay.js';
+
+enum ScanType {
+  BARCODE = 'barcode',
+  DOCUMENT = 'document',
+}
+
+function getScanTypeFromElement(el: HTMLInputElement): ScanType {
+  return assertEnumVariant(ScanType, el.dataset['scantype']);
+}
+
+function getElemetFromScanType(type: ScanType): HTMLInputElement {
+  return dom.get(`input[data-scantype=${type}]`, HTMLInputElement);
+}
+
+const DEFAULT_SCAN_TYPE = ScanType.DOCUMENT;
+
+/**
+ * Controller for the scan options of Camera view.
+ */
+export class ScanOptions {
+  /**
+   * Togglable barcode option in photo mode.
+   */
+  private readonly photoBarcodeOption =
+      dom.get('#toggle-barcode', HTMLInputElement);
+
+  private readonly scanOptions =
+      [...dom.getAll('#scan-modes-group [data-scantype]', HTMLInputElement)];
+
+  /**
+   * Whether preview have attached as scan frame source.
+   */
+  private previewAttached = false;
+
+  /**
+   * May be null if preview is not ready.
+   */
+  private barcodeScanner: BarcodeScanner|null = null;
+
+  private readonly documentCornerOverylay: DocumentCornerOverlay;
+
+  /**
+   * Called when scan option changed.
+   * TODO(pihsun): Change to use a setter function to set this callback,
+   * instead of a public property.
+   */
+  onChange = (): void => {
+    // Do nothing.
+  };
+
+  /*
+   * @param updatePointOfInterest function to update point of interest on the
+   *     stream.
+   */
+  constructor(updatePointOfInterest: (point: Point) => Promise<void>) {
+    this.documentCornerOverylay =
+        new DocumentCornerOverlay(updatePointOfInterest);
+
+    [this.photoBarcodeOption, ...this.scanOptions].forEach((opt) => {
+      opt.addEventListener('click', (evt) => {
+        if (state.get(state.State.CAMERA_CONFIGURING)) {
+          evt.preventDefault();
+        }
+      });
+    });
+    this.photoBarcodeOption.addEventListener('change', () => {
+      this.updateOption(
+          this.photoBarcodeOption.checked ? ScanType.BARCODE : null);
+    });
+    this.scanOptions.forEach((opt) => {
+      opt.addEventListener('change', () => {
+        if (opt.checked) {
+          this.updateOption(this.getToggledScanOption());
+        }
+      });
+    });
+  }
+
+  /**
+   * @return Returns scan type of checked radio buttons in scan type option
+   *     groups.
+   */
+  private getToggledScanOption(): ScanType {
+    const checkedEl = this.scanOptions.find(({checked}) => checked);
+    return checkedEl === undefined ? DEFAULT_SCAN_TYPE :
+                                     getScanTypeFromElement(checkedEl);
+  }
+
+  /**
+   * Attaches to preview video as source of frames to be scanned.
+   */
+  async attachPreview(video: HTMLVideoElement): Promise<void> {
+    assert(!this.previewAttached);
+    this.barcodeScanner = new BarcodeScanner(video, (value) => {
+      barcodeChip.show(value);
+    });
+    const {deviceId} = assertInstanceof(video.srcObject, MediaStream)
+                           .getVideoTracks()[0]
+                           .getSettings();
+    this.documentCornerOverylay.attach(deviceId);
+    this.previewAttached = true;
+    const scanType = state.get(Mode.SCAN) ? this.getToggledScanOption() : null;
+    await this.updateOption(scanType);
+  }
+
+  isDocumentModeEanbled(): boolean {
+    return this.documentCornerOverylay.isEnabled();
+  }
+
+  /**
+   * @param scanType Scan type to be enabled, null for no type is
+   *     enabled.
+   */
+  private async updateOption(scanType: ScanType|null) {
+    if (!this.previewAttached) {
+      return;
+    }
+    assert(this.barcodeScanner !== null);
+
+    this.updateOptionsUI(scanType);
+    const mode = state.get(state.State.SHOW_SCAN_MODE) ? Mode.SCAN : Mode.PHOTO;
+    if (state.get(mode) && scanType === ScanType.BARCODE) {
+      sendBarcodeEnabledEvent();
+      this.barcodeScanner.start();
+      state.set(state.State.ENABLE_SCAN_BARCODE, true);
+    } else {
+      this.stopBarcodeScanner();
+    }
+
+    if (state.get(Mode.SCAN) && scanType === ScanType.DOCUMENT) {
+      await this.documentCornerOverylay.start();
+    } else {
+      await this.documentCornerOverylay.stop();
+    }
+
+    this.onChange();
+  }
+
+  private stopBarcodeScanner() {
+    this.barcodeScanner.stop();
+    barcodeChip.dismiss();
+    state.set(state.State.ENABLE_SCAN_BARCODE, false);
+  }
+
+  private updateOptionsUI(scanType: ScanType|null) {
+    if (state.get(Mode.SCAN)) {
+      assert(scanType !== null);
+      getElemetFromScanType(scanType).checked = true;
+    } else if (state.get(Mode.PHOTO)) {
+      this.photoBarcodeOption.checked = scanType === ScanType.BARCODE;
+    }
+  }
+
+  /**
+   * Stops all scanner and detach from current preview.
+   */
+  async detachPreview(): Promise<void> {
+    if (this.barcodeScanner !== null) {
+      this.stopBarcodeScanner();
+      this.barcodeScanner = null;
+    }
+    await this.documentCornerOverylay.detach();
+    this.previewAttached = false;
+  }
+}
diff --git a/ash/webui/camera_app_ui/resources/js/views/camera/timertick.js b/ash/webui/camera_app_ui/resources/js/views/camera/timertick.ts
similarity index 91%
rename from ash/webui/camera_app_ui/resources/js/views/camera/timertick.js
rename to ash/webui/camera_app_ui/resources/js/views/camera/timertick.ts
index 009c127..eeda977 100644
--- a/ash/webui/camera_app_ui/resources/js/views/camera/timertick.js
+++ b/ash/webui/camera_app_ui/resources/js/views/camera/timertick.ts
@@ -10,15 +10,14 @@
 
 /**
  * Handler to cancel the active running timer-ticks.
- * @type {?function(): void}
  */
-let doCancel = null;
+let doCancel: (() => void)|null = null;
 
 /**
  * Starts timer ticking if applicable.
- * @return {!Promise} Promise for the operation.
+ * @return Promise for the operation.
  */
-export function start() {
+export function start(): Promise<void> {
   doCancel = null;
   if (!state.get(state.State.TIMER)) {
     return Promise.resolve();
@@ -63,7 +62,7 @@
 /**
  * Cancels active timer ticking if applicable.
  */
-export function cancel() {
+export function cancel(): void {
   if (doCancel) {
     doCancel();
     doCancel = null;
diff --git a/ash/webui/camera_app_ui/resources/js/views/camera/video_encoder_options.js b/ash/webui/camera_app_ui/resources/js/views/camera/video_encoder_options.js
deleted file mode 100644
index cdcb678..0000000
--- a/ash/webui/camera_app_ui/resources/js/views/camera/video_encoder_options.js
+++ /dev/null
@@ -1,236 +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.
-
-import {assert, assertInstanceof, assertNumber} from '../../assert.js';
-import * as dom from '../../dom.js';
-import {reportError} from '../../error.js';
-import * as h264 from '../../h264.js';
-import * as state from '../../state.js';
-// eslint-disable-next-line no-unused-vars
-import {
-  ErrorLevel,
-  ErrorType,
-  Resolution,
-} from '../../type.js';
-import * as util from '../../util.js';
-
-/**
- * Precondition states to toggle custom video encoder parameters.
- * @type {!Array<!state.State>}
- */
-const preconditions = [
-  state.State.EXPERT,
-  state.State.CUSTOM_VIDEO_PARAMETERS,
-];
-
-/**
- * Creates a controller for expert mode video encoder options of Camera view.
- */
-export class VideoEncoderOptions {
-  /**
-   * @param {function(?h264.EncoderParameters): void} onChange Called when video
-   * encoder option changed.
-   */
-  constructor(onChange) {
-    /**
-     * @const {function(?h264.EncoderParameters): void}
-     * @private
-     */
-    this.onChange_ = onChange;
-
-    /**
-     * @const {!HTMLSelectElement}
-     * @private
-     */
-    this.videoProfile_ = dom.get('#video-profile', HTMLSelectElement);
-
-    /**
-     * @const {!HTMLDivElement}
-     * @private
-     */
-    this.bitrateSlider_ = dom.get('#bitrate-slider', HTMLDivElement);
-
-    /**
-     * @const {!HTMLInputElement}
-     * @private
-     */
-    this.bitrateMultiplierInput_ =
-        dom.getFrom(this.bitrateSlider_, 'input[type=range]', HTMLInputElement);
-
-    /**
-     * @const {!HTMLDivElement}
-     * @private
-     */
-    this.bitrateMultiplerText_ = dom.get('#bitrate-multiplier', HTMLDivElement);
-
-    /**
-     * @const {!HTMLDivElement}
-     * @private
-     */
-    this.bitrateText_ = dom.get('#bitrate-number', HTMLDivElement);
-
-    /**
-     * @type {?Resolution}
-     * @private
-     */
-    this.resolution_ = null;
-
-    /**
-     * @type {?number}
-     * @private
-     */
-    this.fps_ = null;
-  }
-
-  /**
-   * @return {boolean}
-   * @private
-   */
-  get enable_() {
-    return preconditions.every((s) => state.get(s)) &&
-        this.resolution_ !== null && this.fps_ !== null;
-  }
-
-  /**
-   * @return {?h264.Profile}
-   */
-  get selectedProfile_() {
-    if (this.videoProfile_.value === '') {
-      return null;
-    }
-    return h264.assertProfile(Number(this.videoProfile_.value));
-  }
-
-  /**
-   * @private
-   */
-  disableBitrateSlider_() {
-    this.bitrateSlider_.hidden = true;
-  }
-
-  /**
-   * @private
-   */
-  updateBitrate_() {
-    if (!this.enable_ || this.selectedProfile_ === null) {
-      this.onChange_(null);
-      return;
-    }
-    const fps = assertNumber(this.fps_);
-    const resolution = assertInstanceof(this.resolution_, Resolution);
-    const profile = this.selectedProfile_;
-    assert(profile !== null);
-    const multiplier = this.bitrateMultiplierInput_.valueAsNumber;
-    this.bitrateMultiplerText_.textContent = 'x' + multiplier;
-    const bitrate = multiplier * resolution.area;
-    this.bitrateText_.textContent = `${(bitrate / 1e6).toFixed(1)} Mbps`;
-    const level = h264.getMinimalLevel(profile, bitrate, fps, resolution);
-    if (level === null) {
-      reportError(
-          ErrorType.NO_AVAILABLE_LEVEL, ErrorLevel.WARNING,
-          new Error(
-              `No available level for profile=${
-                  h264.getProfileName(profile)}, ` +
-              `resolution=${resolution}, ` +
-              `fps=${fps}, ` +
-              `bitrate=${bitrate}`));
-      this.onChange_(null);
-      return;
-    }
-    this.onChange_({profile, level, bitrate});
-  }
-
-  /**
-   * @private
-   */
-  updateBitrateRange_() {
-    if (!this.enable_ || this.selectedProfile_ === null) {
-      this.disableBitrateSlider_();
-      this.onChange_(null);
-      return;
-    }
-    const fps = assertNumber(this.fps_);
-    const resolution = assertInstanceof(this.resolution_, Resolution);
-    const profile = this.selectedProfile_;
-    assert(profile !== null);
-
-    const maxLevel = h264.Levels[h264.Levels.length - 1];
-    if (!h264.checkLevelLimits(maxLevel, fps, resolution)) {
-      reportError(
-          ErrorType.NO_AVAILABLE_LEVEL, ErrorLevel.WARNING,
-          new Error(
-              `No available level for profile=${
-                  h264.getProfileName(profile)}, ` +
-              `resolution=${resolution}, ` +
-              `fps=${fps}`));
-      this.disableBitrateSlider_();
-      this.onChange_(null);
-      return;
-    }
-    const maxBitrate = h264.getMaxBitrate(profile, maxLevel);
-
-    // The slider is used to select bitrate multiplier with respect to
-    // resolution pixels.  It comply with chrome's logic of selecting default
-    // bitrate with multiplier 2. Limits multiplier up to 15 for confining
-    // result video size.
-    const max = Math.min(Math.floor(maxBitrate / resolution.area), 15);
-    this.bitrateMultiplierInput_.max = max.toString();
-    this.bitrateMultiplierInput_.value =
-        (this.bitrateMultiplierInput_.valueAsNumber || Math.min(max, 2))
-            .toString();
-    this.updateBitrate_();
-    this.bitrateSlider_.hidden = false;
-  }
-
-  /**
-   * @private
-   */
-  initBitrateSlider_() {
-    for (const evt of ['input', 'change']) {
-      this.bitrateMultiplierInput_.addEventListener(
-          evt, () => this.updateBitrate_());
-    }
-  }
-
-  /**
-   * @private
-   */
-  initVideoProfile_() {
-    // TODO(b/151047420): Remove options and use the largest supported profile.
-    for (const profile of h264.profileValues) {
-      const tpl = util.instantiateTemplate('#video-profile-option-template');
-      const option = dom.getFrom(tpl, 'option', HTMLOptionElement);
-      option.value = profile.toString();
-      option.textContent = h264.getProfileName(profile);
-      this.videoProfile_.appendChild(option);
-    }
-
-    this.videoProfile_.addEventListener(
-        'change', () => this.updateBitrateRange_());
-  }
-
-  /**
-   * @public
-   */
-  initialize() {
-    this.initVideoProfile_();
-    this.initBitrateSlider_();
-
-    for (const s of preconditions) {
-      state.addObserver(s, () => this.updateBitrateRange_());
-    }
-  }
-
-  /**
-   * @param {!Resolution} resolution
-   * @param {number} fps
-   */
-  updateValues(resolution, fps) {
-    this.resolution_ = resolution;
-    this.fps_ = fps;
-    // TODO(b/151047420): Restore profile/bitrate preference for current camera,
-    // resolution, fps.
-    this.updateBitrateRange_();
-  }
-}
diff --git a/ash/webui/camera_app_ui/resources/js/views/camera/video_encoder_options.ts b/ash/webui/camera_app_ui/resources/js/views/camera/video_encoder_options.ts
new file mode 100644
index 0000000..017a059
--- /dev/null
+++ b/ash/webui/camera_app_ui/resources/js/views/camera/video_encoder_options.ts
@@ -0,0 +1,168 @@
+// 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.
+
+import {assert, assertInstanceof, assertNumber} from '../../assert.js';
+import * as dom from '../../dom.js';
+import {reportError} from '../../error.js';
+import * as h264 from '../../h264.js';
+import * as state from '../../state.js';
+import {
+  ErrorLevel,
+  ErrorType,
+  Resolution,
+} from '../../type.js';
+import * as util from '../../util.js';
+
+/**
+ * Precondition states to toggle custom video encoder parameters.
+ */
+const preconditions = [
+  state.State.EXPERT,
+  state.State.CUSTOM_VIDEO_PARAMETERS,
+];
+
+/**
+ * Creates a controller for expert mode video encoder options of Camera view.
+ */
+export class VideoEncoderOptions {
+  private readonly videoProfile = dom.get('#video-profile', HTMLSelectElement);
+  private readonly bitrateSlider = dom.get('#bitrate-slider', HTMLDivElement);
+  private readonly bitrateMultiplierInput: HTMLInputElement;
+  private readonly bitrateMultiplerText =
+      dom.get('#bitrate-multiplier', HTMLDivElement);
+  private readonly bitrateText = dom.get('#bitrate-number', HTMLDivElement);
+  private resolution: Resolution|null = null;
+  private fps: number|null = null;
+
+  /**
+   * @param onChange Called when video encoder option changed.
+   */
+  constructor(
+      private readonly onChange: (param: h264.EncoderParameters|null) => void) {
+    this.bitrateMultiplierInput =
+        dom.getFrom(this.bitrateSlider, 'input[type=range]', HTMLInputElement);
+  }
+
+  private get enable(): boolean {
+    return preconditions.every((s) => state.get(s)) &&
+        this.resolution !== null && this.fps !== null;
+  }
+
+  private get selectedProfile(): h264.Profile|null {
+    if (this.videoProfile.value === '') {
+      return null;
+    }
+    return h264.assertProfile(Number(this.videoProfile.value));
+  }
+
+  private disableBitrateSlider() {
+    this.bitrateSlider.hidden = true;
+  }
+
+  private updateBitrate() {
+    if (!this.enable || this.selectedProfile === null) {
+      this.onChange(null);
+      return;
+    }
+    const fps = assertNumber(this.fps);
+    const resolution = assertInstanceof(this.resolution, Resolution);
+    const profile = this.selectedProfile;
+    assert(profile !== null);
+    const multiplier = this.bitrateMultiplierInput.valueAsNumber;
+    this.bitrateMultiplerText.textContent = 'x' + multiplier;
+    const bitrate = multiplier * resolution.area;
+    this.bitrateText.textContent = `${(bitrate / 1e6).toFixed(1)} Mbps`;
+    const level = h264.getMinimalLevel(profile, bitrate, fps, resolution);
+    if (level === null) {
+      reportError(
+          ErrorType.NO_AVAILABLE_LEVEL, ErrorLevel.WARNING,
+          new Error(
+              `No available level for profile=${
+                  h264.getProfileName(profile)}, ` +
+              `resolution=${resolution}, ` +
+              `fps=${fps}, ` +
+              `bitrate=${bitrate}`));
+      this.onChange(null);
+      return;
+    }
+    this.onChange({profile, level, bitrate});
+  }
+
+  private updateBitrateRange() {
+    if (!this.enable || this.selectedProfile === null) {
+      this.disableBitrateSlider();
+      this.onChange(null);
+      return;
+    }
+    const fps = assertNumber(this.fps);
+    const resolution = assertInstanceof(this.resolution, Resolution);
+    const profile = this.selectedProfile;
+    assert(profile !== null);
+
+    const maxLevel = h264.Levels[h264.Levels.length - 1];
+    if (!h264.checkLevelLimits(maxLevel, fps, resolution)) {
+      reportError(
+          ErrorType.NO_AVAILABLE_LEVEL, ErrorLevel.WARNING,
+          new Error(
+              `No available level for profile=${
+                  h264.getProfileName(profile)}, ` +
+              `resolution=${resolution}, ` +
+              `fps=${fps}`));
+      this.disableBitrateSlider();
+      this.onChange(null);
+      return;
+    }
+    const maxBitrate = h264.getMaxBitrate(profile, maxLevel);
+
+    // The slider is used to select bitrate multiplier with respect to
+    // resolution pixels.  It comply with chrome's logic of selecting default
+    // bitrate with multiplier 2. Limits multiplier up to 15 for confining
+    // result video size.
+    const max = Math.min(Math.floor(maxBitrate / resolution.area), 15);
+    this.bitrateMultiplierInput.max = max.toString();
+    this.bitrateMultiplierInput.value =
+        (this.bitrateMultiplierInput.valueAsNumber || Math.min(max, 2))
+            .toString();
+    this.updateBitrate();
+    this.bitrateSlider.hidden = false;
+  }
+
+  private initBitrateSlider() {
+    for (const evt of ['input', 'change']) {
+      this.bitrateMultiplierInput.addEventListener(
+          evt, () => this.updateBitrate());
+    }
+  }
+
+  private initVideoProfile() {
+    // TODO(b/151047420): Remove options and use the largest supported profile.
+    for (const profile of h264.profileValues) {
+      const tpl = util.instantiateTemplate('#video-profile-option-template');
+      const option = dom.getFrom(tpl, 'option', HTMLOptionElement);
+      option.value = profile.toString();
+      option.textContent = h264.getProfileName(profile);
+      this.videoProfile.appendChild(option);
+    }
+
+    this.videoProfile.addEventListener(
+        'change', () => this.updateBitrateRange());
+  }
+
+  initialize(): void {
+    this.initVideoProfile();
+    this.initBitrateSlider();
+
+    for (const s of preconditions) {
+      state.addObserver(s, () => this.updateBitrateRange());
+    }
+  }
+
+  updateValues(resolution: Resolution, fps: number): void {
+    this.resolution = resolution;
+    this.fps = fps;
+    // TODO(b/151047420): Restore profile/bitrate preference for current camera,
+    // resolution, fps.
+    this.updateBitrateRange();
+  }
+}
diff --git a/ash/webui/camera_app_ui/resources/js/views/camera_intent.js b/ash/webui/camera_app_ui/resources/js/views/camera_intent.js
deleted file mode 100644
index e7ee685..0000000
--- a/ash/webui/camera_app_ui/resources/js/views/camera_intent.js
+++ /dev/null
@@ -1,186 +0,0 @@
-// Copyright (c) 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.
-
-// eslint-disable-next-line no-unused-vars
-import {assert, assertNotReached} from '../assert.js';
-import {
-  PhotoConstraintsPreferrer,  // eslint-disable-line no-unused-vars
-  VideoConstraintsPreferrer,  // eslint-disable-line no-unused-vars
-} from '../device/constraints_preferrer.js';
-// eslint-disable-next-line no-unused-vars
-import {DeviceInfoUpdater} from '../device/device_info_updater.js';
-import {I18nString} from '../i18n_string.js';
-// eslint-disable-next-line no-unused-vars
-import {Intent} from '../intent.js';
-import * as metrics from '../metrics.js';
-// eslint-disable-next-line no-unused-vars
-import {FileAccessEntry} from '../models/file_system_access_entry.js';
-// eslint-disable-next-line no-unused-vars
-import {ResultSaver} from '../models/result_saver.js';
-import {VideoSaver} from '../models/video_saver.js';
-// eslint-disable-next-line no-unused-vars
-import {PerfLogger} from '../perf.js';
-import {scaleImage} from '../thumbnailer.js';
-// eslint-disable-next-line no-unused-vars
-import {Mode, Resolution} from '../type.js';
-import * as util from '../util.js';
-
-import {Camera} from './camera.js';
-// eslint-disable-next-line no-unused-vars
-import {PhotoResult, VideoResult} from './camera/mode/index.js';
-import * as review from './review.js';
-
-/**
- * The maximum number of pixels in the downscaled intent photo result. Reference
- * from GCA: https://goto.google.com/gca-inline-bitmap-max-pixel-num
- * @type {number}
- * @const
- */
-const DOWNSCALE_INTENT_MAX_PIXEL_NUM = 50 * 1024;
-
-/**
- * @typedef {{
- *   resolution: !Resolution,
- *   duration?: number,
- * }}
- */
-let MetricArgs;  // eslint-disable-line no-unused-vars
-
-/**
- * Camera-intent-view controller.
- */
-export class CameraIntent extends Camera {
-  /**
-   * @param {!Intent} intent
-   * @param {!DeviceInfoUpdater} infoUpdater
-   * @param {!PhotoConstraintsPreferrer} photoPreferrer
-   * @param {!VideoConstraintsPreferrer} videoPreferrer
-   * @param {!Mode} mode
-   * @param {!PerfLogger} perfLogger
-   */
-  constructor(
-      intent, infoUpdater, photoPreferrer, videoPreferrer, mode, perfLogger) {
-    const resultSaver = /** @type {!ResultSaver} */ ({
-      savePhoto: async (blob) => {
-        if (intent.shouldDownScale) {
-          const image = await util.blobToImage(blob);
-          const ratio = Math.sqrt(
-              DOWNSCALE_INTENT_MAX_PIXEL_NUM / (image.width * image.height));
-          blob = await scaleImage(
-              blob, Math.floor(image.width * ratio),
-              Math.floor(image.height * ratio));
-        }
-        const buf = await blob.arrayBuffer();
-        await this.intent_.appendData(new Uint8Array(buf));
-      },
-      startSaveVideo: async (outputVideoRotation) => {
-        return VideoSaver.createForIntent(intent, outputVideoRotation);
-      },
-      finishSaveVideo: async (video) => {
-        this.videoResultFile_ = await video.endWrite();
-      },
-      saveGif: () => {
-        assertNotReached();
-      },
-    });
-    super(
-        resultSaver, infoUpdater, photoPreferrer, videoPreferrer, mode,
-        perfLogger, /* facing= */ null);
-
-    /**
-     * @type {!Intent}
-     * @private
-     */
-    this.intent_ = intent;
-
-    /**
-     * @type {?PhotoResult}
-     * @private
-     */
-    this.photoResult_ = null;
-
-    /**
-     * @type {?FileAccessEntry}
-     * @private
-     */
-    this.videoResultFile_ = null;
-  }
-
-  /**
-   * @param {!MetricArgs} metricArgs
-   * @return {!Promise<void>}
-   * @private
-   */
-  reviewIntentResult_(metricArgs) {
-    return this.prepareReview(async () => {
-      const confirmed = await this.review.startReview(new review.OptionGroup({
-        template: review.ButtonGroupTemplate.intent,
-        options: [
-          new review.Option(
-              {
-                label: I18nString.CONFIRM_REVIEW_BUTTON,
-                templateId: 'review-intent-button-template',
-              },
-              {exitValue: true}),
-          new review.Option(
-              {
-                label: I18nString.CANCEL_REVIEW_BUTTON,
-                templateId: 'review-intent-button-template',
-              },
-              {exitValue: false}),
-        ],
-
-      }));
-      metrics.sendCaptureEvent({
-        facing: this.facingMode,
-        ...metricArgs,
-        intentResult: confirmed ? metrics.IntentResultType.CONFIRMED :
-                                  metrics.IntentResultType.CANCELED,
-        shutterType: this.shutterType,
-      });
-      if (confirmed) {
-        await this.intent_.finish();
-
-        const appWindow = window.appWindow;
-        if (appWindow === null) {
-          window.close();
-        } else {
-          // For test session, we notify tests and let test close the window for
-          // us.
-          await appWindow.notifyClosingItself();
-        }
-      } else {
-        await this.intent_.clearData();
-      }
-    });
-  }
-
-  /**
-   * @override
-   */
-  async onPhotoCaptureDone(pendingPhotoResult) {
-    await super.onPhotoCaptureDone(pendingPhotoResult);
-    const {blob, resolution} = await pendingPhotoResult;
-    await this.review.setReviewPhoto(blob);
-    await this.reviewIntentResult_({resolution});
-  }
-
-  /**
-   * @override
-   */
-  async onVideoCaptureDone(videoResult) {
-    await super.onVideoCaptureDone(videoResult);
-    assert(this.videoResultFile_ !== null);
-    await this.review.setReviewVideo(this.videoResultFile_);
-    await this.reviewIntentResult_(
-        {resolution: videoResult.resolution, duration: videoResult.duration});
-  }
-
-  /**
-   * @override
-   */
-  async startWithDevice(deviceId) {
-    return this.startWithMode(deviceId, this.defaultMode);
-  }
-}
diff --git a/ash/webui/camera_app_ui/resources/js/views/camera_intent.ts b/ash/webui/camera_app_ui/resources/js/views/camera_intent.ts
new file mode 100644
index 0000000..a466c7a
--- /dev/null
+++ b/ash/webui/camera_app_ui/resources/js/views/camera_intent.ts
@@ -0,0 +1,140 @@
+// Copyright (c) 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 {assert, assertNotReached} from '../assert.js';
+import {
+  PhotoConstraintsPreferrer,
+  VideoConstraintsPreferrer,
+} from '../device/constraints_preferrer.js';
+import {DeviceInfoUpdater} from '../device/device_info_updater.js';
+import {I18nString} from '../i18n_string.js';
+import {Intent} from '../intent.js';
+import * as metrics from '../metrics.js';
+import {FileAccessEntry} from '../models/file_system_access_entry.js';
+import {VideoSaver} from '../models/video_saver.js';
+import {PerfLogger} from '../perf.js';
+import {scaleImage} from '../thumbnailer.js';
+import {Mode, Resolution} from '../type.js';
+import * as util from '../util.js';
+
+import {Camera} from './camera.js';
+import {PhotoResult, VideoResult} from './camera/mode/index.js';
+import * as review from './review.js';
+
+/**
+ * The maximum number of pixels in the downscaled intent photo result. Reference
+ * from GCA: https://goto.google.com/gca-inline-bitmap-max-pixel-num
+ */
+const DOWNSCALE_INTENT_MAX_PIXEL_NUM = 50 * 1024;
+
+interface MetricArgs {
+  resolution: Resolution;
+  duration?: number;
+}
+
+/**
+ * Camera-intent-view controller.
+ */
+export class CameraIntent extends Camera {
+  private videoResultFile: FileAccessEntry|null = null;
+
+  constructor(
+      private readonly intent: Intent,
+      infoUpdater: DeviceInfoUpdater,
+      photoPreferrer: PhotoConstraintsPreferrer,
+      videoPreferrer: VideoConstraintsPreferrer,
+      mode: Mode,
+      perfLogger: PerfLogger,
+  ) {
+    super(
+        {
+          savePhoto: async (blob) => {
+            if (intent.shouldDownScale) {
+              const image = await util.blobToImage(blob);
+              const ratio = Math.sqrt(
+                  DOWNSCALE_INTENT_MAX_PIXEL_NUM /
+                  (image.width * image.height));
+              blob = await scaleImage(
+                  blob, Math.floor(image.width * ratio),
+                  Math.floor(image.height * ratio));
+            }
+            const buf = await blob.arrayBuffer();
+            await this.intent.appendData(new Uint8Array(buf));
+          },
+          startSaveVideo: async (outputVideoRotation) => {
+            return VideoSaver.createForIntent(intent, outputVideoRotation);
+          },
+          finishSaveVideo: async (video) => {
+            this.videoResultFile = await video.endWrite();
+          },
+          saveGif: () => {
+            assertNotReached();
+          },
+        },
+        infoUpdater, photoPreferrer, videoPreferrer, mode, perfLogger,
+        /* facing= */ null);
+  }
+
+  private reviewIntentResult(metricArgs: MetricArgs): Promise<void> {
+    return this.prepareReview(async () => {
+      const confirmed = await this.review.startReview(new review.OptionGroup({
+        template: review.ButtonGroupTemplate.intent,
+        options: [
+          new review.Option(
+              {
+                label: I18nString.CONFIRM_REVIEW_BUTTON,
+                templateId: 'review-intent-button-template',
+              },
+              {exitValue: true}),
+          new review.Option(
+              {
+                label: I18nString.CANCEL_REVIEW_BUTTON,
+                templateId: 'review-intent-button-template',
+              },
+              {exitValue: false}),
+        ],
+      }));
+      metrics.sendCaptureEvent({
+        facing: this.facingMode,
+        ...metricArgs,
+        intentResult: confirmed ? metrics.IntentResultType.CONFIRMED :
+                                  metrics.IntentResultType.CANCELED,
+        shutterType: this.shutterType,
+      });
+      if (confirmed) {
+        await this.intent.finish();
+        const appWindow = window.appWindow;
+        if (appWindow === null) {
+          window.close();
+        } else {
+          // For test session, we notify tests and let test close the window for
+          // us.
+          await appWindow.notifyClosingItself();
+        }
+      } else {
+        await this.intent.clearData();
+      }
+    });
+  }
+
+  async onPhotoCaptureDone(pendingPhotoResult: Promise<PhotoResult>):
+      Promise<void> {
+    await super.onPhotoCaptureDone(pendingPhotoResult);
+    const {blob, resolution} = await pendingPhotoResult;
+    await this.review.setReviewPhoto(blob);
+    await this.reviewIntentResult({resolution});
+  }
+
+  async onVideoCaptureDone(videoResult: VideoResult): Promise<void> {
+    await super.onVideoCaptureDone(videoResult);
+    assert(this.videoResultFile !== null);
+    await this.review.setReviewVideo(this.videoResultFile);
+    await this.reviewIntentResult(
+        {resolution: videoResult.resolution, duration: videoResult.duration});
+  }
+
+  async startWithDevice(deviceId: string): Promise<boolean> {
+    return this.startWithMode(deviceId, this.defaultMode);
+  }
+}
diff --git a/ash/webui/camera_app_ui/resources/js/views/dialog.js b/ash/webui/camera_app_ui/resources/js/views/dialog.js
deleted file mode 100644
index 5456109..0000000
--- a/ash/webui/camera_app_ui/resources/js/views/dialog.js
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright (c) 2013 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 {assertString} from '../assert.js';
-import * as dom from '../dom.js';
-import {ViewName} from '../type.js';  // eslint-disable-line no-unused-vars
-
-import {View} from './view.js';
-
-/**
- * Creates the Dialog view controller.
- */
-export class Dialog extends View {
-  /**
-   * @param {!ViewName} name View name of the dialog.
-   */
-  constructor(name) {
-    super(
-        name,
-        {dismissByEsc: true, defaultFocusSelector: '.dialog-positive-button'});
-
-    /**
-     * @type {!HTMLButtonElement}
-     * @private
-     */
-    this.positiveButton_ =
-        dom.getFrom(this.root, '.dialog-positive-button', HTMLButtonElement);
-
-    /**
-     * @type {?HTMLButtonElement}
-     * @private
-     */
-    this.negativeButton_ = (() => {
-      const btn = dom.getAllFrom(
-          this.root, '.dialog-negative-button', HTMLButtonElement)[0];
-      return btn || null;
-    })();
-
-    /**
-     * @type {!HTMLElement}
-     * @private
-     */
-    this.messageHolder_ =
-        dom.getFrom(this.root, '.dialog-msg-holder', HTMLElement);
-
-    this.positiveButton_.addEventListener('click', () => this.leave(true));
-    if (this.negativeButton_ !== null) {
-      this.negativeButton_.addEventListener('click', () => this.leave());
-    }
-  }
-
-  /**
-   * @override
-   */
-  entering({message = undefined, cancellable = false} = {}) {
-    if (message !== undefined) {
-      this.messageHolder_.textContent = assertString(message);
-    }
-    if (this.negativeButton_ !== null) {
-      this.negativeButton_.hidden = !cancellable;
-    }
-  }
-}
diff --git a/ash/webui/camera_app_ui/resources/js/views/dialog.ts b/ash/webui/camera_app_ui/resources/js/views/dialog.ts
new file mode 100644
index 0000000..b4bfcd6e
--- /dev/null
+++ b/ash/webui/camera_app_ui/resources/js/views/dialog.ts
@@ -0,0 +1,53 @@
+// Copyright (c) 2013 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 {assertString} from '../assert.js';
+import * as dom from '../dom.js';
+import {ViewName} from '../type.js';
+
+import {DialogEnterOptions, View} from './view.js';
+
+/**
+ * Creates the Dialog view controller.
+ */
+export class Dialog extends View {
+  private positiveButton: HTMLButtonElement;
+  private negativeButton: HTMLButtonElement|null;
+  private messageHolder: HTMLElement;
+
+  /**
+   * @param name View name of the dialog.
+   */
+  constructor(name: ViewName) {
+    super(
+        name,
+        {dismissByEsc: true, defaultFocusSelector: '.dialog-positive-button'});
+
+    this.positiveButton =
+        dom.getFrom(this.root, '.dialog-positive-button', HTMLButtonElement);
+
+    this.negativeButton = (() => {
+      const btn = dom.getAllFrom(
+          this.root, '.dialog-negative-button', HTMLButtonElement)[0];
+      return btn || null;
+    })();
+
+    this.messageHolder =
+        dom.getFrom(this.root, '.dialog-msg-holder', HTMLElement);
+
+    this.positiveButton.addEventListener('click', () => this.leave(true));
+    if (this.negativeButton !== null) {
+      this.negativeButton.addEventListener('click', () => this.leave());
+    }
+  }
+
+  entering({message, cancellable = false}: DialogEnterOptions = {}): void {
+    if (message !== undefined) {
+      this.messageHolder.textContent = assertString(message);
+    }
+    if (this.negativeButton !== null) {
+      this.negativeButton.hidden = !cancellable;
+    }
+  }
+}
diff --git a/ash/webui/camera_app_ui/resources/js/views/view.ts b/ash/webui/camera_app_ui/resources/js/views/view.ts
index 75cf32b..7911b32 100644
--- a/ash/webui/camera_app_ui/resources/js/views/view.ts
+++ b/ash/webui/camera_app_ui/resources/js/views/view.ts
@@ -11,7 +11,7 @@
  * message for message of the dialog view, cancellable for whether the dialog
  * view is cancellable.
  */
-interface DialogEnterOptions {
+export interface DialogEnterOptions {
   message?: string;
   cancellable?: boolean;
 }
@@ -40,6 +40,9 @@
   }
 }
 
+// TODO(pihsun): After we migrate all files into TypeScript, we can have some
+// sort of "global" view registration, so we can enforce the enter / leave type
+// at compile time.
 export type EnterOptions =
     DialogEnterOptions|WarningEnterOptions|PTZPanelOptions;
 
diff --git a/ash/webui/camera_app_ui/resources/js/views/warning.js b/ash/webui/camera_app_ui/resources/js/views/warning.js
deleted file mode 100644
index 0662c28..0000000
--- a/ash/webui/camera_app_ui/resources/js/views/warning.js
+++ /dev/null
@@ -1,98 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import {assert, assertString} from '../assert.js';
-import * as dom from '../dom.js';
-import {I18nString} from '../i18n_string.js';
-import * as loadTimeData from '../models/load_time_data.js';
-import {ViewName} from '../type.js';
-
-import {View} from './view.js';
-
-/**
- * The type of warning.
- * @enum {string}
- */
-export const WarningType = {
-  CAMERA_PAUSED: I18nString.ERROR_MSG_CAMERA_PAUSED,
-  FILESYSTEM_FAILURE: I18nString.ERROR_MSG_FILE_SYSTEM_FAILED,
-  NO_CAMERA: I18nString.ERROR_MSG_NO_CAMERA,
-};
-
-/**
- * @param {*} value The value to check.
- * @return {!I18nString}
- */
-function assertI18nString(value) {
-  assertString(value);
-  assert(
-      Object.values(I18nString).includes(value),
-      `${value} is not a valid I18nString`);
-  return /** @type {I18nString} */ (value);
-}
-
-/**
- * Creates the warning-view controller.
- */
-export class Warning extends View {
-  /**
-   * @public
-   */
-  constructor() {
-    super(ViewName.WARNING);
-
-    /**
-     * @type {!Array<!I18nString>}
-     * @private
-     */
-    this.errorNames_ = [];
-  }
-
-  /**
-   * Updates the error message for the latest error-name in the stack.
-   * @private
-   */
-  updateMessage_() {
-    const message = this.errorNames_[this.errorNames_.length - 1];
-    dom.get('#error-msg', HTMLElement).textContent =
-        loadTimeData.getI18nMessage(message);
-  }
-
-  /**
-   * @override
-   */
-  entering(name) {
-    name = assertI18nString(name);
-
-    // Remove the error-name from the stack to avoid duplication. Then make the
-    // error-name the latest one to show its message.
-    const index = this.errorNames_.indexOf(name);
-    if (index !== -1) {
-      this.errorNames_.splice(index, 1);
-    }
-    this.errorNames_.push(name);
-    this.updateMessage_();
-  }
-
-  /**
-   * @override
-   */
-  leaving(...args) {
-    // Recovered error-name for leaving the view.
-    const name = assertI18nString(args[0]);
-
-    // Remove the recovered error from the stack but don't leave the view until
-    // there is no error left in the stack.
-    const index = this.errorNames_.indexOf(name);
-    if (index !== -1) {
-      this.errorNames_.splice(index, 1);
-    }
-    if (this.errorNames_.length) {
-      this.updateMessage_();
-      return false;
-    }
-    dom.get('#error-msg', HTMLElement).textContent = '';
-    return true;
-  }
-}
diff --git a/ash/webui/camera_app_ui/resources/js/views/warning.ts b/ash/webui/camera_app_ui/resources/js/views/warning.ts
new file mode 100644
index 0000000..e64e55cd
--- /dev/null
+++ b/ash/webui/camera_app_ui/resources/js/views/warning.ts
@@ -0,0 +1,77 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {assertString} from '../assert.js';
+import * as dom from '../dom.js';
+import {I18nString} from '../i18n_string.js';
+import * as loadTimeData from '../models/load_time_data.js';
+import {ViewName} from '../type.js';
+import {assertEnumVariant} from '../util.js';
+
+import {EnterOptions, View} from './view.js';
+
+/**
+ * The type of warning.
+ */
+export const WarningType = {
+  CAMERA_PAUSED: I18nString.ERROR_MSG_CAMERA_PAUSED,
+  FILESYSTEM_FAILURE: I18nString.ERROR_MSG_FILE_SYSTEM_FAILED,
+  NO_CAMERA: I18nString.ERROR_MSG_NO_CAMERA,
+};
+
+function assertI18nString(value: unknown): I18nString {
+  const stringValue = assertString(value);
+  return assertEnumVariant(I18nString, stringValue);
+}
+
+/**
+ * Creates the warning-view controller.
+ */
+export class Warning extends View {
+  private readonly errorNames: I18nString[] = [];
+
+  constructor() {
+    super(ViewName.WARNING);
+  }
+
+  /**
+   * Updates the error message for the latest error-name in the stack.
+   */
+  private updateMessage() {
+    const message = this.errorNames[this.errorNames.length - 1];
+    dom.get('#error-msg', HTMLElement).textContent =
+        loadTimeData.getI18nMessage(message);
+  }
+
+  entering(nameOption?: EnterOptions): void {
+    const name = assertI18nString(nameOption);
+
+    // Remove the error-name from the stack to avoid duplication. Then make the
+    // error-name the latest one to show its message.
+    const index = this.errorNames.indexOf(name);
+    if (index !== -1) {
+      this.errorNames.splice(index, 1);
+    }
+    this.errorNames.push(name);
+    this.updateMessage();
+  }
+
+  leaving(condition?: unknown): boolean {
+    // Recovered error-name for leaving the view.
+    const name = assertI18nString(condition);
+
+    // Remove the recovered error from the stack but don't leave the view until
+    // there is no error left in the stack.
+    const index = this.errorNames.indexOf(name);
+    if (index !== -1) {
+      this.errorNames.splice(index, 1);
+    }
+    if (this.errorNames.length) {
+      this.updateMessage();
+      return false;
+    }
+    dom.get('#error-msg', HTMLElement).textContent = '';
+    return true;
+  }
+}
diff --git a/ash/webui/firmware_update_ui/resources/firmware_update_dialog.html b/ash/webui/firmware_update_ui/resources/firmware_update_dialog.html
index 0bdfc39..1faf0ad 100644
--- a/ash/webui/firmware_update_ui/resources/firmware_update_dialog.html
+++ b/ash/webui/firmware_update_ui/resources/firmware_update_dialog.html
@@ -27,18 +27,19 @@
     width: 95%;
   }
 </style>
-<template is="dom-if" if="[[shouldShowUpdateDialog_(dialogState)]]" restamp>
+<template is="dom-if"
+    if="[[shouldShowUpdateDialog_(installationProgress.state)]]" restamp>
   <cr-dialog id="updateDialog" show-on-attach
       on-close="closeDialog_">
     <div slot="title" id="updateDialogTitle" class="firmware-dialog-title-font">
-      [[computeUpdateDialogTitle_(dialogState)]]
+      [[computeUpdateDialogTitle_(installationProgress.state)]]
     </div>
     <div slot="body" class="firmware-dialog-body-font">
       <div>
-        [[computeUpdateDialogBodyText_(dialogState)]]
+        [[computeUpdateDialogBodyText_(installationProgress.state)]]
       </div>
     </div>
-    <div slot="footer" hidden$="[[!isUpdateInProgress_(dialogState)]]">
+    <div slot="footer" hidden$="[[!isUpdateInProgress_(installationProgress.state)]]">
       <label id="progress" class="firmware-dialog-installing-font">
         [[computeProgressText_(installationProgress.percentage)]]
       </label>
@@ -48,7 +49,7 @@
       </paper-progress>
     </div>
     <div slot="button-container"
-        hidden$="[[isUpdateInProgress_(dialogState)]]">
+        hidden$="[[isUpdateInProgress_(installationProgress.state)]]">
       <cr-button class="action-button"
           on-click="closeDialog_"
           id="updateDoneButton">
diff --git a/ash/webui/firmware_update_ui/resources/firmware_update_dialog.js b/ash/webui/firmware_update_ui/resources/firmware_update_dialog.js
index 6749573..12ac9e1 100644
--- a/ash/webui/firmware_update_ui/resources/firmware_update_dialog.js
+++ b/ash/webui/firmware_update_ui/resources/firmware_update_dialog.js
@@ -19,13 +19,6 @@
 import {getUpdateProvider} from './mojo_interface_provider.js';
 import {mojoString16ToString} from './mojo_utils.js';
 
-/** @enum {number} */
-export const DialogState = {
-  CLOSED: 0,
-  UPDATING: 2,
-  UPDATE_DONE: 3,
-};
-
 /**
  * @fileoverview
  * 'firmware-update-dialog' displays information related to a firmware update.
@@ -57,17 +50,10 @@
         type: Object,
       },
 
-      /** @type {?InstallationProgress} */
+      /** @type {!InstallationProgress} */
       installationProgress: {
         type: Object,
       },
-
-      /** @type {!DialogState} */
-      dialogState: {
-        type: Number,
-        value: DialogState.CLOSED,
-        computed: 'onStateChanged_(installationProgress.state)'
-      },
     };
   }
 
@@ -109,28 +95,10 @@
   }
 
   /** @protected */
-  onStateChanged_() {
-    if (!this.installationProgress) {
-      return DialogState.CLOSED;
-    }
-    // TODO(michaelcheco): Handle restarting and failed states.
-    switch (this.installationProgress.state) {
-      case UpdateState.kUnknown:
-      case UpdateState.kIdle:
-        return DialogState.CLOSED;
-      case UpdateState.kUpdating:
-      case UpdateState.kRestarting:
-        return DialogState.UPDATING;
-      case UpdateState.kFailed:
-      case UpdateState.kSuccess:
-        return DialogState.UPDATE_DONE;
-    }
-  }
-
-  /** @protected */
   closeDialog_() {
-    this.dialogState = DialogState.CLOSED;
-    this.installationProgress = null;
+    // Resetting |installationProgress| triggers a call to
+    // |shouldShowUpdateDialog_|.
+    this.installationProgress = {percentage: 0, state: UpdateState.kIdle};
   }
 
   /** @protected */
@@ -165,8 +133,14 @@
    * @return {boolean}
    */
   shouldShowUpdateDialog_() {
-    return this.isUpdateInProgress_() ||
-        this.dialogState === DialogState.UPDATE_DONE;
+    /** @type {!Array<!UpdateState>} */
+    const activeDialogStates = [
+      UpdateState.kUpdating,
+      UpdateState.kRestarting,
+      UpdateState.kFailed,
+      UpdateState.kSuccess,
+    ];
+    return activeDialogStates.includes(this.installationProgress.state);
   }
 
   /**
@@ -185,7 +159,7 @@
    * @return {boolean}
    */
   isUpdateInProgress_() {
-    return this.dialogState === DialogState.UPDATING;
+    return this.installationProgress.state === UpdateState.kUpdating;
   }
 
   /**
@@ -213,7 +187,7 @@
    */
   computeUpdateDialogBodyText_() {
     const {deviceName, deviceVersion} = this.update;
-    return this.dialogState === DialogState.UPDATE_DONE ?
+    return this.installationProgress.state === UpdateState.kSuccess ?
         this.i18n(
             'hasBeenUpdated', mojoString16ToString(deviceName), deviceVersion) :
         this.i18n('updatingInfo');
diff --git a/base/message_loop/message_pump_for_io.h b/base/message_loop/message_pump_for_io.h
index 1b65f2a8..b6cc2ddb 100644
--- a/base/message_loop/message_pump_for_io.h
+++ b/base/message_loop/message_pump_for_io.h
@@ -16,7 +16,7 @@
 #include "base/message_loop/message_pump_io_ios.h"
 #elif defined(OS_MAC)
 #include "base/message_loop/message_pump_kqueue.h"
-#elif defined(OS_NACL_SFI)
+#elif defined(OS_NACL)
 #include "base/message_loop/message_pump_default.h"
 #elif defined(OS_FUCHSIA)
 #include "base/message_loop/message_pump_fuchsia.h"
@@ -33,7 +33,7 @@
 using MessagePumpForIO = MessagePumpIOSForIO;
 #elif defined(OS_MAC)
 using MessagePumpForIO = MessagePumpKqueue;
-#elif defined(OS_NACL_SFI)
+#elif defined(OS_NACL)
 using MessagePumpForIO = MessagePumpDefault;
 #elif defined(OS_FUCHSIA)
 using MessagePumpForIO = MessagePumpFuchsia;
diff --git a/base/message_loop/message_pump_glib_unittest.cc b/base/message_loop/message_pump_glib_unittest.cc
index 2a05e5b..3fcbde3 100644
--- a/base/message_loop/message_pump_glib_unittest.cc
+++ b/base/message_loop/message_pump_glib_unittest.cc
@@ -554,6 +554,9 @@
   }
 
   void TearDown() override {
+    // Wait for the IO thread to exit before closing FDs which may have been
+    // passed to it.
+    io_thread_.Stop();
     if (IGNORE_EINTR(close(pipefds_[0])) < 0)
       PLOG(ERROR) << "close";
     if (IGNORE_EINTR(close(pipefds_[1])) < 0)
diff --git a/base/message_loop/message_pump_unittest.cc b/base/message_loop/message_pump_unittest.cc
index fe86036..eb6c16e 100644
--- a/base/message_loop/message_pump_unittest.cc
+++ b/base/message_loop/message_pump_unittest.cc
@@ -24,7 +24,7 @@
 #include <windows.h>
 #endif
 
-#if defined(OS_POSIX) && !defined(OS_NACL_SFI)
+#if defined(OS_POSIX) && !defined(OS_NACL)
 #include "base/message_loop/message_pump_libevent.h"
 #endif
 
@@ -79,7 +79,7 @@
 
   void AddPostDoWorkExpectations(
       testing::StrictMock<MockMessagePumpDelegate>& delegate) {
-#if defined(OS_POSIX) && !defined(OS_NACL_SFI)
+#if defined(OS_POSIX) && !defined(OS_NACL)
     if ((GetParam() == MessagePumpType::UI &&
          std::is_same<MessagePumpForUI, MessagePumpLibevent>::value) ||
         (GetParam() == MessagePumpType::IO &&
@@ -89,7 +89,7 @@
       EXPECT_CALL(delegate, OnBeginWorkItem);
       EXPECT_CALL(delegate, OnEndWorkItem);
     }
-#endif  // defined(OS_POSIX) && !defined(OS_NACL_SFI)
+#endif  // defined(OS_POSIX) && !defined(OS_NACL)
   }
 
   std::unique_ptr<MessagePump> message_pump_;
diff --git a/base/task/current_thread.cc b/base/task/current_thread.cc
index 9e033bdc..dbbf82e 100644
--- a/base/task/current_thread.cc
+++ b/base/task/current_thread.cc
@@ -204,7 +204,7 @@
   return static_cast<MessagePumpForIO*>(current_->GetMessagePump());
 }
 
-#if !defined(OS_NACL_SFI)
+#if !defined(OS_NACL)
 
 #if defined(OS_WIN)
 HRESULT CurrentIOThread::RegisterIOHandler(
@@ -244,7 +244,7 @@
 }
 #endif
 
-#endif  // !defined(OS_NACL_SFI)
+#endif  // !defined(OS_NACL)
 
 #if defined(OS_FUCHSIA)
 // Additional watch API for native platform resources.
diff --git a/base/task/current_thread.h b/base/task/current_thread.h
index 7ca1e0e..5da7cf2a 100644
--- a/base/task/current_thread.h
+++ b/base/task/current_thread.h
@@ -266,7 +266,7 @@
 
   CurrentIOThread* operator->() { return this; }
 
-#if !defined(OS_NACL_SFI)
+#if !defined(OS_NACL)
 
 #if defined(OS_WIN)
   // Please see MessagePumpWin for definitions of these methods.
@@ -298,7 +298,7 @@
                      MessagePumpForIO::ZxHandleWatcher* delegate);
 #endif  // defined(OS_FUCHSIA)
 
-#endif  // !defined(OS_NACL_SFI)
+#endif  // !defined(OS_NACL)
 
  private:
   explicit CurrentIOThread(
diff --git a/base/task/thread_pool/thread_pool_impl.cc b/base/task/thread_pool/thread_pool_impl.cc
index 44e3c7f..e517776 100644
--- a/base/task/thread_pool/thread_pool_impl.cc
+++ b/base/task/thread_pool/thread_pool_impl.cc
@@ -168,7 +168,7 @@
   // FileDescriptorWatcher in the scope in which tasks run.
   ServiceThread::Options service_thread_options;
   service_thread_options.message_pump_type =
-#if defined(OS_POSIX) && !defined(OS_NACL_SFI)
+#if defined(OS_POSIX) && !defined(OS_NACL)
       MessagePumpType::IO;
 #else
       MessagePumpType::DEFAULT;
@@ -178,11 +178,11 @@
   if (g_synchronous_thread_start_for_testing)
     service_thread_.WaitUntilThreadStarted();
 
-#if defined(OS_POSIX) && !defined(OS_NACL_SFI)
+#if defined(OS_POSIX) && !defined(OS_NACL)
   // Needs to happen after starting the service thread to get its
   // task_runner().
   task_tracker_->set_io_thread_task_runner(service_thread_.task_runner());
-#endif  // defined(OS_POSIX) && !defined(OS_NACL_SFI)
+#endif  // defined(OS_POSIX) && !defined(OS_NACL)
 
   // Update the CanRunPolicy based on |has_disable_best_effort_switch_|.
   UpdateCanRunPolicy();
diff --git a/base/task/thread_pool/thread_pool_impl.h b/base/task/thread_pool/thread_pool_impl.h
index a56392e7..2708fc7 100644
--- a/base/task/thread_pool/thread_pool_impl.h
+++ b/base/task/thread_pool/thread_pool_impl.h
@@ -31,7 +31,7 @@
 #include "base/task/updateable_sequenced_task_runner.h"
 #include "build/build_config.h"
 
-#if defined(OS_POSIX) && !defined(OS_NACL_SFI)
+#if defined(OS_POSIX) && !defined(OS_NACL)
 #include "base/task/thread_pool/task_tracker_posix.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #endif
@@ -51,7 +51,7 @@
                                    public PooledTaskRunnerDelegate {
  public:
   using TaskTrackerImpl =
-#if defined(OS_POSIX) && !defined(OS_NACL_SFI)
+#if defined(OS_POSIX) && !defined(OS_NACL)
       TaskTrackerPosix;
 #else
       TaskTracker;
diff --git a/build/android/pylib/local/device/local_device_test_run.py b/build/android/pylib/local/device/local_device_test_run.py
index 3fa75c3..3a27c84 100644
--- a/build/android/pylib/local/device/local_device_test_run.py
+++ b/build/android/pylib/local/device/local_device_test_run.py
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import fnmatch
+import hashlib
 import logging
 import posixpath
 import signal
@@ -274,10 +275,10 @@
   # Sort by hash so we don't put all tests in a slow suite in the same
   # partition.
   def _SortTests(self, tests):
-    return sorted(
-        tests,
-        key=lambda t: hash(
-            self._GetUniqueTestName(t[0] if isinstance(t, list) else t)))
+    return sorted(tests,
+                  key=lambda t: hashlib.sha256(
+                      self._GetUniqueTestName(t[0] if isinstance(t, list) else t
+                                              ).encode()).hexdigest())
 
   # Partition tests evenly into |num_desired_partitions| partitions where
   # possible. However, many constraints make partitioning perfectly impossible.
diff --git a/build/build_config.h b/build/build_config.h
index 1b25276..ebd66fe 100644
--- a/build/build_config.h
+++ b/build/build_config.h
@@ -52,7 +52,6 @@
 #if defined(__native_client__)
 // __native_client__ must be first, so that other OS_ defines are not set.
 #define OS_NACL 1
-#define OS_NACL_SFI
 #elif defined(ANDROID)
 #define OS_ANDROID 1
 #elif defined(__APPLE__)
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index f017fdc6..7c8f770 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-7.20220111.2.2
+7.20220112.0.1
diff --git a/build/fuchsia/linux_internal.sdk.sha1 b/build/fuchsia/linux_internal.sdk.sha1
index 96c8be9..7c8f770 100644
--- a/build/fuchsia/linux_internal.sdk.sha1
+++ b/build/fuchsia/linux_internal.sdk.sha1
@@ -1 +1 @@
-7.20220111.3.1
+7.20220112.0.1
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index d22b94c3..7c8f770 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-7.20220111.2.3
+7.20220112.0.1
diff --git a/chrome/VERSION b/chrome/VERSION
index d36fc56..444224c 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=99
 MINOR=0
-BUILD=4824
+BUILD=4825
 PATCH=0
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index 418eef00..3d3d6bba 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -389,7 +389,6 @@
   "java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInternalStateHandler.java",
   "java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagementDelegate.java",
   "java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java",
-  "java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerSupplier.java",
   "java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchNetworkCommunicator.java",
   "java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchObserver.java",
   "java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchPolicy.java",
diff --git a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantCollectUserDataIntegrationTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantCollectUserDataIntegrationTest.java
index 13f34d7..06c75827 100644
--- a/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantCollectUserDataIntegrationTest.java
+++ b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantCollectUserDataIntegrationTest.java
@@ -82,6 +82,7 @@
 import org.chromium.chrome.browser.autofill_assistant.proto.KeyboardValueFillStrategy;
 import org.chromium.chrome.browser.autofill_assistant.proto.LoginDetailsProto;
 import org.chromium.chrome.browser.autofill_assistant.proto.ModelProto.ModelValue;
+import org.chromium.chrome.browser.autofill_assistant.proto.PaymentInstrumentProto;
 import org.chromium.chrome.browser.autofill_assistant.proto.PopupListSectionProto;
 import org.chromium.chrome.browser.autofill_assistant.proto.ProcessedActionProto;
 import org.chromium.chrome.browser.autofill_assistant.proto.ProcessedActionStatusProto;
@@ -989,4 +990,59 @@
         assertThat(getElementValue(getWebContents(), "profile_name"), is("John Doe"));
         assertThat(getElementValue(getWebContents(), "email"), is("johndoe@google.com"));
     }
+
+    /**
+     * Load and show a card from backend.
+     * TODO(b/214022384): Fill it into a form (requires unmasking).
+     */
+    @Test
+    @MediumTest
+    public void testShowBackendCard() throws Exception {
+        UserDataProto.Builder
+                data = UserDataProto.newBuilder().setLocale("en-US").addAvailablePaymentInstruments(
+                PaymentInstrumentProto.newBuilder()
+                        .putCardValues(55, AutofillEntryProto.newBuilder().setValue("2050").build())
+                        .putCardValues(53, AutofillEntryProto.newBuilder().setValue("7").build())
+                        .putCardValues(
+                                51, AutofillEntryProto.newBuilder().setValue("John Doe").build())
+                        .setNetwork("visaCC")
+                        .setLastFourDigits("1111")
+                        .putAddressValues(
+                                35, AutofillEntryProto.newBuilder().setValue("80302").build())
+                        .putAddressValues(
+                                36, AutofillEntryProto.newBuilder().setValue("US").build())
+                        .putAddressValues(
+                                33, AutofillEntryProto.newBuilder().setValue("Boulder").build())
+                        .putAddressValues(30,
+                                AutofillEntryProto.newBuilder().setValue("123 Broadway St").build())
+                        .putAddressValues(
+                                34, AutofillEntryProto.newBuilder().setValue("CO").build())
+                        .putAddressValues(
+                                7, AutofillEntryProto.newBuilder().setValue("John Doe").build()));
+
+        ArrayList<ActionProto> list = new ArrayList<>();
+        list.add(ActionProto.newBuilder()
+                         .setCollectUserData(CollectUserDataProto.newBuilder()
+                                                     .setUserData(data)
+                                                     .setRequestPaymentMethod(true)
+                                                     .setBillingAddressName("billing_address")
+                                                     .addSupportedBasicCardNetworks("visa")
+                                                     .setRequestTermsAndConditions(false))
+                         .build());
+        AutofillAssistantTestScript script = new AutofillAssistantTestScript(
+                SupportedScriptProto.newBuilder()
+                        .setPath("form_target_website.html")
+                        .setPresentation(PresentationProto.newBuilder().setAutostart(true))
+                        .build(),
+                list);
+
+        AutofillAssistantTestService testService =
+                new AutofillAssistantTestService(Collections.singletonList(script));
+        startAutofillAssistant(mTestRule.getActivity(), testService);
+
+        waitUntilViewMatchesCondition(allOf(withId(R.id.credit_card_number),
+                                              isDescendantOfA(withId(R.id.payment_method_summary))),
+                allOf(withText(containsString("1111")), isDisplayed()));
+        waitUntilViewMatchesCondition(withContentDescription("Continue"), isEnabled());
+    }
 }
diff --git a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingMediator.java b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingMediator.java
index 1b77772c..fb8430e 100644
--- a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingMediator.java
+++ b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingMediator.java
@@ -33,7 +33,6 @@
 import org.chromium.chrome.browser.app.ChromeActivity;
 import org.chromium.chrome.browser.compositor.CompositorViewHolder;
 import org.chromium.chrome.browser.contextualsearch.ContextualSearchManager;
-import org.chromium.chrome.browser.contextualsearch.ContextualSearchManagerSupplier;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.fullscreen.FullscreenManager;
 import org.chromium.chrome.browser.fullscreen.FullscreenOptions;
@@ -486,8 +485,7 @@
         if (VrModuleProvider.getDelegate().isInVr()) return false;
 
         // Don't open the accessory inside the contextual search panel.
-        ContextualSearchManager contextualSearch =
-                ContextualSearchManagerSupplier.getValueOrNullFrom(mWindowAndroid);
+        ContextualSearchManager contextualSearch = mActivity.getContextualSearchManager();
         if (contextualSearch != null && contextualSearch.isSearchPanelOpened()) return false;
 
         // If an accessory sheet was opened, the accessory bar must be visible.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index eb62eaa8..99a4267 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -1608,7 +1608,7 @@
     protected RootUiCoordinator createRootUiCoordinator() {
         return new TabbedRootUiCoordinator(this, this::onOmniboxFocusChanged,
                 getShareDelegateSupplier(), getActivityTabProvider(), mTabModelProfileSupplier,
-                mBookmarkBridgeSupplier, mContextualSearchManagerSupplier,
+                mBookmarkBridgeSupplier, this::getContextualSearchManager,
                 getTabModelSelectorSupplier(), mStartSurfaceSupplier,
                 mIntentMetadataOneshotSupplier, mLayoutStateProviderOneshotSupplier,
                 mStartSurfaceParentTabSupplier, getBrowserControlsManager(), getWindowAndroid(),
@@ -2620,7 +2620,7 @@
         // TODO(crbug.com/1157310): Transition this::method refs to dedicated suppliers.
         mTabModalHandler = new TabModalLifetimeHandler(this, getLifecycleDispatcher(), manager,
                 this::getAppBrowserControlsVisibilityDelegate, this::getTabObscuringHandler,
-                this::getToolbarManager, mContextualSearchManagerSupplier,
+                this::getToolbarManager, this::getContextualSearchManager,
                 getTabModelSelectorSupplier(), this::getBrowserControlsManager,
                 this::getFullscreenManager);
         return manager;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/DEPS b/chrome/android/java/src/org/chromium/chrome/browser/DEPS
index a288fdd6..c65c5ff7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/DEPS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/DEPS
@@ -24,6 +24,9 @@
   "LaunchIntentDispatcher\.java": [
     "+chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
   ],
+  "ContextualSearchTabHelper\.java": [
+    "+chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
+  ],
   "BaseCustomTabActivity\.java": [
     "+chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
   ],
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
index c3aad0f..ad9c155 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
@@ -99,7 +99,6 @@
 import org.chromium.chrome.browser.contextualsearch.ContextualSearchFieldTrial;
 import org.chromium.chrome.browser.contextualsearch.ContextualSearchManager;
 import org.chromium.chrome.browser.contextualsearch.ContextualSearchManager.ContextualSearchTabPromotionDelegate;
-import org.chromium.chrome.browser.contextualsearch.ContextualSearchManagerSupplier;
 import org.chromium.chrome.browser.dependency_injection.ChromeActivityCommonsModule;
 import org.chromium.chrome.browser.dependency_injection.ChromeActivityComponent;
 import org.chromium.chrome.browser.dependency_injection.ModuleFactoryOverrides;
@@ -282,9 +281,6 @@
     // TODO(crbug.com/1209864): Move ownership to RootUiCoordinator.
     private final UnownedUserDataSupplier<BrowserControlsManager> mBrowserControlsManagerSupplier =
             new BrowserControlsManagerSupplier();
-    // Provided as a supplier by other clasess in ChromeActivity hierarchy.
-    protected final UnownedUserDataSupplier<ContextualSearchManager>
-            mContextualSearchManagerSupplier = new ContextualSearchManagerSupplier();
 
     protected TabModelSelectorProfileSupplier mTabModelProfileSupplier =
             new TabModelSelectorProfileSupplier(mTabModelSelectorSupplier);
@@ -325,6 +321,7 @@
     private ObservableSupplierImpl<LayoutManagerImpl> mLayoutManagerSupplier =
             new ObservableSupplierImpl<>();
     private InsetObserverView mInsetObserverView;
+    private ContextualSearchManager mContextualSearchManager;
     private SnackbarManager mSnackbarManager;
 
     // Timestamp in ms when initial layout inflation begins
@@ -493,7 +490,6 @@
         mTabModelSelectorSupplier.attach(getWindowAndroid().getUnownedUserDataHost());
         mTabCreatorManagerSupplier.attach(getWindowAndroid().getUnownedUserDataHost());
         mManualFillingComponentSupplier.attach(getWindowAndroid().getUnownedUserDataHost());
-        mContextualSearchManagerSupplier.attach(getWindowAndroid().getUnownedUserDataHost());
         mBrowserControlsManagerSupplier.attach(getWindowAndroid().getUnownedUserDataHost());
         // BrowserControlsManager is ready immediately.
         mBrowserControlsManagerSupplier.set(
@@ -509,7 +505,7 @@
         // clang-format off
         return new RootUiCoordinator(this, null, getShareDelegateSupplier(),
                 getActivityTabProvider(), mTabModelProfileSupplier, mBookmarkBridgeSupplier,
-                mContextualSearchManagerSupplier, getTabModelSelectorSupplier(),
+                this::getContextualSearchManager, getTabModelSelectorSupplier(),
                 new OneshotSupplierImpl<>(), new OneshotSupplierImpl<>(),
                 new OneshotSupplierImpl<>(),
                 () -> null, mBrowserControlsManagerSupplier.get(), getWindowAndroid(),
@@ -980,10 +976,10 @@
 
         // TODO(1107916): Move contextual search initialization to the RootUiCoordinator.
         if (ContextualSearchFieldTrial.isEnabled()) {
-            mContextualSearchManagerSupplier.set(new ContextualSearchManager(this, this,
+            mContextualSearchManager = new ContextualSearchManager(this, this,
                     mRootUiCoordinator.getScrimCoordinator(), getActivityTabProvider(),
                     getFullscreenManager(), getBrowserControlsManager(), getWindowAndroid(),
-                    getTabModelSelectorSupplier().get(), () -> getLastUserInteractionTime()));
+                    getTabModelSelectorSupplier().get(), () -> getLastUserInteractionTime());
         }
 
         TraceEvent.end("ChromeActivity:CompositorInitialization");
@@ -1089,20 +1085,18 @@
         final SyncService syncService = SyncService.get();
 
         if (syncService != null && syncService.isSyncingUrlsWithKeystorePassphrase()) {
-            ContextReporter.SelectionReporter controller = null;
-            if (mContextualSearchManagerSupplier.hasValue()) {
-                controller = new ContextReporter.SelectionReporter() {
-                    @Override
-                    public void enable(Callback<GSAContextDisplaySelection> callback) {
-                        mContextualSearchManagerSupplier.get().enableContextReporting(callback);
-                    }
+            ContextReporter.SelectionReporter controller =
+                    getContextualSearchManager() != null ? new ContextReporter.SelectionReporter() {
+                        @Override
+                        public void enable(Callback<GSAContextDisplaySelection> callback) {
+                            getContextualSearchManager().enableContextReporting(callback);
+                        }
 
-                    @Override
-                    public void disable() {
-                        mContextualSearchManagerSupplier.get().disableContextReporting();
-                    }
-                };
-            }
+                        @Override
+                        public void disable() {
+                            getContextualSearchManager().disableContextReporting();
+                        }
+                    } : null;
             mContextReporter = AppHooks.get().createGsaHelper().getContextReporter(
                     getActivityTabProvider(), mTabModelSelectorSupplier, controller);
 
@@ -1532,10 +1526,10 @@
     @SuppressLint("NewApi")
     @Override
     protected final void onDestroy() {
-        if (mContextualSearchManagerSupplier.hasValue()) {
-            mContextualSearchManagerSupplier.get().destroy();
+        if (mContextualSearchManager != null) {
+            mContextualSearchManager.destroy();
+            mContextualSearchManager = null;
         }
-        mContextualSearchManagerSupplier.destroy();
 
         if (mSnackbarManager != null) {
             SnackbarManagerProvider.detach(mSnackbarManager);
@@ -2156,6 +2150,13 @@
     }
 
     /**
+     * @return The {@code ContextualSearchManager} or {@code null} if none;
+     */
+    public ContextualSearchManager getContextualSearchManager() {
+        return mContextualSearchManager;
+    }
+
+    /**
      * Exits the fullscreen mode, if any. Does nothing if no fullscreen is present.
      * @return Whether the fullscreen mode is currently showing.
      */
@@ -2198,8 +2199,8 @@
 
         mActivityTabProvider.setLayoutStateProvider(layoutManager);
 
-        if (mContextualSearchManagerSupplier.hasValue()) {
-            mContextualSearchManagerSupplier.get().initialize(contentContainer, layoutManager,
+        if (mContextualSearchManager != null) {
+            mContextualSearchManager.initialize(contentContainer, layoutManager,
                     mRootUiCoordinator.getBottomSheetController(), compositorViewHolder,
                     getControlContainerHeightResource() == ActivityUtils.NO_RESOURCE_ID
                             ? 0f
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerSupplier.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerSupplier.java
deleted file mode 100644
index 6dddecd..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerSupplier.java
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2022 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.contextualsearch;
-
-import androidx.annotation.Nullable;
-
-import org.chromium.base.UnownedUserDataKey;
-import org.chromium.base.supplier.ObservableSupplier;
-import org.chromium.base.supplier.UnownedUserDataSupplier;
-import org.chromium.ui.base.WindowAndroid;
-
-/**
- * A {@link UnownedUserDataSupplier} which manages the supplier and UnownedUserData for a
- * {@link ContextualSearchManager}.
- */
-public class ContextualSearchManagerSupplier
-        extends UnownedUserDataSupplier<ContextualSearchManager> {
-    private static final UnownedUserDataKey<ContextualSearchManagerSupplier> KEY =
-            new UnownedUserDataKey<ContextualSearchManagerSupplier>(
-                    ContextualSearchManagerSupplier.class);
-
-    /**
-     * Returns {@link ContextualSearchManager} supplier associated with the given {@link
-     * WindowAndroid} or {@code null}.
-     */
-    public static @Nullable ObservableSupplier<ContextualSearchManager> from(
-            @Nullable WindowAndroid windowAndroid) {
-        if (windowAndroid == null) return null;
-        return KEY.retrieveDataFromHost(windowAndroid.getUnownedUserDataHost());
-    }
-
-    /** Retrieves a {@link ContextualSearchManager} from {@link WindowAndroid}.  */
-    public static @Nullable ContextualSearchManager getValueOrNullFrom(
-            @Nullable WindowAndroid windowAndroid) {
-        ObservableSupplier<ContextualSearchManager> supplier = from(windowAndroid);
-        return supplier == null ? null : supplier.get();
-    }
-
-    public ContextualSearchManagerSupplier() {
-        super(KEY);
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTabHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTabHelper.java
index 6f85415..eb2e1e2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTabHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTabHelper.java
@@ -4,14 +4,15 @@
 
 package org.chromium.chrome.browser.contextualsearch;
 
+import android.app.Activity;
 import android.content.Context;
 
-import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import org.chromium.base.Log;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.NativeMethods;
+import org.chromium.chrome.browser.app.ChromeActivity;
 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel.StateChangeReason;
 import org.chromium.chrome.browser.contextualsearch.ContextualSearchFieldTrial.ContextualSearchSwitch;
 import org.chromium.chrome.browser.firstrun.FirstRunStatus;
@@ -48,6 +49,12 @@
      */
     private WebContents mWebContents;
 
+    /**
+     * The {@link ContextualSearchManager} that's managing this tab. This may point to
+     * the manager from another activity during reparenting, or be {@code null} during startup.
+     */
+    private ContextualSearchManager mContextualSearchManager;
+
     /** The GestureListener used for handling events from the current WebContents. */
     private GestureStateListener mGestureStateListener;
 
@@ -95,9 +102,7 @@
     public void onPageLoadStarted(Tab tab, GURL url) {
         updateHooksForTab(tab);
         ContextualSearchManager manager = getContextualSearchManager(tab);
-        if (manager != null) {
-            manager.onBasePageLoadStarted();
-        }
+        if (manager != null) manager.onBasePageLoadStarted();
     }
 
     @Override
@@ -146,6 +151,7 @@
         }
         removeContextualSearchHooks(mWebContents);
         mWebContents = null;
+        mContextualSearchManager = null;
         mSelectionClientManager = null;
         mGestureStateListener = null;
     }
@@ -156,6 +162,7 @@
             updateHooksForTab(tab);
         } else {
             removeContextualSearchHooks(mWebContents);
+            mContextualSearchManager = null;
         }
     }
 
@@ -188,7 +195,8 @@
     private void updateHooksForTab(Tab tab) {
         WebContents currentWebContents = tab.getWebContents();
         boolean webContentsChanged = currentWebContents != mWebContents;
-        if (webContentsChanged) {
+        if (webContentsChanged || mContextualSearchManager != getContextualSearchManager(tab)) {
+            mContextualSearchManager = getContextualSearchManager(tab);
             if (webContentsChanged && currentWebContents != null) {
                 // Ensure the hooks are cleared on the old web contents before proceeding. All of
                 // the objects associated with the web content need to be recreated in order for
@@ -257,9 +265,9 @@
                         mSelectionClientManager.removeContextualSearchSelectionClient());
             }
             // Also make sure the UI is hidden if the device is offline.
-            ContextualSearchManager manager = getContextualSearchManager(mTab);
-            if (manager != null && !isDeviceOnline(manager)) {
-                manager.hideContextualSearch(StateChangeReason.UNKNOWN);
+            ContextualSearchManager contextualSearchManager = getContextualSearchManager(mTab);
+            if (contextualSearchManager != null && !isDeviceOnline(contextualSearchManager)) {
+                contextualSearchManager.hideContextualSearch(StateChangeReason.UNKNOWN);
             }
         }
     }
@@ -312,12 +320,16 @@
     }
 
     /**
-     * Gets the {@link ContextualSearchManager} associated with the given tab.
+     * Gets the {@link ContextualSearchManager} associated with the given tab's activity.
      * @param tab The {@link Tab} that we're getting the manager for.
      * @return The Contextual Search manager controlling that Tab.
      */
-    private ContextualSearchManager getContextualSearchManager(@NonNull Tab tab) {
-        return ContextualSearchManagerSupplier.getValueOrNullFrom(tab.getWindowAndroid());
+    private ContextualSearchManager getContextualSearchManager(Tab tab) {
+        Activity activity = tab.getWindowAndroid().getActivity().get();
+        if (activity instanceof ChromeActivity) {
+            return ((ChromeActivity) activity).getContextualSearchManager();
+        }
+        return null;
     }
 
     // ============================================================================================
@@ -341,9 +353,9 @@
     @CalledByNative
     void onShowUnhandledTapUIIfNeeded(int x, int y, int fontSizeDips, int textRunLength) {
         // Only notify the manager if we currently have a valid listener.
-        ContextualSearchManager manager = getContextualSearchManager(mTab);
-        if (mGestureStateListener != null && manager != null) {
-            manager.onShowUnhandledTapUIIfNeeded(x, y, fontSizeDips, textRunLength);
+        if (mGestureStateListener != null && getContextualSearchManager(mTab) != null) {
+            getContextualSearchManager(mTab).onShowUnhandledTapUIIfNeeded(
+                    x, y, fontSizeDips, textRunLength);
         }
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java
index 8b6e698..807987c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java
@@ -154,7 +154,7 @@
         mBaseCustomTabRootUiCoordinator = new BaseCustomTabRootUiCoordinator(this,
                 getShareDelegateSupplier(),
                 getActivityTabProvider(), mTabModelProfileSupplier, mBookmarkBridgeSupplier,
-                mContextualSearchManagerSupplier, getTabModelSelectorSupplier(),
+                this::getContextualSearchManager, getTabModelSelectorSupplier(),
                 getBrowserControlsManager(), getWindowAndroid(), getLifecycleDispatcher(),
                 getLayoutManagerSupplier(),
                 /* menuOrKeyboardActionController= */ this, this::getActivityThemeColor,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/ui/ChromeMediaNotificationControllerDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/media/ui/ChromeMediaNotificationControllerDelegate.java
index a6834771..acbdece 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/ui/ChromeMediaNotificationControllerDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/ui/ChromeMediaNotificationControllerDelegate.java
@@ -19,6 +19,7 @@
 import androidx.mediarouter.media.MediaRouter;
 
 import org.chromium.base.ContextUtils;
+import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.base.SplitCompatService;
 import org.chromium.chrome.browser.notifications.NotificationConstants;
@@ -165,7 +166,19 @@
                 Intent i = new Intent(getContext(),
                         ChromeMediaNotificationControllerServices.PlaybackListenerService.class);
                 i.setAction(intent.getAction());
-                getContext().startService(i);
+                boolean succeeded = true;
+                try {
+                    getContext().startService(i);
+                } catch (RuntimeException e) {
+                    // This happens occasionally with "cannot start foreground service".  It's not
+                    // at all clear what causes it; no combination of multi-window / background
+                    // unplugging headphones has managed to repro it locally.  While it might be
+                    // possible to trampoline this through an activity like we do elsewhere for
+                    // notifications, that's a fairly invasive change without a local repro.  So,
+                    // for now, just log that this happened and move on. https://crbug.com/1245017
+                    succeeded = false;
+                }
+                RecordHistogram.recordBooleanHistogram("Media.Android.BecomingNoisy", succeeded);
             }
         };
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInstrumentationBase.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInstrumentationBase.java
index b235abb..7ccbde21 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInstrumentationBase.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInstrumentationBase.java
@@ -13,6 +13,7 @@
 import android.app.Instrumentation.ActivityMonitor;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.SharedPreferences;
 import android.graphics.Point;
 import android.os.SystemClock;
 import android.support.test.InstrumentationRegistry;
@@ -30,6 +31,7 @@
 import org.junit.ClassRule;
 import org.junit.Rule;
 
+import org.chromium.base.ContextUtils;
 import org.chromium.base.FeatureList;
 import org.chromium.base.Log;
 import org.chromium.base.ThreadUtils;
@@ -49,7 +51,6 @@
 import org.chromium.chrome.browser.locale.LocaleManager;
 import org.chromium.chrome.browser.locale.LocaleManagerDelegate;
 import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
-import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.batch.BlankCTATabInitialStateRule;
@@ -220,10 +221,7 @@
 
         sActivityTestRule.loadUrl(mTestServer.getURL(mTestPage));
 
-        mManager = TestThreadUtils.runOnUiThreadBlocking(() -> {
-            return ContextualSearchManagerSupplier.getValueOrNullFrom(
-                    sActivityTestRule.getActivity().getWindowAndroid());
-        });
+        mManager = sActivityTestRule.getActivity().getContextualSearchManager();
         mTestHost = new ContextualSearchInstrumentationTestHost();
 
         Assert.assertNotNull(mManager);
@@ -1030,10 +1028,14 @@
      */
     private void resetCounters() {
         TestThreadUtils.runOnUiThreadBlocking(() -> {
-            SharedPreferencesManager prefs = SharedPreferencesManager.getInstance();
+            // TODO(donnd): Use SharedPreferencesManager instead to access SharedPreferences.
+            SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
             boolean freStatus =
-                    prefs.readBoolean(ChromePreferenceKeys.FIRST_RUN_FLOW_COMPLETE, false);
-            prefs.writeBoolean(ChromePreferenceKeys.FIRST_RUN_FLOW_COMPLETE, freStatus);
+                    prefs.getBoolean(ChromePreferenceKeys.FIRST_RUN_FLOW_COMPLETE, false);
+            prefs.edit()
+                    .clear()
+                    .putBoolean(ChromePreferenceKeys.FIRST_RUN_FLOW_COMPLETE, freStatus)
+                    .apply();
         });
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java
index b2696fd..45ab0b33 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java
@@ -283,10 +283,7 @@
 
         sActivityTestRule.loadUrl(mTestServer.getURL(TEST_PAGE));
 
-        mManager = TestThreadUtils.runOnUiThreadBlocking(() -> {
-            return ContextualSearchManagerSupplier.getValueOrNullFrom(
-                    sActivityTestRule.getActivity().getWindowAndroid());
-        });
+        mManager = sActivityTestRule.getActivity().getContextualSearchManager();
         mTestHost = new ContextualSearchManagerTestHost();
 
         Assert.assertNotNull(mManager);
@@ -3743,10 +3740,9 @@
         // Trigger on a word and wait for the selection to be established.
         triggerNode(activity2.getActivityTab(), "search");
         CriteriaHelper.pollUiThread(() -> {
-            String selection =
-                    ContextualSearchManagerSupplier.getValueOrNullFrom(activity2.getWindowAndroid())
-                            .getSelectionController()
-                            .getSelectedText();
+            String selection = activity2.getContextualSearchManager()
+                                       .getSelectionController()
+                                       .getSelectedText();
             Criteria.checkThat(selection, Matchers.is("Search"));
         });
         TestThreadUtils.runOnUiThreadBlocking(() -> activity2.getCurrentTabModel().closeAllTabs());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/previewtab/PreviewTabTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/previewtab/PreviewTabTest.java
index 09947c48..751bb96e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/previewtab/PreviewTabTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/previewtab/PreviewTabTest.java
@@ -20,7 +20,6 @@
 import org.chromium.chrome.browser.app.ChromeActivity;
 import org.chromium.chrome.browser.compositor.bottombar.ephemeraltab.EphemeralTabCoordinator;
 import org.chromium.chrome.browser.contextualsearch.ContextualSearchManager;
-import org.chromium.chrome.browser.contextualsearch.ContextualSearchManagerSupplier;
 import org.chromium.chrome.browser.firstrun.DisableFirstRun;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabbed_mode.TabbedRootUiCoordinator;
@@ -159,15 +158,14 @@
     @Feature({"PreviewTab"})
     public void testSuppressContextualSearch() throws Throwable {
         ChromeActivity activity = mActivityTestRule.getActivity();
-        ContextualSearchManager csManager = TestThreadUtils.runOnUiThreadBlocking(() -> {
-            return ContextualSearchManagerSupplier.getValueOrNullFrom(activity.getWindowAndroid());
-        });
+        ContextualSearchManager csManager = activity.getContextualSearchManager();
         Assert.assertFalse("Contextual Search should be active", csManager.isSuppressed());
 
-        TestThreadUtils.runOnUiThreadBlocking(() -> {
-            mEphemeralTabCoordinator.requestOpenSheet(
-                    new GURL(mTestServer.getServer().getURL(PREVIEW_TAB)), "PreviewTab", false);
-        });
+        TestThreadUtils.runOnUiThreadBlocking(
+                ()
+                        -> mEphemeralTabCoordinator.requestOpenSheet(
+                                new GURL(mTestServer.getServer().getURL(PREVIEW_TAB)), "PreviewTab",
+                                false));
         endAnimations();
         Assert.assertTrue("The Preview Tab did not open", mEphemeralTabCoordinator.isOpened());
         Assert.assertTrue("Contextual Search should be suppressed", csManager.isSuppressed());
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 599783f..4fb2ca1 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -3573,29 +3573,6 @@
       "accuracy_tips/accuracy_service_delegate.h",
       "accuracy_tips/accuracy_service_factory.cc",
       "accuracy_tips/accuracy_service_factory.h",
-      "apps/app_discovery_service/app_discovery_features.cc",
-      "apps/app_discovery_service/app_discovery_features.h",
-      "apps/app_discovery_service/app_discovery_service.cc",
-      "apps/app_discovery_service/app_discovery_service.h",
-      "apps/app_discovery_service/app_discovery_service_factory.cc",
-      "apps/app_discovery_service/app_discovery_service_factory.h",
-      "apps/app_discovery_service/app_discovery_util.h",
-      "apps/app_discovery_service/app_fetcher_manager.cc",
-      "apps/app_discovery_service/app_fetcher_manager.h",
-      "apps/app_discovery_service/play_extras.cc",
-      "apps/app_discovery_service/play_extras.h",
-      "apps/app_discovery_service/recommended_arc_app_fetcher.cc",
-      "apps/app_discovery_service/recommended_arc_app_fetcher.h",
-      "apps/app_discovery_service/remote_url_search/remote_url_client.cc",
-      "apps/app_discovery_service/remote_url_search/remote_url_client.h",
-      "apps/app_discovery_service/remote_url_search/remote_url_fetcher.cc",
-      "apps/app_discovery_service/remote_url_search/remote_url_fetcher.h",
-      "apps/app_discovery_service/remote_url_search/remote_url_index.cc",
-      "apps/app_discovery_service/remote_url_search/remote_url_index.h",
-      "apps/app_discovery_service/result.cc",
-      "apps/app_discovery_service/result.h",
-      "apps/app_discovery_service/test_fetcher.cc",
-      "apps/app_discovery_service/test_fetcher.h",
       "apps/app_service/app_icon/app_icon_factory.cc",
       "apps/app_service/app_icon/app_icon_factory.h",
       "apps/app_service/app_icon/app_icon_loader.cc",
@@ -4557,6 +4534,29 @@
   if (is_chromeos_ash) {
     assert(enable_system_notifications)
     sources += [
+      "apps/app_discovery_service/app_discovery_features.cc",
+      "apps/app_discovery_service/app_discovery_features.h",
+      "apps/app_discovery_service/app_discovery_service.cc",
+      "apps/app_discovery_service/app_discovery_service.h",
+      "apps/app_discovery_service/app_discovery_service_factory.cc",
+      "apps/app_discovery_service/app_discovery_service_factory.h",
+      "apps/app_discovery_service/app_discovery_util.h",
+      "apps/app_discovery_service/app_fetcher_manager.cc",
+      "apps/app_discovery_service/app_fetcher_manager.h",
+      "apps/app_discovery_service/play_extras.cc",
+      "apps/app_discovery_service/play_extras.h",
+      "apps/app_discovery_service/recommended_arc_app_fetcher.cc",
+      "apps/app_discovery_service/recommended_arc_app_fetcher.h",
+      "apps/app_discovery_service/remote_url_search/remote_url_client.cc",
+      "apps/app_discovery_service/remote_url_search/remote_url_client.h",
+      "apps/app_discovery_service/remote_url_search/remote_url_fetcher.cc",
+      "apps/app_discovery_service/remote_url_search/remote_url_fetcher.h",
+      "apps/app_discovery_service/remote_url_search/remote_url_index.cc",
+      "apps/app_discovery_service/remote_url_search/remote_url_index.h",
+      "apps/app_discovery_service/result.cc",
+      "apps/app_discovery_service/result.h",
+      "apps/app_discovery_service/test_fetcher.cc",
+      "apps/app_discovery_service/test_fetcher.h",
       "apps/app_service/app_icon/arc_activity_adaptive_icon_impl.cc",
       "apps/app_service/app_icon/arc_activity_adaptive_icon_impl.h",
       "apps/app_service/app_icon/arc_icon_once_loader.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index f7cbd57..efb5d7c 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -3184,6 +3184,10 @@
     {"enable-wireguard", flag_descriptions::kEnableWireGuardName,
      flag_descriptions::kEnableWireGuardDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(ash::features::kEnableWireGuard)},
+    {"enforce-ash-extension-keeplist",
+     flag_descriptions::kEnforceAshExtensionKeeplistName,
+     flag_descriptions::kEnforceAshExtensionKeeplistDescription, kOsCrOS,
+     FEATURE_VALUE_TYPE(ash::features::kEnforceAshExtensionKeeplist)},
     {"esim-policy", flag_descriptions::kESimPolicyName,
      flag_descriptions::kESimPolicyDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(chromeos::features::kESimPolicy)},
@@ -5584,11 +5588,6 @@
 #endif
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-    {"app-service-external-protocol",
-     flag_descriptions::kAppServiceExternalProtocolName,
-     flag_descriptions::kAppServiceExternalProtocolDescription, kOsCrOS,
-     FEATURE_VALUE_TYPE(features::kAppServiceExternalProtocol)},
-
     {"arc-ghost-window", flag_descriptions::kArcGhostWindowName,
      flag_descriptions::kArcGhostWindowDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(full_restore::features::kArcGhostWindow)},
diff --git a/chrome/browser/android/autofill_assistant/client_android.cc b/chrome/browser/android/autofill_assistant/client_android.cc
index 64f660a..cbf066a 100644
--- a/chrome/browser/android/autofill_assistant/client_android.cc
+++ b/chrome/browser/android/autofill_assistant/client_android.cc
@@ -656,7 +656,7 @@
       base::DefaultTickClock::GetInstance(),
       RuntimeManager::GetForWebContents(GetWebContents())->GetWeakPtr(),
       std::move(service), std::move(tts_controller), ukm::UkmRecorder::Get(),
-      AnnotateDomModelServiceFactory::GetInstance()->GetForBrowserContext(
+      dependencies_->GetAnnotateDomModelService(
           GetWebContents()->GetBrowserContext()));
   controller_->SetStatusMessage(status_message);
   if (progress_bar_config) {
diff --git a/chrome/browser/android/autofill_assistant/dependencies.h b/chrome/browser/android/autofill_assistant/dependencies.h
index e48a316..70bc6c3 100644
--- a/chrome/browser/android/autofill_assistant/dependencies.h
+++ b/chrome/browser/android/autofill_assistant/dependencies.h
@@ -9,7 +9,9 @@
 #include "base/android/scoped_java_ref.h"
 #include "base/strings/string_piece.h"
 #include "chrome/browser/android/autofill_assistant/assistant_field_trial_util.h"
+#include "components/autofill_assistant/content/browser/annotate_dom_model_service.h"
 #include "components/variations/service/variations_service.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/browser/web_contents.h"
 
 namespace autofill_assistant {
@@ -38,6 +40,9 @@
   virtual std::string GetChromeSignedInEmailAddress(
       content::WebContents* web_contents) const = 0;
 
+  virtual AnnotateDomModelService* GetAnnotateDomModelService(
+      content::BrowserContext* browser_context) const = 0;
+
  protected:
   Dependencies(JNIEnv* env,
                const base::android::JavaParamRef<jobject>& java_object);
diff --git a/chrome/browser/android/autofill_assistant/dependencies_chrome.cc b/chrome/browser/android/autofill_assistant/dependencies_chrome.cc
index 72f1ebf8..e33f64df 100644
--- a/chrome/browser/android/autofill_assistant/dependencies_chrome.cc
+++ b/chrome/browser/android/autofill_assistant/dependencies_chrome.cc
@@ -7,13 +7,16 @@
 #include "base/android/scoped_java_ref.h"
 #include "base/strings/string_piece.h"
 #include "chrome/android/features/autofill_assistant/jni_headers/AssistantStaticDependenciesChrome_jni.h"
+#include "chrome/browser/android/autofill_assistant/annotate_dom_model_service_factory.h"
 #include "chrome/browser/android/autofill_assistant/assistant_field_trial_util.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
+#include "components/autofill_assistant/content/browser/annotate_dom_model_service.h"
 #include "components/signin/public/identity_manager/account_info.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/browser/web_contents.h"
 
 using ::base::StringPiece;
@@ -62,4 +65,10 @@
   return account_info.email;
 }
 
+AnnotateDomModelService* DependenciesChrome::GetAnnotateDomModelService(
+    content::BrowserContext* browser_context) const {
+  return AnnotateDomModelServiceFactory::GetInstance()->GetForBrowserContext(
+      browser_context);
+}
+
 }  // namespace autofill_assistant
diff --git a/chrome/browser/android/autofill_assistant/dependencies_chrome.h b/chrome/browser/android/autofill_assistant/dependencies_chrome.h
index 46f00b8..db870f0 100644
--- a/chrome/browser/android/autofill_assistant/dependencies_chrome.h
+++ b/chrome/browser/android/autofill_assistant/dependencies_chrome.h
@@ -10,8 +10,10 @@
 #include "base/android/scoped_java_ref.h"
 #include "base/strings/string_piece.h"
 #include "chrome/browser/android/autofill_assistant/assistant_field_trial_util.h"
+#include "components/autofill_assistant/content/browser/annotate_dom_model_service.h"
 #include "components/metrics/metrics_service_accessor.h"
 #include "components/variations/service/variations_service.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/browser/web_contents.h"
 
 namespace autofill_assistant {
@@ -30,6 +32,9 @@
 
   std::string GetChromeSignedInEmailAddress(
       content::WebContents* web_contents) const override;
+
+  AnnotateDomModelService* GetAnnotateDomModelService(
+      content::BrowserContext* browser_context) const override;
 };
 
 }  // namespace autofill_assistant
diff --git a/chrome/browser/apps/app_discovery_service/app_discovery_service_unittest.cc b/chrome/browser/apps/app_discovery_service/app_discovery_service_unittest.cc
index ab78a6b..954800e 100644
--- a/chrome/browser/apps/app_discovery_service/app_discovery_service_unittest.cc
+++ b/chrome/browser/apps/app_discovery_service/app_discovery_service_unittest.cc
@@ -69,14 +69,15 @@
 
   std::vector<Result> fake_results;
   fake_results.emplace_back(
-      Result(AppSource::kPlay, kTestAppId, kTestAppTitle, nullptr));
+      Result(AppSource::kTestSource, kTestAppId, kTestAppTitle, nullptr));
   test_fetcher()->SetResults(std::move(fake_results));
 
   app_discovery_service->GetApps(
-      ResultType::kRecommendedArcApps,
+      ResultType::kTestType,
       base::BindLambdaForTesting([this](std::vector<Result> results) {
         EXPECT_EQ(results.size(), 1u);
-        CheckResult(results[0], AppSource::kPlay, kTestAppId, kTestAppTitle);
+        CheckResult(results[0], AppSource::kTestSource, kTestAppId,
+                    kTestAppTitle);
         EXPECT_FALSE(results[0].GetSourceExtras());
       }));
 }
@@ -91,13 +92,13 @@
   auto play_extras = std::make_unique<PlayExtras>(
       kTestPlayAppPackageName, kTestIconUrl, kTestPlayAppCategory,
       kTestPlayAppDescription, kTestPlayAppContentRating, kTestIconUrl, true,
-      false, false);
+      false, false, false);
   fake_results.emplace_back(Result(AppSource::kPlay, kTestAppId, kTestAppTitle,
                                    std::move(play_extras)));
   test_fetcher()->SetResults(std::move(fake_results));
 
   app_discovery_service->GetApps(
-      ResultType::kRecommendedArcApps,
+      ResultType::kTestType,
       base::BindLambdaForTesting([this](std::vector<Result> results) {
         GURL kTestIconUrl(kTestPlayAppIconUrl);
         EXPECT_EQ(results.size(), 1u);
@@ -114,6 +115,7 @@
         EXPECT_EQ(play_extras->GetHasInAppPurchases(), true);
         EXPECT_EQ(play_extras->GetWasPreviouslyInstalled(), false);
         EXPECT_EQ(play_extras->GetContainsAds(), false);
+        EXPECT_EQ(play_extras->GetOptimizedForChrome(), false);
       }));
 }
 
diff --git a/chrome/browser/apps/app_discovery_service/app_discovery_util.h b/chrome/browser/apps/app_discovery_service/app_discovery_util.h
index 28ae988..cb90d5f 100644
--- a/chrome/browser/apps/app_discovery_service/app_discovery_util.h
+++ b/chrome/browser/apps/app_discovery_service/app_discovery_util.h
@@ -13,11 +13,13 @@
 namespace apps {
 
 enum class ResultType {
+  kTestType,
   kRecommendedArcApps,
   kRemoteUrlSearch,
 };
 
 enum class AppSource {
+  kTestSource,
   kPlay,
 };
 
diff --git a/chrome/browser/apps/app_discovery_service/app_fetcher_manager.cc b/chrome/browser/apps/app_discovery_service/app_fetcher_manager.cc
index d3ac823d..0c2426ff 100644
--- a/chrome/browser/apps/app_discovery_service/app_fetcher_manager.cc
+++ b/chrome/browser/apps/app_discovery_service/app_fetcher_manager.cc
@@ -24,16 +24,17 @@
 
 void AppFetcherManager::GetApps(ResultType result_type,
                                 ResultCallback callback) {
-  if (g_test_fetcher_) {
-    g_test_fetcher_->GetApps(std::move(callback));
-    return;
-  }
-
   switch (result_type) {
+    case ResultType::kTestType:
+      DCHECK(g_test_fetcher_);
+      g_test_fetcher_->GetApps(std::move(callback));
+      return;
     case ResultType::kRecommendedArcApps:
+      DCHECK(recommended_arc_app_fetcher_);
       recommended_arc_app_fetcher_->GetApps(std::move(callback));
       return;
     case ResultType::kRemoteUrlSearch:
+      DCHECK(remote_url_fetcher_);
       remote_url_fetcher_->GetApps(std::move(callback));
       return;
   }
diff --git a/chrome/browser/apps/app_discovery_service/play_extras.cc b/chrome/browser/apps/app_discovery_service/play_extras.cc
index d89d8ae..d3a29ac 100644
--- a/chrome/browser/apps/app_discovery_service/play_extras.cc
+++ b/chrome/browser/apps/app_discovery_service/play_extras.cc
@@ -14,7 +14,8 @@
                        const GURL& content_rating_icon_url,
                        const bool has_in_app_purchases,
                        const bool was_previously_installed,
-                       const bool contains_ads)
+                       const bool contains_ads,
+                       const bool optimized_for_chrome)
     : package_name_(package_name),
       icon_url_(icon_url),
       category_(category),
@@ -23,7 +24,8 @@
       content_rating_icon_url_(content_rating_icon_url),
       has_in_app_purchases_(has_in_app_purchases),
       was_previously_installed_(was_previously_installed),
-      contains_ads_(contains_ads) {}
+      contains_ads_(contains_ads),
+      optimized_for_chrome_(optimized_for_chrome) {}
 
 PlayExtras::~PlayExtras() = default;
 
@@ -63,6 +65,10 @@
   return contains_ads_;
 }
 
+bool PlayExtras::GetOptimizedForChrome() const {
+  return optimized_for_chrome_;
+}
+
 PlayExtras* PlayExtras::AsPlayExtras() {
   return this;
 }
diff --git a/chrome/browser/apps/app_discovery_service/play_extras.h b/chrome/browser/apps/app_discovery_service/play_extras.h
index 29cb65ac..4efa24d 100644
--- a/chrome/browser/apps/app_discovery_service/play_extras.h
+++ b/chrome/browser/apps/app_discovery_service/play_extras.h
@@ -22,7 +22,8 @@
                       const GURL& content_rating_icon_url,
                       const bool in_app_purchases,
                       const bool previously_installed,
-                      const bool contains_ads);
+                      const bool contains_ads,
+                      const bool optimized_for_chrome);
   PlayExtras(const PlayExtras&) = delete;
   PlayExtras& operator=(const PlayExtras&) = delete;
   ~PlayExtras() override;
@@ -38,6 +39,7 @@
   // that this user owns.
   bool GetWasPreviouslyInstalled() const;
   bool GetContainsAds() const;
+  bool GetOptimizedForChrome() const;
 
   // Result::SourceExtras:
   PlayExtras* AsPlayExtras() override;
@@ -52,6 +54,7 @@
   bool has_in_app_purchases_;
   bool was_previously_installed_;
   bool contains_ads_;
+  bool optimized_for_chrome_;
 };
 
 }  // namespace apps
diff --git a/chrome/browser/apps/app_discovery_service/recommended_arc_app_fetcher.cc b/chrome/browser/apps/app_discovery_service/recommended_arc_app_fetcher.cc
index d4d3be19..1285b06 100644
--- a/chrome/browser/apps/app_discovery_service/recommended_arc_app_fetcher.cc
+++ b/chrome/browser/apps/app_discovery_service/recommended_arc_app_fetcher.cc
@@ -6,11 +6,97 @@
 
 #include <utility>
 
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/apps/app_discovery_service/play_extras.h"
+#include "chrome/browser/ash/login/screens/recommend_apps/recommend_apps_fetcher.h"
+
 namespace apps {
 
+RecommendedArcAppFetcher::RecommendedArcAppFetcher() = default;
+RecommendedArcAppFetcher::~RecommendedArcAppFetcher() = default;
+
 void RecommendedArcAppFetcher::GetApps(ResultCallback callback) {
-  // TODO(crbug.com/1223321) : Implement.
-  std::move(callback).Run({});
+  callback_ = std::move(callback);
+  recommend_apps_fetcher_ = ash::RecommendAppsFetcher::Create(this);
+  recommend_apps_fetcher_->Start();
+}
+
+void RecommendedArcAppFetcher::OnLoadSuccess(const base::Value& app_list) {
+  if (!callback_)
+    return;
+  if (!app_list.is_dict()) {
+    std::move(callback_).Run({});
+    return;
+  }
+
+  const base::Value* app_value = app_list.FindListKey("recommendedApp");
+  if (!app_value || !app_value->is_list()) {
+    std::move(callback_).Run({});
+    return;
+  }
+
+  base::Value::ConstListView apps = app_value->GetList();
+  if (apps.empty()) {
+    std::move(callback_).Run({});
+    return;
+  }
+
+  std::vector<Result> results;
+  for (auto& big_app : apps) {
+    if (big_app.is_dict()) {
+      const base::Value* app = big_app.FindDictKey("androidApp");
+      if (!app) {
+        continue;
+      }
+      const std::string* package_name = app->FindStringKey("packageName");
+      const std::string* title = app->FindStringKey("title");
+      if (!package_name || !title)
+        continue;
+      const std::string* icon_url = app->FindStringPath("icon.imageUri");
+      const std::string* category = app->FindStringKey("category");
+      const std::string* app_description =
+          app->FindStringPath("appDescription.shortDescription");
+      const std::string* content_rating =
+          app->FindStringPath("contentRating.name");
+      const std::string* content_rating_url =
+          app->FindStringPath("contentRating.image.imageUri");
+      const std::string* in_app_purchases =
+          app->FindStringPath("inAppPurchaseInformation.disclaimerText");
+      const std::string* previously_installed =
+          app->FindStringPath("fastAppReinstall.explanationText");
+      const std::string* contain_ads =
+          app->FindStringPath("adsInformation.disclaimerText");
+      const base::Value* optimized_for_chrome =
+          big_app.FindDictKey("merchCurated");
+
+      auto extras = std::make_unique<PlayExtras>(
+          *package_name, icon_url ? GURL(*icon_url) : GURL(),
+          category ? base::UTF8ToUTF16(*category) : u"",
+          app_description ? base::UTF8ToUTF16(*app_description) : u"",
+          content_rating ? base::UTF8ToUTF16(*content_rating) : u"",
+          content_rating_url ? GURL(*content_rating_url) : GURL(),
+          (in_app_purchases != nullptr), (previously_installed != nullptr),
+          (contain_ads != nullptr), (optimized_for_chrome != nullptr));
+      results.emplace_back(Result(AppSource::kPlay, *package_name,
+                                  base::UTF8ToUTF16(*title),
+                                  std::move(extras)));
+    }
+  }
+  std::move(callback_).Run(std::move(results));
+}
+
+void RecommendedArcAppFetcher::OnLoadError() {
+  if (callback_)
+    std::move(callback_).Run({});
+}
+
+void RecommendedArcAppFetcher::OnParseResponseError() {
+  if (callback_)
+    std::move(callback_).Run({});
+}
+
+void RecommendedArcAppFetcher::SetCallbackForTesting(ResultCallback callback) {
+  callback_ = std::move(callback);
 }
 
 }  // namespace apps
diff --git a/chrome/browser/apps/app_discovery_service/recommended_arc_app_fetcher.h b/chrome/browser/apps/app_discovery_service/recommended_arc_app_fetcher.h
index e09861ca..eef6829 100644
--- a/chrome/browser/apps/app_discovery_service/recommended_arc_app_fetcher.h
+++ b/chrome/browser/apps/app_discovery_service/recommended_arc_app_fetcher.h
@@ -5,20 +5,39 @@
 #ifndef CHROME_BROWSER_APPS_APP_DISCOVERY_SERVICE_RECOMMENDED_ARC_APP_FETCHER_H_
 #define CHROME_BROWSER_APPS_APP_DISCOVERY_SERVICE_RECOMMENDED_ARC_APP_FETCHER_H_
 
+#include "base/values.h"
 #include "chrome/browser/apps/app_discovery_service/app_discovery_util.h"
 #include "chrome/browser/apps/app_discovery_service/app_fetcher_manager.h"
+#include "chrome/browser/ash/login/screens/recommend_apps/recommend_apps_fetcher_delegate.h"
+
+namespace ash {
+class RecommendAppsFetcher;
+}
 
 namespace apps {
 
-class RecommendedArcAppFetcher : public AppFetcher {
+class RecommendedArcAppFetcher : public AppFetcher,
+                                 public ash::RecommendAppsFetcherDelegate {
  public:
-  RecommendedArcAppFetcher() = default;
+  RecommendedArcAppFetcher();
   RecommendedArcAppFetcher(const RecommendedArcAppFetcher&) = delete;
   RecommendedArcAppFetcher& operator=(const RecommendedArcAppFetcher&) = delete;
-  ~RecommendedArcAppFetcher() override = default;
+  ~RecommendedArcAppFetcher() override;
 
   // AppFetcher:
   void GetApps(ResultCallback callback) override;
+
+  // RecommendAppsFetcherDelegate:
+  void OnLoadSuccess(const base::Value& app_list) override;
+  void OnLoadError() override;
+  void OnParseResponseError() override;
+
+  // For Testing:
+  void SetCallbackForTesting(ResultCallback callback);
+
+ private:
+  apps::ResultCallback callback_;
+  std::unique_ptr<ash::RecommendAppsFetcher> recommend_apps_fetcher_;
 };
 
 }  // namespace apps
diff --git a/chrome/browser/apps/app_discovery_service/recommended_arc_app_fetcher_unittest.cc b/chrome/browser/apps/app_discovery_service/recommended_arc_app_fetcher_unittest.cc
new file mode 100644
index 0000000..a3d5515
--- /dev/null
+++ b/chrome/browser/apps/app_discovery_service/recommended_arc_app_fetcher_unittest.cc
@@ -0,0 +1,119 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/apps/app_discovery_service/recommended_arc_app_fetcher.h"
+
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/test/bind.h"
+#include "chrome/browser/apps/app_discovery_service/play_extras.h"
+#include "content/public/test/browser_task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace apps {
+
+class RecommendedArcAppFetcherTest : public testing::Test {
+ public:
+  RecommendedArcAppFetcherTest() = default;
+
+  void SetUp() override {
+    arc_app_fetcher_ = std::make_unique<RecommendedArcAppFetcher>();
+  }
+
+  RecommendedArcAppFetcher* arc_app_fetcher() { return arc_app_fetcher_.get(); }
+
+ private:
+  content::BrowserTaskEnvironment task_environment_;
+  std::unique_ptr<RecommendedArcAppFetcher> arc_app_fetcher_;
+};
+
+TEST_F(RecommendedArcAppFetcherTest, OnLoadSuccess) {
+  const std::string response =
+      R"json({"recommendedApp": [{
+    "androidApp": {
+      "packageName": "com.game.name",
+      "title": "NameOfFunGame",
+      "icon": {
+        "imageUri": "https://play-lh.googleusercontent.com/1234IDECLAREATHUMBWAR",
+        "dimensions": {
+          "width": 512,
+          "height": 512
+        }
+      },
+      "starRating": {
+        "averageRating": 4.3319736
+      },
+      "category": "Casual",
+      "appDescription": {
+        "shortDescription": "Wow this game is so fun!"
+      },
+      "contentRating": {
+        "name": "General",
+        "image": {
+          "imageUri": "https://play-lh.googleusercontent.com/5678WHODOWEAPPRECIATE",
+          "dimensions": {
+            "width": 272,
+            "height": 272
+          }
+        },
+        "imageCanBeDisplayedWithoutName": false
+      },
+      "downloadsInformation": {
+        "numDownloadsRounded": "100000000"
+      },
+      "adsInformation": {
+        "disclaimerText": "Contains ads"
+      },
+      "inAppPurchaseInformation": {
+        "disclaimerText": "In-app purchases"
+      }
+    },
+    "merchCurated": {
+    }
+  }]})json";
+  arc_app_fetcher()->SetCallbackForTesting(
+      base::BindLambdaForTesting([](std::vector<Result> results) {
+        ASSERT_EQ(results.size(), 1u);
+        EXPECT_EQ(results[0].GetAppSource(), AppSource::kPlay);
+        EXPECT_EQ(results[0].GetAppId(), "com.game.name");
+        EXPECT_EQ(results[0].GetAppTitle(), u"NameOfFunGame");
+        EXPECT_TRUE(results[0].GetSourceExtras());
+        auto* play_extras = results[0].GetSourceExtras()->AsPlayExtras();
+        EXPECT_TRUE(play_extras);
+        EXPECT_EQ(play_extras->GetPackageName(), "com.game.name");
+        EXPECT_EQ(
+            play_extras->GetIconUrl(),
+            GURL(
+                "https://play-lh.googleusercontent.com/1234IDECLAREATHUMBWAR"));
+        EXPECT_EQ(play_extras->GetCategory(), u"Casual");
+        EXPECT_EQ(play_extras->GetDescription(), u"Wow this game is so fun!");
+        EXPECT_EQ(play_extras->GetContentRating(), u"General");
+        EXPECT_EQ(
+            play_extras->GetContentRatingIconUrl(),
+            GURL(
+                "https://play-lh.googleusercontent.com/5678WHODOWEAPPRECIATE"));
+        EXPECT_EQ(play_extras->GetHasInAppPurchases(), true);
+        EXPECT_EQ(play_extras->GetWasPreviouslyInstalled(), false);
+        EXPECT_EQ(play_extras->GetContainsAds(), true);
+        EXPECT_EQ(play_extras->GetOptimizedForChrome(), true);
+      }));
+  absl::optional<base::Value> output =
+      base::JSONReader::ReadAndReturnValueWithError(response).value;
+  ASSERT_TRUE(output.has_value());
+  arc_app_fetcher()->OnLoadSuccess(std::move(output.value()));
+}
+
+TEST_F(RecommendedArcAppFetcherTest, OnLoadError) {
+  arc_app_fetcher()->SetCallbackForTesting(base::BindLambdaForTesting(
+      [](std::vector<Result> results) { ASSERT_EQ(results.size(), 0); }));
+  arc_app_fetcher()->OnLoadError();
+}
+
+TEST_F(RecommendedArcAppFetcherTest, OnParseResponseError) {
+  arc_app_fetcher()->SetCallbackForTesting(base::BindLambdaForTesting(
+      [](std::vector<Result> results) { ASSERT_EQ(results.size(), 0); }));
+  arc_app_fetcher()->OnParseResponseError();
+}
+
+}  // namespace apps
diff --git a/chrome/browser/apps/app_service/publishers/extension_apps_chromeos.cc b/chrome/browser/apps/app_service/publishers/extension_apps_chromeos.cc
index 63691b5a..fb07756 100644
--- a/chrome/browser/apps/app_service/publishers/extension_apps_chromeos.cc
+++ b/chrome/browser/apps/app_service/publishers/extension_apps_chromeos.cc
@@ -41,6 +41,7 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chromeos/arc/arc_web_contents_data.h"
 #include "chrome/browser/chromeos/extensions/gfx_utils.h"
+#include "chrome/browser/extensions/extension_keeplist_ash.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/extension_uninstall_dialog.h"
 #include "chrome/browser/extensions/extension_util.h"
@@ -186,10 +187,10 @@
   bool is_quickoffice =
       extension->id() == extension_misc::kQuickOfficeComponentExtensionId;
   if (extension->is_app() || is_quickoffice) {
+    auto launch_source = params.launch_source;
     content::WebContents* web_contents = LaunchImpl(std::move(params));
 
-    if (params.launch_source == apps::mojom::LaunchSource::kFromArc &&
-        web_contents) {
+    if (launch_source == apps::mojom::LaunchSource::kFromArc && web_contents) {
       // Add a flag to remember this web_contents originated in the ARC context.
       web_contents->SetUserData(
           &arc::ArcWebContentsData::kArcTransitionFlag,
@@ -759,7 +760,7 @@
   const bool disable_for_lacros =
       extension->is_platform_app() &&
       crosapi::browser_util::IsLacrosChromeAppsEnabled() &&
-      !apps::ExtensionAppRunsInAsh(extension->id());
+      !extensions::ExtensionAppRunsInAsh(extension->id());
   const bool is_app_disabled =
       base::Contains(disabled_apps_, extension->id()) || disable_for_lacros;
 
@@ -779,7 +780,7 @@
   const bool disable_for_lacros =
       extension->is_platform_app() &&
       crosapi::browser_util::IsLacrosChromeAppsEnabled() &&
-      !apps::ExtensionAppRunsInAsh(extension->id());
+      !extensions::ExtensionAppRunsInAsh(extension->id());
   const bool is_app_disabled =
       base::Contains(disabled_apps_, extension->id()) || disable_for_lacros;
 
diff --git a/chrome/browser/apps/app_service/publishers/extension_apps_util.cc b/chrome/browser/apps/app_service/publishers/extension_apps_util.cc
index 02fbe86..0d353b82 100644
--- a/chrome/browser/apps/app_service/publishers/extension_apps_util.cc
+++ b/chrome/browser/apps/app_service/publishers/extension_apps_util.cc
@@ -4,14 +4,6 @@
 
 #include "chrome/browser/apps/app_service/publishers/extension_apps_util.h"
 
-#include <set>
-
-#include "base/containers/contains.h"
-#include "base/no_destructor.h"
-#include "chrome/browser/ash/file_manager/app_id.h"
-#include "chrome/common/extensions/extension_constants.h"
-#include "extensions/common/constants.h"
-
 namespace apps {
 
 extensions::UninstallReason GetExtensionUninstallReason(
@@ -29,16 +21,4 @@
   }
 }
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-bool ExtensionAppRunsInAsh(const std::string& app_id) {
-  static base::NoDestructor<std::set<std::string>> keep_list(
-      {file_manager::kAudioPlayerAppId, extension_misc::kFeedbackExtensionId,
-       extension_misc::kFilesManagerAppId, extension_misc::kGoogleKeepAppId,
-       extension_misc::kCalculatorAppId, extension_misc::kTextEditorAppId,
-       extension_misc::kInAppPaymentsSupportAppId,
-       extension_misc::kWallpaperManagerId});
-  return base::Contains(*keep_list, app_id);
-}
-#endif
-
 }  // namespace apps
diff --git a/chrome/browser/apps/app_service/publishers/extension_apps_util.h b/chrome/browser/apps/app_service/publishers/extension_apps_util.h
index c57be66..d6c1e91 100644
--- a/chrome/browser/apps/app_service/publishers/extension_apps_util.h
+++ b/chrome/browser/apps/app_service/publishers/extension_apps_util.h
@@ -5,9 +5,6 @@
 #ifndef CHROME_BROWSER_APPS_APP_SERVICE_PUBLISHERS_EXTENSION_APPS_UTIL_H_
 #define CHROME_BROWSER_APPS_APP_SERVICE_PUBLISHERS_EXTENSION_APPS_UTIL_H_
 
-#include <string>
-
-#include "build/chromeos_buildflags.h"
 #include "components/services/app_service/public/mojom/types.mojom.h"
 #include "extensions/browser/uninstall_reason.h"
 
@@ -16,14 +13,6 @@
 // Converts an apps UninstallSource to an extension uninstall reason.
 extensions::UninstallReason GetExtensionUninstallReason(
     apps::mojom::UninstallSource uninstall_source);
-
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-// Some extension apps will continue to run in ash until they are either
-// deprecated or migrated. This function returns whether a given app_id is on
-// that keep list. This function must only be called from the UI thread.
-bool ExtensionAppRunsInAsh(const std::string& app_id);
-#endif
-
 }  // namespace apps
 
 #endif  // CHROME_BROWSER_APPS_APP_SERVICE_PUBLISHERS_EXTENSION_APPS_UTIL_H_
diff --git a/chrome/browser/apps/intent_helper/OWNERS b/chrome/browser/apps/intent_helper/OWNERS
index d339cb2..047993d 100644
--- a/chrome/browser/apps/intent_helper/OWNERS
+++ b/chrome/browser/apps/intent_helper/OWNERS
@@ -1,6 +1,8 @@
-dominickn@chromium.org
 mxcai@chromium.org
+tsergeant@chromium.org
 # For ARC related code
 djacobo@chromium.org
 # For ARC related code, backup reviewers
 file://chrome/browser/ash/arc/OWNERS
+# For backup
+dominickn@chromium.org
diff --git a/chrome/browser/ash/arc/intent_helper/arc_external_protocol_dialog.cc b/chrome/browser/ash/arc/intent_helper/arc_external_protocol_dialog.cc
index 5e662b8..703c157 100644
--- a/chrome/browser/ash/arc/intent_helper/arc_external_protocol_dialog.cc
+++ b/chrome/browser/ash/arc/intent_helper/arc_external_protocol_dialog.cc
@@ -502,16 +502,13 @@
   // |package_name| matches a valid option and return the index.
   const size_t selected_app_index = GetAppIndex(handlers, selected_app_package);
 
-  // Make sure that the instance at least supports HandleUrl.
+  // Make sure ARC intent helper instance is connected.
   auto* arc_service_manager = ArcServiceManager::Get();
-  mojom::IntentHelperInstance* instance = nullptr;
-  if (arc_service_manager) {
-    instance = ARC_GET_INSTANCE_FOR_METHOD(
-        arc_service_manager->arc_bridge_service()->intent_helper(), HandleUrl);
-  }
-
-  if (!instance)
+  if (!arc_service_manager || !arc_service_manager->arc_bridge_service()
+                                   ->intent_helper()
+                                   ->IsConnected()) {
     reason = apps::IntentPickerCloseReason::ERROR_AFTER_PICKER;
+  }
 
   if (reason == apps::IntentPickerCloseReason::OPEN_APP ||
       reason == apps::IntentPickerCloseReason::STAY_IN_CHROME) {
@@ -529,9 +526,10 @@
       DCHECK(arc_service_manager);
 
       if (should_persist) {
-        if (ARC_GET_INSTANCE_FOR_METHOD(
-                arc_service_manager->arc_bridge_service()->intent_helper(),
-                AddPreferredPackage)) {
+        mojom::IntentHelperInstance* instance = ARC_GET_INSTANCE_FOR_METHOD(
+            arc_service_manager->arc_bridge_service()->intent_helper(),
+            AddPreferredPackage);
+        if (instance) {
           instance->AddPreferredPackage(
               handlers[selected_app_index]->package_name);
         }
@@ -556,8 +554,7 @@
       [[fallthrough]];
     case apps::IntentPickerCloseReason::ERROR_AFTER_PICKER:
       LOG(ERROR) << "IntentPickerBubbleView returned CloseReason::ERROR: "
-                 << "instance=" << instance
-                 << ", selected_app_index=" << selected_app_index
+                 << "selected_app_index=" << selected_app_index
                  << ", handlers.size=" << handlers.size();
       [[fallthrough]];
     case apps::IntentPickerCloseReason::DIALOG_DEACTIVATED:
@@ -668,16 +665,6 @@
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
   auto* arc_service_manager = ArcServiceManager::Get();
-  if (!arc_service_manager) {
-    // ARC is not running anymore. Show the Chrome OS dialog.
-    ShowExternalProtocolDialogWithoutApps(render_process_host_id, routing_id,
-                                          url, initiating_origin,
-                                          std::move(handled_cb));
-    return;
-  }
-
-  auto* instance = ARC_GET_INSTANCE_FOR_METHOD(
-      arc_service_manager->arc_bridge_service()->intent_helper(), HandleUrl);
 
   WebContents* web_contents =
       tab_util::GetWebContentsByID(render_process_host_id, routing_id);
@@ -688,7 +675,11 @@
   // We only reach here if Chrome doesn't think it can handle the URL. If ARC is
   // not running anymore, or Chrome is the only candidate returned, show the
   // usual Chrome OS dialog that says we cannot handle the URL.
-  if (!instance || !intent_helper_bridge || handlers.empty() ||
+  if (!arc_service_manager ||
+      !arc_service_manager->arc_bridge_service()
+           ->intent_helper()
+           ->IsConnected() ||
+      !intent_helper_bridge || handlers.empty() ||
       IsChromeOnlyAppCandidate(handlers)) {
     ShowExternalProtocolDialogWithoutApps(render_process_host_id, routing_id,
                                           url, initiating_origin,
diff --git a/chrome/browser/ash/arc/process/arc_process.cc b/chrome/browser/ash/arc/process/arc_process.cc
index ba79fca..7f93bda 100644
--- a/chrome/browser/ash/arc/process/arc_process.cc
+++ b/chrome/browser/ash/arc/process/arc_process.cc
@@ -6,6 +6,7 @@
 
 #include <utility>
 
+#include "ash/components/arc/arc_features.h"
 #include "ash/components/arc/mojom/process.mojom.h"
 #include "base/strings/string_util.h"
 
@@ -15,9 +16,16 @@
 
 namespace {
 
-constexpr char kCloudDpcrocessName[] =
+constexpr const char kCloudDpcrocessName[] =
     "com.google.android.apps.work.clouddpc.arc";
 
+constexpr const char* kGmsCoreProtectedServices[] = {
+    "com.google.process.gservices",
+    "com.google.android.gms",
+    "com.google.android.gms.persistent",
+    "com.google.android.gms.unstable",
+};
+
 bool IsImportantState(ProcessState state) {
   switch (state) {
     case ProcessState::IMPORTANT_FOREGROUND:
@@ -96,13 +104,15 @@
 ArcProcess& ArcProcess::operator=(ArcProcess&& other) = default;
 
 bool ArcProcess::IsImportant() const {
-  return IsImportantState(process_state()) || IsArcProtected();
+  return IsImportantState(process_state()) || IsArcProtected() ||
+         IsGmsCoreProtected();
 }
 
 bool ArcProcess::IsPersistent() const {
   // Protect PERSISTENT, PERSISTENT_UI, our HOME and custom set of ARC processes
   // since they should have lower priority to be killed.
-  return IsPersistentState(process_state()) || IsArcProtected();
+  return IsPersistentState(process_state()) || IsArcProtected() ||
+         IsGmsCoreProtected();
 }
 
 bool ArcProcess::IsCached() const {
@@ -117,6 +127,17 @@
   return process_name() == kCloudDpcrocessName;
 }
 
+bool ArcProcess::IsGmsCoreProtected() const {
+  if (!base::FeatureList::IsEnabled(arc::kGmsCoreLowMemoryKillerProtection))
+    return false;
+
+  for (const char* service : kGmsCoreProtectedServices) {
+    if (process_name() == service)
+      return true;
+  }
+  return false;
+}
+
 std::ostream& operator<<(std::ostream& out, const ArcProcess& arc_process) {
   out << "process_name: " << arc_process.process_name()
       << ", pid: " << arc_process.pid()
diff --git a/chrome/browser/ash/arc/process/arc_process.h b/chrome/browser/ash/arc/process/arc_process.h
index 96ac21e..32d4be0 100644
--- a/chrome/browser/ash/arc/process/arc_process.h
+++ b/chrome/browser/ash/arc/process/arc_process.h
@@ -73,6 +73,10 @@
   // Returns true if this is ARC protected process which we don't allow to kill.
   bool IsArcProtected() const;
 
+  // Returns true if this is key GMS Core or related service which we don't
+  // allow to kill.
+  bool IsGmsCoreProtected() const;
+
   base::ProcessId nspid_;
   base::ProcessId pid_;
   std::string process_name_;
diff --git a/chrome/browser/ash/arc/process/arc_process_unittest.cc b/chrome/browser/ash/arc/process/arc_process_unittest.cc
index 046b084..4fd51e0 100644
--- a/chrome/browser/ash/arc/process/arc_process_unittest.cc
+++ b/chrome/browser/ash/arc/process/arc_process_unittest.cc
@@ -9,13 +9,22 @@
 #include <list>
 #include <sstream>
 
+#include "ash/components/arc/arc_features.h"
 #include "ash/components/arc/mojom/process.mojom.h"
+#include "base/test/scoped_feature_list.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace arc {
 
 namespace {
 
+ArcProcess CreateFromPattern(const ArcProcess& pattern,
+                             const std::string process_name) {
+  return ArcProcess(pattern.nspid(), pattern.pid(), process_name,
+                    pattern.process_state(), pattern.is_focused(),
+                    pattern.last_activity_time());
+}
+
 // Tests that ArcProcess objects can be sorted by their priority (higher to
 // lower). This is critical for the OOM handler to work correctly.
 TEST(ArcProcess, TestSorting) {
@@ -209,6 +218,60 @@
   EXPECT_FALSE(s.str().empty());
 }
 
+TEST(ArcProcess, GmsCoreProtection) {
+  const ArcProcess pattern(0 /* nspid */, 0 /* pid */,
+                           std::string() /* process_name */,
+                           mojom::ProcessState::CACHED_EMPTY,
+                           false /* is_focused */, 0 /* last_activity_time */);
+
+  EXPECT_FALSE(CreateFromPattern(pattern, "com.google.process.gservices")
+                   .IsPersistent());
+  EXPECT_FALSE(
+      CreateFromPattern(pattern, "com.google.process.gservices").IsImportant());
+  EXPECT_FALSE(
+      CreateFromPattern(pattern, "com.google.android.gms").IsPersistent());
+  EXPECT_FALSE(
+      CreateFromPattern(pattern, "com.google.android.gms").IsImportant());
+  EXPECT_FALSE(CreateFromPattern(pattern, "com.google.android.gms.persistent")
+                   .IsPersistent());
+  EXPECT_FALSE(CreateFromPattern(pattern, "com.google.android.gms.persistent")
+                   .IsImportant());
+  EXPECT_FALSE(
+      CreateFromPattern(pattern, "com.google.android.gms.ui").IsPersistent());
+  EXPECT_FALSE(
+      CreateFromPattern(pattern, "com.google.android.gms.ui").IsImportant());
+  EXPECT_FALSE(CreateFromPattern(pattern, "com.google.android.gms.unstable")
+                   .IsPersistent());
+  EXPECT_FALSE(CreateFromPattern(pattern, "com.google.android.gms.unstable")
+                   .IsImportant());
+
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatureState(kGmsCoreLowMemoryKillerProtection,
+                                    true /* enabled */);
+
+  EXPECT_TRUE(CreateFromPattern(pattern, "com.google.process.gservices")
+                  .IsPersistent());
+  EXPECT_TRUE(
+      CreateFromPattern(pattern, "com.google.process.gservices").IsImportant());
+  EXPECT_TRUE(
+      CreateFromPattern(pattern, "com.google.android.gms").IsPersistent());
+  EXPECT_TRUE(
+      CreateFromPattern(pattern, "com.google.android.gms").IsImportant());
+  EXPECT_TRUE(CreateFromPattern(pattern, "com.google.android.gms.persistent")
+                  .IsPersistent());
+  EXPECT_TRUE(CreateFromPattern(pattern, "com.google.android.gms.persistent")
+                  .IsImportant());
+  // GMS UI is not protected.
+  EXPECT_FALSE(
+      CreateFromPattern(pattern, "com.google.android.gms.ui").IsPersistent());
+  EXPECT_FALSE(
+      CreateFromPattern(pattern, "com.google.android.gms.ui").IsImportant());
+  EXPECT_TRUE(CreateFromPattern(pattern, "com.google.android.gms.unstable")
+                  .IsPersistent());
+  EXPECT_TRUE(CreateFromPattern(pattern, "com.google.android.gms.unstable")
+                  .IsImportant());
+}
+
 }  // namespace
 
 }  // namespace arc
diff --git a/chrome/browser/ash/crosapi/browser_data_migrator.cc b/chrome/browser/ash/crosapi/browser_data_migrator.cc
index fe24d69..9a2513cd 100644
--- a/chrome/browser/ash/crosapi/browser_data_migrator.cc
+++ b/chrome/browser/ash/crosapi/browser_data_migrator.cc
@@ -11,7 +11,6 @@
 #include "base/callback_helpers.h"
 #include "base/command_line.h"
 #include "base/containers/contains.h"
-#include "base/containers/span.h"
 #include "base/feature_list.h"
 #include "base/files/file.h"
 #include "base/files/file_enumerator.h"
@@ -40,68 +39,6 @@
 
 namespace ash {
 namespace {
-// The base names of files and directories directly under the original profile
-// data directory that does not need to be copied nor need to remain in ash e.g.
-// cache data.
-constexpr const char* const kNoCopyPathsDeprecated[] = {kTmpDir, "Cache"};
-constexpr const char* const kNoCopyPaths[] = {
-    kTmpDir,
-    "Cache",
-    "Code Cache",
-    "crash",
-    "blob_storage",
-    "GCache",
-    "data_reduction_proxy_leveldb",
-    "previews_opt_out.db",
-    "Download Service",
-    "Network Persistent State",
-    "Reporting and NEL",
-    "TransportSecurity",
-    "optimization_guide_hint_cache_store",
-    "Site Characteristics Database",
-    "Network Action Predictor"};
-// The base names of files and directories that should remain in ash data
-// directory.
-constexpr const char* const kAshDataPathsDeprecated[]{"Downloads", "MyFiles"};
-constexpr const char* const kAshDataPaths[] = {"FullRestoreData",
-                                               "Downloads",
-                                               "MyFiles",
-                                               "arc.apps",
-                                               "crostini.icons",
-                                               "PreferredApps",
-                                               "autobrightness",
-                                               "extension_install_log",
-                                               "google-assistant-library",
-                                               "login-times",
-                                               "logout-times",
-                                               "structured_metrics",
-                                               "PrintJobDatabase",
-                                               "PPDCache",
-                                               "BudgetDatabase",
-                                               "RLZ Data",
-                                               "app_ranker.pb",
-                                               "zero_state_group_ranker.pb",
-                                               "zero_state_local_files.pb"};
-// The base names of files/dirs that are needed only by the browser part of
-// chrome i.e. data that should be moved to lacros.
-constexpr const char* const kLacrosDataPathsDeprecated[]{"Bookmarks"};
-constexpr const char* const kLacrosDataPaths[]{"AutofillStrikeDatabase",
-                                               "Bookmarks",
-                                               "Cookies",
-                                               "Extension Cookies",
-                                               "Extension Rules",
-                                               "Extension State",
-                                               "Extensions",
-                                               "Local App Settings",
-                                               "Local Extension Settings",
-                                               "Managed Extension Settings",
-                                               "Sync App Settings",
-                                               "DNR Extension Rules",
-                                               "Favicons",
-                                               "History",
-                                               "Top Sites",
-                                               "Shortcuts",
-                                               "Sessions"};
 // Flag values for `switches::kForceBrowserDataMigrationForTesting`.
 const char kBrowserDataMigrationForceSkip[] = "force-skip";
 const char kBrowserDataMigrationForceMigration[] = "force-migration";
@@ -113,82 +50,14 @@
 // vector<TargetItem> in TargetInfo.
 constexpr char kLacrosCategory[] = "lacros";
 constexpr char kCommonCategory[] = "common";
-
-// Enable these to fallback to an older version of paths lists.
-const base::Feature kLacrosProfileMigrationUseDeprecatedNoCopyPaths{
-    "LacrosProfileMigrationUseDeprecatedNoCopyPaths",
-    base::FEATURE_DISABLED_BY_DEFAULT};
-const base::Feature kLacrosProfileMigrationUseDeprecatedAshDataPaths{
-    "LacrosProfileMigrationUseDeprecatedAshDataPaths",
-    base::FEATURE_DISABLED_BY_DEFAULT};
-const base::Feature kLacrosProfileMigrationUseDeprecatedLacrosDataPaths{
-    "LacrosProfileMigrationUseDeprecatedLacrosDataPaths",
-    base::FEATURE_DISABLED_BY_DEFAULT};
-
-// Ensures that each path in UDD appears in one of `kNoCopyPaths`,
-// `kAshDataPaths` or `kLacrosDataPaths`.
-constexpr bool HasNoOverlapBetweenPathsSets() {
-  for (const char* no_copy_path : kNoCopyPaths) {
-    for (const char* ash_data_path : kAshDataPaths) {
-      if (base::StringPiece(no_copy_path) == base::StringPiece(ash_data_path))
-        return false;
-    }
-  }
-
-  for (const char* ash_data_path : kAshDataPaths) {
-    for (const char* lacros_data_path : kLacrosDataPaths) {
-      if (base::StringPiece(ash_data_path) ==
-          base::StringPiece(lacros_data_path))
-        return false;
-    }
-  }
-
-  for (const char* lacros_data_path : kLacrosDataPaths) {
-    for (const char* no_copy_path : kNoCopyPaths) {
-      if (base::StringPiece(lacros_data_path) ==
-          base::StringPiece(no_copy_path))
-        return false;
-    }
-  }
-
-  return true;
-}
-
-static_assert(HasNoOverlapBetweenPathsSets(),
-              "There must be no overlap between kNoCopyPaths, kAshDataPaths "
-              "and kLacrosDataPaths");
-
-base::span<const char* const> GetNoCopyDataPaths() {
-  if (base::FeatureList::IsEnabled(
-          kLacrosProfileMigrationUseDeprecatedNoCopyPaths)) {
-    return base::make_span(kNoCopyPathsDeprecated);
-  }
-  return base::make_span(kNoCopyPaths);
-}
-
-base::span<const char* const> GetAshDataPaths() {
-  if (base::FeatureList::IsEnabled(
-          kLacrosProfileMigrationUseDeprecatedAshDataPaths)) {
-    return base::make_span(kAshDataPathsDeprecated);
-  }
-  return base::make_span(kAshDataPaths);
-}
-
-base::span<const char* const> GetLacrosDataPaths() {
-  if (base::FeatureList::IsEnabled(
-          kLacrosProfileMigrationUseDeprecatedLacrosDataPaths)) {
-    return base::make_span(kLacrosDataPathsDeprecated);
-  }
-  return base::make_span(kLacrosDataPaths);
-}
 }  // namespace
 
 CancelFlag::CancelFlag() : cancelled_(false) {}
 CancelFlag::~CancelFlag() = default;
 
 BrowserDataMigratorImpl::TargetInfo::TargetInfo()
-    : ash_data_size(0),
-      no_copy_data_size(0),
+    : remain_in_ash_data_size(0),
+      deletable_data_size(0),
       lacros_data_size(0),
       common_data_size(0) {}
 BrowserDataMigratorImpl::TargetInfo::TargetInfo(TargetInfo&&) = default;
@@ -210,7 +79,7 @@
 }
 
 int64_t BrowserDataMigratorImpl::TargetInfo::TotalDirSize() const {
-  return no_copy_data_size + ash_data_size + lacros_data_size +
+  return deletable_data_size + remain_in_ash_data_size + lacros_data_size +
          common_data_size;
 }
 
@@ -435,7 +304,8 @@
                               target_info->TotalCopySize() / 1024 / 1024, 1,
                               10000, 100);
   UMA_HISTOGRAM_CUSTOM_COUNTS(
-      kAshDataSize, target_info->ash_data_size / 1024 / 1024, 1, 10000, 100);
+      kAshDataSize, target_info->remain_in_ash_data_size / 1024 / 1024, 1,
+      10000, 100);
   UMA_HISTOGRAM_CUSTOM_COUNTS(kLacrosDataSize,
                               target_info->lacros_data_size / 1024 / 1024, 1,
                               10000, 100);
@@ -443,7 +313,7 @@
                               target_info->common_data_size / 1024 / 1024, 1,
                               10000, 100);
   UMA_HISTOGRAM_CUSTOM_COUNTS(kNoCopyDataSize,
-                              target_info->no_copy_data_size / 1024 / 1024, 1,
+                              target_info->deletable_data_size / 1024 / 1024, 1,
                               10000, 100);
 
   if (!timer || final_status != FinalStatus::kSuccess)
@@ -594,9 +464,6 @@
 BrowserDataMigratorImpl::TargetInfo BrowserDataMigratorImpl::GetTargetInfo(
     const base::FilePath& original_user_dir) {
   TargetInfo target_info;
-  const base::span<const char* const> no_copy_data_paths = GetNoCopyDataPaths();
-  const base::span<const char* const> ash_data_paths = GetAshDataPaths();
-  const base::span<const char* const> lacros_data_paths = GetLacrosDataPaths();
 
   base::FileEnumerator enumerator(original_user_dir, false /* recursive */,
                                   base::FileEnumerator::FILES |
@@ -619,22 +486,22 @@
       continue;
     }
 
-    if (base::Contains(ash_data_paths, entry.BaseName().value())) {
-      target_info.ash_data_items.emplace_back(
+    if (base::Contains(kRemainInAshDataPaths, entry.BaseName().value())) {
+      target_info.remain_in_ash_items.emplace_back(
           TargetItem{entry, size, item_type});
-      target_info.ash_data_size += size;
-    } else if (base::Contains(no_copy_data_paths, entry.BaseName().value())) {
-      target_info.no_copy_data_items.emplace_back(
+      target_info.remain_in_ash_data_size += size;
+    } else if (base::Contains(kDeletablePaths, entry.BaseName().value())) {
+      target_info.deletable_items.emplace_back(
           TargetItem{entry, size, item_type});
-      target_info.no_copy_data_size += size;
-    } else if (base::Contains(lacros_data_paths, entry.BaseName().value())) {
+      target_info.deletable_data_size += size;
+    } else if (base::Contains(kLacrosDataPaths, entry.BaseName().value())) {
       // Items that should be moved to lacros.
       target_info.lacros_data_items.emplace_back(
           TargetItem{entry, size, item_type});
       target_info.lacros_data_size += size;
-    } else {
-      // Items that are not explicitly ash, no_copy or lacros are put into
-      // common category.
+    } else if (base::Contains(kCommonDataPaths, entry.BaseName().value())) {
+      // Items that are not explicitly ash, deletable_data or lacros are put
+      // into common category.
       target_info.common_data_items.emplace_back(
           TargetItem{entry, size, item_type});
       target_info.common_data_size += size;
@@ -659,11 +526,11 @@
       break;
     case Mode::kDeleteAndCopy:
       required_space =
-          target_info.TotalCopySize() - target_info.no_copy_data_size;
+          target_info.TotalCopySize() - target_info.deletable_data_size;
       break;
     case Mode::kDeleteAndMove:
       required_space =
-          target_info.common_data_size - target_info.no_copy_data_size;
+          target_info.common_data_size - target_info.deletable_data_size;
       break;
     case Mode::kCopy:
     default:
@@ -862,11 +729,11 @@
   TargetInfo target_info = GetTargetInfo(profile_data_dir);
 
   base::UmaHistogramCustomCounts(kDryRunNoCopyDataSize,
-                                 target_info.no_copy_data_size / 1024 / 1024, 1,
-                                 10000, 100);
-  base::UmaHistogramCustomCounts(kDryRunAshDataSize,
-                                 target_info.ash_data_size / 1024 / 1024, 1,
-                                 10000, 100);
+                                 target_info.deletable_data_size / 1024 / 1024,
+                                 1, 10000, 100);
+  base::UmaHistogramCustomCounts(
+      kDryRunAshDataSize, target_info.remain_in_ash_data_size / 1024 / 1024, 1,
+      10000, 100);
   base::UmaHistogramCustomCounts(kDryRunLacrosDataSize,
                                  target_info.lacros_data_size / 1024 / 1024, 1,
                                  10000, 100);
@@ -876,8 +743,8 @@
 
   browser_data_migrator_util::RecordTotalSize(target_info.TotalDirSize());
 
-  RecordTargetItemSizes(target_info.no_copy_data_items);
-  RecordTargetItemSizes(target_info.ash_data_items);
+  RecordTargetItemSizes(target_info.deletable_items);
+  RecordTargetItemSizes(target_info.remain_in_ash_items);
   RecordTargetItemSizes(target_info.lacros_data_items);
   RecordTargetItemSizes(target_info.common_data_items);
 
diff --git a/chrome/browser/ash/crosapi/browser_data_migrator.h b/chrome/browser/ash/crosapi/browser_data_migrator.h
index ddb2fff..12ec54c 100644
--- a/chrome/browser/ash/crosapi/browser_data_migrator.h
+++ b/chrome/browser/ash/crosapi/browser_data_migrator.h
@@ -71,6 +71,101 @@
 constexpr char kDryRunDeleteAndMoveMigrationHasEnoughDiskSpace[] =
     "Ash.BrowserDataMigrator.DryRunHasEnoughDiskSpace.DeleteAndMove";
 
+// The base names of files/dirs directly under the original profile
+// data directory that can be deleted if needed because they are temporary
+// storages.
+constexpr const char* const kDeletablePaths[] = {
+    kTmpDir,
+    "blob_storage",
+    "Cache",
+    "Code Cache",
+    "crash",
+    "data_reduction_proxy_leveldb",
+    "Download Service",
+    "GCache",
+    "heavy_ad_intervention_opt_out.db",
+    "Network Action Predictor",
+    "Network Persistent State",
+    "optimization_guide_hint_cache_store",
+    "previews_opt_out.db",
+    "Reporting and NEL",
+    "Site Characteristics Database",
+    "TransportSecurity"};
+
+// The base names of files/dirs that should remain in ash data
+// directory.
+constexpr const char* const kRemainInAshDataPaths[] = {
+    "AccountManagerTokens.bin",
+    "Accounts",
+    "app_ranker.pb",
+    "arc.apps",
+    "autobrightness",
+    "BudgetDatabase",
+    "crostini.icons",
+    "Downloads",
+    "extension_install_log",
+    "FullRestoreData",
+    "GCM Store",
+    "google-assistant-library",
+    "GPUCache",
+    "login-times",
+    "logout-times",
+    "MyFiles",
+    "NearbySharePublicCertificateDatabase",
+    "PPDCache",
+    "PreferredApps",
+    "PreferredApps",
+    "PrintJobDatabase",
+    "README",
+    "RLZ Data",
+    "smartcharging",
+    "structured_metrics",
+    "Translate Ranker Model",
+    "Trusted Vault",
+    "WebRTC Logs",
+    "webrtc_event_logs",
+    "zero_state_group_ranker.pb",
+    "zero_state_local_files.pb"};
+
+// The base names of files/dirs that are required for browsing and should be
+// moved to lacros data dir.
+constexpr const char* const kLacrosDataPaths[]{"AutofillStrikeDatabase",
+                                               "Bookmarks",
+                                               "Cookies",
+                                               "databases",
+                                               "DNR Extension Rules",
+                                               "Extension Cookies",
+                                               "Extension Rules",
+                                               "Extension State",
+                                               "Extensions",
+                                               "Favicons",
+                                               "File System",
+                                               "History",
+                                               "IndexedDB",
+                                               "Local App Settings",
+                                               "Local Extension Settings",
+                                               "Managed Extension Settings",
+                                               "QuotaManager",
+                                               "Service Worker",
+                                               "Session Storage",
+                                               "Sessions",
+                                               "Shortcuts",
+                                               "Sync App Settings",
+                                               "Top Sites",
+                                               "Visited Links",
+                                               "Web Applications",
+                                               "Web Data"};
+
+// The base names of files/dirs that are required by both ash and lacros and
+// thus should be copied to lacros while keeping the original files/dirs in ash
+// data dir.
+constexpr const char* const kCommonDataPaths[]{"Affiliation Database",
+                                               "Login Data",
+                                               "Platform Notifications",
+                                               "Policy",
+                                               "Preferences",
+                                               "shared_proto_db"};
+
 // Local state pref name, which is used to keep track of what step migration is
 // at. This ensures that ash does not get repeatedly for migration.
 // 1. The user logs in and restarts ash if necessary to apply flags.
@@ -142,17 +237,17 @@
     TargetInfo(TargetInfo&&);
 
     // Items that should stay in ash data dir.
-    std::vector<TargetItem> ash_data_items;
+    std::vector<TargetItem> remain_in_ash_items;
     // Items that should be moved to lacros data dir.
     std::vector<TargetItem> lacros_data_items;
     // Items that will be duplicated in both ash and lacros data dir.
     std::vector<TargetItem> common_data_items;
     // Items that can be deleted from both ash and lacros data dir.
-    std::vector<TargetItem> no_copy_data_items;
-    // The total size of `ash_data_items`.
-    int64_t ash_data_size;
+    std::vector<TargetItem> deletable_items;
+    // The total size of `remain_in_ash_items`.
+    int64_t remain_in_ash_data_size;
     // The total size of items that can be deleted during the migration.
-    int64_t no_copy_data_size;
+    int64_t deletable_data_size;
     // The total size of `lacros_data_items`.
     int64_t lacros_data_size;
     // The total size of `common_data_items`.
@@ -254,16 +349,16 @@
   static void DryRunToCollectUMA(const base::FilePath& profile_data_dir);
 
  private:
-  FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorTest,
+  FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorImplTest,
                            ManipulateMigrationAttemptCount);
-  FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorTest, GetTargetInfo);
-  FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorTest, CopyDirectory);
-  FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorTest, SetupTmpDir);
-  FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorTest, CancelSetupTmpDir);
-  FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorTest, RecordStatus);
-  FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorTest, MigrateInternal);
-  FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorTest, Migrate);
-  FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorTest, MigrateCancelled);
+  FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorImplTest, GetTargetInfo);
+  FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorImplTest, CopyDirectory);
+  FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorImplTest, SetupTmpDir);
+  FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorImplTest, CancelSetupTmpDir);
+  FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorImplTest, RecordStatus);
+  FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorImplTest, MigrateInternal);
+  FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorImplTest, Migrate);
+  FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorImplTest, MigrateCancelled);
 
   // Sets the value of `kMigrationStep` in Local State.
   static void SetMigrationStep(PrefService* local_state, MigrationStep step);
diff --git a/chrome/browser/ash/crosapi/browser_data_migrator_unittest.cc b/chrome/browser/ash/crosapi/browser_data_migrator_unittest.cc
index 26c2591f..ad096a2b 100644
--- a/chrome/browser/ash/crosapi/browser_data_migrator_unittest.cc
+++ b/chrome/browser/ash/crosapi/browser_data_migrator_unittest.cc
@@ -10,6 +10,7 @@
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/run_loop.h"
+#include "base/strings/stringprintf.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
@@ -47,14 +48,51 @@
 
 }  // namespace
 
-class BrowserDataMigratorTest : public ::testing::Test {
+TEST(BrowserDataMigratorTest, NoPathOverlaps) {
+  base::span<const char* const> remain_in_ash_paths =
+      base::make_span(kRemainInAshDataPaths);
+  base::span<const char* const> lacros_data_paths =
+      base::make_span(kLacrosDataPaths);
+  base::span<const char* const> deletable_paths =
+      base::make_span(kDeletablePaths);
+  base::span<const char* const> common_data_paths =
+      base::make_span(kCommonDataPaths);
+
+  std::vector<base::span<const char* const>> paths_groups{
+      remain_in_ash_paths, lacros_data_paths, deletable_paths,
+      common_data_paths};
+
+  auto overlap_checker = [](base::span<const char* const> paths_group_a,
+                            base::span<const char* const> paths_group_b) {
+    for (const char* path_a : paths_group_a) {
+      for (const char* path_b : paths_group_b) {
+        if (base::StringPiece(path_a) == base::StringPiece(path_b)) {
+          LOG(ERROR) << "The following path appears in multiple sets: "
+                     << path_a;
+          return false;
+        }
+      }
+    }
+
+    return true;
+  };
+
+  for (int i = 0; i < paths_groups.size() - 1; i++) {
+    for (int j = i + 1; j < paths_groups.size(); j++) {
+      SCOPED_TRACE(base::StringPrintf("i %d j %d", i, j));
+      EXPECT_TRUE(overlap_checker(paths_groups[i], paths_groups[j]));
+    }
+  }
+}
+
+class BrowserDataMigratorImplTest : public ::testing::Test {
  public:
   void SetUp() override {
     // Setup `user_data_dir_` as below.
     // ./                             /* user_data_dir_ */
     // |- 'First Run'
     // |- user/                       /* from_dir_ */
-    //     |- Cache                   /* no copy */
+    //     |- Cache                   /* deletable */
     //     |- Downloads/data          /* ash */
     //     |- FullRestoreData         /* ash */
     //     |- Bookmarks               /* lacros */
@@ -114,7 +152,7 @@
   TestingPrefServiceSimple pref_service_;
 };
 
-TEST_F(BrowserDataMigratorTest, ManipulateMigrationAttemptCount) {
+TEST_F(BrowserDataMigratorImplTest, ManipulateMigrationAttemptCount) {
   const std::string user_id_hash = "user";
 
   EXPECT_EQ(BrowserDataMigratorImpl::GetMigrationAttemptCountForUser(
@@ -139,27 +177,31 @@
             0);
 }
 
-TEST_F(BrowserDataMigratorTest, GetTargetInfo) {
+TEST_F(BrowserDataMigratorImplTest, GetTargetInfo) {
   BrowserDataMigratorImpl::TargetInfo target_info =
       BrowserDataMigratorImpl::GetTargetInfo(from_dir_);
 
-  EXPECT_EQ(target_info.ash_data_size, kFileSize * 2 /* expect two files */);
-  EXPECT_EQ(target_info.no_copy_data_size, kFileSize /* expect one file */);
+  EXPECT_EQ(target_info.remain_in_ash_data_size,
+            kFileSize * 2 /* expect two files */);
+  EXPECT_EQ(target_info.deletable_data_size, kFileSize /* expect one file */);
   EXPECT_EQ(target_info.lacros_data_size, kFileSize * 2 /* expect two files */);
   EXPECT_EQ(target_info.common_data_size, kFileSize * 2 /* expect two file */);
 
   // Check for ash data.
-  std::vector<BrowserDataMigratorImpl::TargetItem> expected_ash_data_items = {
-      {from_dir_.Append(kDownloads), kFileSize,
-       BrowserDataMigratorImpl::TargetItem::ItemType::kDirectory},
-      {from_dir_.Append(kFullRestoreData), kFileSize,
-       BrowserDataMigratorImpl::TargetItem::ItemType::kFile}};
-  std::sort(target_info.ash_data_items.begin(),
-            target_info.ash_data_items.end(), TargetItemComparator());
-  ASSERT_EQ(target_info.ash_data_items.size(), expected_ash_data_items.size());
-  for (int i = 0; i < target_info.ash_data_items.size(); i++) {
-    SCOPED_TRACE(target_info.ash_data_items[i].path.value());
-    EXPECT_EQ(target_info.ash_data_items[i], expected_ash_data_items[i]);
+  std::vector<BrowserDataMigratorImpl::TargetItem>
+      expected_remain_in_ash_items = {
+          {from_dir_.Append(kDownloads), kFileSize,
+           BrowserDataMigratorImpl::TargetItem::ItemType::kDirectory},
+          {from_dir_.Append(kFullRestoreData), kFileSize,
+           BrowserDataMigratorImpl::TargetItem::ItemType::kFile}};
+  std::sort(target_info.remain_in_ash_items.begin(),
+            target_info.remain_in_ash_items.end(), TargetItemComparator());
+  ASSERT_EQ(target_info.remain_in_ash_items.size(),
+            expected_remain_in_ash_items.size());
+  for (int i = 0; i < target_info.remain_in_ash_items.size(); i++) {
+    SCOPED_TRACE(target_info.remain_in_ash_items[i].path.value());
+    EXPECT_EQ(target_info.remain_in_ash_items[i],
+              expected_remain_in_ash_items[i]);
   }
 
   // Check for lacros data.
@@ -193,7 +235,7 @@
   }
 }
 
-TEST_F(BrowserDataMigratorTest, CopyDirectory) {
+TEST_F(BrowserDataMigratorImplTest, CopyDirectory) {
   const base::FilePath copy_from = user_data_dir_.GetPath().Append("copy_from");
   const base::FilePath copy_to = user_data_dir_.GetPath().Append("copy_to");
 
@@ -240,12 +282,9 @@
   EXPECT_FALSE(base::PathExists(copy_to.Append(kFirstRun)));
 }
 
-TEST_F(BrowserDataMigratorTest, DryRunToCollectUMA) {
+TEST_F(BrowserDataMigratorImplTest, DryRunToCollectUMA) {
   base::HistogramTester histogram_tester;
 
-  ASSERT_TRUE(base::WriteFile(from_dir_.Append(FILE_PATH_LITERAL("abcd")),
-                              kDataContent, kFileSize));
-
   BrowserDataMigratorImpl::DryRunToCollectUMA(from_dir_);
 
   std::string uma_name_cache =
@@ -266,9 +305,6 @@
   std::string uma_name_afiiliation_database =
       std::string(browser_data_migrator_util::kUserDataStatsRecorderDataSize) +
       "AffiliationDatabase";
-  std::string uma_name_unknown =
-      std::string(browser_data_migrator_util::kUserDataStatsRecorderDataSize) +
-      browser_data_migrator_util::kUnknownUMAName;
 
   histogram_tester.ExpectTotalCount(uma_name_cache, 1);
   histogram_tester.ExpectTotalCount(uma_name_downloads, 1);
@@ -276,7 +312,6 @@
   histogram_tester.ExpectTotalCount(uma_name_bookmarks, 1);
   histogram_tester.ExpectTotalCount(uma_name_cookies, 1);
   histogram_tester.ExpectTotalCount(uma_name_afiiliation_database, 1);
-  histogram_tester.ExpectTotalCount(uma_name_unknown, 1);
 
   histogram_tester.ExpectBucketCount(uma_name_cache, kFileSize / 1024 / 1024,
                                      1);
@@ -290,8 +325,6 @@
                                      1);
   histogram_tester.ExpectBucketCount(uma_name_afiiliation_database,
                                      kFileSize * 2 / 1024 / 1024, 1);
-  histogram_tester.ExpectBucketCount(uma_name_unknown, kFileSize / 1024 / 1024,
-                                     1);
 
   histogram_tester.ExpectBucketCount(kDryRunNoCopyDataSize,
                                      kFileSize / 1024 / 1024, 1);
@@ -300,7 +333,7 @@
   histogram_tester.ExpectBucketCount(kDryRunLacrosDataSize,
                                      kFileSize / 1024 / 1024, 1);
   histogram_tester.ExpectBucketCount(kDryRunCommonDataSize,
-                                     kFileSize * 3 / 1024 / 1024, 1);
+                                     kFileSize * 2 / 1024 / 1024, 1);
 
   histogram_tester.ExpectTotalCount(kDryRunCopyMigrationHasEnoughDiskSpace, 1);
   histogram_tester.ExpectTotalCount(kDryRunMoveMigrationHasEnoughDiskSpace, 1);
@@ -310,7 +343,7 @@
       kDryRunDeleteAndMoveMigrationHasEnoughDiskSpace, 1);
 }
 
-TEST_F(BrowserDataMigratorTest, RecordStatus) {
+TEST_F(BrowserDataMigratorImplTest, RecordStatus) {
   {
     // If `FinalStatus::kSkipped`, only record the status and do not record
     // copied data size or total time.
@@ -333,10 +366,10 @@
     base::HistogramTester histogram_tester;
 
     BrowserDataMigratorImpl::TargetInfo target_info;
-    target_info.ash_data_size = /* 300 MBs */ 300 * 1024 * 1024;
+    target_info.remain_in_ash_data_size = /* 300 MBs */ 300 * 1024 * 1024;
     target_info.lacros_data_size = /* 400 MBs */ 400 * 1024 * 1024;
     target_info.common_data_size = /* 500 MBs */ 500 * 1024 * 1024;
-    target_info.no_copy_data_size = /* 600 MBs */ 600 * 1024 * 1024;
+    target_info.deletable_data_size = /* 600 MBs */ 600 * 1024 * 1024;
 
     base::ElapsedTimer timer;
 
@@ -355,17 +388,17 @@
     histogram_tester.ExpectBucketCount(
         kCopiedDataSize, target_info.TotalCopySize() / (1024 * 1024), 1);
     histogram_tester.ExpectBucketCount(
-        kAshDataSize, target_info.ash_data_size / (1024 * 1024), 1);
+        kAshDataSize, target_info.remain_in_ash_data_size / (1024 * 1024), 1);
     histogram_tester.ExpectBucketCount(
         kLacrosDataSize, target_info.lacros_data_size / (1024 * 1024), 1);
     histogram_tester.ExpectBucketCount(
         kCommonDataSize, target_info.common_data_size / (1024 * 1024), 1);
     histogram_tester.ExpectBucketCount(
-        kNoCopyDataSize, target_info.no_copy_data_size / (1024 * 1024), 1);
+        kNoCopyDataSize, target_info.deletable_data_size / (1024 * 1024), 1);
   }
 }
 
-TEST_F(BrowserDataMigratorTest, SetupTmpDir) {
+TEST_F(BrowserDataMigratorImplTest, SetupTmpDir) {
   base::FilePath tmp_dir = from_dir_.Append(kTmpDir);
   scoped_refptr<CancelFlag> cancel_flag = base::MakeRefCounted<CancelFlag>();
   BrowserDataMigratorImpl::TargetInfo target_info =
@@ -392,7 +425,7 @@
                                    .Append(kDataFile)));
 }
 
-TEST_F(BrowserDataMigratorTest, CancelSetupTmpDir) {
+TEST_F(BrowserDataMigratorImplTest, CancelSetupTmpDir) {
   base::FilePath tmp_dir = from_dir_.Append(kTmpDir);
   scoped_refptr<CancelFlag> cancel_flag = base::MakeRefCounted<CancelFlag>();
   FakeMigrationProgressTracker progress_tracker;
@@ -413,7 +446,7 @@
       base::PathExists(tmp_dir.Append(kLacrosProfilePath).Append(kCookies)));
 }
 
-TEST_F(BrowserDataMigratorTest, MigrateInternal) {
+TEST_F(BrowserDataMigratorImplTest, MigrateInternal) {
   base::HistogramTester histogram_tester;
 
   {
@@ -476,7 +509,7 @@
                                      kFileSize * 4 / (1024 * 1024), 1);
 }
 
-TEST_F(BrowserDataMigratorTest, Migrate) {
+TEST_F(BrowserDataMigratorImplTest, Migrate) {
   base::test::TaskEnvironment task_environment;
   scoped_refptr<CancelFlag> cancelled = base::MakeRefCounted<CancelFlag>();
   std::unique_ptr<MigrationProgressTracker> progress_tracker =
@@ -517,7 +550,7 @@
             version_info::GetVersion());
 }
 
-TEST_F(BrowserDataMigratorTest, MigrateCancelled) {
+TEST_F(BrowserDataMigratorImplTest, MigrateCancelled) {
   base::test::TaskEnvironment task_environment;
   scoped_refptr<CancelFlag> cancelled = base::MakeRefCounted<CancelFlag>();
   std::unique_ptr<MigrationProgressTracker> progress_tracker =
diff --git a/chrome/browser/ash/login/enrollment/enrollment_embedded_policy_server_browsertest.cc b/chrome/browser/ash/login/enrollment/enrollment_embedded_policy_server_browsertest.cc
index 77d4e031..652a8a5f 100644
--- a/chrome/browser/ash/login/enrollment/enrollment_embedded_policy_server_browsertest.cc
+++ b/chrome/browser/ash/login/enrollment/enrollment_embedded_policy_server_browsertest.cc
@@ -834,7 +834,8 @@
   OobeScreenWaiter(EnrollmentScreenView::kScreenId).Wait();
   enrollment_ui_.SetExitHandler();
   enrollment_screen()->OnCancel();
-  EXPECT_EQ(EnrollmentScreen::Result::BACK, enrollment_ui_.WaitForScreenExit());
+  EXPECT_EQ(EnrollmentScreen::Result::BACK_TO_AUTO_ENROLLMENT_CHECK,
+            enrollment_ui_.WaitForScreenExit());
 }
 
 // Device is disabled.
@@ -990,7 +991,8 @@
   // User can't skip.
   enrollment_ui_.SetExitHandler();
   enrollment_screen()->OnCancel();
-  EXPECT_EQ(EnrollmentScreen::Result::BACK, enrollment_ui_.WaitForScreenExit());
+  EXPECT_EQ(EnrollmentScreen::Result::BACK_TO_AUTO_ENROLLMENT_CHECK,
+            enrollment_ui_.WaitForScreenExit());
 
   enrollment_screen()->OnLoginDone(FakeGaiaMixin::kEnterpriseUser1,
                                    FakeGaiaMixin::kFakeAuthCode);
@@ -1034,7 +1036,8 @@
   // User can't skip.
   enrollment_ui_.SetExitHandler();
   enrollment_screen()->OnCancel();
-  EXPECT_EQ(EnrollmentScreen::Result::BACK, enrollment_ui_.WaitForScreenExit());
+  EXPECT_EQ(EnrollmentScreen::Result::BACK_TO_AUTO_ENROLLMENT_CHECK,
+            enrollment_ui_.WaitForScreenExit());
 
   // Domain is actually different from what the server sent down. But Chrome
   // does not enforce that domain if device is not locked.
diff --git a/chrome/browser/ash/login/enrollment/enrollment_screen.cc b/chrome/browser/ash/login/enrollment/enrollment_screen.cc
index 82d0891..63eb308 100644
--- a/chrome/browser/ash/login/enrollment/enrollment_screen.cc
+++ b/chrome/browser/ash/login/enrollment/enrollment_screen.cc
@@ -111,6 +111,8 @@
       return "TpmError";
     case Result::TPM_DBUS_ERROR:
       return "TpmDbusError";
+    case Result::BACK_TO_AUTO_ENROLLMENT_CHECK:
+      return "BackToAutoEnrollmentCheck";
   }
 }
 
@@ -478,8 +480,10 @@
   // The callback passed to ClearAuth is called either immediately or gets
   // wrapped in a callback bound to a weak pointer from `weak_factory_` - in
   // either case, passing exit_callback_ directly should be safe.
-  ClearAuth(base::BindRepeating(
-      exit_callback_, config_.is_forced() ? Result::BACK : Result::COMPLETED));
+  ClearAuth(base::BindRepeating(exit_callback_,
+                                config_.is_forced()
+                                    ? Result::BACK_TO_AUTO_ENROLLMENT_CHECK
+                                    : Result::BACK));
 }
 
 void EnrollmentScreen::OnConfirmationClosed() {
diff --git a/chrome/browser/ash/login/enrollment/enrollment_screen.h b/chrome/browser/ash/login/enrollment/enrollment_screen.h
index dc6f299a..69d867c 100644
--- a/chrome/browser/ash/login/enrollment/enrollment_screen.h
+++ b/chrome/browser/ash/login/enrollment/enrollment_screen.h
@@ -51,7 +51,8 @@
     BACK,
     SKIPPED_FOR_TESTS,
     TPM_ERROR,
-    TPM_DBUS_ERROR
+    TPM_DBUS_ERROR,
+    BACK_TO_AUTO_ENROLLMENT_CHECK,
   };
 
   static std::string GetResultString(Result result);
diff --git a/chrome/browser/ash/login/enrollment/enrollment_screen_browsertest.cc b/chrome/browser/ash/login/enrollment/enrollment_screen_browsertest.cc
index ac36163..42a1951 100644
--- a/chrome/browser/ash/login/enrollment/enrollment_screen_browsertest.cc
+++ b/chrome/browser/ash/login/enrollment/enrollment_screen_browsertest.cc
@@ -161,7 +161,7 @@
   enrollment_ui_.WaitForStep(test::ui::kEnrollmentStepTPMChecking);
   test::OobeJS().TapOnPath(kEnrollmentTPMCheckCancelButton);
   EnrollmentScreen::Result screen_result = enrollment_ui_.WaitForScreenExit();
-  EXPECT_EQ(screen_result, EnrollmentScreen::Result::COMPLETED);
+  EXPECT_EQ(screen_result, EnrollmentScreen::Result::BACK);
 }
 
 INSTANTIATE_TEST_SUITE_P(All,
@@ -175,7 +175,7 @@
   enrollment_ui_.SetExitHandler();
   enrollment_screen()->OnCancel();
   EnrollmentScreen::Result screen_result = enrollment_ui_.WaitForScreenExit();
-  EXPECT_EQ(EnrollmentScreen::Result::COMPLETED, screen_result);
+  EXPECT_EQ(EnrollmentScreen::Result::BACK, screen_result);
 }
 
 IN_PROC_BROWSER_TEST_F(EnrollmentScreenTest, TestSuccess) {
@@ -212,7 +212,7 @@
   enrollment_ui_.SetExitHandler();
   enrollment_screen()->OnCancel();
   EnrollmentScreen::Result screen_result = enrollment_ui_.WaitForScreenExit();
-  EXPECT_EQ(EnrollmentScreen::Result::COMPLETED, screen_result);
+  EXPECT_EQ(EnrollmentScreen::Result::BACK, screen_result);
 }
 
 IN_PROC_BROWSER_TEST_F(EnrollmentScreenTest, EnrollmentSpinner) {
@@ -256,7 +256,8 @@
   enrollment_ui_.SetExitHandler();
   enrollment_screen()->OnCancel();
   EnrollmentScreen::Result screen_result = enrollment_ui_.WaitForScreenExit();
-  EXPECT_EQ(EnrollmentScreen::Result::BACK, screen_result);
+  EXPECT_EQ(EnrollmentScreen::Result::BACK_TO_AUTO_ENROLLMENT_CHECK,
+            screen_result);
 }
 
 class MultiAuthEnrollmentScreenTest : public EnrollmentScreenTest {
@@ -289,7 +290,8 @@
   enrollment_ui_.SetExitHandler();
   enrollment_screen()->OnCancel();
   EnrollmentScreen::Result screen_result = enrollment_ui_.WaitForScreenExit();
-  EXPECT_EQ(EnrollmentScreen::Result::BACK, screen_result);
+  EXPECT_EQ(EnrollmentScreen::Result::BACK_TO_AUTO_ENROLLMENT_CHECK,
+            screen_result);
 }
 
 class ProvisionedEnrollmentScreenTest : public EnrollmentScreenTest {
@@ -320,7 +322,8 @@
   enrollment_ui_.SetExitHandler();
   enrollment_screen()->OnCancel();
   EnrollmentScreen::Result screen_result = enrollment_ui_.WaitForScreenExit();
-  EXPECT_EQ(EnrollmentScreen::Result::BACK, screen_result);
+  EXPECT_EQ(EnrollmentScreen::Result::BACK_TO_AUTO_ENROLLMENT_CHECK,
+            screen_result);
 }
 
 class OobeCompletedUnownedTest : public OobeBaseTest {
diff --git a/chrome/browser/ash/login/help_app_launcher.cc b/chrome/browser/ash/login/help_app_launcher.cc
index c465d3a..e4bda34 100644
--- a/chrome/browser/ash/login/help_app_launcher.cc
+++ b/chrome/browser/ash/login/help_app_launcher.cc
@@ -11,6 +11,7 @@
 #include "chrome/browser/ash/login/ui/login_web_dialog.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/common/extensions/extension_constants.h"
 #include "chrome/grit/generated_resources.h"
 #include "chrome/grit/locale_settings.h"
 #include "content/public/browser/browser_thread.h"
@@ -26,9 +27,6 @@
 using ::content::BrowserThread;
 using ::extensions::ExtensionRegistry;
 
-// Official HelpApp extension id.
-const char kExtensionId[] = "honijodknafkokifofgiaalefdiedpko";
-
 const char kHelpAppFormat[] = "chrome-extension://%s/oobe.html?id=%d";
 
 const char* g_extension_id_for_test = nullptr;
@@ -49,7 +47,7 @@
   if (!registry)
     return;
 
-  const char* extension_id = kExtensionId;
+  const char* extension_id = extension_misc::kHelpAppExtensionId;
   if (g_extension_id_for_test && *g_extension_id_for_test != '\0') {
     extension_id = g_extension_id_for_test;
   }
diff --git a/chrome/browser/ash/login/screens/recommend_apps/recommend_apps_fetcher_impl.cc b/chrome/browser/ash/login/screens/recommend_apps/recommend_apps_fetcher_impl.cc
index a863d77..0587dcea 100644
--- a/chrome/browser/ash/login/screens/recommend_apps/recommend_apps_fetcher_impl.cc
+++ b/chrome/browser/ash/login/screens/recommend_apps/recommend_apps_fetcher_impl.cc
@@ -16,7 +16,9 @@
 #include "base/strings/string_util.h"
 #include "base/task/post_task.h"
 #include "base/task/thread_pool.h"
+#include "chrome/browser/about_flags.h"
 #include "chrome/browser/ash/login/screens/recommend_apps/recommend_apps_fetcher_delegate.h"
+#include "chrome/common/chrome_features.h"
 #include "content/public/browser/gpu_data_manager.h"
 #include "extensions/common/api/system_display.h"
 #include "gpu/config/gpu_info.h"
@@ -40,6 +42,9 @@
 constexpr const char kGetAppListUrl[] =
     "https://android.clients.google.com/fdfe/chrome/getfastreinstallappslist";
 
+constexpr const char kGetRevisedAppListUrl[] =
+    "https://android.clients.google.com/fdfe/chrome/getSetupAppRecommendations";
+
 constexpr int kResponseErrorNotEnoughApps = 5;
 
 constexpr int kResponseErrorNotFirstTimeChromebookUser = 6;
@@ -430,7 +435,11 @@
         })");
 
   auto resource_request = std::make_unique<network::ResourceRequest>();
-  resource_request->url = GURL(kGetAppListUrl);
+  if (base::FeatureList::IsEnabled(features::kAppDiscoveryForOobe)) {
+    resource_request->url = GURL(kGetRevisedAppListUrl);
+  } else {
+    resource_request->url = GURL(kGetAppListUrl);
+  }
   resource_request->method = "GET";
   resource_request->load_flags =
       net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE;
@@ -500,13 +509,23 @@
   base::StringPiece response_body_json(*response_body);
   if (base::StartsWith(response_body_json, json_xss_prevention_prefix))
     response_body_json.remove_prefix(json_xss_prevention_prefix.length());
-  absl::optional<base::Value> output = ParseResponse(response_body_json);
-  if (!output.has_value()) {
-    RecordUmaResponseAppCount(0);
-    delegate_->OnParseResponseError();
-    return;
-  }
 
+  absl::optional<base::Value> output;
+  if (base::FeatureList::IsEnabled(features::kAppDiscoveryForOobe)) {
+    output =
+        base::JSONReader::ReadAndReturnValueWithError(response_body_json).value;
+    if (!output.has_value()) {
+      delegate_->OnParseResponseError();
+      return;
+    }
+  } else {
+    output = ParseResponse(response_body_json);
+    if (!output.has_value()) {
+      RecordUmaResponseAppCount(0);
+      delegate_->OnParseResponseError();
+      return;
+    }
+  }
   delegate_->OnLoadSuccess(std::move(output.value()));
 }
 
diff --git a/chrome/browser/ash/login/screens/recommend_apps/recommend_apps_fetcher_impl_unittest.cc b/chrome/browser/ash/login/screens/recommend_apps/recommend_apps_fetcher_impl_unittest.cc
index 57a43d7..802105e0 100644
--- a/chrome/browser/ash/login/screens/recommend_apps/recommend_apps_fetcher_impl_unittest.cc
+++ b/chrome/browser/ash/login/screens/recommend_apps/recommend_apps_fetcher_impl_unittest.cc
@@ -15,10 +15,12 @@
 #include "base/run_loop.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "base/values.h"
 #include "chrome/browser/ash/login/screens/recommend_apps/fake_recommend_apps_fetcher_delegate.h"
 #include "chrome/browser/ash/login/screens/recommend_apps/recommend_apps_fetcher.h"
+#include "chrome/common/chrome_features.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile_manager.h"
 #include "components/user_manager/scoped_user_manager.h"
@@ -307,6 +309,10 @@
               header_reader.GetSystemAvailableFeatures());
   }
 
+  void EnableAppDiscoveryFlag() {
+    scoped_feature_list_.InitAndEnableFeature(features::kAppDiscoveryForOobe);
+  }
+
   FakeRecommendAppsFetcherDelegate delegate_;
   network::TestURLLoaderFactory test_url_loader_factory_;
   std::unique_ptr<RecommendAppsFetcher> recommend_apps_fetcher_;
@@ -319,10 +325,17 @@
 
  private:
   void InterceptRequest(const network::ResourceRequest& request) {
-    ASSERT_EQ(
-        "https://android.clients.google.com/fdfe/chrome/"
-        "getfastreinstallappslist",
-        request.url.spec());
+    if (base::FeatureList::IsEnabled(features::kAppDiscoveryForOobe)) {
+      ASSERT_EQ(
+          "https://android.clients.google.com/fdfe/chrome/"
+          "getSetupAppRecommendations",
+          request.url.spec());
+    } else {
+      ASSERT_EQ(
+          "https://android.clients.google.com/fdfe/chrome/"
+          "getfastreinstallappslist",
+          request.url.spec());
+    }
     if (request_waiter_)
       request_waiter_->Quit();
   }
@@ -333,6 +346,7 @@
   }
 
   content::BrowserTaskEnvironment task_environment_;
+  base::test::ScopedFeatureList scoped_feature_list_;
 
   std::unique_ptr<base::RunLoop> request_waiter_;
 };
@@ -1379,4 +1393,65 @@
   recommend_apps_fetcher_->Start();
 }
 
+TEST_F(RecommendAppsFetcherImplTest, AppDiscoveryValidResponse) {
+  EnableAppDiscoveryFlag();
+
+  ASSERT_TRUE(recommend_apps_fetcher_);
+
+  recommend_apps_fetcher_->Start();
+
+  cros_display_config_->Flush();
+  ASSERT_TRUE(cros_display_config_->RunGetDisplayUnitInfoListCallback(
+      CreateDisplayUnitInfo(Dpi(117, 117), absl::nullopt)));
+
+  ASSERT_TRUE(arc_features_callback_);
+  std::move(arc_features_callback_).Run(CreateArcFeaturesForTest());
+
+  network::ResourceRequest* request = WaitForAppListRequest();
+  ASSERT_TRUE(request);
+
+  const std::string response =
+      R"({"recommendedApp": [{
+    "androidApp": {
+      "packageName": "com.game.name",
+      "title": "NameOfFunGame",
+      "icon": {
+        "imageUri": "https://play-lh.googleusercontent.com/1234IDECLAREATHUMBWAR",
+        "dimensions": {
+          "width": 512,
+          "height": 512
+        }
+      }
+    }
+  }]})";
+
+  test_url_loader_factory_.AddResponse(request->url.spec(), response);
+
+  EXPECT_EQ(FakeRecommendAppsFetcherDelegate::Result::SUCCESS,
+            delegate_.WaitForResult());
+}
+
+TEST_F(RecommendAppsFetcherImplTest, AppDiscoveryParseErrorResponse) {
+  EnableAppDiscoveryFlag();
+
+  ASSERT_TRUE(recommend_apps_fetcher_);
+
+  recommend_apps_fetcher_->Start();
+
+  cros_display_config_->Flush();
+  ASSERT_TRUE(cros_display_config_->RunGetDisplayUnitInfoListCallback(
+      CreateDisplayUnitInfo(Dpi(117, 117), absl::nullopt)));
+
+  ASSERT_TRUE(arc_features_callback_);
+  std::move(arc_features_callback_).Run(CreateArcFeaturesForTest());
+
+  network::ResourceRequest* request = WaitForAppListRequest();
+  ASSERT_TRUE(request);
+
+  test_url_loader_factory_.AddResponse(request->url.spec(), ")}]'!2%^$");
+
+  EXPECT_EQ(FakeRecommendAppsFetcherDelegate::Result::PARSE_ERROR,
+            delegate_.WaitForResult());
+}
+
 }  // namespace ash
diff --git a/chrome/browser/ash/login/wizard_controller.cc b/chrome/browser/ash/login/wizard_controller.cc
index 75e7d52..c5bb074f 100644
--- a/chrome/browser/ash/login/wizard_controller.cc
+++ b/chrome/browser/ash/login/wizard_controller.cc
@@ -1423,12 +1423,11 @@
           << EnrollmentScreen::GetResultString(result) << ").";
   switch (result) {
     case EnrollmentScreen::Result::COMPLETED:
-    case EnrollmentScreen::Result::SKIPPED_FOR_TESTS:
       OnEnrollmentDone();
       break;
     case EnrollmentScreen::Result::BACK:
-      retry_auto_enrollment_check_ = true;
-      ShowAutoEnrollmentCheckScreen();
+    case EnrollmentScreen::Result::SKIPPED_FOR_TESTS:
+      ShowPackagedLicenseScreen();
       break;
     case EnrollmentScreen::Result::TPM_ERROR:
       DCHECK(switches::IsTpmDynamic());
@@ -1440,6 +1439,10 @@
       wizard_context_->tpm_dbus_error = true;
       AdvanceToScreen(TpmErrorView::kScreenId);
       break;
+    case EnrollmentScreen::Result::BACK_TO_AUTO_ENROLLMENT_CHECK:
+      retry_auto_enrollment_check_ = true;
+      ShowAutoEnrollmentCheckScreen();
+      break;
   }
 }
 
diff --git a/chrome/browser/ash/login/wizard_controller_browsertest.cc b/chrome/browser/ash/login/wizard_controller_browsertest.cc
index 6029f0a..66108cbb 100644
--- a/chrome/browser/ash/login/wizard_controller_browsertest.cc
+++ b/chrome/browser/ash/login/wizard_controller_browsertest.cc
@@ -2177,7 +2177,8 @@
   // Make sure enterprise enrollment page shows up right after update screen.
   CheckCurrentScreen(EnrollmentScreenView::kScreenId);
   EXPECT_CALL(*mock_auto_enrollment_check_screen_, ShowImpl()).Times(1);
-  mock_enrollment_screen_->ExitScreen(EnrollmentScreen::Result::BACK);
+  mock_enrollment_screen_->ExitScreen(
+      EnrollmentScreen::Result::BACK_TO_AUTO_ENROLLMENT_CHECK);
 
   CheckCurrentScreen(AutoEnrollmentCheckScreenView::kScreenId);
   EXPECT_FALSE(StartupUtils::IsOobeCompleted());
diff --git a/chrome/browser/ash/wallpaper_handlers/mock_wallpaper_handlers.cc b/chrome/browser/ash/wallpaper_handlers/mock_wallpaper_handlers.cc
index 75b96bf..00903511 100644
--- a/chrome/browser/ash/wallpaper_handlers/mock_wallpaper_handlers.cc
+++ b/chrome/browser/ash/wallpaper_handlers/mock_wallpaper_handlers.cc
@@ -3,6 +3,10 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/ash/wallpaper_handlers/mock_wallpaper_handlers.h"
+
+#include "base/callback.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/threading/sequenced_task_runner_handle.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
 namespace wallpaper_handlers {
@@ -10,7 +14,7 @@
 MockGooglePhotosCountFetcher::MockGooglePhotosCountFetcher(Profile* profile)
     : GooglePhotosCountFetcher(profile) {
   ON_CALL(*this, AddCallbackAndStartIfNecessary)
-      .WillByDefault([](OnGooglePhotosCountFetched callback) {
+      .WillByDefault([](base::OnceCallback<void(int)> callback) {
         base::SequencedTaskRunnerHandle::Get()->PostTask(
             FROM_HERE, base::BindOnce(std::move(callback), /*count=*/0));
       });
diff --git a/chrome/browser/ash/wallpaper_handlers/mock_wallpaper_handlers.h b/chrome/browser/ash/wallpaper_handlers/mock_wallpaper_handlers.h
index 8f20cbbc..68fc65c 100644
--- a/chrome/browser/ash/wallpaper_handlers/mock_wallpaper_handlers.h
+++ b/chrome/browser/ash/wallpaper_handlers/mock_wallpaper_handlers.h
@@ -5,7 +5,7 @@
 #ifndef CHROME_BROWSER_ASH_WALLPAPER_HANDLERS_MOCK_WALLPAPER_HANDLERS_H_
 #define CHROME_BROWSER_ASH_WALLPAPER_HANDLERS_MOCK_WALLPAPER_HANDLERS_H_
 
-#include "ash/webui/personalization_app/mojom/personalization_app.mojom.h"
+#include "base/callback_forward.h"
 #include "chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
@@ -24,11 +24,9 @@
   ~MockGooglePhotosCountFetcher() override;
 
   // GooglePhotosCountFetcher:
-  using OnGooglePhotosCountFetched = ash::personalization_app::mojom::
-      WallpaperProvider::FetchGooglePhotosCountCallback;
   MOCK_METHOD(void,
               AddCallbackAndStartIfNecessary,
-              (OnGooglePhotosCountFetched callback),
+              (base::OnceCallback<void(int)> callback),
               (override));
 };
 
diff --git a/chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.cc b/chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.cc
index d2a3f06..9504119 100644
--- a/chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.cc
+++ b/chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.cc
@@ -4,7 +4,6 @@
 
 #include "chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.h"
 
-#include <tuple>
 #include <utility>
 
 #include "ash/constants/ash_features.h"
@@ -403,8 +402,8 @@
                            surprise_me_image_response.resume_token());
 }
 
-template <typename... Args>
-GooglePhotosFetcher<Args...>::GooglePhotosFetcher(
+template <typename T>
+GooglePhotosFetcher<T>::GooglePhotosFetcher(
     Profile* profile,
     const char* service_url,
     const net::NetworkTrafficAnnotationTag& traffic_annotation)
@@ -419,11 +418,11 @@
   identity_manager_observation_.Observe(identity_manager_);
 }
 
-template <typename... Args>
-GooglePhotosFetcher<Args...>::~GooglePhotosFetcher() = default;
+template <typename T>
+GooglePhotosFetcher<T>::~GooglePhotosFetcher() = default;
 
-template <typename... Args>
-void GooglePhotosFetcher<Args...>::AddCallbackAndStartIfNecessary(
+template <typename T>
+void GooglePhotosFetcher<T>::AddCallbackAndStartIfNecessary(
     ClientCallback callback) {
   pending_client_callbacks_.push_back(std::move(callback));
   if (pending_client_callbacks_.size() > 1)
@@ -441,8 +440,8 @@
       signin::ConsentLevel::kSignin);
 }
 
-template <typename... Args>
-void GooglePhotosFetcher<Args...>::OnTokenReceived(
+template <typename T>
+void GooglePhotosFetcher<T>::OnTokenReceived(
     GoogleServiceAuthError error,
     signin::AccessTokenInfo token_info) {
   token_fetcher_.reset();
@@ -471,8 +470,8 @@
                      base::Unretained(this) /*`this` owns `url_loader_`.*/));
 }
 
-template <typename... Args>
-void GooglePhotosFetcher<Args...>::OnJsonReceived(
+template <typename T>
+void GooglePhotosFetcher<T>::OnJsonReceived(
     std::unique_ptr<std::string> response_body) {
   const int net_error = url_loader_->NetError();
   url_loader_.reset();
@@ -491,17 +490,13 @@
                                weak_factory_.GetWeakPtr())));
 }
 
-template <typename... Args>
-void GooglePhotosFetcher<Args...>::OnResponseReady(
+template <typename T>
+void GooglePhotosFetcher<T>::OnResponseReady(
     absl::optional<base::Value> response) {
-  std::tuple<Args...> args = ParseResponse(std::move(response));
-  std::apply(
-      [&](Args... args) {
-        for (auto& callback : pending_client_callbacks_)
-          std::move(callback).Run(std::forward<Args>(args)...);
-        pending_client_callbacks_.clear();
-      },
-      args);
+  T args = ParseResponse(std::move(response));
+  for (auto& callback : pending_client_callbacks_)
+    std::move(callback).Run(args);
+  pending_client_callbacks_.clear();
 }
 
 GooglePhotosCountFetcher::GooglePhotosCountFetcher(Profile* profile)
@@ -513,21 +508,21 @@
 
 GooglePhotosCountFetcher::~GooglePhotosCountFetcher() = default;
 
-std::tuple<int> GooglePhotosCountFetcher::ParseResponse(
+int GooglePhotosCountFetcher::ParseResponse(
     absl::optional<base::Value> response) {
   if (!response.has_value())
-    return std::make_tuple(-1);
+    return -1;
 
   const base::Value* user = response->FindDictPath("user");
   if (!user)
-    return std::make_tuple(-1);
+    return -1;
 
   const std::string* count_string = user->FindStringKey("numPhotos");
   int64_t count;
   if (!count_string || !base::StringToInt64(*count_string, &count))
-    return std::make_tuple(-1);
+    return -1;
 
-  return std::make_tuple(base::saturated_cast<int>(count));
+  return base::saturated_cast<int>(count);
 }
 
 }  // namespace wallpaper_handlers
diff --git a/chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.h b/chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.h
index 8e6d7298..871c39b0e 100644
--- a/chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.h
+++ b/chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.h
@@ -6,7 +6,6 @@
 #define CHROME_BROWSER_ASH_WALLPAPER_HANDLERS_WALLPAPER_HANDLERS_H_
 
 #include <memory>
-#include <tuple>
 
 #include "base/callback.h"
 #include "base/scoped_observation.h"
@@ -145,7 +144,8 @@
 };
 
 // Base class for common logic among fetchers that query the Google Photos API.
-template <typename... Args>
+// Parametrized by the client callback's argument type.
+template <typename T>
 class GooglePhotosFetcher : public signin::IdentityManager::Observer {
  public:
   GooglePhotosFetcher(
@@ -159,7 +159,7 @@
   ~GooglePhotosFetcher() override;
 
   // Issues an API request if and only if one is not in progress.
-  using ClientCallback = base::OnceCallback<void(Args...)>;
+  using ClientCallback = base::OnceCallback<void(T)>;
   virtual void AddCallbackAndStartIfNecessary(ClientCallback callback);
 
  protected:
@@ -167,8 +167,7 @@
   // was an error in sending the request, receiving the response, or parsing the
   // response; otherwise, it will hold a response in the API's specified
   // structure.
-  virtual std::tuple<Args...> ParseResponse(
-      absl::optional<base::Value> response) = 0;
+  virtual T ParseResponse(absl::optional<base::Value> response) = 0;
 
  private:
   void OnTokenReceived(GoogleServiceAuthError error,
@@ -218,7 +217,7 @@
 
  private:
   // GooglePhotosFetcher:
-  std::tuple<int> ParseResponse(absl::optional<base::Value> response) override;
+  int ParseResponse(absl::optional<base::Value> response) override;
 };
 
 }  // namespace wallpaper_handlers
diff --git a/chrome/browser/chromeos/extensions/signin_screen_policy_provider_unittest.cc b/chrome/browser/chromeos/extensions/signin_screen_policy_provider_unittest.cc
index 82e99bd..124df3c 100644
--- a/chrome/browser/chromeos/extensions/signin_screen_policy_provider_unittest.cc
+++ b/chrome/browser/chromeos/extensions/signin_screen_policy_provider_unittest.cc
@@ -6,6 +6,7 @@
 
 #include "base/memory/ref_counted.h"
 #include "base/strings/utf_string_conversions.h"
+#include "chrome/common/extensions/extension_constants.h"
 #include "components/version_info/version_info.h"
 #include "extensions/common/extension_builder.h"
 #include "extensions/common/features/feature_channel.h"
@@ -18,8 +19,6 @@
 namespace {
 
 const char kRandomExtensionId[] = "abacabadabacabaeabacabadabacabaf";
-// Gnubby
-const char kGnubbyExtensionId[] = "beknehfpfkghjoafdifaflglpjkojoco";
 //  smart card connector
 const char kSampleSigninExtensionId[] = "khpfeaanjngmcnplbdlpegiifgpfgdco";
 
@@ -74,8 +73,8 @@
 
 TEST_F(SigninScreenPolicyProviderTest, AllowEssentialExtension) {
   // Essential component extensions for the login screen should always work.
-  scoped_refptr<const extensions::Extension> extension =
-      CreateTestApp(kGnubbyExtensionId, ManifestLocation::kExternalComponent);
+  scoped_refptr<const extensions::Extension> extension = CreateTestApp(
+      extension_misc::kGnubbyAppId, ManifestLocation::kExternalComponent);
   std::u16string error;
   EXPECT_TRUE(provider_.UserMayLoad(extension.get(), &error));
   EXPECT_TRUE(error.empty());
diff --git a/chrome/browser/client_hints/client_hints_browsertest.cc b/chrome/browser/client_hints/client_hints_browsertest.cc
index cb4fca0..0851798d 100644
--- a/chrome/browser/client_hints/client_hints_browsertest.cc
+++ b/chrome/browser/client_hints/client_hints_browsertest.cc
@@ -1549,8 +1549,7 @@
 }
 
 // Flaky on all platforms. https://crbug.com/1285479.
-IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest,
-                       DISABLED_DelegateToFoo_MetaName) {
+IN_PROC_BROWSER_TEST_F(ClientHintsBrowserTest, DelegateToFoo_MetaName) {
   // Go to a page which delegates hints to `foo.com`.
   GURL gurl = meta_name_accept_ch_delegation_foo();
   SetClientHintExpectationsOnMainFrame(false);
diff --git a/chrome/browser/commerce/price_tracking/android/DEPS b/chrome/browser/commerce/price_tracking/android/DEPS
index 26a8e4a..2da1d46 100644
--- a/chrome/browser/commerce/price_tracking/android/DEPS
+++ b/chrome/browser/commerce/price_tracking/android/DEPS
@@ -2,11 +2,10 @@
   "+chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceTrackingUtilities.java",
   "+chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java",
   "+chrome/android/java/src/org/chromium/chrome/browser/ShortcutHelper.java",
-  "+chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java",
   "+chrome/android/java/src/org/chromium/chrome/browser/document/ChromeLauncherActivity.java",
   "+chrome/android/java/src/org/chromium/chrome/browser/init/ChromeBrowserInitializer.java",
   "+chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationUmaTracker.java",
   "+chrome/android/java/src/org/chromium/chrome/browser/notifications/channels/ChromeChannelDefinitions.java",
   "+components/image_fetcher/android/java/src/org/chromium/components/image_fetcher/ImageFetcher.java",
   "+components/image_fetcher/android/java/src/org/chromium/components/image_fetcher/ImageFetcherFactory.java",
-]
+]
\ No newline at end of file
diff --git a/chrome/browser/commerce/price_tracking/android/java/src/org/chromium/chrome/browser/price_tracking/PriceDropNotificationManager.java b/chrome/browser/commerce/price_tracking/android/java/src/org/chromium/chrome/browser/price_tracking/PriceDropNotificationManager.java
index 9c96877e..893cfa9a 100644
--- a/chrome/browser/commerce/price_tracking/android/java/src/org/chromium/chrome/browser/price_tracking/PriceDropNotificationManager.java
+++ b/chrome/browser/commerce/price_tracking/android/java/src/org/chromium/chrome/browser/price_tracking/PriceDropNotificationManager.java
@@ -26,14 +26,12 @@
 import org.chromium.base.IntentUtils;
 import org.chromium.base.Log;
 import org.chromium.base.metrics.RecordHistogram;
-import org.chromium.chrome.browser.bookmarks.BookmarkBridge;
 import org.chromium.chrome.browser.browserservices.intents.WebappConstants;
 import org.chromium.chrome.browser.document.ChromeLauncherActivity;
 import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
 import org.chromium.chrome.browser.notifications.NotificationIntentInterceptor;
 import org.chromium.chrome.browser.notifications.NotificationUmaTracker;
 import org.chromium.chrome.browser.notifications.channels.ChromeChannelDefinitions;
-import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.subscriptions.CommerceSubscription;
 import org.chromium.chrome.browser.subscriptions.CommerceSubscription.CommerceSubscriptionType;
 import org.chromium.chrome.browser.subscriptions.CommerceSubscription.SubscriptionManagementType;
@@ -233,40 +231,19 @@
                         String.format(
                                 Locale.US, "Failed to remove subscriptions. Status: %d", status));
             };
-            BookmarkBridge bookmarkBridge = new BookmarkBridge(Profile.getLastUsedRegularProfile());
-
-            Runnable unsubscribeRunnable = () -> {
-                if (offerId != null) {
-                    subscriptionsManager.unsubscribe(
-                            new CommerceSubscription(CommerceSubscriptionType.PRICE_TRACK, offerId,
-                                    SubscriptionManagementType.CHROME_MANAGED,
-                                    TrackingIdType.OFFER_ID),
-                            callback);
-                }
-                if (clusterId != null) {
-                    subscriptionsManager.unsubscribe(
-                            new CommerceSubscription(CommerceSubscriptionType.PRICE_TRACK,
-                                    clusterId, SubscriptionManagementType.USER_MANAGED,
-                                    TrackingIdType.PRODUCT_CLUSTER_ID),
-                            callback);
-                }
-            };
-
-            // Only attempt to unsubscribe once the corresponding bookmarks can also be updated.
-            if (bookmarkBridge.isBookmarkModelLoaded()) {
-                unsubscribeRunnable.run();
-            } else {
-                bookmarkBridge.addObserver(new BookmarkBridge.BookmarkModelObserver() {
-                    @Override
-                    public void bookmarkModelLoaded() {
-                        unsubscribeRunnable.run();
-                    }
-
-                    @Override
-                    public void bookmarkModelChanged() {}
-                });
+            if (offerId != null) {
+                subscriptionsManager.unsubscribe(
+                        new CommerceSubscription(CommerceSubscriptionType.PRICE_TRACK, offerId,
+                                SubscriptionManagementType.CHROME_MANAGED, TrackingIdType.OFFER_ID),
+                        callback);
             }
-
+            if (clusterId != null) {
+                subscriptionsManager.unsubscribe(
+                        new CommerceSubscription(CommerceSubscriptionType.PRICE_TRACK, clusterId,
+                                SubscriptionManagementType.USER_MANAGED,
+                                TrackingIdType.PRODUCT_CLUSTER_ID),
+                        callback);
+            }
             if (recordMetrics) {
                 NotificationUmaTracker.getInstance().onNotificationActionClick(
                         NotificationUmaTracker.ActionType.PRICE_DROP_TURN_OFF_ALERT,
diff --git a/chrome/browser/commerce/subscriptions/android/java/src/org/chromium/chrome/browser/subscriptions/CommerceSubscriptionsService.java b/chrome/browser/commerce/subscriptions/android/java/src/org/chromium/chrome/browser/subscriptions/CommerceSubscriptionsService.java
index e9afc28..2325625 100644
--- a/chrome/browser/commerce/subscriptions/android/java/src/org/chromium/chrome/browser/subscriptions/CommerceSubscriptionsService.java
+++ b/chrome/browser/commerce/subscriptions/android/java/src/org/chromium/chrome/browser/subscriptions/CommerceSubscriptionsService.java
@@ -22,7 +22,8 @@
     /** Performs any deferred startup tasks required by {@link Subscriptions}. */
     public void initDeferredStartupForActivity(TabModelSelector tabModelSelector,
             ActivityLifecycleDispatcher activityLifecycleDispatcher) {
-        if (mImplicitPriceDropSubscriptionsManager == null) {
+        if (CommerceSubscriptionsServiceConfig.isImplicitSubscriptionsEnabled()
+                && mImplicitPriceDropSubscriptionsManager == null) {
             mImplicitPriceDropSubscriptionsManager = new ImplicitPriceDropSubscriptionsManager(
                     tabModelSelector, activityLifecycleDispatcher, mSubscriptionManager);
         }
diff --git a/chrome/browser/commerce/subscriptions/android/java/src/org/chromium/chrome/browser/subscriptions/CommerceSubscriptionsServiceConfig.java b/chrome/browser/commerce/subscriptions/android/java/src/org/chromium/chrome/browser/subscriptions/CommerceSubscriptionsServiceConfig.java
index 3af6f12..04da4850 100644
--- a/chrome/browser/commerce/subscriptions/android/java/src/org/chromium/chrome/browser/subscriptions/CommerceSubscriptionsServiceConfig.java
+++ b/chrome/browser/commerce/subscriptions/android/java/src/org/chromium/chrome/browser/subscriptions/CommerceSubscriptionsServiceConfig.java
@@ -24,6 +24,9 @@
     private static final String STALE_TAB_LOWER_BOUND_SECONDS_PARAM =
             "price_tracking_stale_tab_lower_bound_seconds";
 
+    private static final String IMPLICIT_SUBSCRIPTIONS_ENABLED_PARAM =
+            "implicit_subscriptions_enabled";
+
     private static final int DEFAULT_STALE_TAB_LOWER_BOUND_DAYS = 1;
 
     public static String getDefaultServiceUrl() {
@@ -45,4 +48,13 @@
         }
         return defaultValue;
     }
+
+    public static boolean isImplicitSubscriptionsEnabled() {
+        if (FeatureList.isInitialized()) {
+            return ChromeFeatureList.getFieldTrialParamByFeatureAsBoolean(
+                    ChromeFeatureList.COMMERCE_PRICE_TRACKING, IMPLICIT_SUBSCRIPTIONS_ENABLED_PARAM,
+                    true);
+        }
+        return true;
+    }
 }
diff --git a/chrome/browser/devtools/devtools_browsertest.cc b/chrome/browser/devtools/devtools_browsertest.cc
index 5616953..578cdd2 100644
--- a/chrome/browser/devtools/devtools_browsertest.cc
+++ b/chrome/browser/devtools/devtools_browsertest.cc
@@ -529,8 +529,10 @@
         .Set("manifest_version", 2)
         // simple_test_page.html is currently the only page referenced outside
         // of its own extension in the tests
-        .Set("web_accessible_resources",
-             extensions::ListBuilder().Append("simple_test_page.html").Build());
+        .Set("web_accessible_resources", extensions::ListBuilder()
+                                             .Append("simple_test_page.html")
+                                             .Append("source.map")
+                                             .Build());
 
     // If |devtools_page| isn't empty, make it a devtools extension in the
     // manifest.
@@ -567,6 +569,9 @@
                    "    }\n"
                    ");\n");
 
+    dir->WriteFile(FILE_PATH_LITERAL("source.map"),
+                   R"({"version":3,"sources":["foo.js"],"mappings":"AAyCAA"})");
+
     dir->WriteFile(FILE_PATH_LITERAL("sidebarpane_devtools_page.html"),
                    "<html><head><script src='sidebarpane_devtools_page.js'>"
                    "</script></head><body></body></html>");
@@ -2720,6 +2725,30 @@
   CloseDevToolsWindow();
 }
 
+IN_PROC_BROWSER_TEST_F(DevToolsExtensionTest, SourceMapsFromExtension) {
+  const Extension* extension =
+      LoadExtensionForTest("Non-DevTools Extension", "" /* devtools_page */,
+                           "" /* panel_iframe_src */);
+  ASSERT_TRUE(extension);
+  OpenDevToolsWindow(kEmptyTestPage, /* is_docked */ false);
+  DispatchOnTestSuite(window_, "testSourceMapsFromExtension",
+                      extension->id().c_str());
+  CloseDevToolsWindow();
+}
+
+IN_PROC_BROWSER_TEST_F(DevToolsTest, SourceMapsFromDevtools) {
+  OpenDevToolsWindow(kEmptyTestPage, /* is_docked */ false);
+  DispatchOnTestSuite(window_, "testSourceMapsFromDevtools");
+  CloseDevToolsWindow();
+}
+
+IN_PROC_BROWSER_TEST_F(DevToolsTest,
+                       DoesNotCrashOnSourceMapsFromUnknownScheme) {
+  OpenDevToolsWindow(kEmptyTestPage, /* is_docked */ false);
+  DispatchOnTestSuite(window_, "testDoesNotCrashOnSourceMapsFromUnknownScheme");
+  CloseDevToolsWindow();
+}
+
 IN_PROC_BROWSER_TEST_F(DevToolsExtensionTest,
                        ExtensionWebSocketOfflineNetworkConditions) {
   net::SpawnedTestServer websocket_server(
diff --git a/chrome/browser/extensions/BUILD.gn b/chrome/browser/extensions/BUILD.gn
index fb0a4345..3cdb9b58 100644
--- a/chrome/browser/extensions/BUILD.gn
+++ b/chrome/browser/extensions/BUILD.gn
@@ -1087,6 +1087,10 @@
       "chrome_kiosk_delegate_chromeos.cc",
       "extension_assets_manager_chromeos.cc",
       "extension_assets_manager_chromeos.h",
+      "ash_extension_keeplist_manager.cc",
+      "ash_extension_keeplist_manager.h",
+      "extension_keeplist_ash.cc",
+      "extension_keeplist_ash.h",
       "extension_garbage_collector_chromeos.cc",
       "extension_garbage_collector_chromeos.h",
       "system_display/display_info_provider_chromeos.cc",
diff --git a/chrome/browser/extensions/api/networking_private/networking_private_apitest.cc b/chrome/browser/extensions/api/networking_private/networking_private_apitest.cc
index ec104d5..6d98fd7 100644
--- a/chrome/browser/extensions/api/networking_private/networking_private_apitest.cc
+++ b/chrome/browser/extensions/api/networking_private/networking_private_apitest.cc
@@ -69,7 +69,7 @@
   }
 
   void SetProperties(const std::string& guid,
-                     std::unique_ptr<base::DictionaryValue> properties,
+                     base::Value properties,
                      bool allow_set_shared_config,
                      VoidCallback success_callback,
                      FailureCallback failure_callback) override {
@@ -77,7 +77,7 @@
   }
 
   void CreateNetwork(bool shared,
-                     std::unique_ptr<base::DictionaryValue> properties,
+                     base::Value properties,
                      StringCallback success_callback,
                      FailureCallback failure_callback) override {
     StringResult(std::move(success_callback), std::move(failure_callback),
@@ -100,13 +100,15 @@
     if (fail_) {
       std::move(failure_callback).Run(kFailure);
     } else {
-      std::unique_ptr<base::ListValue> result(new base::ListValue);
-      std::unique_ptr<base::DictionaryValue> network(new base::DictionaryValue);
-      network->SetString(::onc::network_config::kType,
-                         ::onc::network_config::kEthernet);
-      network->SetString(::onc::network_config::kGUID, kGuid);
-      result->Append(std::move(network));
-      std::move(success_callback).Run(std::move(result));
+      base::Value result(base::Value::Type::LIST);
+      base::Value network(base::Value::Type::DICTIONARY);
+      network.SetStringPath(::onc::network_config::kType,
+                            ::onc::network_config::kEthernet);
+      network.SetStringPath(::onc::network_config::kGUID, kGuid);
+      result.Append(std::move(network));
+      std::move(success_callback)
+          .Run(base::ListValue::From(
+              base::Value::ToUniquePtrValue(std::move(result))));
     }
   }
 
@@ -182,12 +184,12 @@
     return result;
   }
 
-  std::unique_ptr<base::DictionaryValue> GetGlobalPolicy() override {
-    return std::make_unique<base::DictionaryValue>();
+  base::Value GetGlobalPolicy() override {
+    return base::Value(base::Value::Type::DICTIONARY);
   }
 
-  std::unique_ptr<base::DictionaryValue> GetCertificateLists() override {
-    return std::make_unique<base::DictionaryValue>();
+  base::Value GetCertificateLists() override {
+    return base::Value(base::Value::Type::DICTIONARY);
   }
 
   bool EnableNetworkType(const std::string& type) override {
@@ -216,10 +218,10 @@
     if (fail_) {
       std::move(failure_callback).Run(kFailure);
     } else {
-      std::unique_ptr<base::DictionaryValue> result(new base::DictionaryValue);
-      result->SetString(::onc::network_config::kGUID, guid);
-      result->SetString(::onc::network_config::kType,
-                        ::onc::network_config::kWiFi);
+      base::Value result(base::Value::Type::DICTIONARY);
+      result.SetStringPath(::onc::network_config::kGUID, guid);
+      result.SetStringPath(::onc::network_config::kType,
+                           ::onc::network_config::kWiFi);
       std::move(success_callback).Run(std::move(result));
     }
   }
diff --git a/chrome/browser/extensions/ash_extension_keeplist_manager.cc b/chrome/browser/extensions/ash_extension_keeplist_manager.cc
new file mode 100644
index 0000000..b7a7eb5
--- /dev/null
+++ b/chrome/browser/extensions/ash_extension_keeplist_manager.cc
@@ -0,0 +1,118 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/extensions/ash_extension_keeplist_manager.h"
+
+#include "ash/constants/ash_features.h"
+#include "base/check.h"
+#include "base/feature_list.h"
+#include "chrome/browser/ash/crosapi/browser_util.h"
+#include "chrome/browser/extensions/extension_keeplist_ash.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/profiles/profile.h"
+#include "extensions/browser/extension_prefs.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/common/extension.h"
+
+namespace extensions {
+
+AshExtensionKeeplistManager::AshExtensionKeeplistManager(
+    Profile* profile,
+    ExtensionPrefs* extension_prefs,
+    ExtensionService* extension_service)
+    : extension_prefs_(extension_prefs),
+      extension_service_(extension_service),
+      registry_(ExtensionRegistry::Get(profile)) {
+  // We should enforce the keep list when Lacros is the only browser. However,
+  // Lacros as the only browser is not supported yet. To make it easy to test,
+  // allow enforcing the keep list with Lacros as primary browser.
+  // TODO(crbug.com/1268846): Enable the enforcement when Lacros is the only
+  // browser when Lacros as the only browser is supported.
+  should_enforce_keeplist_ =
+      crosapi::browser_util::IsLacrosPrimaryBrowser() &&
+      base::FeatureList::IsEnabled(
+          chromeos::features::kEnforceAshExtensionKeeplist);
+  if (should_enforce_keeplist_)
+    registry_observation_.Observe(registry_);
+}
+
+AshExtensionKeeplistManager::~AshExtensionKeeplistManager() = default;
+
+void AshExtensionKeeplistManager::Init() {
+  if (should_enforce_keeplist_)
+    ActivateKeeplistEnforcement();
+  else
+    DeactivateKeeplistEnforcement();
+}
+
+void AshExtensionKeeplistManager::ActivateKeeplistEnforcement() {
+  DCHECK(should_enforce_keeplist_);
+
+  std::unique_ptr<ExtensionSet> all_extensions =
+      registry_->GenerateInstalledExtensionsSet();
+
+  for (const auto& extension : *all_extensions) {
+    if (ShouldDisable(extension.get()))
+      Disable(extension->id());
+  }
+}
+
+bool AshExtensionKeeplistManager::ShouldDisable(
+    const Extension* extension) const {
+  if (extension->is_extension() && !ExtensionRunsInAsh(extension->id()))
+    return true;
+
+  if (extension->is_platform_app() &&
+      crosapi::browser_util::IsLacrosChromeAppsEnabled() &&
+      !ExtensionAppRunsInAsh(extension->id())) {
+    return true;
+  }
+
+  return false;
+}
+
+void AshExtensionKeeplistManager::Disable(const std::string& extension_id) {
+  DCHECK(should_enforce_keeplist_);
+
+  extension_service_->DisableExtension(
+      extension_id, disable_reason::DISABLE_NOT_ASH_KEEPLISTED);
+
+  // An extension is not allowed to be disabled by user due to different reasons
+  // (shared module, installed as a component extension or installed by policy,
+  // etc.). We would log a message here to track the extensions that can't be
+  // disabled and analyze to see if we have missed any extensions in the keep
+  // list during the audit.
+  if (registry_->enabled_extensions().Contains(extension_id)) {
+    LOG(WARNING) << "Can not enforce disabling extension id:" << extension_id;
+  }
+}
+
+void AshExtensionKeeplistManager::DeactivateKeeplistEnforcement() {
+  DCHECK(!should_enforce_keeplist_);
+
+  std::unique_ptr<ExtensionSet> all_extensions =
+      registry_->GenerateInstalledExtensionsSet();
+
+  // Find all extensions disabled by keeplist enforcement, remove the disable
+  // reason.
+  for (const auto& extension : *all_extensions) {
+    if (extension_prefs_->HasDisableReason(
+            extension->id(), disable_reason::DISABLE_NOT_ASH_KEEPLISTED)) {
+      extension_service_->RemoveDisableReasonAndMaybeEnable(
+          extension->id(), disable_reason::DISABLE_NOT_ASH_KEEPLISTED);
+    }
+  }
+}
+
+void AshExtensionKeeplistManager::OnExtensionReady(
+    content::BrowserContext* browser_context,
+    const Extension* extension) {
+  if (!should_enforce_keeplist_)
+    return;
+
+  if (ShouldDisable(extension))
+    Disable(extension->id());
+}
+
+}  // namespace extensions
diff --git a/chrome/browser/extensions/ash_extension_keeplist_manager.h b/chrome/browser/extensions/ash_extension_keeplist_manager.h
new file mode 100644
index 0000000..8122ea1
--- /dev/null
+++ b/chrome/browser/extensions/ash_extension_keeplist_manager.h
@@ -0,0 +1,77 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_ASH_EXTENSION_KEEPLIST_MANAGER_H_
+#define CHROME_BROWSER_EXTENSIONS_ASH_EXTENSION_KEEPLIST_MANAGER_H_
+
+#include <string>
+
+#include "base/memory/raw_ptr.h"
+#include "base/scoped_observation.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_registry_observer.h"
+
+class Profile;
+
+namespace extensions {
+class ExtensionPrefs;
+class ExtensionRegistry;
+class ExtensionService;
+
+// This class manages the 1st party Ash extension keeplist. When Lacros becomes
+// the only browser, all extensions should be installed in Lacros instead of
+// Ash. However, there is a small exception set of 1st party extensions and
+// platform apps we will keep running in Ash, since they are either needed to
+// support some Chrome OS features such as accessibility, or are in the process
+// of deprecation, or not completely Lacros compatible yet. This class will
+// manage to disable all the extensions and platform apps in Ash if they are
+// not in the keep list.
+class AshExtensionKeeplistManager : private ExtensionRegistryObserver {
+ public:
+  AshExtensionKeeplistManager(Profile* profile,
+                              ExtensionPrefs* extension_prefs,
+                              ExtensionService* extension_service);
+  AshExtensionKeeplistManager(const AshExtensionKeeplistManager&) = delete;
+  AshExtensionKeeplistManager& operator=(const AshExtensionKeeplistManager&) =
+      delete;
+  ~AshExtensionKeeplistManager() override;
+
+  void Init();
+
+ private:
+  // Returns true if |extension| should be disabled.
+  bool ShouldDisable(const Extension* extension) const;
+
+  // Disables the extension with 'DISABLE_NOT_ASH_KEEPLISTED'.
+  void Disable(const std::string& extension_id);
+
+  // Blocks all extensions not on the keeplist by disabling them with
+  // 'DISABLE_NOT_ASH_KEEPLISTED'.
+  void ActivateKeeplistEnforcement();
+
+  // Unblocks all extensions by removing 'DISABLE_NOT_ASH_KEEPLISTED' from
+  // disable reasons. It will be called when Lacros is not primary browser or
+  // features::kEnforceAshExtensionKeeplist is turned off.
+  void DeactivateKeeplistEnforcement();
+
+  // ExtensionRegistryObserver:
+  void OnExtensionReady(content::BrowserContext* browser_context,
+                        const Extension* extension) override;
+
+  // The |extension_prefs_|, |extension_service_| and |registry_| are passed
+  // from ctor and owned by the caller, and they are guaranteed to outlive this
+  // object.
+  raw_ptr<ExtensionPrefs> const extension_prefs_ = nullptr;      // not owned
+  raw_ptr<ExtensionService> const extension_service_ = nullptr;  // not owned
+  raw_ptr<ExtensionRegistry> const registry_ = nullptr;          // not owned
+
+  bool should_enforce_keeplist_ = false;
+
+  base::ScopedObservation<ExtensionRegistry, ExtensionRegistryObserver>
+      registry_observation_{this};
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_BROWSER_EXTENSIONS_ASH_EXTENSION_KEEPLIST_MANAGER_H_
diff --git a/chrome/browser/extensions/extension_keeplist_ash.cc b/chrome/browser/extensions/extension_keeplist_ash.cc
new file mode 100644
index 0000000..11fb755
--- /dev/null
+++ b/chrome/browser/extensions/extension_keeplist_ash.cc
@@ -0,0 +1,61 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/extensions/extension_keeplist_ash.h"
+
+#include <stddef.h>
+
+#include "base/containers/contains.h"
+#include "base/no_destructor.h"
+#include "base/strings/string_piece.h"
+#include "chrome/browser/ash/file_manager/app_id.h"
+#include "chrome/browser/ash/input_method/component_extension_ime_manager_delegate_impl.h"
+#include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
+#include "chrome/common/buildflags.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "extensions/common/constants.h"
+
+namespace extensions {
+
+bool ExtensionRunsInAsh(const std::string& extension_id) {
+  static base::NoDestructor<std::set<base::StringPiece>> keep_list({
+#if BUILDFLAG(ENABLE_HANGOUT_SERVICES_EXTENSION)
+    extension_misc::kHangoutServiceExtensionId,
+#endif
+        extension_misc::kEspeakSpeechSynthesisExtensionId,
+        extension_misc::kGoogleSpeechSynthesisExtensionId,
+        extension_misc::kEnhancedNetworkTtsExtensionId,
+        extension_misc::kSelectToSpeakExtensionId,
+        extension_misc::kAccessibilityCommonExtensionId,
+        extension_misc::kChromeVoxExtensionId,
+        extension_misc::kSwitchAccessExtensionId,
+        extension_misc::kSigninProfileTestExtensionId,
+        extension_misc::kAssessmentAssistantExtensionId,
+        extension_misc::kQuickOfficeComponentExtensionId,
+        extension_misc::kQuickOfficeInternalExtensionId,
+        extension_misc::kQuickOfficeExtensionId,
+        extension_misc::kGuestModeTestExtensionId,
+        extension_misc::kCryptotokenExtensionId,
+        extension_misc::kKeyboardExtensionId,
+        extension_misc::kHelpAppExtensionId, extension_misc::kEchoExtensionId,
+        extension_misc::kGCSEExtensionId, extension_misc::kGnubbyV3ExtensionId,
+        file_manager::kImageLoaderExtensionId
+  });
+  return base::Contains(*keep_list, extension_id) ||
+         ash::input_method::ComponentExtensionIMEManagerDelegateImpl::
+             IsIMEExtensionID(extension_id);
+}
+
+bool ExtensionAppRunsInAsh(const std::string& app_id) {
+  static base::NoDestructor<std::set<base::StringPiece>> keep_list(
+      {file_manager::kAudioPlayerAppId, extension_misc::kFeedbackExtensionId,
+       extension_misc::kFilesManagerAppId, extension_misc::kGoogleKeepAppId,
+       extension_misc::kCalculatorAppId, extension_misc::kTextEditorAppId,
+       extension_misc::kInAppPaymentsSupportAppId,
+       extension_misc::kWallpaperManagerId, arc::kPlayStoreAppId,
+       extension_misc::kIdentityApiUiAppId, extension_misc::kGnubbyAppId});
+  return base::Contains(*keep_list, app_id);
+}
+
+}  // namespace extensions
diff --git a/chrome/browser/extensions/extension_keeplist_ash.h b/chrome/browser/extensions/extension_keeplist_ash.h
new file mode 100644
index 0000000..9705bab
--- /dev/null
+++ b/chrome/browser/extensions/extension_keeplist_ash.h
@@ -0,0 +1,25 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_EXTENSION_KEEPLIST_ASH_H_
+#define CHROME_BROWSER_EXTENSIONS_EXTENSION_KEEPLIST_ASH_H_
+
+#include <string>
+
+namespace extensions {
+
+// Returns true if the extension is kept to run in Ash. A small list of
+// 1st party extensions will continue to run in Ash either since they are
+// used to support Chrome OS features such as text to speech or vox,
+// or they are not compatible with Lacros yet.
+bool ExtensionRunsInAsh(const std::string& extension_id);
+
+// Some extension apps will continue to run in Ash until they are either
+// deprecated or migrated. This function returns whether a given app_id is on
+// that keep list. This function must only be called from the UI thread.
+bool ExtensionAppRunsInAsh(const std::string& app_id);
+
+}  // namespace extensions
+
+#endif  // CHROME_BROWSER_EXTENSIONS_EXTENSION_KEEPLIST_ASH_H_
diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc
index 3af18ca9..5501141 100644
--- a/chrome/browser/extensions/extension_service.cc
+++ b/chrome/browser/extensions/extension_service.cc
@@ -379,7 +379,12 @@
       extension_registrar_(profile_, this),
       force_installed_tracker_(registry_, profile_),
       force_installed_metrics_(registry_, profile_, &force_installed_tracker_),
-      corrupted_extension_reinstaller_(profile_) {
+      corrupted_extension_reinstaller_(profile_)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+      ,
+      ash_keeplist_manager_(profile, extension_prefs, this)
+#endif
+{
   CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
   TRACE_EVENT0("browser,startup", "ExtensionService::ExtensionService::ctor");
 
@@ -510,6 +515,10 @@
   // Must be called after extensions are loaded.
   allowlist_.Init();
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  ash_keeplist_manager_.Init();
+#endif
+
   // Check for updates especially for corrupted user installed extension from
   // the webstore. This will do nothing if an extension update check was
   // triggered before and is still running.
diff --git a/chrome/browser/extensions/extension_service.h b/chrome/browser/extensions/extension_service.h
index f8a8cf7e..863537c 100644
--- a/chrome/browser/extensions/extension_service.h
+++ b/chrome/browser/extensions/extension_service.h
@@ -55,6 +55,10 @@
 #error "Extensions must be enabled"
 #endif
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "chrome/browser/extensions/ash_extension_keeplist_manager.h"
+#endif
+
 class BlocklistedExtensionSyncServiceTest;
 class Profile;
 
@@ -744,6 +748,10 @@
       std::map<ExtensionPrefs::DelayReason, InstallGate*>;
   InstallGateRegistry install_delayer_registry_;
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  AshExtensionKeeplistManager ash_keeplist_manager_;
+#endif
+
   FRIEND_TEST_ALL_PREFIXES(ExtensionServiceTest,
                            DestroyingProfileClearsExtensions);
   FRIEND_TEST_ALL_PREFIXES(ExtensionServiceTest, SetUnsetBlocklistInPrefs);
diff --git a/chrome/browser/extensions/extension_sync_service.cc b/chrome/browser/extensions/extension_sync_service.cc
index 6a76b2d..ca96123c 100644
--- a/chrome/browser/extensions/extension_sync_service.cc
+++ b/chrome/browser/extensions/extension_sync_service.cc
@@ -83,7 +83,7 @@
   return result;
 }
 
-static_assert(extensions::disable_reason::DISABLE_REASON_LAST == (1LL << 21),
+static_assert(extensions::disable_reason::DISABLE_REASON_LAST == (1LL << 22),
               "Please consider whether your new disable reason should be"
               " syncable, and if so update this bitmask accordingly!");
 const int kKnownSyncableDisableReasons =
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 0566aea..29d63bb 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -170,11 +170,6 @@
     "expiry_milestone": 96
   },
   {
-    "name": "app-service-external-protocol",
-    "owners": [ "chromeos-apps-foundation-team@google.com" ],
-    "expiry_milestone": 96
-  },
-   {
     "name": "apps-shortcut-default-off",
     "owners": [ "chrome-desktop-ui-sea@google.com", "cyan" ],
     "expiry_milestone": 99
@@ -365,7 +360,7 @@
   {
     "name": "audio-url",
     "owners": [ "enshuo", "chromeos-audio@google.com" ],
-    "expiry_milestone": 98
+    "expiry_milestone": 104
   },
   {
     "name": "auto-framing-override",
@@ -3011,6 +3006,11 @@
     "owners": [ "robsc", "napper", "jennyz", "thanhdng" ],
     "expiry_milestone": 98
   },
+  {
+    "name": "enforce-ash-extension-keeplist",
+    "owners": ["jennyz", "erikchen"],
+    "expiry_milestone": 130
+  },
    {
     "name": "enforce-system-aec",
     "owners": [ "peah" ],
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 28404f62..db2b1856 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -3990,10 +3990,6 @@
 const char kAppDiscoveryRemoteUrlSearchDescription[] =
     "Surface results from a URL in the app discovery service.";
 
-const char kAppServiceExternalProtocolName[] = "App Service External Protocol";
-const char kAppServiceExternalProtocolDescription[] =
-    "Use the App Service to provide data for external protocol dialog.";
-
 const char kArcAccountRestrictionsName[] = "Enable ARC account restrictions";
 const char kArcAccountRestrictionsDescription[] =
     "ARC account restrictions feature for multi-profile account consistency";
@@ -4591,6 +4587,12 @@
     "Enable the support of WireGuard VPN as a native VPN option. Requires a "
     "kernel version that support it.";
 
+const char kEnforceAshExtensionKeeplistName[] =
+    "Enforce Ash extension keeplist";
+const char kEnforceAshExtensionKeeplistDescription[] =
+    "Enforce the Ash extension keeplist. Only the extensions and Chrome apps on"
+    " the keeplist are enabled in Ash.";
+
 const char kESimPolicyName[] = "Enable ESim Policy";
 const char kESimPolicyDescription[] =
     "Enable the support for policy controlled provisioning and configuration "
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 118689d..012deca 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -2290,9 +2290,6 @@
 extern const char kAppDiscoveryRemoteUrlSearchName[];
 extern const char kAppDiscoveryRemoteUrlSearchDescription[];
 
-extern const char kAppServiceExternalProtocolName[];
-extern const char kAppServiceExternalProtocolDescription[];
-
 extern const char kArcAccountRestrictionsName[];
 extern const char kArcAccountRestrictionsDescription[];
 
@@ -2632,6 +2629,9 @@
 extern const char kEnableWireGuardName[];
 extern const char kEnableWireGuardDescription[];
 
+extern const char kEnforceAshExtensionKeeplistName[];
+extern const char kEnforceAshExtensionKeeplistDescription[];
+
 extern const char kExoGamepadVibrationName[];
 extern const char kExoGamepadVibrationDescription[];
 
diff --git a/chrome/browser/lacros/browser_service_lacros.cc b/chrome/browser/lacros/browser_service_lacros.cc
index f23ade8..b3afc4f 100644
--- a/chrome/browser/lacros/browser_service_lacros.cc
+++ b/chrome/browser/lacros/browser_service_lacros.cc
@@ -9,6 +9,7 @@
 #include "base/files/file_util.h"
 #include "base/logging.h"
 #include "base/metrics/statistics_recorder.h"
+#include "base/ranges/algorithm.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
 #include "chrome/browser/feedback/feedback_dialog_utils.h"
@@ -17,6 +18,7 @@
 #include "chrome/browser/lacros/system_logs/lacros_system_log_fetcher.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/sessions/session_restore.h"
 #include "chrome/browser/sessions/session_service_factory.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_commands.h"
@@ -26,6 +28,7 @@
 #include "chrome/browser/ui/browser_navigator_params.h"
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/profile_picker.h"
+#include "chrome/browser/ui/startup/startup_tab.h"
 #include "chrome/browser/ui/views/tabs/tab_scrubber_chromeos.h"
 #include "chrome/browser/ui/webui/tab_strip/tab_strip_ui_util.h"
 #include "chrome/common/channel_info.h"
@@ -61,32 +64,21 @@
   }
 }
 
-// Finds any (Lacros) Browser which has a tab matching a given URL
-// without ref (e.g. chrome://flags == chrome://flags/#).
-// If such a tab exists, it gets activated, and true gets returned.
-bool ActivateTabMatchingURLWithoutRef(Profile* profile, const GURL& url) {
-  BrowserList* browser_list = BrowserList::GetInstance();
-  for (Browser* browser : *browser_list) {
-    if (browser->profile() == profile) {
-      TabStripModel* tab_strip = browser->tab_strip_model();
-      for (int i = 0; i < tab_strip->count(); ++i) {
-        if (tab_strip->ContainsIndex(i) && !tab_strip->IsTabBlocked(i)) {
-          content::WebContents* content = tab_strip->GetWebContentsAt(i);
-          if (content->GetVisibleURL().EqualsIgnoringRef(url)) {
-            browser->window()->Activate();
-            tab_strip->ActivateTabAt(i);
-            return true;
-          }
-        }
-      }
-    }
-  }
-  return false;
-}
-
 }  // namespace
 
+// A struct to keep the pending OpenUrl task.
+struct BrowserServiceLacros::PendingOpenUrl {
+  Profile* profile;
+  GURL url;
+  OpenUrlCallback callback;
+};
+
 BrowserServiceLacros::BrowserServiceLacros() {
+  session_restored_subscription_ =
+      SessionRestore::RegisterOnSessionRestoredCallback(
+          base::BindRepeating(&BrowserServiceLacros::OnSessionRestored,
+                              weak_ptr_factory_.GetWeakPtr()));
+
   auto* lacros_service = chromeos::LacrosService::Get();
   const auto* init_params = lacros_service->init_params();
 
@@ -140,9 +132,9 @@
 
   bool session_restore_available = false;
   if (should_trigger_session_restore) {
-    SessionService* sessionService =
+    SessionService* session_service =
         SessionServiceFactory::GetForProfileForSessionRestore(profile);
-    if (sessionService && sessionService->ShouldRestore(nullptr))
+    if (session_service && session_service->ShouldRestore(nullptr))
       session_restore_available = true;
   }
 
@@ -270,29 +262,28 @@
   Profile* profile = ProfileManager::GetLastUsedProfileAllowedByPolicy();
   DCHECK(profile) << "No last used profile is found.";
 
-  // Try to re-activate an existing tab for a few specified URLs.
-  if (url.SchemeIs(content::kChromeUIScheme) &&
-      (url.host() == chrome::kChromeUIFlagsHost ||
-       url.host() == chrome::kChromeUIVersionHost ||
-       url.host() == chrome::kChromeUIAboutHost ||
-       url.host() == chrome::kChromeUIComponentsHost)) {
-    if (ActivateTabMatchingURLWithoutRef(profile, url)) {
-      std::move(callback).Run();
-      return;
-    }
+  // If there is on-going session restoring task, wait for its completion.
+  if (SessionRestore::IsRestoring(profile)) {
+    pending_open_urls_.push_back(
+        PendingOpenUrl{profile, url, std::move(callback)});
+    return;
   }
 
-  NavigateParams navigate_params(
-      profile, url,
-      ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
-                                ui::PAGE_TRANSITION_FROM_API));
-  navigate_params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
-  // Ensure the browser window is showing when the URL is opened. This avoids
-  // the user being unaware a new tab with `url` has been opened (if the window
-  // was minimized for example).
-  navigate_params.window_action = NavigateParams::SHOW_WINDOW;
-  Navigate(&navigate_params);
-  std::move(callback).Run();
+  // If there's no available browsers, but there's a session to be restored,
+  // trigger it, and wait for its completion.
+  SessionService* session_service =
+      SessionServiceFactory::GetForProfileForSessionRestore(profile);
+  if (!chrome::FindBrowserWithProfile(profile) && session_service &&
+      session_service->ShouldRestore(nullptr)) {
+    pending_open_urls_.push_back(
+        PendingOpenUrl{profile, url, std::move(callback)});
+    session_service->RestoreIfNecessary(StartupTabs(),
+                                        /* restore apps */ false);
+    return;
+  }
+
+  // Otherwise, directly try to open the URL.
+  OpenUrlImpl(profile, url, std::move(callback));
 }
 
 void BrowserServiceLacros::RestoreTab(RestoreTabCallback callback) {
@@ -393,6 +384,55 @@
   std::move(callback).Run(compressed_histograms);
 }
 
+void BrowserServiceLacros::OnSessionRestored(Profile* profile,
+                                             int num_tabs_restored) {
+  if (pending_open_urls_.empty())
+    return;
+  // Extract pending OpenUrl tasks for the restored |profile|.
+  std::vector<PendingOpenUrl> pendings;
+  for (auto& pending : pending_open_urls_) {
+    if (pending.profile == profile) {
+      pendings.push_back(std::move(pending));
+      pending.profile = nullptr;  // Mark as moved.
+    }
+  }
+  // Remove marked entries.
+  pending_open_urls_.erase(base::ranges::remove(pending_open_urls_, nullptr,
+                                                [](PendingOpenUrl& pending) {
+                                                  return pending.profile;
+                                                }),
+                           pending_open_urls_.end());
+
+  // Then, run for each.
+  for (auto& pending : pendings)
+    OpenUrlImpl(pending.profile, pending.url, std::move(pending.callback));
+}
+
+void BrowserServiceLacros::OpenUrlImpl(Profile* profile,
+                                       const GURL& url,
+                                       OpenUrlCallback callback) {
+  NavigateParams navigate_params(
+      profile, url,
+      ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
+                                ui::PAGE_TRANSITION_FROM_API));
+  if (url.SchemeIs(content::kChromeUIScheme) &&
+      (url.host() == chrome::kChromeUIFlagsHost ||
+       url.host() == chrome::kChromeUIVersionHost ||
+       url.host() == chrome::kChromeUIAboutHost ||
+       url.host() == chrome::kChromeUIComponentsHost)) {
+    // Try to re-activate an existing tab for a few specified URLs.
+    navigate_params.disposition = WindowOpenDisposition::SWITCH_TO_TAB;
+  } else {
+    navigate_params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
+  }
+  // Ensure the browser window is showing when the URL is opened. This avoids
+  // the user being unaware a new tab with `url` has been opened (if the window
+  // was minimized for example).
+  navigate_params.window_action = NavigateParams::SHOW_WINDOW;
+  Navigate(&navigate_params);
+  std::move(callback).Run();
+}
+
 void BrowserServiceLacros::OnBrowserAdded(Browser* broser) {
   // Note: this happens only when ash-chrome is too old.
   // Please see the comment in the ctor for the detail.
diff --git a/chrome/browser/lacros/browser_service_lacros.h b/chrome/browser/lacros/browser_service_lacros.h
index be26b25..34db564 100644
--- a/chrome/browser/lacros/browser_service_lacros.h
+++ b/chrome/browser/lacros/browser_service_lacros.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include <string>
 
+#include "base/callback_list.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/ui/browser_list_observer.h"
 #include "chromeos/crosapi/mojom/crosapi.mojom.h"
@@ -15,6 +16,7 @@
 #include "mojo/public/cpp/bindings/receiver.h"
 
 class GURL;
+class Profile;
 class ScopedKeepAlive;
 
 // BrowserSerivce's Lacros implementation.
@@ -50,6 +52,8 @@
   void UpdateKeepAlive(bool enabled) override;
 
  private:
+  struct PendingOpenUrl;
+
   void OnSystemInformationReady(
       GetFeedbackDataCallback callback,
       std::unique_ptr<system_logs::SystemLogsResponse> sys_info);
@@ -57,13 +61,20 @@
   void OnGetCompressedHistograms(GetHistogramsCallback callback,
                                  const std::string& compressed_histogram);
 
+  void OpenUrlImpl(Profile* profile, const GURL& url, OpenUrlCallback callback);
+
+  // Called when a session is restored.
+  void OnSessionRestored(Profile* profile, int num_tabs_restored);
+
   // BrowserListObserver:
   void OnBrowserAdded(Browser* browser) override;
 
   // Keeps the Lacros browser alive in the background. This is destroyed once
   // any browser window is opened.
   std::unique_ptr<ScopedKeepAlive> keep_alive_;
+  std::vector<PendingOpenUrl> pending_open_urls_;
 
+  base::CallbackListSubscription session_restored_subscription_;
   mojo::Receiver<crosapi::mojom::BrowserService> receiver_{this};
   base::WeakPtrFactory<BrowserServiceLacros> weak_ptr_factory_{this};
 };
diff --git a/chrome/browser/media/extension_media_access_handler.cc b/chrome/browser/media/extension_media_access_handler.cc
index 7690b6d..a0db8e5 100644
--- a/chrome/browser/media/extension_media_access_handler.cc
+++ b/chrome/browser/media/extension_media_access_handler.cc
@@ -8,6 +8,7 @@
 
 #include "chrome/browser/media/webrtc/media_stream_device_permissions.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/common/extensions/extension_constants.h"
 #include "chrome/common/pref_names.h"
 #include "content/public/browser/web_contents.h"
 #include "extensions/common/extension.h"
@@ -29,7 +30,7 @@
 // 8. Accessibility Common extension (used for Dictation)
 // Once http://crbug.com/292856 is fixed, remove this allowlist.
 bool IsMediaRequestAllowedForExtension(const extensions::Extension* extension) {
-  return extension->id() == "mppnpdlheglhdfmldimlhpnegondlapf" ||
+  return extension->id() == extension_misc::kKeyboardExtensionId ||
          extension->id() == "jokbpnebhdcladagohdnfgjcpejggllo" ||
          extension->id() == "clffjmdilanldobdnedchkdbofoimcgb" ||
          extension->id() == "nnckehldicaciogcbchegobnafnjkcne" ||
diff --git a/chrome/browser/metrics/extensions_metrics_provider.cc b/chrome/browser/metrics/extensions_metrics_provider.cc
index 4410e77..7c108429 100644
--- a/chrome/browser/metrics/extensions_metrics_provider.cc
+++ b/chrome/browser/metrics/extensions_metrics_provider.cc
@@ -121,8 +121,7 @@
                                 content::BrowserContext* context) {
   ExtensionState state = NO_EXTENSIONS;
   for (extensions::ExtensionSet::const_iterator it = extensions.begin();
-       it != extensions.end() && state < OFF_STORE;
-       ++it) {
+       it != extensions.end() && state < OFF_STORE; ++it) {
     // Combine the state of each extension, always favoring the higher state as
     // defined by the order of ExtensionState.
     state = std::max(state, IsOffStoreExtension(**it, verifier, context));
@@ -217,7 +216,7 @@
   return ExtensionInstallProto::NO_BACKGROUND_SCRIPT;
 }
 
-static_assert(extensions::disable_reason::DISABLE_REASON_LAST == (1LL << 21),
+static_assert(extensions::disable_reason::DISABLE_REASON_LAST == (1LL << 22),
               "Adding a new disable reason? Be sure to include the new reason "
               "below, update the test to exercise it, and then adjust this "
               "value for DISABLE_REASON_LAST");
@@ -258,6 +257,10 @@
        ExtensionInstallProto::REINSTALL},
       {extensions::disable_reason::DISABLE_NOT_ALLOWLISTED,
        ExtensionInstallProto::NOT_ALLOWLISTED},
+      // TODO(crbug.com/1268846): Uncomment after ExtensionInstallProto is
+      // updated in third party.
+      // {extensions::disable_reason::DISABLE_NOT_ASH_KEEPLISTED,
+      // ExtensionInstallProto::NOT_ASH_KEEPLISTED},
   };
 
   int disable_reasons = prefs->GetDisableReasons(id);
@@ -365,8 +368,7 @@
   DCHECK(metrics_state_manager_);
 }
 
-ExtensionsMetricsProvider::~ExtensionsMetricsProvider() {
-}
+ExtensionsMetricsProvider::~ExtensionsMetricsProvider() = default;
 
 // static
 int ExtensionsMetricsProvider::HashExtension(const std::string& extension_id,
@@ -469,8 +471,7 @@
 
   std::set<int> buckets;
   for (extensions::ExtensionSet::const_iterator it = extensions->begin();
-       it != extensions->end();
-       ++it) {
+       it != extensions->end(); ++it) {
     buckets.insert(HashExtension((*it)->id(), client_key));
   }
 
diff --git a/chrome/browser/new_tab_page/modules/task_module/BUILD.gn b/chrome/browser/new_tab_page/modules/task_module/BUILD.gn
index d21ea57..fd065dc 100644
--- a/chrome/browser/new_tab_page/modules/task_module/BUILD.gn
+++ b/chrome/browser/new_tab_page/modules/task_module/BUILD.gn
@@ -6,5 +6,6 @@
 
 mojom("mojo_bindings") {
   sources = [ "task_module.mojom" ]
+  webui_module_path = "/"
   public_deps = [ "//url/mojom:url_mojom_gurl" ]
 }
diff --git a/chrome/browser/pdf/pdf_extension_test.cc b/chrome/browser/pdf/pdf_extension_test.cc
index c7d1ed61..7e74078 100644
--- a/chrome/browser/pdf/pdf_extension_test.cc
+++ b/chrome/browser/pdf/pdf_extension_test.cc
@@ -3164,8 +3164,8 @@
   bool clipboard_changed_ = false;
 };
 
-// TODO(crbug.com/1268983): Fix flakiness on Linux and reenable.
-#if defined(OS_LINUX)
+// TODO(crbug.com/1121446): Fix flakiness.
+#if defined(OS_LINUX) || defined(OS_CHROMEOS)
 #define MAYBE_IndividualShiftRightArrowPresses \
   DISABLED_IndividualShiftRightArrowPresses
 #else
@@ -3219,8 +3219,8 @@
   SendCopyCommandAndCheckCopyPasteClipboard("HEL");
 }
 
-// Flaky, http://crbug.com/1269104
-#if defined(OS_LINUX)
+// Flaky, http://crbug.com/1121446
+#if defined(OS_LINUX) || defined(OS_CHROMEOS)
 #define MAYBE_CombinedShiftRightArrowPresses \
   DISABLED_CombinedShiftRightArrowPresses
 #else
@@ -3254,7 +3254,7 @@
 }
 
 // Flaky on Linux (https://crbug.com/1121446)
-#if defined(OS_LINUX)
+#if defined(OS_LINUX) || defined(OS_CHROMEOS)
 #define MAYBE_CombinedShiftArrowPresses DISABLED_CombinedShiftArrowPresses
 #else
 #define MAYBE_CombinedShiftArrowPresses CombinedShiftArrowPresses
diff --git a/chrome/browser/resources/new_tab_page/BUILD.gn b/chrome/browser/resources/new_tab_page/BUILD.gn
index 823c5a1..f188beda 100644
--- a/chrome/browser/resources/new_tab_page/BUILD.gn
+++ b/chrome/browser/resources/new_tab_page/BUILD.gn
@@ -28,6 +28,9 @@
                     "js_module_root=" + rebase_path(
                             "$root_gen_dir/mojom-webui/chrome/browser/new_tab_page/modules/drive",
                             root_build_dir),
+                    "js_module_root=" + rebase_path(
+                            "$root_gen_dir/mojom-webui/chrome/browser/new_tab_page/modules/task_module",
+                            root_build_dir),
                   ]
 
   deps = [
@@ -355,12 +358,10 @@
 generate_grd("build_task_module_mojo_grdp") {
   grd_prefix = grd_prefix
   out_grd = "$target_gen_dir/task_module_mojo_resources.grdp"
-  input_files = [ "task_module.mojom-lite.js" ]
-  input_files_base_dir =
-      rebase_path(
-          "$root_gen_dir/chrome/browser/new_tab_page/modules/task_module",
+  input_files = [ "task_module.mojom-webui.js" ]
+  input_files_base_dir = rebase_path(
+          "$root_gen_dir/mojom-webui/chrome/browser/new_tab_page/modules/task_module",
           root_build_dir)
-  resource_path_prefix = "modules/task_module"
 }
 
 generate_grd("build_chrome_cart_mojo_grdp") {
@@ -522,7 +523,7 @@
       "realbox/realbox.mojom-lite.js",
       "drive.mojom-webui.js",
       "photos.mojom-webui.js",
-      "modules/task_module/task_module.mojom-lite.js",
+      "task_module.mojom-webui.js",
       "foo.mojom-webui.js",
       "chrome_cart.mojom-webui.js",
     ]
diff --git a/chrome/browser/resources/new_tab_page/modules/recipes_v2/BUILD.gn b/chrome/browser/resources/new_tab_page/modules/recipes_v2/BUILD.gn
index 9b14e88c..d385431cb 100644
--- a/chrome/browser/resources/new_tab_page/modules/recipes_v2/BUILD.gn
+++ b/chrome/browser/resources/new_tab_page/modules/recipes_v2/BUILD.gn
@@ -9,6 +9,7 @@
   deps = [
     "..:module_descriptor",
     "..:module_header",
+    "//chrome/browser/new_tab_page/modules/task_module:mojo_bindings_webui_js",
     "//chrome/browser/resources/new_tab_page/modules/task_module:task_module_handler_proxy",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_elements/cr_auto_img",
diff --git a/chrome/browser/resources/new_tab_page/modules/recipes_v2/module.js b/chrome/browser/resources/new_tab_page/modules/recipes_v2/module.js
index e18f9f1..26a2b10 100644
--- a/chrome/browser/resources/new_tab_page/modules/recipes_v2/module.js
+++ b/chrome/browser/resources/new_tab_page/modules/recipes_v2/module.js
@@ -7,6 +7,7 @@
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {I18nBehavior, loadTimeData} from '../../i18n_setup.js';
+import {TaskItem, TaskModuleType} from '../../task_module.mojom-webui.js';
 import {ModuleDescriptorV2, ModuleHeight} from '../module_descriptor.js';
 import {TaskModuleHandlerProxy} from '../task_module/task_module_handler_proxy.js';
 
@@ -26,7 +27,7 @@
 
   static get properties() {
     return {
-      /** @type {!Array<!taskModule.mojom.TaskItem>} */
+      /** @type {!Array<!TaskItem>} */
       recipes: Array,
     };
   }
@@ -37,7 +38,7 @@
 /** @return {!Promise<!HTMLElement>} */
 async function createModule() {
   const {task} = await TaskModuleHandlerProxy.getHandler().getPrimaryTask(
-      taskModule.mojom.TaskModuleType.kRecipe);
+      TaskModuleType.kRecipe);
   const element = new RecipeModuleElement();
   element.recipes = (task && task.taskItems) || [];
   return element;
diff --git a/chrome/browser/resources/new_tab_page/modules/task_module/BUILD.gn b/chrome/browser/resources/new_tab_page/modules/task_module/BUILD.gn
index 1fa3765..12d79db 100644
--- a/chrome/browser/resources/new_tab_page/modules/task_module/BUILD.gn
+++ b/chrome/browser/resources/new_tab_page/modules/task_module/BUILD.gn
@@ -11,6 +11,7 @@
     "..:info_dialog",
     "..:module_descriptor",
     "../..:i18n_setup",
+    "//chrome/browser/new_tab_page/modules/task_module:mojo_bindings_webui_js",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_elements/cr_auto_img",
     "//ui/webui/resources/cr_elements/cr_grid",
@@ -19,7 +20,9 @@
 }
 
 js_library("task_module_handler_proxy") {
-  deps = [ "//chrome/browser/new_tab_page/modules/task_module:mojo_bindings_js_library_for_compile" ]
+  deps = [
+    "//chrome/browser/new_tab_page/modules/task_module:mojo_bindings_webui_js",
+  ]
 }
 
 html_to_js("web_components") {
diff --git a/chrome/browser/resources/new_tab_page/modules/task_module/module.js b/chrome/browser/resources/new_tab_page/modules/task_module/module.js
index cb515878..1897973 100644
--- a/chrome/browser/resources/new_tab_page/modules/task_module/module.js
+++ b/chrome/browser/resources/new_tab_page/modules/task_module/module.js
@@ -10,6 +10,7 @@
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {I18nBehavior, loadTimeData} from '../../i18n_setup.js';
+import {Task, TaskModuleType} from '../../task_module.mojom-webui.js';
 import {InfoDialogElement} from '../info_dialog.js';
 import {ModuleDescriptor} from '../module_descriptor.js';
 
@@ -34,13 +35,13 @@
 
   static get properties() {
     return {
-      /** @type {!taskModule.mojom.TaskModuleType} */
+      /** @type {!TaskModuleType} */
       taskModuleType: {
         type: Number,
         observer: 'onTaskModuleTypeChange_',
       },
 
-      /** @type {!taskModule.mojom.Task} */
+      /** @type {!Task} */
       task: Object,
 
       /** @private {string} */
@@ -75,9 +76,9 @@
    */
   computeTitle_() {
     switch (this.taskModuleType) {
-      case taskModule.mojom.TaskModuleType.kRecipe:
+      case TaskModuleType.kRecipe:
         return loadTimeData.getString('modulesRecipeTasksSentence');
-      case taskModule.mojom.TaskModuleType.kShopping:
+      case TaskModuleType.kShopping:
         return this.task.title;
       default:
         return '';
@@ -90,9 +91,9 @@
    */
   computeDismissName_() {
     switch (this.taskModuleType) {
-      case taskModule.mojom.TaskModuleType.kRecipe:
+      case TaskModuleType.kRecipe:
         return loadTimeData.getString('modulesRecipeTasksLowerThese');
-      case taskModule.mojom.TaskModuleType.kShopping:
+      case TaskModuleType.kShopping:
         return this.task.name;
       default:
         return '';
@@ -105,9 +106,9 @@
    */
   computeDisableName_() {
     switch (this.taskModuleType) {
-      case taskModule.mojom.TaskModuleType.kRecipe:
+      case TaskModuleType.kRecipe:
         return loadTimeData.getString('modulesRecipeTasksLower');
-      case taskModule.mojom.TaskModuleType.kShopping:
+      case TaskModuleType.kShopping:
         return loadTimeData.getString('modulesShoppingTasksLower');
       default:
         return '';
@@ -119,7 +120,7 @@
    * @private
    */
   isRecipe_() {
-    return this.taskModuleType === taskModule.mojom.TaskModuleType.kRecipe;
+    return this.taskModuleType === TaskModuleType.kRecipe;
   }
 
   /**
@@ -127,16 +128,16 @@
    * @private
    */
   isShopping_() {
-    return this.taskModuleType === taskModule.mojom.TaskModuleType.kShopping;
+    return this.taskModuleType === TaskModuleType.kShopping;
   }
 
   /** @private */
   onTaskModuleTypeChange_() {
     switch (this.taskModuleType) {
-      case taskModule.mojom.TaskModuleType.kRecipe:
+      case TaskModuleType.kRecipe:
         this.toggleAttribute('recipe');
         break;
-      case taskModule.mojom.TaskModuleType.kShopping:
+      case TaskModuleType.kShopping:
         this.toggleAttribute('shopping');
         break;
     }
@@ -176,10 +177,10 @@
         this.taskModuleType, this.task.name);
     let taskName = '';
     switch (this.taskModuleType) {
-      case taskModule.mojom.TaskModuleType.kRecipe:
+      case TaskModuleType.kRecipe:
         taskName = loadTimeData.getString('modulesRecipeTasksSentence');
         break;
-      case taskModule.mojom.TaskModuleType.kShopping:
+      case TaskModuleType.kShopping:
         taskName = this.task.name;
         break;
     }
@@ -248,10 +249,10 @@
 export const recipeTasksDescriptor = new ModuleDescriptor(
     /*id=*/ 'recipe_tasks',
     /*name=*/ loadTimeData.getString('modulesRecipeTasksSentence'),
-    createModule.bind(null, taskModule.mojom.TaskModuleType.kRecipe));
+    createModule.bind(null, TaskModuleType.kRecipe));
 
 /** @type {!ModuleDescriptor} */
 export const shoppingTasksDescriptor = new ModuleDescriptor(
     /*id=*/ 'shopping_tasks',
     /*name=*/ loadTimeData.getString('modulesShoppingTasksSentence'),
-    createModule.bind(null, taskModule.mojom.TaskModuleType.kShopping));
+    createModule.bind(null, TaskModuleType.kShopping));
diff --git a/chrome/browser/resources/new_tab_page/modules/task_module/task_module_handler_proxy.js b/chrome/browser/resources/new_tab_page/modules/task_module/task_module_handler_proxy.js
index 1a5ba0f..587c51b 100644
--- a/chrome/browser/resources/new_tab_page/modules/task_module/task_module_handler_proxy.js
+++ b/chrome/browser/resources/new_tab_page/modules/task_module/task_module_handler_proxy.js
@@ -2,27 +2,23 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// TODO(crbug.com/1179821): Migrate to JS module Mojo bindings.
-import 'chrome://resources/mojo/mojo/public/js/mojo_bindings_lite.js';
-import 'chrome://resources/mojo/url/mojom/url.mojom-lite.js';
-import './task_module.mojom-lite.js';
+import {TaskModuleHandler, TaskModuleHandlerRemote} from '../../task_module.mojom-webui.js';
 
 /**
  * @fileoverview This file provides a class that exposes the Mojo handler
  * interface used for retrieving a shopping task for a task module.
  */
 
-/** @type {?taskModule.mojom.TaskModuleHandlerRemote} */
+/** @type {?TaskModuleHandlerRemote} */
 let handler = null;
 
 export class TaskModuleHandlerProxy {
-  /** @return {!taskModule.mojom.TaskModuleHandlerRemote} */
+  /** @return {!TaskModuleHandlerRemote} */
   static getHandler() {
-    return handler ||
-        (handler = taskModule.mojom.TaskModuleHandler.getRemote());
+    return handler || (handler = TaskModuleHandler.getRemote());
   }
 
-  /** @param {!taskModule.mojom.TaskModuleHandlerRemote} newHandler */
+  /** @param {!TaskModuleHandlerRemote} newHandler */
   static setHandler(newHandler) {
     handler = newHandler;
   }
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_menu.html b/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_menu.html
index a15e01dfa..b003163 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_menu.html
+++ b/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_menu.html
@@ -16,7 +16,7 @@
       title="$i18n{moreActions}"
       id="moreNetworkDetail"
       on-click="onDotsClick_"
-      disabled="[[isDotsMenuButtonDisabled_(deviceState.*)]]">
+      disabled="[[isDotsMenuButtonDisabled_(eSimNetworkState_, deviceState.*)]]">
   </cr-icon-button>
 </template>
 <cr-lazy-render id="menu">
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/manage_a11y_page.html b/chrome/browser/resources/settings/chromeos/os_a11y_page/manage_a11y_page.html
index 6527102..e4e146e4 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/manage_a11y_page.html
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/manage_a11y_page.html
@@ -193,14 +193,27 @@
     label="$i18n{onScreenKeyboardLabel}"
     deep-link-focus-id$="[[Setting.kOnScreenKeyboard]]">
 </settings-toggle-button>
-<settings-toggle-button
-    id="enableDictation"
-    class="hr"
-    pref="{{prefs.settings.a11y.dictation}}"
-    label="$i18n{dictationLabel}"
-    sub-label="[[dictationSubtitle_]]"
-    deep-link-focus-id$="[[Setting.kDictation]]">
-</settings-toggle-button>
+<template is="dom-if" if="[[isDictationCommandsFeatureEnabled_]]">
+  <settings-toggle-button
+      id="enableDictation"
+      class="hr"
+      pref="{{prefs.settings.a11y.dictation}}"
+      label="$i18n{dictationLabel}"
+      sub-label="[[dictationSubtitle_]]"
+      deep-link-focus-id$="[[Setting.kDictation]]"
+      learn-more-url="[[dictationLearnMoreUrl_]]">
+  </settings-toggle-button>
+</template>
+<template is="dom-if" if="[[!isDictationCommandsFeatureEnabled_]]">
+  <settings-toggle-button
+        id="enableDictation"
+        class="hr"
+        pref="{{prefs.settings.a11y.dictation}}"
+        label="$i18n{dictationLabel}"
+        sub-label="[[dictationSubtitle_]]"
+        deep-link-focus-id$="[[Setting.kDictation]]">
+  </settings-toggle-button>
+</template>
 <template is="dom-if" if="[[areDictationLocalePrefsAllowed_]]">
   <template is="dom-if" if="[[prefs.settings.a11y.dictation.value]]">
     <div class="settings-box continuation indented">
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/manage_a11y_page.js b/chrome/browser/resources/settings/chromeos/os_a11y_page/manage_a11y_page.js
index eac029d..6bbb493 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/manage_a11y_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/manage_a11y_page.js
@@ -298,6 +298,25 @@
       value: false,
     },
 
+    /** @private */
+    isDictationCommandsFeatureEnabled_: {
+      type: Boolean,
+      readOnly: true,
+      value() {
+        return loadTimeData.getBoolean('isDictationCommandsFeatureEnabled');
+      }
+    },
+
+    /**
+     * TODO(crbug.com/1247299): This support page does not exist. Make sure to
+     * get the correct URL before launch.
+     * @private
+     */
+    dictationLearnMoreUrl_: {
+      type: String,
+      value: 'https://support.google.com/chromebook?p=dictation',
+    },
+
     /**
      * |hasKeyboard_|, |hasMouse_|, |hasPointingStick_|, and |hasTouchpad_|
      * start undefined so observers don't trigger until they have been
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/multipart_uploader_unittest.cc b/chrome/browser/safe_browsing/cloud_content_scanning/multipart_uploader_unittest.cc
index 085ba1b..0d7070c2 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/multipart_uploader_unittest.cc
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/multipart_uploader_unittest.cc
@@ -326,7 +326,13 @@
 
 INSTANTIATE_TEST_CASE_P(, MultipartUploadDataPipeRequestTest, testing::Bool());
 
-TEST_P(MultipartUploadDataPipeRequestTest, Retries) {
+// Disabled due to flakiness on Windows https://crbug.com/1286638
+#if BUILDFLAG(IS_WIN)
+#define MAYBE_Retries DISABLED_Retries
+#else
+#define MAYBE_Retries Retries
+#endif
+TEST_P(MultipartUploadDataPipeRequestTest, MAYBE_Retries) {
   std::string expected_body =
       "--boundary\r\n"
       "Content-Type: application/octet-stream\r\n"
diff --git a/chrome/browser/ui/app_list/app_service/app_service_app_item.cc b/chrome/browser/ui/app_list/app_service/app_service_app_item.cc
index ff48539d4..eda98aa 100644
--- a/chrome/browser/ui/app_list/app_service/app_service_app_item.cc
+++ b/chrome/browser/ui/app_list/app_service/app_service_app_item.cc
@@ -72,13 +72,13 @@
     const app_list::AppListSyncableService::SyncItem* sync_item,
     const apps::AppUpdate& app_update)
     : ChromeAppListItem(profile, app_update.AppId()),
-      app_type_(app_update.AppType()) {
+      app_type_(apps::ConvertMojomAppTypToAppType(app_update.AppType())) {
   OnAppUpdate(app_update, /*in_constructor=*/true);
   if (sync_item && sync_item->item_ordinal.IsValid()) {
     InitFromSync(sync_item);
   } else {
     // Handle the case that the app under construction is a remote app.
-    if (app_type_ == apps::mojom::AppType::kRemote) {
+    if (app_type_ == apps::AppType::kRemote) {
       ash::RemoteAppsManager* remote_manager =
           ash::RemoteAppsManagerFactory::GetForProfile(profile);
       if (remote_manager->ShouldAddToFront(app_update.AppId()))
@@ -97,7 +97,7 @@
     }
 
     // Crostini apps and the Terminal System App start in the crostini folder.
-    if (app_type_ == apps::mojom::AppType::kCrostini ||
+    if (app_type_ == apps::AppType::kCrostini ||
         id() == crostini::kCrostiniTerminalSystemAppId) {
       DCHECK(folder_id().empty());
       SetChromeFolderId(ash::kCrostiniFolderId);
@@ -211,8 +211,7 @@
 
   // TODO(crbug.com/826982): drop the if, and call MaybeDismissAppList
   // unconditionally?
-  if (app_type_ == apps::mojom::AppType::kArc ||
-      app_type_ == apps::mojom::AppType::kRemote) {
+  if (app_type_ == apps::AppType::kArc || app_type_ == apps::AppType::kRemote) {
     MaybeDismissAppList();
   }
 }
@@ -231,15 +230,15 @@
 void AppServiceAppItem::CallLoadIcon(bool allow_placeholder_icon) {
   if (base::FeatureList::IsEnabled(features::kAppServiceLoadIconWithoutMojom)) {
     apps::AppServiceProxyFactory::GetForProfile(profile())->LoadIcon(
-        apps::ConvertMojomAppTypToAppType(app_type_), id(),
-        apps::IconType::kStandard,
+        app_type_, id(), apps::IconType::kStandard,
         ash::SharedAppListConfig::instance().default_grid_icon_dimension(),
         allow_placeholder_icon,
         base::BindOnce(&AppServiceAppItem::OnLoadIcon,
                        weak_ptr_factory_.GetWeakPtr()));
   } else {
     apps::AppServiceProxyFactory::GetForProfile(profile())->LoadIcon(
-        app_type_, id(), apps::mojom::IconType::kStandard,
+        apps::ConvertAppTypeToMojomAppType(app_type_), id(),
+        apps::mojom::IconType::kStandard,
         ash::SharedAppListConfig::instance().default_grid_icon_dimension(),
         allow_placeholder_icon,
         apps::MojomIconValueToIconValueCallback(base::BindOnce(
diff --git a/chrome/browser/ui/app_list/app_service/app_service_app_item.h b/chrome/browser/ui/app_list/app_service/app_service_app_item.h
index b8c28c04..b384f9ff 100644
--- a/chrome/browser/ui/app_list/app_service/app_service_app_item.h
+++ b/chrome/browser/ui/app_list/app_service/app_service_app_item.h
@@ -10,6 +10,7 @@
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/ui/app_list/app_context_menu_delegate.h"
 #include "chrome/browser/ui/app_list/chrome_app_list_item.h"
+#include "components/services/app_service/public/cpp/app_types.h"
 #include "components/services/app_service/public/cpp/app_update.h"
 #include "components/services/app_service/public/cpp/icon_types.h"
 #include "components/services/app_service/public/mojom/types.mojom-forward.h"
@@ -49,7 +50,7 @@
   void CallLoadIcon(bool allow_placeholder_icon);
   void OnLoadIcon(apps::IconValuePtr icon_value);
 
-  const apps::mojom::AppType app_type_;
+  const apps::AppType app_type_;
   bool is_platform_app_ = false;
 
   std::unique_ptr<app_list::AppContextMenu> context_menu_;
diff --git a/chrome/browser/ui/app_list/app_service/app_service_context_menu.cc b/chrome/browser/ui/app_list/app_service/app_service_context_menu.cc
index d3006dd..ba7b5df 100644
--- a/chrome/browser/ui/app_list/app_service/app_service_context_menu.cc
+++ b/chrome/browser/ui/app_list/app_service/app_service_context_menu.cc
@@ -133,11 +133,11 @@
   proxy_->AppRegistryCache().ForOneApp(
       app_id, [this](const apps::AppUpdate& update) {
         app_type_ = apps_util::IsInstalled(update.Readiness())
-                        ? update.AppType()
-                        : apps::mojom::AppType::kUnknown;
+                        ? apps::ConvertMojomAppTypToAppType(update.AppType())
+                        : apps::AppType::kUnknown;
       });
 
-  if (app_type_ == apps::mojom::AppType::kStandaloneBrowserChromeApp) {
+  if (app_type_ == apps::AppType::kStandaloneBrowserChromeApp) {
     standalone_browser_extension_menu_ =
         std::make_unique<StandaloneBrowserExtensionAppContextMenu>(
             app_id, StandaloneBrowserExtensionAppContextMenu::Source::kAppList);
@@ -147,14 +147,14 @@
 AppServiceContextMenu::~AppServiceContextMenu() = default;
 
 void AppServiceContextMenu::GetMenuModel(GetMenuModelCallback callback) {
-  if (app_type_ == apps::mojom::AppType::kUnknown) {
+  if (app_type_ == apps::AppType::kUnknown) {
     std::move(callback).Run(nullptr);
     return;
   }
 
   // StandaloneBrowserExtension handles its own context menus. Forward to that
   // class.
-  if (app_type_ == apps::mojom::AppType::kStandaloneBrowserChromeApp) {
+  if (app_type_ == apps::AppType::kStandaloneBrowserChromeApp) {
     standalone_browser_extension_menu_->GetMenuModel(std::move(callback));
     return;
   }
@@ -210,7 +210,7 @@
     case ash::APP_CONTEXT_MENU_NEW_INCOGNITO_WINDOW: {
       const bool is_incognito =
           command_id == ash::APP_CONTEXT_MENU_NEW_INCOGNITO_WINDOW;
-      if (app_type_ == apps::mojom::AppType::kStandaloneBrowser) {
+      if (app_type_ == apps::AppType::kStandaloneBrowser) {
         crosapi::BrowserManager::Get()->NewWindow(
             is_incognito, /*should_trigger_session_restore=*/false);
       } else {
@@ -250,7 +250,7 @@
     default:
       if (command_id >= ash::USE_LAUNCH_TYPE_COMMAND_START &&
           command_id < ash::USE_LAUNCH_TYPE_COMMAND_END) {
-        if (app_type_ == apps::mojom::AppType::kWeb &&
+        if (app_type_ == apps::AppType::kWeb &&
             command_id == ash::USE_LAUNCH_TYPE_TABBED_WINDOW) {
           proxy_->SetWindowMode(app_id(),
                                 apps::mojom::WindowMode::kTabbedWindow);
@@ -286,7 +286,7 @@
   }
 
   switch (app_type_) {
-    case apps::mojom::AppType::kWeb:
+    case apps::AppType::kWeb:
       if (command_id >= ash::USE_LAUNCH_TYPE_COMMAND_START &&
           command_id < ash::USE_LAUNCH_TYPE_COMMAND_END) {
         auto user_window_mode = apps::mojom::WindowMode::kUnknown;
@@ -300,7 +300,7 @@
       }
       return AppContextMenu::IsCommandIdChecked(command_id);
 
-    case apps::mojom::AppType::kChromeApp:
+    case apps::AppType::kChromeApp:
       if (command_id >= ash::USE_LAUNCH_TYPE_COMMAND_START &&
           command_id < ash::USE_LAUNCH_TYPE_COMMAND_END) {
         return static_cast<int>(
@@ -313,15 +313,15 @@
       }
       return AppContextMenu::IsCommandIdChecked(command_id);
 
-    case apps::mojom::AppType::kArc:
+    case apps::AppType::kArc:
       [[fallthrough]];
-    case apps::mojom::AppType::kCrostini:
+    case apps::AppType::kCrostini:
       [[fallthrough]];
-    case apps::mojom::AppType::kBuiltIn:
+    case apps::AppType::kBuiltIn:
       [[fallthrough]];
-    case apps::mojom::AppType::kPluginVm:
+    case apps::AppType::kPluginVm:
       [[fallthrough]];
-    case apps::mojom::AppType::kBorealis:
+    case apps::AppType::kBorealis:
       [[fallthrough]];
     default:
       return AppContextMenu::IsCommandIdChecked(command_id);
@@ -357,7 +357,7 @@
   // The special rule to ensure that FilesManager's first menu item is "New
   // window".
   const bool build_extension_menu_before_default =
-      (app_type_ == apps::mojom::AppType::kChromeApp &&
+      (app_type_ == apps::AppType::kChromeApp &&
        app_id() == extension_misc::kFilesManagerAppId);
 
   if (build_extension_menu_before_default)
@@ -366,8 +366,8 @@
   // Create default items for non-Remote apps.
   if (app_id() != extension_misc::kChromeAppId &&
       app_id() != extension_misc::kLacrosAppId &&
-      app_type_ != apps::mojom::AppType::kUnknown &&
-      app_type_ != apps::mojom::AppType::kRemote) {
+      app_type_ != apps::AppType::kUnknown &&
+      app_type_ != apps::AppType::kRemote) {
     app_list::AppContextMenu::BuildMenu(menu_model.get());
   }
 
@@ -433,7 +433,7 @@
 }
 
 void AppServiceContextMenu::ShowAppInfo() {
-  if (app_type_ == apps::mojom::AppType::kArc) {
+  if (app_type_ == apps::AppType::kArc) {
     chrome::ShowAppManagementPage(
         profile(), app_id(),
         AppManagementEntryPoint::kAppListContextMenuAppInfoArc);
@@ -445,7 +445,7 @@
 
 void AppServiceContextMenu::SetLaunchType(int command_id) {
   switch (app_type_) {
-    case apps::mojom::AppType::kWeb: {
+    case apps::AppType::kWeb: {
       // Web apps can only toggle between kWindow and kBrowser.
       apps::mojom::WindowMode user_window_mode =
           ConvertUseLaunchTypeCommandToWindowMode(command_id);
@@ -453,7 +453,7 @@
         proxy_->SetWindowMode(app_id(), user_window_mode);
       return;
     }
-    case apps::mojom::AppType::kChromeApp: {
+    case apps::AppType::kChromeApp: {
       // Hosted apps can only toggle between LAUNCH_TYPE_WINDOW and
       // LAUNCH_TYPE_REGULAR.
       extensions::LaunchType launch_type =
@@ -464,15 +464,15 @@
       controller()->SetExtensionLaunchType(profile(), app_id(), launch_type);
       return;
     }
-    case apps::mojom::AppType::kArc:
+    case apps::AppType::kArc:
       [[fallthrough]];
-    case apps::mojom::AppType::kCrostini:
+    case apps::AppType::kCrostini:
       [[fallthrough]];
-    case apps::mojom::AppType::kBuiltIn:
+    case apps::AppType::kBuiltIn:
       [[fallthrough]];
-    case apps::mojom::AppType::kPluginVm:
+    case apps::AppType::kPluginVm:
       [[fallthrough]];
-    case apps::mojom::AppType::kBorealis:
+    case apps::AppType::kBorealis:
       [[fallthrough]];
     default:
       return;
diff --git a/chrome/browser/ui/app_list/app_service/app_service_context_menu.h b/chrome/browser/ui/app_list/app_service/app_service_context_menu.h
index 80c0819..ea40169 100644
--- a/chrome/browser/ui/app_list/app_service/app_service_context_menu.h
+++ b/chrome/browser/ui/app_list/app_service/app_service_context_menu.h
@@ -13,6 +13,7 @@
 #include "chrome/browser/apps/app_service/app_service_proxy_forward.h"
 #include "chrome/browser/apps/app_service/app_shortcut_item.h"
 #include "chrome/browser/ui/app_list/app_context_menu.h"
+#include "components/services/app_service/public/cpp/app_types.h"
 #include "components/services/app_service/public/mojom/types.mojom.h"
 
 class AppContextMenuDelegate;
@@ -55,7 +56,7 @@
 
   void ExecutePublisherContextMenuCommand(int command_id);
 
-  apps::mojom::AppType app_type_ = apps::mojom::AppType::kUnknown;
+  apps::AppType app_type_ = apps::AppType::kUnknown;
 
   // The SimpleMenuModel used to hold the submenu items.
   std::unique_ptr<ui::SimpleMenuModel> submenu_;
diff --git a/chrome/browser/ui/app_list/search/files/drive_search_browsertest.cc b/chrome/browser/ui/app_list/search/files/drive_search_browsertest.cc
index ed094f8..840183a5 100644
--- a/chrome/browser/ui/app_list/search/files/drive_search_browsertest.cc
+++ b/chrome/browser/ui/app_list/search/files/drive_search_browsertest.cc
@@ -85,4 +85,30 @@
   EXPECT_EQ(base::UTF16ToASCII(results[0]->title()), "my_folder");
 }
 
+// Test that files are ordered based on modification time.
+IN_PROC_BROWSER_TEST_F(AppListDriveSearchBrowserTest, DriveFileResultOrdering) {
+  base::ScopedAllowBlockingForTesting allow_blocking;
+
+  drive::DriveIntegrationService* drive_service =
+      drive::DriveIntegrationServiceFactory::FindForProfile(GetProfile());
+  ASSERT_TRUE(drive_service->IsMounted());
+  base::FilePath mount_path = drive_service->GetMountPointPath();
+
+  base::FilePath older = mount_path.Append("ranking_older.gdoc");
+  base::FilePath newer = mount_path.Append("ranking_newer.gdoc");
+  base::Time now = base::Time::Now();
+  base::Time then = now - base::Seconds(1);
+  ASSERT_TRUE(base::WriteFile(older, "content"));
+  ASSERT_TRUE(base::WriteFile(newer, "content"));
+  ASSERT_TRUE(base::TouchFile(older, then, then));
+  ASSERT_TRUE(base::TouchFile(newer, now, now));
+
+  SearchAndWaitForProviders("ranking", {ResultType::kDriveSearch});
+
+  const auto results = PublishedResultsForProvider(ResultType::kDriveSearch);
+  ASSERT_EQ(results.size(), 2u);
+  EXPECT_EQ(base::UTF16ToASCII(results[0]->title()), "ranking_newer");
+  EXPECT_EQ(base::UTF16ToASCII(results[1]->title()), "ranking_older");
+}
+
 }  // namespace app_list
diff --git a/chrome/browser/ui/app_list/search/files/drive_search_provider.cc b/chrome/browser/ui/app_list/search/files/drive_search_provider.cc
index 3848e92..4c87a713 100644
--- a/chrome/browser/ui/app_list/search/files/drive_search_provider.cc
+++ b/chrome/browser/ui/app_list/search/files/drive_search_provider.cc
@@ -70,12 +70,10 @@
   last_query_ = query;
   last_tokenized_query_.emplace(query, TokenizedString::Mode::kWords);
 
-  // New scores will be assigned for sorting purposes so use the default
-  // SortField. The SortDirection does nothing in this case.
   drive_service_->SearchDriveByFileName(
       base::UTF16ToUTF8(query), kMaxResults,
-      drivefs::mojom::QueryParameters::SortField::kNone,
-      drivefs::mojom::QueryParameters::SortDirection::kAscending,
+      drivefs::mojom::QueryParameters::SortField::kLastModified,
+      drivefs::mojom::QueryParameters::SortDirection::kDescending,
       drivefs::mojom::QueryParameters::QuerySource::kLocalOnly,
       base::BindOnce(&DriveSearchProvider::SetSearchResults,
                      weak_factory_.GetWeakPtr()));
@@ -92,15 +90,20 @@
   }
 
   SearchProvider::Results results;
-  for (const auto& item : items) {
+  for (size_t i = 0; i < items.size(); ++i) {
+    const auto& item = items[i];
+    // Results are returned in descending order of modification time. Set the
+    // relevance in (0,1] based on that.
+    double relevance = 1.0 - static_cast<double>(i) / items.size();
     if (item->metadata->type ==
         drivefs::mojom::FileMetadata::Type::kDirectory) {
       const auto type = item->metadata->shared
                             ? FileResult::Type::kSharedDirectory
                             : FileResult::Type::kDirectory;
-      results.emplace_back(MakeResult(item->path, type));
+      results.emplace_back(MakeResult(item->path, relevance, type));
     } else {
-      results.emplace_back(MakeResult(item->path, FileResult::Type::kFile));
+      results.emplace_back(
+          MakeResult(item->path, relevance, FileResult::Type::kFile));
     }
   }
 
@@ -112,6 +115,7 @@
 
 std::unique_ptr<FileResult> DriveSearchProvider::MakeResult(
     const base::FilePath& path,
+    double relevance,
     FileResult::Type type) {
   // Strip leading separators so that the path can be reparented.
   // TODO(crbug.com/1154513): Remove this step once the drive backend returns
@@ -127,8 +131,6 @@
   const base::FilePath& reparented_path =
       drive_service_->GetMountPointPath().Append(relative_path.value());
 
-  const double relevance =
-      FileResult::CalculateRelevance(last_tokenized_query_, reparented_path);
   return std::make_unique<FileResult>(
       kDriveSearchSchema, reparented_path,
       ash::AppListSearchResultType::kDriveSearch,
diff --git a/chrome/browser/ui/app_list/search/files/drive_search_provider.h b/chrome/browser/ui/app_list/search/files/drive_search_provider.h
index 9ca6bb52..364629a6 100644
--- a/chrome/browser/ui/app_list/search/files/drive_search_provider.h
+++ b/chrome/browser/ui/app_list/search/files/drive_search_provider.h
@@ -42,6 +42,7 @@
   void SetSearchResults(drive::FileError error,
                         std::vector<drivefs::mojom::QueryItemPtr> paths);
   std::unique_ptr<FileResult> MakeResult(const base::FilePath& path,
+                                         double relevance,
                                          FileResult::Type type);
 
   base::TimeTicks query_start_time_;
diff --git a/chrome/browser/ui/app_list/search/omnibox_provider.cc b/chrome/browser/ui/app_list/search/omnibox_provider.cc
index d2a147c..60cd255 100644
--- a/chrome/browser/ui/app_list/search/omnibox_provider.cc
+++ b/chrome/browser/ui/app_list/search/omnibox_provider.cc
@@ -92,9 +92,11 @@
 }
 
 void OmniboxProvider::StartZeroState() {
-  // TODO(crbug.com/1258415): Remove zero-state once productivity launcher
-  // launched.
-  Start(std::u16string());
+  // Do not perform zero-state queries in the productivity launcher, because
+  // Omnibox is not shown in zero-state.
+  if (!app_list_features::IsCategoricalSearchEnabled()) {
+    Start(std::u16string());
+  }
 }
 
 ash::AppListSearchResultType OmniboxProvider::ResultType() const {
diff --git a/chrome/browser/ui/ash/shelf/app_service/app_service_shelf_context_menu.cc b/chrome/browser/ui/ash/shelf/app_service/app_service_shelf_context_menu.cc
index a592639..605f534 100644
--- a/chrome/browser/ui/ash/shelf/app_service/app_service_shelf_context_menu.cc
+++ b/chrome/browser/ui/ash/shelf/app_service/app_service_shelf_context_menu.cc
@@ -92,7 +92,7 @@
     // For Crostini app_id with the prefix "crostini:", set app_type as Unknown
     // to skip the ArcAppShelfId valid. App type can't be set as Crostini,
     // because the pin item should not be added for it.
-    app_type_ = apps::mojom::AppType::kUnknown;
+    app_type_ = apps::AppType::kUnknown;
     return;
   }
 
@@ -100,9 +100,10 @@
   const arc::ArcAppShelfId arc_shelf_id =
       arc::ArcAppShelfId::FromString(item->id.app_id);
   DCHECK(arc_shelf_id.valid());
-  app_type_ = apps::AppServiceProxyFactory::GetForProfile(controller->profile())
-                  ->AppRegistryCache()
-                  .GetAppType(arc_shelf_id.app_id());
+  app_type_ = apps::ConvertMojomAppTypToAppType(
+      apps::AppServiceProxyFactory::GetForProfile(controller->profile())
+          ->AppRegistryCache()
+          .GetAppType(arc_shelf_id.app_id()));
 }
 
 AppServiceShelfContextMenu::~AppServiceShelfContextMenu() = default;
@@ -130,9 +131,9 @@
       break;
 
     case ash::MENU_NEW_WINDOW:
-      if (app_type_ == apps::mojom::AppType::kCrostini) {
+      if (app_type_ == apps::AppType::kCrostini) {
         ShelfContextMenu::ExecuteCommand(ash::MENU_OPEN_NEW, event_flags);
-      } else if (app_type_ == apps::mojom::AppType::kStandaloneBrowser) {
+      } else if (app_type_ == apps::AppType::kStandaloneBrowser) {
         crosapi::BrowserManager::Get()->NewWindow(
             /*incongnito=*/false, /*should_trigger_session_restore=*/false);
       } else {
@@ -145,7 +146,7 @@
       break;
 
     case ash::MENU_NEW_INCOGNITO_WINDOW:
-      if (app_type_ == apps::mojom::AppType::kStandaloneBrowser) {
+      if (app_type_ == apps::AppType::kStandaloneBrowser) {
         crosapi::BrowserManager::Get()->NewWindow(
             /*incognito=*/true, /*should_trigger_session_restore=*/false);
       } else {
@@ -223,8 +224,8 @@
 
 bool AppServiceShelfContextMenu::IsCommandIdChecked(int command_id) const {
   switch (app_type_) {
-    case apps::mojom::AppType::kWeb:
-    case apps::mojom::AppType::kSystemWeb: {
+    case apps::AppType::kWeb:
+    case apps::AppType::kSystemWeb: {
       if ((command_id >= ash::LAUNCH_TYPE_PINNED_TAB &&
            command_id <= ash::LAUNCH_TYPE_WINDOW) ||
           command_id == ash::LAUNCH_TYPE_TABBED_WINDOW) {
@@ -241,7 +242,7 @@
       }
       return ShelfContextMenu::IsCommandIdChecked(command_id);
     }
-    case apps::mojom::AppType::kChromeApp:
+    case apps::AppType::kChromeApp:
       if (command_id >= ash::LAUNCH_TYPE_PINNED_TAB &&
           command_id <= ash::LAUNCH_TYPE_WINDOW) {
         return GetExtensionLaunchType() ==
@@ -252,15 +253,15 @@
         return (extension_menu_items_ &&
                 extension_menu_items_->IsCommandIdChecked(command_id));
       }
-    case apps::mojom::AppType::kArc:
+    case apps::AppType::kArc:
       [[fallthrough]];
-    case apps::mojom::AppType::kCrostini:
+    case apps::AppType::kCrostini:
       [[fallthrough]];
-    case apps::mojom::AppType::kBuiltIn:
+    case apps::AppType::kBuiltIn:
       [[fallthrough]];
-    case apps::mojom::AppType::kPluginVm:
+    case apps::AppType::kPluginVm:
       [[fallthrough]];
-    case apps::mojom::AppType::kBorealis:
+    case apps::AppType::kBorealis:
       [[fallthrough]];
     default:
       return ShelfContextMenu::IsCommandIdChecked(command_id);
@@ -295,7 +296,7 @@
   // The special rule to ensure that FilesManager's first menu item is "New
   // window".
   const bool build_extension_menu_before_pin =
-      (app_type_ == apps::mojom::AppType::kChromeApp &&
+      (app_type_ == apps::AppType::kChromeApp &&
        item().id.app_id == extension_misc::kFilesManagerAppId);
 
   if (build_extension_menu_before_pin)
@@ -335,15 +336,15 @@
     }
   }
 
-  if (app_type_ == apps::mojom::AppType::kArc) {
+  if (app_type_ == apps::AppType::kArc) {
     BuildArcAppShortcutsMenu(std::move(menu_items), std::move(menu_model),
                              std::move(callback), shortcut_index);
     return;
   }
 
-  if (app_type_ == apps::mojom::AppType::kWeb ||
-      app_type_ == apps::mojom::AppType::kSystemWeb ||
-      app_type_ == apps::mojom::AppType::kCrostini) {
+  if (app_type_ == apps::AppType::kWeb ||
+      app_type_ == apps::AppType::kSystemWeb ||
+      app_type_ == apps::AppType::kCrostini) {
     BuildAppShortcutsMenu(std::move(menu_items), std::move(menu_model),
                           std::move(callback), shortcut_index);
     return;
@@ -451,7 +452,7 @@
 }
 
 void AppServiceShelfContextMenu::ShowAppInfo() {
-  if (app_type_ == apps::mojom::AppType::kArc) {
+  if (app_type_ == apps::AppType::kArc) {
     chrome::ShowAppManagementPage(
         controller()->profile(), item().id.app_id,
         AppManagementEntryPoint::kShelfContextMenuAppInfoArc);
@@ -468,8 +469,8 @@
 
 void AppServiceShelfContextMenu::SetLaunchType(int command_id) {
   switch (app_type_) {
-    case apps::mojom::AppType::kWeb:
-    case apps::mojom::AppType::kSystemWeb: {
+    case apps::AppType::kWeb:
+    case apps::AppType::kSystemWeb: {
       // Web apps can only toggle between kWindow, kTabbed and kBrowser.
       apps::mojom::WindowMode user_window_mode =
           ConvertLaunchTypeCommandToWindowMode(command_id);
@@ -479,18 +480,18 @@
       }
       return;
     }
-    case apps::mojom::AppType::kChromeApp:
+    case apps::AppType::kChromeApp:
       SetExtensionLaunchType(command_id);
       return;
-    case apps::mojom::AppType::kArc:
+    case apps::AppType::kArc:
       [[fallthrough]];
-    case apps::mojom::AppType::kCrostini:
+    case apps::AppType::kCrostini:
       [[fallthrough]];
-    case apps::mojom::AppType::kBuiltIn:
+    case apps::AppType::kBuiltIn:
       [[fallthrough]];
-    case apps::mojom::AppType::kPluginVm:
+    case apps::AppType::kPluginVm:
       [[fallthrough]];
-    case apps::mojom::AppType::kBorealis:
+    case apps::AppType::kBorealis:
       [[fallthrough]];
     default:
       return;
@@ -542,7 +543,7 @@
 
 bool AppServiceShelfContextMenu::ShouldAddPinMenu() {
   switch (app_type_) {
-    case apps::mojom::AppType::kArc: {
+    case apps::AppType::kArc: {
       const arc::ArcAppShelfId& arc_shelf_id =
           arc::ArcAppShelfId::FromString(item().id.app_id);
       DCHECK(arc_shelf_id.valid());
@@ -555,8 +556,8 @@
         return true;
       return false;
     }
-    case apps::mojom::AppType::kPluginVm:
-    case apps::mojom::AppType::kBuiltIn: {
+    case apps::AppType::kPluginVm:
+    case apps::AppType::kBuiltIn: {
       bool show_in_launcher = false;
       apps::AppServiceProxyFactory::GetForProfile(controller()->profile())
           ->AppRegistryCache()
@@ -567,24 +568,25 @@
           });
       return show_in_launcher;
     }
-    case apps::mojom::AppType::kCrostini:
-    case apps::mojom::AppType::kBorealis:
-    case apps::mojom::AppType::kChromeApp:
-    case apps::mojom::AppType::kWeb:
-    case apps::mojom::AppType::kSystemWeb:
-    case apps::mojom::AppType::kStandaloneBrowserChromeApp:
+    case apps::AppType::kCrostini:
+    case apps::AppType::kBorealis:
+    case apps::AppType::kChromeApp:
+    case apps::AppType::kWeb:
+    case apps::AppType::kSystemWeb:
+    case apps::AppType::kStandaloneBrowserChromeApp:
       return true;
-    case apps::mojom::AppType::kStandaloneBrowser:
+    case apps::AppType::kStandaloneBrowser:
       // Lacros behaves like the Chrome browser icon and cannot be unpinned.
       return false;
-    case apps::mojom::AppType::kUnknown:
+    case apps::AppType::kUnknown:
       // Type kUnknown is used for "unregistered" Crostini apps, which do not
       // have a .desktop file and can only be closed, not pinned.
       return false;
-    case apps::mojom::AppType::kMacOs:
-    case apps::mojom::AppType::kRemote:
-    case apps::mojom::AppType::kExtension:
-      NOTREACHED() << "Type " << app_type_ << " should not appear in shelf.";
+    case apps::AppType::kMacOs:
+    case apps::AppType::kRemote:
+    case apps::AppType::kExtension:
+      NOTREACHED() << "Type " << (int)app_type_
+                   << " should not appear in shelf.";
       return false;
   }
 }
diff --git a/chrome/browser/ui/ash/shelf/app_service/app_service_shelf_context_menu.h b/chrome/browser/ui/ash/shelf/app_service/app_service_shelf_context_menu.h
index 60b6735..c2e4ba3 100644
--- a/chrome/browser/ui/ash/shelf/app_service/app_service_shelf_context_menu.h
+++ b/chrome/browser/ui/ash/shelf/app_service/app_service_shelf_context_menu.h
@@ -11,6 +11,7 @@
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/apps/app_service/app_shortcut_item.h"
 #include "chrome/browser/ui/ash/shelf/shelf_context_menu.h"
+#include "components/services/app_service/public/cpp/app_types.h"
 #include "components/services/app_service/public/mojom/types.mojom.h"
 #include "extensions/common/constants.h"
 
@@ -78,7 +79,7 @@
 
   void ExecutePublisherContextMenuCommand(int command_id);
 
-  apps::mojom::AppType app_type_;
+  apps::AppType app_type_;
 
   // The SimpleMenuModel used to hold the submenu items.
   std::unique_ptr<ui::SimpleMenuModel> submenu_;
diff --git a/chrome/browser/ui/ash/shelf/chrome_shelf_prefs.cc b/chrome/browser/ui/ash/shelf/chrome_shelf_prefs.cc
index 8ff4157..545ae90 100644
--- a/chrome/browser/ui/ash/shelf/chrome_shelf_prefs.cc
+++ b/chrome/browser/ui/ash/shelf/chrome_shelf_prefs.cc
@@ -24,6 +24,7 @@
 #include "chrome/browser/ash/file_manager/prefs_migration_uma.h"
 #include "chrome/browser/ash/login/demo_mode/demo_session.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
+#include "chrome/browser/extensions/extension_keeplist_ash.h"
 #include "chrome/browser/prefs/pref_service_syncable_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/sync/sync_service_factory.h"
@@ -222,7 +223,8 @@
   for (const auto& policy_dict_entry : policy_apps->GetList()) {
     const std::string* policy_entry =
         policy_dict_entry.is_dict()
-            ? policy_dict_entry.FindStringKey(ChromeShelfPrefs::kPinnedAppsPrefAppIDKey)
+            ? policy_dict_entry.FindStringKey(
+                  ChromeShelfPrefs::kPinnedAppsPrefAppIDKey)
             : nullptr;
 
     if (!policy_entry) {
@@ -656,7 +658,7 @@
 }
 
 bool ChromeShelfPrefs::IsAshKeepListApp(const std::string& app_id) {
-  return apps::ExtensionAppRunsInAsh(app_id);
+  return extensions::ExtensionAppRunsInAsh(app_id);
 }
 
 std::string ChromeShelfPrefs::GetShelfId(const std::string& sync_id) {
@@ -670,7 +672,7 @@
   // If this app is on the ash keep list, immediately return it. Even if there's
   // a lacros chrome app that matches this id, we still want to use the ash
   // version.
-  if (apps::ExtensionAppRunsInAsh(sync_id))
+  if (extensions::ExtensionAppRunsInAsh(sync_id))
     return sync_id;
 
   std::string transformed_app_id = kLacrosChromeAppPrefix + sync_id;
diff --git a/chrome/browser/ui/browser_navigator.cc b/chrome/browser/ui/browser_navigator.cc
index 7cdef5f1..0fd3649 100644
--- a/chrome/browser/ui/browser_navigator.cc
+++ b/chrome/browser/ui/browser_navigator.cc
@@ -734,15 +734,17 @@
       if (params->disposition == WindowOpenDisposition::SWITCH_TO_TAB) {
         // Close orphaned NTP (and the like) with no history when the user
         // switches away from them.
-        if (params->source_contents->GetController().CanGoBack() ||
-            (params->source_contents->GetLastCommittedURL().spec() !=
-                 chrome::kChromeUINewTabURL &&
-             params->source_contents->GetLastCommittedURL().spec() !=
-                 url::kAboutBlankURL)) {
-          // Blur location bar before state save in ActivateTabAt() below.
-          params->source_contents->Focus();
-        } else {
-          should_close_this_tab = true;
+        if (params->source_contents) {
+          if (params->source_contents->GetController().CanGoBack() ||
+              (params->source_contents->GetLastCommittedURL().spec() !=
+                   chrome::kChromeUINewTabURL &&
+               params->source_contents->GetLastCommittedURL().spec() !=
+                   url::kAboutBlankURL)) {
+            // Blur location bar before state save in ActivateTabAt() below.
+            params->source_contents->Focus();
+          } else {
+            should_close_this_tab = true;
+          }
         }
       }
       params->browser->tab_strip_model()->ActivateTabAt(singleton_index,
diff --git a/chrome/browser/ui/exclusive_access/fullscreen_interactive_browsertest.cc b/chrome/browser/ui/exclusive_access/fullscreen_interactive_browsertest.cc
index 37a96f2..39d0d5a 100644
--- a/chrome/browser/ui/exclusive_access/fullscreen_interactive_browsertest.cc
+++ b/chrome/browser/ui/exclusive_access/fullscreen_interactive_browsertest.cc
@@ -75,8 +75,8 @@
 };
 
 // https://crbug.com/1087875: Flaky on Linux and Mac.
-// TODO(crbug.com/1278361): Flaky on lacros.
-#if defined(OS_MAC) || defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
+// TODO(crbug.com/1278361): Flaky on Chrome OS.
+#if defined(OS_MAC) || defined(OS_LINUX) || defined(OS_CHROMEOS)
 #define MAYBE_NotifyFullscreenAcquired DISABLED_NotifyFullscreenAcquired
 #else
 #define MAYBE_NotifyFullscreenAcquired NotifyFullscreenAcquired
diff --git a/chrome/browser/ui/views/web_apps/web_app_integration_browsertest.cc b/chrome/browser/ui/views/web_apps/web_app_integration_browsertest.cc
index a31c010..47731cb2 100644
--- a/chrome/browser/ui/views/web_apps/web_app_integration_browsertest.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_integration_browsertest.cc
@@ -1177,9 +1177,17 @@
   helper_.CheckLaunchIconNotShown();
 }
 
+// TODO(crbug.com/1286616): Flaky on macOS.
+#if defined(OS_MAC)
+#define MAYBE_WebAppIntegration_InstCrtShctWindowedSiteC_InListWinSiteC_NavSiteC_InstIconNotShown_LaunchIconShown \
+  DISABLED_WebAppIntegration_InstCrtShctWindowedSiteC_InListWinSiteC_NavSiteC_InstIconNotShown_LaunchIconShown
+#else
+#define MAYBE_WebAppIntegration_InstCrtShctWindowedSiteC_InListWinSiteC_NavSiteC_InstIconNotShown_LaunchIconShown \
+  WebAppIntegration_InstCrtShctWindowedSiteC_InListWinSiteC_NavSiteC_InstIconNotShown_LaunchIconShown
+#endif
 IN_PROC_BROWSER_TEST_F(
     WebAppIntegrationBrowserTest,
-    WebAppIntegration_InstCrtShctWindowedSiteC_InListWinSiteC_NavSiteC_InstIconNotShown_LaunchIconShown) {
+    MAYBE_WebAppIntegration_InstCrtShctWindowedSiteC_InListWinSiteC_NavSiteC_InstIconNotShown_LaunchIconShown) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -1332,9 +1340,17 @@
   helper_.CheckWindowDisplayMinimal();
 }
 
+// TODO(crbug.com/1286616): Flaky on macOS.
+#if defined(OS_MAC)
+#define MAYBE_WebAppIntegration_InstOmniboxSiteB_NavSiteB_LaunchIconShown \
+  DISABLED_WebAppIntegration_InstOmniboxSiteB_NavSiteB_LaunchIconShown
+#else
+#define MAYBE_WebAppIntegration_InstOmniboxSiteB_NavSiteB_LaunchIconShown \
+  WebAppIntegration_InstOmniboxSiteB_NavSiteB_LaunchIconShown
+#endif
 IN_PROC_BROWSER_TEST_F(
     WebAppIntegrationBrowserTest,
-    WebAppIntegration_InstOmniboxSiteB_NavSiteB_LaunchIconShown) {
+    MAYBE_WebAppIntegration_InstOmniboxSiteB_NavSiteB_LaunchIconShown) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -1406,9 +1422,17 @@
   helper_.CheckWindowDisplayMinimal();
 }
 
+// TODO(crbug.com/1286616): Flaky on macOS.
+#if defined(OS_MAC)
+#define MAYBE_WebAppIntegration_InstMenuOptionSiteB_NavSiteB_LaunchIconShown \
+  DISABLED_WebAppIntegration_InstMenuOptionSiteB_NavSiteB_LaunchIconShown
+#else
+#define MAYBE_WebAppIntegration_InstMenuOptionSiteB_NavSiteB_LaunchIconShown \
+  WebAppIntegration_InstMenuOptionSiteB_NavSiteB_LaunchIconShown
+#endif
 IN_PROC_BROWSER_TEST_F(
     WebAppIntegrationBrowserTest,
-    WebAppIntegration_InstMenuOptionSiteB_NavSiteB_LaunchIconShown) {
+    MAYBE_WebAppIntegration_InstMenuOptionSiteB_NavSiteB_LaunchIconShown) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
diff --git a/chrome/browser/ui/web_applications/web_app_browsertest.cc b/chrome/browser/ui/web_applications/web_app_browsertest.cc
index 3af50a1..43b5affb1 100644
--- a/chrome/browser/ui/web_applications/web_app_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_browsertest.cc
@@ -72,6 +72,7 @@
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/webui_url_constants.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "components/services/app_service/public/mojom/types.mojom-shared.h"
 #include "components/services/app_service/public/mojom/types.mojom.h"
 #include "components/sessions/core/tab_restore_service.h"
 #include "components/webapps/browser/installable/installable_metrics.h"
@@ -80,6 +81,7 @@
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/test_utils.h"
+#include "extensions/common/constants.h"
 #include "net/base/filename_util.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -1374,6 +1376,7 @@
   web_app_info->title = u"A Shortcut App";
   const AppId app_id = InstallWebApp(std::move(web_app_info));
 
+  base::HistogramTester tester;
   NavigateToURLAndWait(browser(), app_url);
   content::WebContents* tab_contents =
       browser()->tab_strip_model()->GetActiveWebContents();
@@ -1392,6 +1395,12 @@
             DisplayMode::kStandalone);
   EXPECT_EQ(provider->registrar().GetAppEffectiveDisplayMode(app_id),
             DisplayMode::kMinimalUi);
+  EXPECT_FALSE(provider->registrar().GetAppLastLaunchTime(app_id).is_null());
+  tester.ExpectUniqueSample(
+      "Extensions.BookmarkAppLaunchContainer",
+      apps::mojom::LaunchContainer::kLaunchContainerWindow, 1);
+  tester.ExpectUniqueSample("Extensions.BookmarkAppLaunchSource",
+                            extensions::AppLaunchSource::kSourceReparenting, 1);
 }
 
 // Tests that the manifest name of the current installable site is used in the
diff --git a/chrome/browser/ui/web_applications/web_app_launch_process.cc b/chrome/browser/ui/web_applications/web_app_launch_process.cc
index 744f7b6f..1b5b78c 100644
--- a/chrome/browser/ui/web_applications/web_app_launch_process.cc
+++ b/chrome/browser/ui/web_applications/web_app_launch_process.cc
@@ -119,7 +119,9 @@
   MaybeEnqueueWebLaunchParams(launch_url, is_file_handling, web_contents,
                               navigate_result.did_navigate);
 
-  RecordMetrics(launch_url, web_contents);
+  RecordMetrics(params_.app_id, params_.container,
+                apps::GetAppLaunchSource(params_.launch_source), launch_url,
+                web_contents);
 
   return web_contents;
 }
@@ -355,31 +357,4 @@
   }
 }
 
-void WebAppLaunchProcess::RecordMetrics(const GURL& launch_url,
-                                        content::WebContents* web_contents) {
-  // TODO(crbug.com/1014328): Populate WebApp metrics instead of Extensions.
-  if (params_.container == apps::mojom::LaunchContainer::kLaunchContainerTab) {
-    UMA_HISTOGRAM_ENUMERATION("Extensions.AppTabLaunchType",
-                              extensions::LAUNCH_TYPE_REGULAR, 100);
-  } else if (params_.container ==
-             apps::mojom::LaunchContainer::kLaunchContainerWindow) {
-    RecordAppWindowLaunch(&profile_, params_.app_id);
-  }
-  UMA_HISTOGRAM_ENUMERATION("Extensions.BookmarkAppLaunchSource",
-                            apps::GetAppLaunchSource(params_.launch_source));
-  UMA_HISTOGRAM_ENUMERATION("Extensions.BookmarkAppLaunchContainer",
-                            params_.container);
-
-  // Record the launch time in the site engagement service. A recent web
-  // app launch will provide an engagement boost to the origin.
-  site_engagement::SiteEngagementService::Get(&profile_)
-      ->SetLastShortcutLaunchTime(web_contents, launch_url);
-  provider_.sync_bridge().SetAppLastLaunchTime(params_.app_id,
-                                               base::Time::Now());
-  // Refresh the app banner added to homescreen event. The user may have
-  // cleared their browsing data since installing the app, which removes the
-  // event and will potentially permit a banner to be shown for the site.
-  RecordAppBanner(web_contents, launch_url);
-}
-
 }  // namespace web_app
diff --git a/chrome/browser/ui/web_applications/web_app_launch_process.h b/chrome/browser/ui/web_applications/web_app_launch_process.h
index 23953c4..ade53e3 100644
--- a/chrome/browser/ui/web_applications/web_app_launch_process.h
+++ b/chrome/browser/ui/web_applications/web_app_launch_process.h
@@ -67,8 +67,6 @@
                                    bool is_file_handling,
                                    content::WebContents* web_contents,
                                    bool is_navigating);
-  void RecordMetrics(const GURL& launch_url,
-                     content::WebContents* web_contents);
 
   Profile& profile_;
   WebAppProvider& provider_;
diff --git a/chrome/browser/ui/web_applications/web_app_launch_utils.cc b/chrome/browser/ui/web_applications/web_app_launch_utils.cc
index 80ffba6d..9d1c2af7 100644
--- a/chrome/browser/ui/web_applications/web_app_launch_utils.cc
+++ b/chrome/browser/ui/web_applications/web_app_launch_utils.cc
@@ -32,13 +32,16 @@
 #include "chrome/browser/web_applications/web_app_helpers.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/browser/web_applications/web_app_registrar.h"
+#include "chrome/browser/web_applications/web_app_sync_bridge.h"
 #include "chrome/browser/web_applications/web_app_tab_helper.h"
 #include "chrome/common/chrome_features.h"
 #include "components/omnibox/browser/location_bar_model.h"
+#include "components/site_engagement/content/site_engagement_service.h"
 #include "content/public/browser/navigation_controller.h"
 #include "content/public/browser/navigation_entry.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_features.h"
+#include "extensions/common/constants.h"
 #include "third_party/blink/public/common/renderer_preferences/renderer_preferences.h"
 #include "url/gurl.h"
 
@@ -165,6 +168,11 @@
     PrunePreScopeNavigationHistory(*app_scope, contents);
   }
 
+  auto launch_url = contents->GetLastCommittedURL();
+  RecordMetrics(app_id, apps::mojom::LaunchContainer::kLaunchContainerWindow,
+                extensions::AppLaunchSource::kSourceReparenting, launch_url,
+                contents);
+
   if (registrar.IsTabbedWindowModeEnabled(app_id)) {
     for (Browser* browser : *BrowserList::GetInstance()) {
       if (AppBrowserController::IsForWebApp(browser, app_id))
@@ -337,4 +345,35 @@
   UMA_HISTOGRAM_ENUMERATION("Launch.WebAppDisplayMode", display);
 }
 
+void RecordMetrics(const AppId& app_id,
+                   apps::mojom::LaunchContainer container,
+                   extensions::AppLaunchSource launch_source,
+                   const GURL& launch_url,
+                   content::WebContents* web_contents) {
+  Profile* profile =
+      Profile::FromBrowserContext(web_contents->GetBrowserContext());
+  // TODO(crbug.com/1014328): Populate WebApp metrics instead of Extensions.
+  if (container == apps::mojom::LaunchContainer::kLaunchContainerTab) {
+    UMA_HISTOGRAM_ENUMERATION("Extensions.AppTabLaunchType",
+                              extensions::LAUNCH_TYPE_REGULAR, 100);
+  } else if (container ==
+             apps::mojom::LaunchContainer::kLaunchContainerWindow) {
+    RecordAppWindowLaunch(profile, app_id);
+  }
+  UMA_HISTOGRAM_ENUMERATION("Extensions.BookmarkAppLaunchSource",
+                            launch_source);
+  UMA_HISTOGRAM_ENUMERATION("Extensions.BookmarkAppLaunchContainer", container);
+
+  // Record the launch time in the site engagement service. A recent web
+  // app launch will provide an engagement boost to the origin.
+  site_engagement::SiteEngagementService::Get(profile)
+      ->SetLastShortcutLaunchTime(web_contents, launch_url);
+  WebAppProvider::GetForWebApps(profile)->sync_bridge().SetAppLastLaunchTime(
+      app_id, base::Time::Now());
+  // Refresh the app banner added to homescreen event. The user may have
+  // cleared their browsing data since installing the app, which removes the
+  // event and will potentially permit a banner to be shown for the site.
+  RecordAppBanner(web_contents, launch_url);
+}
+
 }  // namespace web_app
diff --git a/chrome/browser/ui/web_applications/web_app_launch_utils.h b/chrome/browser/ui/web_applications/web_app_launch_utils.h
index 072de26d..067bed7 100644
--- a/chrome/browser/ui/web_applications/web_app_launch_utils.h
+++ b/chrome/browser/ui/web_applications/web_app_launch_utils.h
@@ -9,6 +9,7 @@
 #include <string>
 
 #include "chrome/browser/web_applications/web_app_id.h"
+#include "extensions/common/constants.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/gfx/geometry/rect.h"
 
@@ -74,6 +75,12 @@
 
 void RecordAppWindowLaunch(Profile* profile, const std::string& app_id);
 
+void RecordMetrics(const AppId& app_id,
+                   apps::mojom::LaunchContainer container,
+                   extensions::AppLaunchSource launch_source,
+                   const GURL& launch_url,
+                   content::WebContents* web_contents);
+
 }  // namespace web_app
 
 #endif  // CHROME_BROWSER_UI_WEB_APPLICATIONS_WEB_APP_LAUNCH_UTILS_H_
diff --git a/chrome/browser/ui/webui/settings/chromeos/accessibility_section.cc b/chrome/browser/ui/webui/settings/chromeos/accessibility_section.cc
index ce65688..3d05897 100644
--- a/chrome/browser/ui/webui/settings/chromeos/accessibility_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/accessibility_section.cc
@@ -772,6 +772,10 @@
   html_source->AddBoolean("areDictationLocalePrefsAllowed",
                           AreDictationLocalePrefsAllowed());
 
+  html_source->AddBoolean(
+      "isDictationCommandsFeatureEnabled",
+      ::features::IsExperimentalAccessibilityDictationCommandsEnabled());
+
   ::settings::AddCaptionSubpageStrings(html_source);
 }
 
diff --git a/chrome/browser/web_applications/app_service/web_apps_publisher_host_browsertest.cc b/chrome/browser/web_applications/app_service/web_apps_publisher_host_browsertest.cc
index ad4bc80a..f2d2064 100644
--- a/chrome/browser/web_applications/app_service/web_apps_publisher_host_browsertest.cc
+++ b/chrome/browser/web_applications/app_service/web_apps_publisher_host_browsertest.cc
@@ -155,12 +155,14 @@
       embedded_test_server()->GetURL("/banners/manifest_test_page.html"));
   mock_app_publisher.Wait();
 
-  // OnWebAppInstalled() and OnWebAppInstalledWithOsHooks() lead to updates.
-  EXPECT_EQ(mock_app_publisher.get_deltas().size(), 4U);
-  EXPECT_EQ(mock_app_publisher.get_deltas().back()->app_id, app_id);
-  EXPECT_EQ(mock_app_publisher.get_deltas().back()->readiness,
+  // OnWebAppInstalled(), OnWebAppInstalledWithOsHooks() and
+  // OnWebAppLastLaunchTimeChanged() lead to updates.
+  const auto& app_deltas = mock_app_publisher.get_deltas();
+  EXPECT_EQ(app_deltas.size(), 5U);
+  EXPECT_EQ(app_deltas[app_deltas.size() - 2]->app_id, app_id);
+  EXPECT_EQ(app_deltas[app_deltas.size() - 2]->readiness,
             apps::mojom::Readiness::kReady);
-  EXPECT_EQ(mock_app_publisher.get_deltas().back()->icon_key->icon_effects,
+  EXPECT_EQ(app_deltas[app_deltas.size() - 2]->icon_key->icon_effects,
             IconEffects::kRoundCorners | IconEffects::kCrOsStandardIcon);
 
   {
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 6669820..49c8972 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1641923982-b421bd00007351f3d3d347fadbedd0d211eadc8c.profdata
+chrome-linux-main-1641967191-1ea8dc629e0d4a1b00e280201af3c405581ba281.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 6f16506..ef4007df 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1641923982-2abdb6ca3337708a0d5c9e656a9973060620435c.profdata
+chrome-mac-main-1641967191-75479048b6e19d32c44ae387dc488978f31aa240.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 246acbaa..d76729e 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1641934172-6d2a8f926ae50b5d54a7c3db91c1a2f86b151807.profdata
+chrome-win32-main-1641956375-6c067d4877898f938d5e3e15d74214dfc895d0cc.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index f27f8bbe..723272f 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1641934172-034527ed13da2733050871a9ff6ab3c9f05b3e2c.profdata
+chrome-win64-main-1641967191-f51a8c7594830305ce4e57fc76a6be89f28bfbd9.profdata
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 8c8188e..ed285c3 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -77,8 +77,6 @@
 
 #if !defined(OS_ANDROID)
 // App Service related flags. See components/services/app_service/README.md.
-const base::Feature kAppServiceExternalProtocol{
-    "AppServiceExternalProtocol", base::FEATURE_DISABLED_BY_DEFAULT};
 const base::Feature kAppServiceLoadIconWithoutMojom{
     "AppServiceLoadIconWithoutMojom", base::FEATURE_ENABLED_BY_DEFAULT};
 const base::Feature kAppServiceExtension{"AppServiceExtension",
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index fdab413..b08e1a7 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -75,8 +75,6 @@
 
 #if !defined(OS_ANDROID)
 COMPONENT_EXPORT(CHROME_FEATURES)
-extern const base::Feature kAppServiceExternalProtocol;
-COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kAppServiceLoadIconWithoutMojom;
 COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kAppServiceExtension;
diff --git a/chrome/common/extensions/extension_constants.cc b/chrome/common/extensions/extension_constants.cc
index 508f4ca..fded91c 100644
--- a/chrome/common/extensions/extension_constants.cc
+++ b/chrome/common/extensions/extension_constants.cc
@@ -34,6 +34,7 @@
 const char kIdentityApiUiAppId[] = "ahjaciijnoiaklcomgnblndopackapon";
 const char kTextEditorAppId[] = "mmfbcljfglbokpmkimbfghdkjmjhdgbg";
 const char kInAppPaymentsSupportAppId[] = "nmmhkkegccagdldgiimedpiccmgmieda";
+const char kKeyboardExtensionId[] = "mppnpdlheglhdfmldimlhpnegondlapf";
 
 const char* const kBuiltInFirstPartyExtensionIds[] = {
     kCalculatorAppId,
@@ -75,6 +76,9 @@
 #if defined(OS_CHROMEOS)
 const char kAssessmentAssistantExtensionId[] =
     "gndmhdcefbhlchkhipcnnbkcmicncehk";
+const char kGnubbyAppId[] = "beknehfpfkghjoafdifaflglpjkojoco";
+const char kGnubbyV3ExtensionId[] = "lfboplenmmjcmpbkeemecobbadnmpfhi";
+const char kGCSEExtensionId[] = "cfmgaohenjcikllcgjpepfadgbflcjof";
 #endif
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 const char kAccessibilityCommonExtensionId[] =
@@ -115,7 +119,13 @@
 const char kGoogleSpeechSynthesisExtensionId[] =
     "gjjabgpgjpampikjhjpfhneeoapjbjaf";
 const char kWallpaperManagerId[] = "obklkkbkpaoaejdabbfldmcfplpdgolj";
+const char kHelpAppExtensionId[] = "honijodknafkokifofgiaalefdiedpko";
+const char kEchoExtensionId[] = "kddnkjkcjddckihglkfcickdhbmaodcn";
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(ENABLE_HANGOUT_SERVICES_EXTENSION)
+// The extension id of the Hangout Service extnsion.
+const char kHangoutServiceExtensionId[] = "nkeimhogjdpnpccoofpliimaahmaaome";
+#endif
 
 const char kAppStateNotInstalled[] = "not_installed";
 const char kAppStateInstalled[] = "installed";
diff --git a/chrome/common/extensions/extension_constants.h b/chrome/common/extensions/extension_constants.h
index 81e5b43..fff878bd 100644
--- a/chrome/common/extensions/extension_constants.h
+++ b/chrome/common/extensions/extension_constants.h
@@ -9,6 +9,7 @@
 
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
+#include "chrome/common/buildflags.h"
 #include "url/gurl.h"
 
 namespace extension_urls {
@@ -67,6 +68,9 @@
 // The extension id of the in-app payments support application.
 extern const char kInAppPaymentsSupportAppId[];
 
+// The extension id of virtual keyboard extension.
+extern const char kKeyboardExtensionId[];
+
 // A list of all the first party extension IDs, last entry is null.
 extern const char* const kBuiltInFirstPartyExtensionIds[];
 
@@ -159,6 +163,12 @@
 #if defined(OS_CHROMEOS)
 // The extension id of the Assessment Assistant extension.
 extern const char kAssessmentAssistantExtensionId[];
+// The extension id of the Gnubby chrome app.
+extern const char kGnubbyAppId[];
+// The extension id of the new v3 Gnubby extension.
+extern const char kGnubbyV3ExtensionId[];
+// The extension id of the GCSE.
+extern const char kGCSEExtensionId[];
 #endif
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 // The extension id of the Accessibility Common extension.
@@ -219,6 +229,13 @@
 extern const char kEspeakSpeechSynthesisExtensionId[];
 // The extension id of the wallpaper manager application.
 extern const char kWallpaperManagerId[];
+// The extension id of official HelpApp extension.
+extern const char kHelpAppExtensionId[];
+extern const char kEchoExtensionId[];
+#endif
+#if BUILDFLAG(ENABLE_HANGOUT_SERVICES_EXTENSION)
+// The extension id of the Hangout Service extnsion.
+extern const char kHangoutServiceExtensionId[];
 #endif
 
 // What causes an extension to be installed? Used in histograms, so don't
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index fbe1e3a..709ab90 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -5034,8 +5034,6 @@
 
   if (!is_android) {
     sources += [
-      "../browser/apps/app_discovery_service/remote_url_search/remote_url_client_unittest.cc",
-      "../browser/apps/app_discovery_service/remote_url_search/remote_url_index_unittest.cc",
       "../browser/browsing_data/chrome_browsing_data_lifetime_manager_unittest.cc",
       "../browser/component_updater/soda_component_installer_unittest.cc",
       "../browser/component_updater/soda_language_pack_component_installer_unittest.cc",
@@ -6385,7 +6383,6 @@
 
   if (!is_android) {
     sources += [
-      "../browser/apps/app_discovery_service/app_discovery_service_unittest.cc",
       "../browser/apps/app_service/app_icon/app_icon_factory_unittest.cc",
       "../browser/apps/app_service/app_service_proxy_unittest.cc",
       "../browser/apps/app_service/app_service_test.cc",
@@ -6547,6 +6544,10 @@
       "../browser/ui/window_sizer/window_sizer_unittest.cc",
     ]
     sources += [
+      "../browser/apps/app_discovery_service/app_discovery_service_unittest.cc",
+      "../browser/apps/app_discovery_service/recommended_arc_app_fetcher_unittest.cc",
+      "../browser/apps/app_discovery_service/remote_url_search/remote_url_client_unittest.cc",
+      "../browser/apps/app_discovery_service/remote_url_search/remote_url_index_unittest.cc",
       "../browser/apps/app_service/file_utils_unittest.cc",
       "../browser/apps/app_service/intent_util_unittest.cc",
       "../browser/apps/app_service/metrics/app_platform_metrics_service_unittest.cc",
diff --git a/chrome/test/data/client_hints/http_equiv_accept_ch_delegation_bar.html b/chrome/test/data/client_hints/http_equiv_accept_ch_delegation_bar.html
index 2d86018f..d962720 100644
--- a/chrome/test/data/client_hints/http_equiv_accept_ch_delegation_bar.html
+++ b/chrome/test/data/client_hints/http_equiv_accept_ch_delegation_bar.html
@@ -2,11 +2,11 @@
 <meta http-equiv="Accept-CH" content="dpr=(https://bar.com/),sec-ch-dpr=(https://bar.com/),device-memory=(https://bar.com/),sec-ch-device-memory=(https://bar.com/),viewport-width=(https://bar.com/),sec-ch-viewport-width=(https://bar.com/),rtt=(https://bar.com/),downlink=(https://bar.com/),ect=(https://bar.com/),sec-ch-ua-arch=(https://bar.com/),sec-ch-ua-platform-version=(https://bar.com/),sec-ch-ua-model=(https://bar.com/),sec-ch-ua-full-version=(https://bar.com/),sec-ch-prefers-color-scheme=(https://bar.com/),sec-ch-ua-bitness=(https://bar.com/),sec-ch-viewport-height=(https://bar.com/),sec-ch-ua-full-version-list=(https://bar.com/)">
 <link rel="icon" href="data:;base64,=">
 <body>
-    <img src="non-existing-image.jpg"></img>
+    <img src="test_img.jpg"></img>
     <img src="https://foo.com/non-existing-image.jpg"></img>
     <iframe src="https://foo.com/non-existing-iframe.html"></iframe>
     <script type="text/javascript">
-        document.body.innerHTML += '<img src="non-existing-image2.jpg"></img>';
+        document.body.innerHTML += '<img src="test_img.jpg?foo"></img>';
         document.body.innerHTML += '<img src="https://bar.com/non-existing-image.jpg"></img>';
         document.body.innerHTML += '<iframe src="https://bar.com/non-existing-iframe.html"></iframe>';
     </script>
diff --git a/chrome/test/data/client_hints/http_equiv_accept_ch_delegation_foo.html b/chrome/test/data/client_hints/http_equiv_accept_ch_delegation_foo.html
index e1e62c2e..92b57fc2 100644
--- a/chrome/test/data/client_hints/http_equiv_accept_ch_delegation_foo.html
+++ b/chrome/test/data/client_hints/http_equiv_accept_ch_delegation_foo.html
@@ -2,11 +2,11 @@
 <meta http-equiv="Accept-CH" content="dpr=(https://foo.com/),sec-ch-dpr=(https://foo.com/),device-memory=(https://foo.com/),sec-ch-device-memory=(https://foo.com/),viewport-width=(https://foo.com/),sec-ch-viewport-width=(https://foo.com/),rtt=(https://foo.com/),downlink=(https://foo.com/),ect=(https://foo.com/),sec-ch-ua-arch=(https://foo.com/),sec-ch-ua-platform-version=(https://foo.com/),sec-ch-ua-model=(https://foo.com/),sec-ch-ua-full-version=(https://foo.com/),sec-ch-prefers-color-scheme=(https://foo.com/),sec-ch-ua-bitness=(https://foo.com/),sec-ch-viewport-height=(https://foo.com/),sec-ch-ua-full-version-list=(https://foo.com/)">
 <link rel="icon" href="data:;base64,=">
 <body>
-    <img src="non-existing-image.jpg"></img>
+    <img src="test_img.jpg"></img>
     <img src="https://foo.com/non-existing-image.jpg"></img>
     <iframe src="https://foo.com/non-existing-iframe.html"></iframe>
     <script type="text/javascript">
-        document.body.innerHTML += '<img src="non-existing-image2.jpg"></img>';
+        document.body.innerHTML += '<img src="test_img.jpg?foo"></img>';
         document.body.innerHTML += '<img src="https://bar.com/non-existing-image.jpg"></img>';
         document.body.innerHTML += '<iframe src="https://bar.com/non-existing-iframe.html"></iframe>';
     </script>
diff --git a/chrome/test/data/client_hints/http_equiv_accept_ch_delegation_merge.html b/chrome/test/data/client_hints/http_equiv_accept_ch_delegation_merge.html
index 2d86018f..d962720 100644
--- a/chrome/test/data/client_hints/http_equiv_accept_ch_delegation_merge.html
+++ b/chrome/test/data/client_hints/http_equiv_accept_ch_delegation_merge.html
@@ -2,11 +2,11 @@
 <meta http-equiv="Accept-CH" content="dpr=(https://bar.com/),sec-ch-dpr=(https://bar.com/),device-memory=(https://bar.com/),sec-ch-device-memory=(https://bar.com/),viewport-width=(https://bar.com/),sec-ch-viewport-width=(https://bar.com/),rtt=(https://bar.com/),downlink=(https://bar.com/),ect=(https://bar.com/),sec-ch-ua-arch=(https://bar.com/),sec-ch-ua-platform-version=(https://bar.com/),sec-ch-ua-model=(https://bar.com/),sec-ch-ua-full-version=(https://bar.com/),sec-ch-prefers-color-scheme=(https://bar.com/),sec-ch-ua-bitness=(https://bar.com/),sec-ch-viewport-height=(https://bar.com/),sec-ch-ua-full-version-list=(https://bar.com/)">
 <link rel="icon" href="data:;base64,=">
 <body>
-    <img src="non-existing-image.jpg"></img>
+    <img src="test_img.jpg"></img>
     <img src="https://foo.com/non-existing-image.jpg"></img>
     <iframe src="https://foo.com/non-existing-iframe.html"></iframe>
     <script type="text/javascript">
-        document.body.innerHTML += '<img src="non-existing-image2.jpg"></img>';
+        document.body.innerHTML += '<img src="test_img.jpg?foo"></img>';
         document.body.innerHTML += '<img src="https://bar.com/non-existing-image.jpg"></img>';
         document.body.innerHTML += '<iframe src="https://bar.com/non-existing-iframe.html"></iframe>';
     </script>
diff --git a/chrome/test/data/client_hints/meta_name_accept_ch_delegation_bar.html b/chrome/test/data/client_hints/meta_name_accept_ch_delegation_bar.html
index ffad773..9886b9c0 100644
--- a/chrome/test/data/client_hints/meta_name_accept_ch_delegation_bar.html
+++ b/chrome/test/data/client_hints/meta_name_accept_ch_delegation_bar.html
@@ -2,11 +2,11 @@
 <meta name="Accept-CH" content="dpr=(https://bar.com/),sec-ch-dpr=(https://bar.com/),device-memory=(https://bar.com/),sec-ch-device-memory=(https://bar.com/),viewport-width=(https://bar.com/),sec-ch-viewport-width=(https://bar.com/),rtt=(https://bar.com/),downlink=(https://bar.com/),ect=(https://bar.com/),sec-ch-ua-arch=(https://bar.com/),sec-ch-ua-platform-version=(https://bar.com/),sec-ch-ua-model=(https://bar.com/),sec-ch-ua-full-version=(https://bar.com/),sec-ch-prefers-color-scheme=(https://bar.com/),sec-ch-ua-bitness=(https://bar.com/),sec-ch-viewport-height=(https://bar.com/),sec-ch-ua-full-version-list=(https://bar.com/)">
 <link rel="icon" href="data:;base64,=">
 <body>
-    <img src="non-existing-image.jpg"></img>
+    <img src="test_img.jpg"></img>
     <img src="https://foo.com/non-existing-image.jpg"></img>
     <iframe src="https://foo.com/non-existing-iframe.html"></iframe>
     <script type="text/javascript">
-        document.body.innerHTML += '<img src="non-existing-image2.jpg"></img>';
+        document.body.innerHTML += '<img src="test_img.jpg?foo"></img>';
         document.body.innerHTML += '<img src="https://bar.com/non-existing-image.jpg"></img>';
         document.body.innerHTML += '<iframe src="https://bar.com/non-existing-iframe.html"></iframe>';
     </script>
diff --git a/chrome/test/data/client_hints/meta_name_accept_ch_delegation_foo.html b/chrome/test/data/client_hints/meta_name_accept_ch_delegation_foo.html
index 943df4a..dc5ef5d 100644
--- a/chrome/test/data/client_hints/meta_name_accept_ch_delegation_foo.html
+++ b/chrome/test/data/client_hints/meta_name_accept_ch_delegation_foo.html
@@ -2,11 +2,11 @@
 <meta name="Accept-CH" content="dpr=(https://foo.com/),sec-ch-dpr=(https://foo.com/),device-memory=(https://foo.com/),sec-ch-device-memory=(https://foo.com/),viewport-width=(https://foo.com/),sec-ch-viewport-width=(https://foo.com/),rtt=(https://foo.com/),downlink=(https://foo.com/),ect=(https://foo.com/),sec-ch-ua-arch=(https://foo.com/),sec-ch-ua-platform-version=(https://foo.com/),sec-ch-ua-model=(https://foo.com/),sec-ch-ua-full-version=(https://foo.com/),sec-ch-prefers-color-scheme=(https://foo.com/),sec-ch-ua-bitness=(https://foo.com/),sec-ch-viewport-height=(https://foo.com/),sec-ch-ua-full-version-list=(https://foo.com/)">
 <link rel="icon" href="data:;base64,=">
 <body>
-    <img src="non-existing-image.jpg"></img>
+    <img src="test_img.jpg"></img>
     <img src="https://foo.com/non-existing-image.jpg"></img>
     <iframe src="https://foo.com/non-existing-iframe.html"></iframe>
     <script type="text/javascript">
-        document.body.innerHTML += '<img src="non-existing-image2.jpg"></img>';
+        document.body.innerHTML += '<img src="test_img.jpg?foo"></img>';
         document.body.innerHTML += '<img src="https://bar.com/non-existing-image.jpg"></img>';
         document.body.innerHTML += '<iframe src="https://bar.com/non-existing-iframe.html"></iframe>';
     </script>
diff --git a/chrome/test/data/client_hints/meta_name_accept_ch_delegation_merge.html b/chrome/test/data/client_hints/meta_name_accept_ch_delegation_merge.html
index ffad773..9886b9c0 100644
--- a/chrome/test/data/client_hints/meta_name_accept_ch_delegation_merge.html
+++ b/chrome/test/data/client_hints/meta_name_accept_ch_delegation_merge.html
@@ -2,11 +2,11 @@
 <meta name="Accept-CH" content="dpr=(https://bar.com/),sec-ch-dpr=(https://bar.com/),device-memory=(https://bar.com/),sec-ch-device-memory=(https://bar.com/),viewport-width=(https://bar.com/),sec-ch-viewport-width=(https://bar.com/),rtt=(https://bar.com/),downlink=(https://bar.com/),ect=(https://bar.com/),sec-ch-ua-arch=(https://bar.com/),sec-ch-ua-platform-version=(https://bar.com/),sec-ch-ua-model=(https://bar.com/),sec-ch-ua-full-version=(https://bar.com/),sec-ch-prefers-color-scheme=(https://bar.com/),sec-ch-ua-bitness=(https://bar.com/),sec-ch-viewport-height=(https://bar.com/),sec-ch-ua-full-version-list=(https://bar.com/)">
 <link rel="icon" href="data:;base64,=">
 <body>
-    <img src="non-existing-image.jpg"></img>
+    <img src="test_img.jpg"></img>
     <img src="https://foo.com/non-existing-image.jpg"></img>
     <iframe src="https://foo.com/non-existing-iframe.html"></iframe>
     <script type="text/javascript">
-        document.body.innerHTML += '<img src="non-existing-image2.jpg"></img>';
+        document.body.innerHTML += '<img src="test_img.jpg?foo"></img>';
         document.body.innerHTML += '<img src="https://bar.com/non-existing-image.jpg"></img>';
         document.body.innerHTML += '<iframe src="https://bar.com/non-existing-iframe.html"></iframe>';
     </script>
diff --git a/chrome/test/data/client_hints/test_img.jpg b/chrome/test/data/client_hints/test_img.jpg
new file mode 100644
index 0000000..3679a331
--- /dev/null
+++ b/chrome/test/data/client_hints/test_img.jpg
Binary files differ
diff --git a/chrome/test/data/webui/chromeos/firmware_update/firmware_update_dialog_test.js b/chrome/test/data/webui/chromeos/firmware_update/firmware_update_dialog_test.js
index 17cd176d..aed7d1c2 100644
--- a/chrome/test/data/webui/chromeos/firmware_update/firmware_update_dialog_test.js
+++ b/chrome/test/data/webui/chromeos/firmware_update/firmware_update_dialog_test.js
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {DialogState, FirmwareUpdateDialogElement} from 'chrome://accessory-update/firmware_update_dialog.js';
+import {FirmwareUpdateDialogElement} from 'chrome://accessory-update/firmware_update_dialog.js';
 import {FirmwareUpdate, UpdatePriority} from 'chrome://accessory-update/firmware_update_types.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
diff --git a/chrome/test/data/webui/chromeos/firmware_update/firmware_update_test.js b/chrome/test/data/webui/chromeos/firmware_update/firmware_update_test.js
index 0b8b49b8..d316713 100644
--- a/chrome/test/data/webui/chromeos/firmware_update/firmware_update_test.js
+++ b/chrome/test/data/webui/chromeos/firmware_update/firmware_update_test.js
@@ -8,8 +8,7 @@
 import {FakeUpdateController} from 'chrome://accessory-update/fake_update_controller.js';
 import {FakeUpdateProvider} from 'chrome://accessory-update/fake_update_provider.js';
 import {FirmwareUpdateAppElement} from 'chrome://accessory-update/firmware_update_app.js';
-import {DialogState} from 'chrome://accessory-update/firmware_update_dialog.js';
-import {FirmwareUpdate, UpdateProviderInterface} from 'chrome://accessory-update/firmware_update_types.js';
+import {FirmwareUpdate, UpdateProviderInterface, UpdateState} from 'chrome://accessory-update/firmware_update_types.js';
 import {getUpdateProvider, setUpdateControllerForTesting, setUpdateProviderForTesting} from 'chrome://accessory-update/mojo_interface_provider.js';
 import {mojoString16ToString} from 'chrome://accessory-update/mojo_utils.js';
 import {UpdateCardElement} from 'chrome://accessory-update/update_card.js';
@@ -69,9 +68,10 @@
         dialogElement.querySelector('#nextButton'));
   }
 
-  /** @return {!DialogState} */
-  function getDialogState() {
-    return page.shadowRoot.querySelector('firmware-update-dialog').dialogState;
+  /** @return {!UpdateState} */
+  function getUpdateState() {
+    return page.shadowRoot.querySelector('firmware-update-dialog')
+        .installationProgress.state;
   }
 
   /** @return {!FirmwareUpdate} */
@@ -117,7 +117,7 @@
     // Process |OnStateChanged| and |OnProgressChanged| calls.
     await flushTasks();
     await flushTasks();
-    assertEquals(DialogState.UPDATING, getDialogState());
+    assertEquals(UpdateState.kUpdating, getUpdateState());
     const fakeFirmwareUpdate = getFirmwareUpdateFromDialog();
     assertEquals(
         `Updating ${mojoString16ToString(fakeFirmwareUpdate.deviceName)}`,
@@ -125,7 +125,7 @@
     // Allow firmware update to complete.
     await controller.getUpdateCompletedPromiseForTesting();
     await flushTasks();
-    assertEquals(DialogState.UPDATE_DONE, getDialogState());
+    assertEquals(UpdateState.kSuccess, getUpdateState());
     assertTrue(getUpdateDialog().open);
     assertEquals(
         `Your ${
diff --git a/chrome/test/data/webui/cr_components/chromeos/bluetooth/bluetooth_pairing_ui_test.js b/chrome/test/data/webui/cr_components/chromeos/bluetooth/bluetooth_pairing_ui_test.js
index 5452035..968542d9 100644
--- a/chrome/test/data/webui/cr_components/chromeos/bluetooth/bluetooth_pairing_ui_test.js
+++ b/chrome/test/data/webui/cr_components/chromeos/bluetooth/bluetooth_pairing_ui_test.js
@@ -266,8 +266,9 @@
     // page.
     assertTrue(!!getSpinnerPage());
     assertFalse(!!getDeviceSelectionPage());
-    await flushTasks();
-    await waitAfterNextRender(bluetoothPairingUi);
+
+    // Wait for DevicePairingHandler.PairDevice() to be called.
+    await bluetoothConfig.getLastCreatedPairingHandler().waitForPairDevice();
 
     // Once we begin pairing we should still be in the spinner page.
     assertTrue(!!getSpinnerPage());
@@ -510,150 +511,148 @@
     await displayPinOrPasskey(PairingAuthType.DISPLAY_PASSKEY);
   });
 
-  // TODO(b/213943745) Fix flaky test.
-  // test('Pairing a new device cancels old pairing', async function() {
-  //   await init();
-  //   let finishedPromise = eventToPromise('finished', bluetoothPairingUi);
-  //   const device = createDefaultBluetoothDevice(
-  //       /*id=*/ '1234321',
-  //       /*publicName=*/ 'BeatsX',
-  //       /*connectionState=*/
-  //       chromeos.bluetoothConfig.mojom.DeviceConnectionState.kConnected,
-  //       /*opt_nickname=*/ 'device 1',
-  //       /*opt_audioCapability=*/
-  //       mojom.AudioOutputCapability.kCapableOfAudioOutput,
-  //       /*opt_deviceType=*/ mojom.DeviceType.kMouse);
+  test('Pairing a new device cancels old pairing', async function() {
+    await init();
+    let finishedPromise = eventToPromise('finished', bluetoothPairingUi);
+    const device = createDefaultBluetoothDevice(
+        /*id=*/ '1234321',
+        /*publicName=*/ 'BeatsX',
+        /*connectionState=*/
+        chromeos.bluetoothConfig.mojom.DeviceConnectionState.kConnected,
+        /*opt_nickname=*/ 'device 1',
+        /*opt_audioCapability=*/
+        mojom.AudioOutputCapability.kCapableOfAudioOutput,
+        /*opt_deviceType=*/ mojom.DeviceType.kMouse);
 
-  //   const device1 = createDefaultBluetoothDevice(
-  //       /*id=*/ '12345654321',
-  //       /*publicName=*/ 'Head phones',
-  //       /*connectionState=*/
-  //       chromeos.bluetoothConfig.mojom.DeviceConnectionState.kConnected,
-  //       /*opt_nickname=*/ 'device 2',
-  //       /*opt_audioCapability=*/
-  //       mojom.AudioOutputCapability.kCapableOfAudioOutput,
-  //       /*opt_deviceType=*/ mojom.DeviceType.kMouse);
+    const device1 = createDefaultBluetoothDevice(
+        /*id=*/ '12345654321',
+        /*publicName=*/ 'Head phones',
+        /*connectionState=*/
+        chromeos.bluetoothConfig.mojom.DeviceConnectionState.kConnected,
+        /*opt_nickname=*/ 'device 2',
+        /*opt_audioCapability=*/
+        mojom.AudioOutputCapability.kCapableOfAudioOutput,
+        /*opt_deviceType=*/ mojom.DeviceType.kMouse);
 
-  //   const device2 = createDefaultBluetoothDevice(
-  //       /*id=*/ '123454321',
-  //       /*publicName=*/ 'Speakers',
-  //       /*connectionState=*/
-  //       chromeos.bluetoothConfig.mojom.DeviceConnectionState.kConnected,
-  //       /*opt_nickname=*/ 'device 3',
-  //       /*opt_audioCapability=*/
-  //       mojom.AudioOutputCapability.kCapableOfAudioOutput,
-  //       /*opt_deviceType=*/ mojom.DeviceType.kMouse);
+    const device2 = createDefaultBluetoothDevice(
+        /*id=*/ '123454321',
+        /*publicName=*/ 'Speakers',
+        /*connectionState=*/
+        chromeos.bluetoothConfig.mojom.DeviceConnectionState.kConnected,
+        /*opt_nickname=*/ 'device 3',
+        /*opt_audioCapability=*/
+        mojom.AudioOutputCapability.kCapableOfAudioOutput,
+        /*opt_deviceType=*/ mojom.DeviceType.kMouse);
 
-  //   bluetoothConfig.appendToDiscoveredDeviceList(
-  //       [device.deviceProperties, device1.deviceProperties]);
-  //   await flushTasks();
-  //   let deviceHandler = bluetoothConfig.getLastCreatedPairingHandler();
+    bluetoothConfig.appendToDiscoveredDeviceList(
+        [device.deviceProperties, device1.deviceProperties]);
+    await flushTasks();
+    let deviceHandler = bluetoothConfig.getLastCreatedPairingHandler();
 
-  //   // Try pairing to first device.
-  //   await selectDevice(device.deviceProperties);
-  //   await waitAfterNextRender(bluetoothPairingUi);
+    // Try pairing to first device.
+    let pairDevicePromise = deviceHandler.waitForPairDevice();
+    await selectDevice(device.deviceProperties);
 
-  //   // Try pairing to second device, before first device has completed
-  //   pairing. await selectDevice(device1.deviceProperties); await
-  //   waitAfterNextRender(bluetoothPairingUi);
+    // Wait for DevicePairingHandler.PairDevice() to be called.
+    await pairDevicePromise;
+    assertEquals(deviceHandler.getPairDeviceCalledCount(), 1);
 
-  //   // Try pairing to third device, before first device has completed
-  //   pairing. await selectDevice(device2.deviceProperties); await
-  //   waitAfterNextRender(bluetoothPairingUi);
+    // Try pairing to second device, before first device has completed pairing.
+    await selectDevice(device1.deviceProperties);
+    await waitAfterNextRender(bluetoothPairingUi);
 
-  //   // Simulate device pairing cancellation.
-  //   deviceHandler.completePairDevice(/*success=*/ false);
-  //   await waitAfterNextRender(bluetoothPairingUi);
+    // Try pairing to third device, before first device has completed pairing.
+    await selectDevice(device2.deviceProperties);
+    await waitAfterNextRender(bluetoothPairingUi);
 
-  //   assertEquals(deviceHandler.getPairDeviceCalledCount(), 2);
+    // Simulate device pairing cancellation.
+    pairDevicePromise = deviceHandler.waitForPairDevice();
+    deviceHandler.completePairDevice(/*success=*/ false);
 
-  //   // Complete second device pairing.
-  //   deviceHandler.completePairDevice(/*success=*/ true);
-  //   await finishedPromise;
-  // });
+    // Wait for DevicePairingHandler.PairDevice() to be called.
+    await pairDevicePromise;
+    assertEquals(deviceHandler.getPairDeviceCalledCount(), 2);
 
-  // TODO(b/213943745) Fix flaky test.
-  // test('Pair with a specific device by address, success', async function() {
-  //   await pairByDeviceAddress(/*address=*/ '123456');
+    // Complete second device pairing.
+    deviceHandler.completePairDevice(/*success=*/ true);
+    await finishedPromise;
+  });
 
-  //   let finishedPromise = eventToPromise('finished', bluetoothPairingUi);
-  //   const deviceHandler = bluetoothConfig.getLastCreatedPairingHandler();
-  //   deviceHandler.completePairDevice(/*success=*/ true);
-  //   await finishedPromise;
-  // });
+  test('Pair with a specific device by address, success', async function() {
+    await pairByDeviceAddress(/*address=*/ '123456');
 
-  // TODO(b/210128630) Fix flaky test. Closure compiler complains about using
-  // test.skip() here, see
-  // https://ci.chromium.org/ui/p/chromium/builders/try/linux-chromeos-rel/1058566/overview.
-  // test(
-  //     'Pair with a specific device by address, failure', async function() {
-  //       const deviceId1 = '123456';
-  //       await pairByDeviceAddress(/*address=*/ deviceId1);
+    let finishedPromise = eventToPromise('finished', bluetoothPairingUi);
+    const deviceHandler = bluetoothConfig.getLastCreatedPairingHandler();
+    deviceHandler.completePairDevice(/*success=*/ true);
+    await finishedPromise;
+  });
 
-  //       const deviceHandler = bluetoothConfig.getLastCreatedPairingHandler();
-  //       deviceHandler.completePairDevice(/*success=*/ false);
+  test('Pair with a specific device by address, failure', async function() {
+    const deviceId1 = '123456';
+    await pairByDeviceAddress(/*address=*/ deviceId1);
 
-  //       // Wait for the callback to finish (flushTasks() doesn't wait long
-  //       // enough here).
-  //       await waitAfterNextRender(bluetoothPairingUi);
+    const deviceHandler = bluetoothConfig.getLastCreatedPairingHandler();
+    deviceHandler.completePairDevice(/*success=*/ false);
+    await waitAfterNextRender(bluetoothPairingUi);
 
-  //       // On failure, the device selection page should be shown.
-  //       assertTrue(!!getDeviceSelectionPage());
-  //       assertEquals(getDeviceSelectionPage().failedPairingDeviceId,
-  //       deviceId1);
+    // On failure, the device selection page should be shown.
+    assertTrue(!!getDeviceSelectionPage());
+    assertEquals(getDeviceSelectionPage().failedPairingDeviceId, deviceId1);
 
-  //       // There should no longer be a device-specific address to pair to.
-  //       assertFalse(!!bluetoothPairingUi.pairingDeviceAddress);
+    // There should no longer be a device-specific address to pair to.
+    assertFalse(!!bluetoothPairingUi.pairingDeviceAddress);
 
-  //       // Verify we can pair with another device.
-  //       const device2 = createDefaultBluetoothDevice(
-  //           /*id=*/ '34567',
-  //           /*publicName=*/ 'BeatsX',
-  //           /*connectionState=*/
-  //           chromeos.bluetoothConfig.mojom.DeviceConnectionState.kConnected,
-  //           /*opt_nickname=*/ 'device1',
-  //           /*opt_audioCapability=*/
-  //           mojom.AudioOutputCapability.kCapableOfAudioOutput,
-  //           /*opt_deviceType=*/ mojom.DeviceType.kMouse);
-  //       bluetoothConfig.appendToDiscoveredDeviceList(
-  //           [device2.deviceProperties]);
-  //       await flushTasks();
+    // Verify we can pair with another device.
+    const device2 = createDefaultBluetoothDevice(
+        /*id=*/ '34567',
+        /*publicName=*/ 'BeatsX',
+        /*connectionState=*/
+        chromeos.bluetoothConfig.mojom.DeviceConnectionState.kConnected,
+        /*opt_nickname=*/ 'device1',
+        /*opt_audioCapability=*/
+        mojom.AudioOutputCapability.kCapableOfAudioOutput,
+        /*opt_deviceType=*/ mojom.DeviceType.kMouse);
+    bluetoothConfig.appendToDiscoveredDeviceList([device2.deviceProperties]);
+    await flushTasks();
 
-  //       let finishedPromise = eventToPromise('finished', bluetoothPairingUi);
-  //       await selectDevice(device2.deviceProperties);
+    let finishedPromise = eventToPromise('finished', bluetoothPairingUi);
+    await selectDevice(device2.deviceProperties);
 
-  //       deviceHandler.completePairDevice(/*success=*/ true);
-  //       await finishedPromise;
-  //     });
+    deviceHandler.completePairDevice(/*success=*/ true);
+    await finishedPromise;
+  });
 
-  // TODO(b/213943745) Fix flaky test.
-  // test('Pair with a specific device by address with auth', async function() {
-  //   await pairByDeviceAddress(/*address=*/ '123456');
+  test('Pair with a specific device by address with auth', async function() {
+    await pairByDeviceAddress(/*address=*/ '123456');
 
-  //   const pairingCode = '123457';
-  //   let deviceHandler = bluetoothConfig.getLastCreatedPairingHandler();
-  //   deviceHandler.requireAuthentication(
-  //       PairingAuthType.CONFIRM_PASSKEY, pairingCode);
-  //   await flushTasks();
+    const pairingCode = '123457';
+    let deviceHandler = bluetoothConfig.getLastCreatedPairingHandler();
+    deviceHandler.requireAuthentication(
+        PairingAuthType.CONFIRM_PASSKEY, pairingCode);
+    await flushTasks();
 
-  //   // Confirmation code page should be shown.
-  //   assertTrue(!!getConfirmCodePage());
-  //   assertEquals(getConfirmCodePage().code, pairingCode);
+    // Confirmation code page should be shown.
+    assertTrue(!!getConfirmCodePage());
+    assertEquals(getConfirmCodePage().code, pairingCode);
 
-  //   // Simulate pressing 'Confirm'.
-  //   let event = new CustomEvent('confirm-code');
-  //   getConfirmCodePage().dispatchEvent(event);
-  //   await waitAfterNextRender(bluetoothPairingUi);
+    // Simulate pressing 'Confirm'.
+    let event = new CustomEvent('confirm-code');
+    let finishRequestConfirmPasskeyPromise =
+        deviceHandler.waitForFinishRequestConfirmPasskey_();
+    getConfirmCodePage().dispatchEvent(event);
 
-  //   // Spinner should be shown.
-  //   assertTrue(!!getSpinnerPage());
-  //   assertTrue(deviceHandler.getConfirmPasskeyResult());
+    // Wait for confirm passkey result to propagate to device handler.
+    await finishRequestConfirmPasskeyPromise;
 
-  //   // Finishing the pairing with success should fire the |finished| event.
-  //   let finishedPromise = eventToPromise('finished', bluetoothPairingUi);
-  //   deviceHandler.completePairDevice(/*success=*/ true);
-  //   await finishedPromise;
-  // });
+    // Spinner should be shown.
+    assertTrue(!!getSpinnerPage());
+    assertTrue(deviceHandler.getConfirmPasskeyResult());
+
+    // Finishing the pairing with success should fire the |finished| event.
+    let finishedPromise = eventToPromise('finished', bluetoothPairingUi);
+    deviceHandler.completePairDevice(/*success=*/ true);
+    await finishedPromise;
+  });
 
   test(
       'Cancel pairing with a specific device by address with auth',
@@ -777,102 +776,109 @@
     assertEquals(2, attemptFocusLastSelectedItemCallCount);
   });
 
-  // TODO(b/213943745) Fix flaky test.
-  // test('Disable Bluetooth during pairing', async function() {
-  //   await init();
-  //   assertTrue(!!getDeviceSelectionPage());
-  //   assertTrue(getDeviceSelectionPage().isBluetoothEnabled);
+  test('Disable Bluetooth during pairing', async function() {
+    await init();
+    assertTrue(!!getDeviceSelectionPage());
+    assertTrue(getDeviceSelectionPage().isBluetoothEnabled);
 
-  //   const deviceId = '123456';
-  //   const device = createDefaultBluetoothDevice(
-  //       deviceId,
-  //       /*publicName=*/ 'BeatsX',
-  //       /*connectionState=*/
-  //       chromeos.bluetoothConfig.mojom.DeviceConnectionState.kConnected,
-  //       /*opt_nickname=*/ 'device1',
-  //       /*opt_audioCapability=*/
-  //       mojom.AudioOutputCapability.kCapableOfAudioOutput,
-  //       /*opt_deviceType=*/ mojom.DeviceType.kMouse);
-  //   bluetoothConfig.appendToDiscoveredDeviceList([device.deviceProperties]);
-  //   await flushTasks();
+    const deviceId = '123456';
+    const device = createDefaultBluetoothDevice(
+        deviceId,
+        /*publicName=*/ 'BeatsX',
+        /*connectionState=*/
+        chromeos.bluetoothConfig.mojom.DeviceConnectionState.kConnected,
+        /*opt_nickname=*/ 'device1',
+        /*opt_audioCapability=*/
+        mojom.AudioOutputCapability.kCapableOfAudioOutput,
+        /*opt_deviceType=*/ mojom.DeviceType.kMouse);
+    bluetoothConfig.appendToDiscoveredDeviceList([device.deviceProperties]);
+    await flushTasks();
 
-  //   // Disable Bluetooth.
-  //   bluetoothConfig.setSystemState(
-  //       chromeos.bluetoothConfig.mojom.BluetoothSystemState.kDisabled);
-  //   await flushTasks();
+    // Disable Bluetooth.
+    bluetoothConfig.setSystemState(
+        chromeos.bluetoothConfig.mojom.BluetoothSystemState.kDisabled);
+    await flushTasks();
 
-  //   // This should propagate to the device selection page.
-  //   assertFalse(getDeviceSelectionPage().isBluetoothEnabled);
+    // This should propagate to the device selection page.
+    assertFalse(getDeviceSelectionPage().isBluetoothEnabled);
 
-  //   // Re-enable and select the device.
-  //   bluetoothConfig.setSystemState(
-  //       chromeos.bluetoothConfig.mojom.BluetoothSystemState.kEnabled);
-  //   await flushTasks();
-  //   await waitAfterNextRender(bluetoothPairingUi);
+    // Re-enable and select the device.
+    let onBluetoothDiscoveryStartedPromise =
+        bluetoothPairingUi.waitForOnBluetoothDiscoveryStartedForTest();
+    bluetoothConfig.setSystemState(
+        chromeos.bluetoothConfig.mojom.BluetoothSystemState.kEnabled);
 
-  //   assertTrue(getDeviceSelectionPage().isBluetoothEnabled);
-  //   await selectDevice(device.deviceProperties);
-  //   await flushTasks();
+    // Wait for |devicePairingHandler_| to be set in
+    // onBluetoothDiscoveryStarted().
+    await onBluetoothDiscoveryStartedPromise;
 
-  //   const pairingCode = '123456';
-  //   let deviceHandler = bluetoothConfig.getLastCreatedPairingHandler();
-  //   deviceHandler.requireAuthentication(
-  //       PairingAuthType.CONFIRM_PASSKEY, pairingCode);
-  //   await flushTasks();
+    assertTrue(getDeviceSelectionPage().isBluetoothEnabled);
+    await selectDevice(device.deviceProperties);
+    await flushTasks();
 
-  //   // Confirmation code page should be shown.
-  //   assertTrue(!!getConfirmCodePage());
-  //   assertEquals(getConfirmCodePage().code, pairingCode);
+    const pairingCode = '123456';
+    let deviceHandler = bluetoothConfig.getLastCreatedPairingHandler();
+    deviceHandler.requireAuthentication(
+        PairingAuthType.CONFIRM_PASSKEY, pairingCode);
+    await flushTasks();
 
-  //   // Disable Bluetooth.
-  //   bluetoothConfig.setSystemState(
-  //       chromeos.bluetoothConfig.mojom.BluetoothSystemState.kDisabled);
-  //   await flushTasks();
+    // Confirmation code page should be shown.
+    assertTrue(!!getConfirmCodePage());
+    assertEquals(getConfirmCodePage().code, pairingCode);
 
-  //   // We should be back to the device selection page again.
-  //   assertFalse(!!getConfirmCodePage());
-  //   assertTrue(!!getDeviceSelectionPage());
-  //   assertFalse(getDeviceSelectionPage().isBluetoothEnabled);
+    // Disable Bluetooth.
+    bluetoothConfig.setSystemState(
+        chromeos.bluetoothConfig.mojom.BluetoothSystemState.kDisabled);
+    await flushTasks();
 
-  //   // Re-enable.
-  //   bluetoothConfig.setSystemState(
-  //       chromeos.bluetoothConfig.mojom.BluetoothSystemState.kEnabled);
-  //   await flushTasks();
-  //   await waitAfterNextRender(bluetoothPairingUi);
+    // We should be back to the device selection page again.
+    assertFalse(!!getConfirmCodePage());
+    assertTrue(!!getDeviceSelectionPage());
+    assertFalse(getDeviceSelectionPage().isBluetoothEnabled);
 
-  //   assertTrue(getDeviceSelectionPage().isBluetoothEnabled);
+    // Re-enable.
+    onBluetoothDiscoveryStartedPromise =
+        bluetoothPairingUi.waitForOnBluetoothDiscoveryStartedForTest();
+    bluetoothConfig.setSystemState(
+        chromeos.bluetoothConfig.mojom.BluetoothSystemState.kEnabled);
 
-  //   // Error text shouldn't be showing because this pairing failed due to
-  //   // Bluetooth disabling.
-  //   assertEquals(getDeviceSelectionPage().failedPairingDeviceId, '');
+    // Wait for |devicePairingHandler_| to be set in
+    // onBluetoothDiscoveryStarted().
+    await onBluetoothDiscoveryStartedPromise;
 
-  //   // Select the device.
-  //   await selectDevice(device.deviceProperties);
-  //   await flushTasks();
+    assertTrue(getDeviceSelectionPage().isBluetoothEnabled);
 
-  //   // Simulate pairing failing.
-  //   deviceHandler = bluetoothConfig.getLastCreatedPairingHandler();
-  //   deviceHandler.completePairDevice(/*success=*/ false);
-  //   await flushTasks();
-  //   await waitAfterNextRender(bluetoothPairingUi);
+    // Error text shouldn't be showing because this pairing failed due to
+    // Bluetooth disabling.
+    assertEquals(getDeviceSelectionPage().failedPairingDeviceId, '');
 
-  //   // Error text should be showing.
-  //   assertTrue(!!getDeviceSelectionPage());
-  //   assertEquals(getDeviceSelectionPage().failedPairingDeviceId, deviceId);
+    // Select the device.
+    await selectDevice(device.deviceProperties);
+    await flushTasks();
 
-  //   // Disable and re-enable.
-  //   bluetoothConfig.setSystemState(
-  //       chromeos.bluetoothConfig.mojom.BluetoothSystemState.kDisabled);
-  //   await flushTasks();
+    // Simulate pairing failing.
+    deviceHandler = bluetoothConfig.getLastCreatedPairingHandler();
+    deviceHandler.completePairDevice(/*success=*/ false);
+    await flushTasks();
+    await waitAfterNextRender(bluetoothPairingUi);
 
-  //   assertFalse(getDeviceSelectionPage().isBluetoothEnabled);
-  //   bluetoothConfig.setSystemState(
-  //       chromeos.bluetoothConfig.mojom.BluetoothSystemState.kEnabled);
-  //   await flushTasks();
+    // Error text should be showing.
+    assertTrue(!!getDeviceSelectionPage());
+    assertEquals(getDeviceSelectionPage().failedPairingDeviceId, deviceId);
 
-  //   assertTrue(getDeviceSelectionPage().isBluetoothEnabled);
+    // Disable and re-enable.
+    bluetoothConfig.setSystemState(
+        chromeos.bluetoothConfig.mojom.BluetoothSystemState.kDisabled);
+    await flushTasks();
 
-  //   // Error text should no longer be showing.
-  //   assertEquals(getDeviceSelectionPage().failedPairingDeviceId, '');
-  // });
+    assertFalse(getDeviceSelectionPage().isBluetoothEnabled);
+    bluetoothConfig.setSystemState(
+        chromeos.bluetoothConfig.mojom.BluetoothSystemState.kEnabled);
+    await flushTasks();
+
+    assertTrue(getDeviceSelectionPage().isBluetoothEnabled);
+
+    // Error text should no longer be showing.
+    assertEquals(getDeviceSelectionPage().failedPairingDeviceId, '');
+  });
 });
diff --git a/chrome/test/data/webui/cr_components/chromeos/bluetooth/fake_device_pairing_handler.js b/chrome/test/data/webui/cr_components/chromeos/bluetooth/fake_device_pairing_handler.js
index 1ac090a..177966fa 100644
--- a/chrome/test/data/webui/cr_components/chromeos/bluetooth/fake_device_pairing_handler.js
+++ b/chrome/test/data/webui/cr_components/chromeos/bluetooth/fake_device_pairing_handler.js
@@ -46,6 +46,12 @@
 
     /** @private {?chromeos.bluetoothConfig.mojom.KeyEnteredHandlerRemote} */
     this.lastKeyEnteredHandlerRemote_ = null;
+
+    /** @private {?function()} */
+    this.waitForPairDeviceCallback_ = null;
+
+    /** @private {?function()} */
+    this.finishRequestConfirmPasskeyCallback_ = null;
   }
 
   /** @override */
@@ -56,10 +62,26 @@
       this.pairDeviceCallback_ = resolve;
       this.pairDeviceRejectCallback_ = reject;
     });
+
+    if (this.waitForPairDeviceCallback_) {
+      this.waitForPairDeviceCallback_();
+    }
+
     return promise;
   }
 
   /**
+   * Returns a promise that will be resolved the next time
+   * pairDevice() is called.
+   * @return {Promise}
+   */
+  waitForPairDevice() {
+    return new Promise((resolve) => {
+      this.waitForPairDeviceCallback_ = resolve;
+    });
+  }
+
+  /**
    * Second step in pair device operation. This method should be called
    * after pairDevice(). Pass in a |PairingAuthType| to simulate each
    * pairing request made to |DevicePairingDelegate|.
@@ -68,6 +90,7 @@
    * passkey/PIN authentication.
    */
   requireAuthentication(authType, opt_pairingCode) {
+    assert(this.devicePairingDelegate_, 'devicePairingDelegate_ was not set.');
     switch (authType) {
       case PairingAuthType.REQUEST_PIN_CODE:
         this.devicePairingDelegate_.requestPinCode()
@@ -106,6 +129,17 @@
   }
 
   /**
+   * Returns a promise that will be resolved the next time
+   * finishRequestConfirmPasskey_() is called.
+   * @return {Promise}
+   */
+  waitForFinishRequestConfirmPasskey_() {
+    return new Promise((resolve) => {
+      this.finishRequestConfirmPasskeyCallback_ = resolve;
+    });
+  }
+
+  /**
    * @return {!chromeos.bluetoothConfig.mojom.KeyEnteredHandlerPendingReceiver}
    * @private
    */
@@ -136,6 +170,10 @@
    */
   finishRequestConfirmPasskey_(confirmed) {
     this.confirmPasskeyResult_ = confirmed;
+
+    if (this.finishRequestConfirmPasskeyCallback_) {
+      this.finishRequestConfirmPasskeyCallback_();
+    }
   }
 
   /**
diff --git a/chrome/test/data/webui/new_tab_page/BUILD.gn b/chrome/test/data/webui/new_tab_page/BUILD.gn
index 8a5fc00..19d1b9d 100644
--- a/chrome/test/data/webui/new_tab_page/BUILD.gn
+++ b/chrome/test/data/webui/new_tab_page/BUILD.gn
@@ -26,6 +26,9 @@
                     "js_module_root=" + rebase_path(
                             "$root_gen_dir/mojom-webui/chrome/browser/new_tab_page/modules/drive",
                             root_build_dir),
+                    "js_module_root=" + rebase_path(
+                            "$root_gen_dir/mojom-webui/chrome/browser/new_tab_page/modules/task_module",
+                            root_build_dir),
                   ]
   deps = [
     ":metrics_utils_test",
diff --git a/chrome/test/data/webui/new_tab_page/modules/recipes_v2/BUILD.gn b/chrome/test/data/webui/new_tab_page/modules/recipes_v2/BUILD.gn
index dc68bc5..cbab02e9 100644
--- a/chrome/test/data/webui/new_tab_page/modules/recipes_v2/BUILD.gn
+++ b/chrome/test/data/webui/new_tab_page/modules/recipes_v2/BUILD.gn
@@ -7,6 +7,7 @@
 js_library("module_test") {
   deps = [
     "../..:test_support",
+    "//chrome/browser/new_tab_page/modules/task_module:mojo_bindings_webui_js",
     "//chrome/browser/resources/new_tab_page",
     "//chrome/test/data/webui:chai_assert",
     "//chrome/test/data/webui:test_browser_proxy",
diff --git a/chrome/test/data/webui/new_tab_page/modules/recipes_v2/module_test.js b/chrome/test/data/webui/new_tab_page/modules/recipes_v2/module_test.js
index 8c8416c..17431fb 100644
--- a/chrome/test/data/webui/new_tab_page/modules/recipes_v2/module_test.js
+++ b/chrome/test/data/webui/new_tab_page/modules/recipes_v2/module_test.js
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import {$$, recipeTasksV2Descriptor, TaskModuleHandlerProxy} from 'chrome://new-tab-page/new_tab_page.js';
+import {TaskModuleHandlerRemote} from 'chrome://new-tab-page/task_module.mojom-webui.js';
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {assertEquals, assertTrue} from 'chrome://test/chai_assert.js';
 import {installMock} from 'chrome://test/new_tab_page/test_support.js';
@@ -15,9 +16,8 @@
   setup(() => {
     document.body.innerHTML = '';
 
-    handler = installMock(
-        taskModule.mojom.TaskModuleHandlerRemote,
-        TaskModuleHandlerProxy.setHandler);
+    handler =
+        installMock(TaskModuleHandlerRemote, TaskModuleHandlerProxy.setHandler);
   });
 
   test('module appears on render with recipes', async () => {
diff --git a/chrome/test/data/webui/new_tab_page/modules/task_module/BUILD.gn b/chrome/test/data/webui/new_tab_page/modules/task_module/BUILD.gn
index 238a27e0..1b40c89 100644
--- a/chrome/test/data/webui/new_tab_page/modules/task_module/BUILD.gn
+++ b/chrome/test/data/webui/new_tab_page/modules/task_module/BUILD.gn
@@ -7,6 +7,7 @@
 js_library("module_test") {
   deps = [
     "../..:test_support",
+    "//chrome/browser/new_tab_page/modules/task_module:mojo_bindings_webui_js",
     "//chrome/browser/resources/new_tab_page",
     "//chrome/test/data/webui:chai_assert",
     "//chrome/test/data/webui:test_browser_proxy",
diff --git a/chrome/test/data/webui/new_tab_page/modules/task_module/module_test.js b/chrome/test/data/webui/new_tab_page/modules/task_module/module_test.js
index e017d9a..e11b4e2a 100644
--- a/chrome/test/data/webui/new_tab_page/modules/task_module/module_test.js
+++ b/chrome/test/data/webui/new_tab_page/modules/task_module/module_test.js
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import {$$, shoppingTasksDescriptor, TaskModuleHandlerProxy} from 'chrome://new-tab-page/new_tab_page.js';
+import {TaskModuleHandlerRemote, TaskModuleType} from 'chrome://new-tab-page/task_module.mojom-webui.js';
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {assertDeepEquals, assertEquals, assertTrue} from 'chrome://test/chai_assert.js';
 import {installMock} from 'chrome://test/new_tab_page/test_support.js';
@@ -16,9 +17,8 @@
   setup(() => {
     document.body.innerHTML = '';
 
-    handler = installMock(
-        taskModule.mojom.TaskModuleHandlerRemote,
-        TaskModuleHandlerProxy.setHandler);
+    handler =
+        installMock(TaskModuleHandlerRemote, TaskModuleHandlerProxy.setHandler);
   });
 
   test('creates no module if no task', async () => {
@@ -194,7 +194,7 @@
     // Assert.
     assertEquals('Hello world hidden', toastMessage);
     assertDeepEquals(
-        [taskModule.mojom.TaskModuleType.kShopping, 'Hello world'],
+        [TaskModuleType.kShopping, 'Hello world'],
         await handler.whenCalled('dismissTask'));
 
     // Act.
@@ -202,7 +202,7 @@
 
     // Assert.
     assertDeepEquals(
-        [taskModule.mojom.TaskModuleType.kShopping, 'Hello world'],
+        [TaskModuleType.kShopping, 'Hello world'],
         await handler.whenCalled('restoreTask'));
   });
 
diff --git a/chromecast/media/cma/decoder/cast_audio_decoder.cc b/chromecast/media/cma/decoder/cast_audio_decoder.cc
index 57dfc51..ed9e7b7 100644
--- a/chromecast/media/cma/decoder/cast_audio_decoder.cc
+++ b/chromecast/media/cma/decoder/cast_audio_decoder.cc
@@ -157,7 +157,7 @@
                                          weak_this_, timestamp));
   }
 
-  void OnInitialized(::media::Status status) {
+  void OnInitialized(::media::DecoderStatus status) {
     DCHECK(!initialized_);
     initialized_ = true;
     if (status.is_ok()) {
@@ -185,7 +185,7 @@
   }
 
   void OnDecodeStatus(base::TimeDelta buffer_timestamp,
-                      ::media::Status status) {
+                      ::media::DecoderStatus status) {
     DCHECK(pending_decode_callback_);
 
     Status result_status = kDecodeOk;
diff --git a/chromeos/crosapi/mojom/app_service_types.mojom b/chromeos/crosapi/mojom/app_service_types.mojom
index 8a2e535..9080345 100644
--- a/chromeos/crosapi/mojom/app_service_types.mojom
+++ b/chromeos/crosapi/mojom/app_service_types.mojom
@@ -88,7 +88,6 @@
   [MinVersion=12]
   // kTrue if the app is able to handle intents and should be shown in intent
   // surfaces.
-  // TODO(https://crbug.com/1285186): Consider using a more accurate name.
   OptionalBool handles_intents@26;
 };
 
diff --git a/components/autofill_assistant/browser/actions/collect_user_data_action.cc b/components/autofill_assistant/browser/actions/collect_user_data_action.cc
index 8b4783c0..4378dbb 100644
--- a/components/autofill_assistant/browser/actions/collect_user_data_action.cc
+++ b/components/autofill_assistant/browser/actions/collect_user_data_action.cc
@@ -1315,6 +1315,10 @@
       credit_card->set_record_type(autofill::CreditCard::MASKED_SERVER_CARD);
       AddProtoDataToAutofillDataModel(payment_data.card_values(),
                                       proto_data.locale(), credit_card.get());
+      if (!payment_data.last_four_digits().empty()) {
+        credit_card->SetNumber(
+            base::UTF8ToUTF16(payment_data.last_four_digits()));
+      }
       if (!payment_data.network().empty()) {
         credit_card->SetNetworkForMaskedCard(payment_data.network());
       }
diff --git a/components/autofill_assistant/browser/actions/collect_user_data_action_unittest.cc b/components/autofill_assistant/browser/actions/collect_user_data_action_unittest.cc
index 57444bd..ddc0f65e 100644
--- a/components/autofill_assistant/browser/actions/collect_user_data_action_unittest.cc
+++ b/components/autofill_assistant/browser/actions/collect_user_data_action_unittest.cc
@@ -2500,7 +2500,16 @@
                                   Pair(field_formatter::Key(57), "08/2050"),
                                   Pair(field_formatter::Key(58), "Visa"),
                                   Pair(field_formatter::Key(-2), "visa"),
-                                  Pair(field_formatter::Key(-5), "Visa")}));
+                                  Pair(field_formatter::Key(-5), "Visa"),
+                                  Pair(field_formatter::Key(-4), "1111")}));
+        // Used for card summary in UI.
+        EXPECT_EQ(user_data_.available_payment_instruments_[0]
+                      ->card->NetworkForDisplay(),
+                  u"Visa");
+        EXPECT_EQ(user_data_.available_payment_instruments_[0]
+                      ->card->LastFourDigits(),
+                  u"1111");
+
         auto address_mappings = field_formatter::CreateAutofillMappings(
             *user_data_.available_payment_instruments_[0]->billing_address,
             "en-US");
@@ -2530,6 +2539,7 @@
   AddCompleteCardEntriesToMap("John Doe",
                               payment_instrument->mutable_card_values());
   payment_instrument->set_network("visaCC");
+  payment_instrument->set_last_four_digits("1111");
   AddCompleteAddressEntriesToMap("John Doe",
                                  payment_instrument->mutable_address_values());
 
diff --git a/components/autofill_assistant/browser/service.proto b/components/autofill_assistant/browser/service.proto
index c7e0792..3a3bdd34 100644
--- a/components/autofill_assistant/browser/service.proto
+++ b/components/autofill_assistant/browser/service.proto
@@ -2359,6 +2359,8 @@
   map<int32, AutofillEntryProto> card_values = 2;
   // The network of the card.
   optional string network = 5;
+  // The last 4 digits of the card.
+  optional string last_four_digits = 6;
   // The values for the billing address, where the key is one of
   // autofill::ServerFieldType.
   map<int32, AutofillEntryProto> address_values = 3;
diff --git a/components/privacy_sandbox/DEPS b/components/privacy_sandbox/DEPS
index f8d4746..6df7f02 100644
--- a/components/privacy_sandbox/DEPS
+++ b/components/privacy_sandbox/DEPS
@@ -2,6 +2,7 @@
   "+components/content_settings/core",
   "+components/keyed_service/core",
   "+components/pref_registry",
+  "+net/base",
   "+net/cookies/site_for_cookies.h",
   "+content/public/test",
   "+components/prefs",
diff --git a/components/privacy_sandbox/privacy_sandbox_prefs.cc b/components/privacy_sandbox/privacy_sandbox_prefs.cc
index f6094c3d..75a339ff 100644
--- a/components/privacy_sandbox/privacy_sandbox_prefs.cc
+++ b/components/privacy_sandbox/privacy_sandbox_prefs.cc
@@ -25,6 +25,9 @@
 
 extern const char kPrivacySandboxFlocEnabled[] = "privacy_sandbox.floc_enabled";
 
+extern const char kPrivacySandboxFledgeJoinBlocked[] =
+    "privacy_sandbox.fledge_join_blocked";
+
 }  // namespace prefs
 
 namespace privacy_sandbox {
@@ -44,6 +47,7 @@
   registry->RegisterBooleanPref(
       prefs::kPrivacySandboxFlocEnabled, true,
       user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
+  registry->RegisterDictionaryPref(prefs::kPrivacySandboxFledgeJoinBlocked);
 }
 
 }  // namespace privacy_sandbox
diff --git a/components/privacy_sandbox/privacy_sandbox_prefs.h b/components/privacy_sandbox/privacy_sandbox_prefs.h
index 22f6bf7..c8cf7def 100644
--- a/components/privacy_sandbox/privacy_sandbox_prefs.h
+++ b/components/privacy_sandbox/privacy_sandbox_prefs.h
@@ -35,6 +35,11 @@
 // kPrivacySandboxApisEnabled preference be enabled to take effect.
 extern const char kPrivacySandboxFlocEnabled[];
 
+// Dictionary of entries representing top frame origins on which the profile
+// cannot be joined to an interest group. Keys are the blocked origins, and
+// values are the time the setting was applied.
+extern const char kPrivacySandboxFledgeJoinBlocked[];
+
 }  // namespace prefs
 
 namespace privacy_sandbox {
diff --git a/components/privacy_sandbox/privacy_sandbox_settings.cc b/components/privacy_sandbox/privacy_sandbox_settings.cc
index 2cce9ad..e1fcb53 100644
--- a/components/privacy_sandbox/privacy_sandbox_settings.cc
+++ b/components/privacy_sandbox/privacy_sandbox_settings.cc
@@ -4,12 +4,15 @@
 
 #include "components/privacy_sandbox/privacy_sandbox_settings.h"
 
+#include "base/json/values_util.h"
 #include "base/time/time.h"
 #include "components/content_settings/core/browser/cookie_settings.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/content_settings/core/common/pref_names.h"
 #include "components/prefs/pref_service.h"
+#include "components/prefs/scoped_user_pref_update.h"
 #include "components/privacy_sandbox/privacy_sandbox_prefs.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
 #include "net/cookies/site_for_cookies.h"
 #include "url/gurl.h"
 #include "url/origin.h"
@@ -138,6 +141,84 @@
                                            conversion_origin, cookie_settings);
 }
 
+void PrivacySandboxSettings::SetFledgeJoiningAllowed(
+    const std::string& top_frame_etld_plus1,
+    bool allowed) {
+  DictionaryPrefUpdate scoped_pref_update(
+      pref_service_, prefs::kPrivacySandboxFledgeJoinBlocked);
+  auto* pref_data = scoped_pref_update.Get();
+  DCHECK(pref_data);
+  DCHECK(pref_data->is_dict());
+
+  // Ensure that the provided etld_plus1 actually is an etld+1.
+  auto effective_top_frame_etld_plus1 =
+      net::registry_controlled_domains::GetDomainAndRegistry(
+          top_frame_etld_plus1,
+          net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
+  DCHECK(effective_top_frame_etld_plus1 == top_frame_etld_plus1);
+
+  // Ignore attempts to configure an empty etld+1.
+  if (effective_top_frame_etld_plus1.length() == 0) {
+    NOTREACHED() << "Cannot control FLEDGE joining for empty eTLD+1";
+    return;
+  }
+
+  if (allowed) {
+    // Existence of the key implies blocking, so simply removing the key is
+    // sufficient. If the key wasn't already present, the following is a no-op.
+    pref_data->RemoveKey(effective_top_frame_etld_plus1);
+  } else {
+    // Overriding the creation date for keys which already exist is acceptable.
+    // Time range based deletions are typically started from the current time,
+    // and so this will be more aggressively removed. This decreases the chance
+    // a potentially sensitive website remains in preferences.
+    pref_data->SetKey(effective_top_frame_etld_plus1,
+                      base::TimeToValue(base::Time::Now()));
+  }
+}
+
+void PrivacySandboxSettings::ClearFledgeJoiningAllowedSettings(
+    base::Time start_time,
+    base::Time end_time) {
+  DictionaryPrefUpdate scoped_pref_update(
+      pref_service_, prefs::kPrivacySandboxFledgeJoinBlocked);
+  auto* pref_data = scoped_pref_update.Get();
+  DCHECK(pref_data);
+  DCHECK(pref_data->is_dict());
+
+  // Shortcut for maximum time range deletion
+  if (start_time == base::Time() && end_time == base::Time::Max()) {
+    pref_data->DictClear();
+    return;
+  }
+
+  std::vector<std::string> keys_to_remove;
+  for (auto entry : pref_data->DictItems()) {
+    absl::optional<base::Time> created_time = base::ValueToTime(entry.second);
+    if (created_time.has_value() && start_time <= created_time &&
+        created_time <= end_time) {
+      keys_to_remove.push_back(entry.first);
+    }
+  }
+
+  for (const auto& key : keys_to_remove)
+    pref_data->RemoveKey(key);
+}
+
+bool PrivacySandboxSettings::IsFledgeJoiningAllowed(
+    const url::Origin& top_frame_origin) const {
+  DictionaryPrefUpdate scoped_pref_update(
+      pref_service_, prefs::kPrivacySandboxFledgeJoinBlocked);
+  auto* pref_data = scoped_pref_update.Get();
+  DCHECK(pref_data);
+  DCHECK(pref_data->is_dict());
+  auto top_frame_etld_plus1 =
+      net::registry_controlled_domains::GetDomainAndRegistry(
+          top_frame_origin,
+          net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
+  return !pref_data->FindKey(top_frame_etld_plus1);
+}
+
 bool PrivacySandboxSettings::IsFledgeAllowed(
     const url::Origin& top_frame_origin,
     const GURL& auction_party) {
diff --git a/components/privacy_sandbox/privacy_sandbox_settings.h b/components/privacy_sandbox/privacy_sandbox_settings.h
index efc48b2..c06be5c 100644
--- a/components/privacy_sandbox/privacy_sandbox_settings.h
+++ b/components/privacy_sandbox/privacy_sandbox_settings.h
@@ -83,6 +83,23 @@
                                   const url::Origin& conversion_origin,
                                   const url::Origin& reporting_origin) const;
 
+  // Sets the ability for |top_frame_etld_plus1| to join the profile to interest
+  // groups to |allowed|. This information is stored in preferences, and is made
+  // available to the API via IsFledgeJoiningAllowed(). |top_frame_etld_plus1|
+  // is DCHECK confirmed to be a non-empty, properly formed eTLD+1.
+  void SetFledgeJoiningAllowed(const std::string& top_frame_etld_plus1,
+                               bool allowed);
+
+  // Clears any FLEDGE joining block settings with creation times between
+  // |start_time| and |end_time|.
+  void ClearFledgeJoiningAllowedSettings(base::Time start_time,
+                                         base::Time end_time);
+
+  // Determines whether the user may be joined to FLEDGE interest groups on, or
+  // by, |top_frame_origin|. This is an additional check that must be
+  // combined with the more generic IsFledgeAllowed().
+  bool IsFledgeJoiningAllowed(const url::Origin& top_frame_origin) const;
+
   // Determine whether |auction_party| can register an interest group, or sell /
   // buy in an auction, on |top_frame_origin|.
   bool IsFledgeAllowed(const url::Origin& top_frame_origin,
diff --git a/components/privacy_sandbox/privacy_sandbox_settings_unittest.cc b/components/privacy_sandbox/privacy_sandbox_settings_unittest.cc
index 50fe8ac..bcaad04 100644
--- a/components/privacy_sandbox/privacy_sandbox_settings_unittest.cc
+++ b/components/privacy_sandbox/privacy_sandbox_settings_unittest.cc
@@ -58,6 +58,10 @@
     return privacy_sandbox_settings_.get();
   }
 
+  content::BrowserTaskEnvironment* task_environment() {
+    return &browser_task_environment_;
+  }
+
  private:
   content::BrowserTaskEnvironment browser_task_environment_;
   sync_preferences::TestingPrefServiceSyncable prefs_;
@@ -531,6 +535,94 @@
             privacy_sandbox_settings()->FlocDataAccessibleSince());
 }
 
+TEST_F(PrivacySandboxSettingsTest, FledgeJoiningAllowed) {
+  // Whether or not a site can join a user to an interest group is independent
+  // of any other profile state.
+  privacy_sandbox_test_util::SetupTestState(
+      prefs(), host_content_settings_map(),
+      /*privacy_sandbox_enabled=*/false,
+      /*block_third_party_cookies=*/true,
+      /*default_cookie_setting=*/ContentSetting::CONTENT_SETTING_BLOCK,
+      /*user_cookie_exceptions=*/
+      {{"https://example.com", "*", ContentSetting::CONTENT_SETTING_BLOCK}},
+      /*managed_cookie_setting=*/ContentSetting::CONTENT_SETTING_BLOCK,
+      /*managed_cookie_exceptions=*/
+      {{"https://example.com", "*", ContentSetting::CONTENT_SETTING_BLOCK}});
+  EXPECT_TRUE(privacy_sandbox_settings()->IsFledgeJoiningAllowed(
+      url::Origin::Create(GURL("https://example.com"))));
+
+  // Settings should match at the eTLD + 1 level.
+  privacy_sandbox_settings()->SetFledgeJoiningAllowed("example.com", false);
+
+  EXPECT_FALSE(privacy_sandbox_settings()->IsFledgeJoiningAllowed(
+      url::Origin::Create(GURL("https://subsite.example.com"))));
+  EXPECT_FALSE(privacy_sandbox_settings()->IsFledgeJoiningAllowed(
+      url::Origin::Create(GURL("http://example.com"))));
+  EXPECT_FALSE(privacy_sandbox_settings()->IsFledgeJoiningAllowed(
+      url::Origin::Create(GURL("https://example.com:888"))));
+  EXPECT_FALSE(privacy_sandbox_settings()->IsFledgeJoiningAllowed(
+      url::Origin::Create(GURL("https://example.com"))));
+  EXPECT_TRUE(privacy_sandbox_settings()->IsFledgeJoiningAllowed(
+      url::Origin::Create(GURL("https://example.com.au"))));
+
+  privacy_sandbox_settings()->SetFledgeJoiningAllowed("example.com", true);
+
+  EXPECT_TRUE(privacy_sandbox_settings()->IsFledgeJoiningAllowed(
+      url::Origin::Create(GURL("https://example.com"))));
+  EXPECT_TRUE(privacy_sandbox_settings()->IsFledgeJoiningAllowed(
+      url::Origin::Create(GURL("https://subsite.example.com"))));
+  EXPECT_TRUE(privacy_sandbox_settings()->IsFledgeJoiningAllowed(
+      url::Origin::Create(GURL("http://example.com"))));
+  EXPECT_TRUE(privacy_sandbox_settings()->IsFledgeJoiningAllowed(
+      url::Origin::Create(GURL("https://example.com:888"))));
+  EXPECT_TRUE(privacy_sandbox_settings()->IsFledgeJoiningAllowed(
+      url::Origin::Create(GURL("https://example.com"))));
+  EXPECT_TRUE(privacy_sandbox_settings()->IsFledgeJoiningAllowed(
+      url::Origin::Create(GURL("https://example.com.au"))));
+}
+
+TEST_F(PrivacySandboxSettingsTest, FledgeJoinSettingTimeRangeDeletion) {
+  // Confirm that time range deletions work appropriately for FLEDGE join
+  // settings.
+  privacy_sandbox_settings()->SetFledgeJoiningAllowed("first.com", false);
+  task_environment()->AdvanceClock(base::Hours(1));
+
+  const base::Time kSecondSettingTime = base::Time::Now();
+  privacy_sandbox_settings()->SetFledgeJoiningAllowed("second.com", false);
+
+  task_environment()->AdvanceClock(base::Hours(1));
+  privacy_sandbox_settings()->SetFledgeJoiningAllowed("third.com", false);
+
+  EXPECT_FALSE(privacy_sandbox_settings()->IsFledgeJoiningAllowed(
+      url::Origin::Create(GURL("https://first.com"))));
+  EXPECT_FALSE(privacy_sandbox_settings()->IsFledgeJoiningAllowed(
+      url::Origin::Create(GURL("https://second.com"))));
+  EXPECT_FALSE(privacy_sandbox_settings()->IsFledgeJoiningAllowed(
+      url::Origin::Create(GURL("https://third.com"))));
+
+  // Construct a deletion which only targets the second setting.
+  privacy_sandbox_settings()->ClearFledgeJoiningAllowedSettings(
+      kSecondSettingTime - base::Seconds(1),
+      kSecondSettingTime + base::Seconds(1));
+  EXPECT_FALSE(privacy_sandbox_settings()->IsFledgeJoiningAllowed(
+      url::Origin::Create(GURL("https://first.com"))));
+  EXPECT_TRUE(privacy_sandbox_settings()->IsFledgeJoiningAllowed(
+      url::Origin::Create(GURL("https://second.com"))));
+  EXPECT_FALSE(privacy_sandbox_settings()->IsFledgeJoiningAllowed(
+      url::Origin::Create(GURL("https://third.com"))));
+
+  // Perform a maximmal time range deletion, which should remove the two
+  // remaining settings.
+  privacy_sandbox_settings()->ClearFledgeJoiningAllowedSettings(
+      base::Time(), base::Time::Max());
+  EXPECT_TRUE(privacy_sandbox_settings()->IsFledgeJoiningAllowed(
+      url::Origin::Create(GURL("https://first.com"))));
+  EXPECT_TRUE(privacy_sandbox_settings()->IsFledgeJoiningAllowed(
+      url::Origin::Create(GURL("https://second.com"))));
+  EXPECT_TRUE(privacy_sandbox_settings()->IsFledgeJoiningAllowed(
+      url::Origin::Create(GURL("https://third.com"))));
+}
+
 class PrivacySandboxSettingsTestCookiesClearOnExitTurnedOff
     : public PrivacySandboxSettingsTest {
  public:
diff --git a/components/services/app_service/public/cpp/app_types.cc b/components/services/app_service/public/cpp/app_types.cc
index c330230..62513837 100644
--- a/components/services/app_service/public/cpp/app_types.cc
+++ b/components/services/app_service/public/cpp/app_types.cc
@@ -65,6 +65,39 @@
   }
 }
 
+mojom::AppType ConvertAppTypeToMojomAppType(AppType app_type) {
+  switch (app_type) {
+    case AppType::kUnknown:
+      return apps::mojom::AppType::kUnknown;
+    case AppType::kArc:
+      return apps::mojom::AppType::kArc;
+    case AppType::kBuiltIn:
+      return apps::mojom::AppType::kBuiltIn;
+    case AppType::kCrostini:
+      return apps::mojom::AppType::kCrostini;
+    case AppType::kChromeApp:
+      return apps::mojom::AppType::kChromeApp;
+    case AppType::kWeb:
+      return apps::mojom::AppType::kWeb;
+    case AppType::kMacOs:
+      return apps::mojom::AppType::kMacOs;
+    case AppType::kPluginVm:
+      return apps::mojom::AppType::kPluginVm;
+    case AppType::kStandaloneBrowser:
+      return apps::mojom::AppType::kStandaloneBrowser;
+    case AppType::kRemote:
+      return apps::mojom::AppType::kRemote;
+    case AppType::kBorealis:
+      return apps::mojom::AppType::kBorealis;
+    case AppType::kSystemWeb:
+      return apps::mojom::AppType::kSystemWeb;
+    case AppType::kStandaloneBrowserChromeApp:
+      return apps::mojom::AppType::kStandaloneBrowserChromeApp;
+    case AppType::kExtension:
+      return apps::mojom::AppType::kExtension;
+  }
+}
+
 Readiness ConvertMojomReadinessToReadiness(
     apps::mojom::Readiness mojom_readiness) {
   switch (mojom_readiness) {
diff --git a/components/services/app_service/public/cpp/app_types.h b/components/services/app_service/public/cpp/app_types.h
index c77d76a..de9ea26 100644
--- a/components/services/app_service/public/cpp/app_types.h
+++ b/components/services/app_service/public/cpp/app_types.h
@@ -138,6 +138,9 @@
 AppType ConvertMojomAppTypToAppType(apps::mojom::AppType mojom_app_type);
 
 COMPONENT_EXPORT(APP_TYPES)
+mojom::AppType ConvertAppTypeToMojomAppType(AppType mojom_app_type);
+
+COMPONENT_EXPORT(APP_TYPES)
 Readiness ConvertMojomReadinessToReadiness(
     apps::mojom::Readiness mojom_readiness);
 
diff --git a/components/translate/core/browser/translate_manager_unittest.cc b/components/translate/core/browser/translate_manager_unittest.cc
index b285f28..fea1ccb 100644
--- a/components/translate/core/browser/translate_manager_unittest.cc
+++ b/components/translate/core/browser/translate_manager_unittest.cc
@@ -684,15 +684,15 @@
 
   base::HistogramTester histogram_tester;
   prefs_.SetBoolean(prefs::kOfferTranslateEnabled, true);
-  translate_manager_->GetLanguageState()->LanguageDetermined("zu", true);
+  translate_manager_->GetLanguageState()->LanguageDetermined("en", true);
   network_notifier_.SimulateOnline();
 
-  translate_manager_->InitiateTranslation("zu");
+  translate_manager_->InitiateTranslation("en");
   EXPECT_THAT(histogram_tester.GetAllSamples(kInitiationStatusName),
               ElementsAre(Bucket(metrics::INITIATION_STATUS_SHOW_INFOBAR, 1),
                           Bucket(metrics::INITIATION_STATUS_SHOW_ICON, 1)));
 
-  translate_manager_->TranslatePage("zu", "hi", false);
+  translate_manager_->TranslatePage("en", "hi", false);
 
   // Accept languages should now contain "hi" because the user chose to
   // translate to it once.
@@ -1137,7 +1137,7 @@
 TEST_F(TranslateManagerTest, PredefinedTargetLanguage) {
   PrepareTranslateManager();
   manager_->set_application_locale("en");
-  ASSERT_TRUE(TranslateDownloadManager::IsSupportedLanguage("zu"));
+  ASSERT_TRUE(TranslateDownloadManager::IsSupportedLanguage("en"));
 
   ON_CALL(mock_translate_client_, IsTranslatableURL(GURL::EmptyGURL()))
       .WillByDefault(Return(true));
@@ -1152,19 +1152,19 @@
       "ru",
       translate_manager_->GetLanguageState()->GetPredefinedTargetLanguage());
 
-  translate_manager_->GetLanguageState()->LanguageDetermined("zu", true);
+  translate_manager_->GetLanguageState()->LanguageDetermined("en", true);
 
   EXPECT_CALL(
       mock_translate_client_,
-      ShowTranslateUI(translate::TRANSLATE_STEP_BEFORE_TRANSLATE, "zu", "ru",
+      ShowTranslateUI(translate::TRANSLATE_STEP_BEFORE_TRANSLATE, "en", "ru",
                       TranslateErrors::NONE, /*triggered_from_menu=*/false))
       .WillOnce(Return(true));
 
   base::HistogramTester histogram_tester;
-  translate_manager_->InitiateTranslation("zu");
+  translate_manager_->InitiateTranslation("en");
   EXPECT_THAT(
       histogram_tester.GetAllSamples(kInitiationStatusName),
-      ::testing::Contains(Bucket(
+      ElementsAre(Bucket(
           metrics::INITIATION_STATUS_SHOW_UI_PREDEFINED_TARGET_LANGUAGE, 1)));
 }
 
diff --git a/components/translate/core/browser/translate_prefs_unittest.cc b/components/translate/core/browser/translate_prefs_unittest.cc
index b845330..eed9b5f 100644
--- a/components/translate/core/browser/translate_prefs_unittest.cc
+++ b/components/translate/core/browser/translate_prefs_unittest.cc
@@ -1138,12 +1138,10 @@
   EXPECT_FALSE(translate_prefs_->CanTranslateLanguage(
       &translate_accept_languages, "en"));
 
-  if (!TranslatePrefs::IsDetailedLanguageSettingsEnabled()) {
-    // Blocked languages that are not in accept languages are not blocked.
-    translate_prefs_->BlockLanguage("de");
-    EXPECT_TRUE(translate_prefs_->CanTranslateLanguage(
-        &translate_accept_languages, "de"));
-  }
+  // Blocked languages that are not in accept languages are not blocked.
+  translate_prefs_->BlockLanguage("de");
+  EXPECT_TRUE(translate_prefs_->CanTranslateLanguage(
+      &translate_accept_languages, "de"));
 
 // When the detailed language settings are enabled blocked languages not in
 // accept languages can be translated.
diff --git a/content/app/content_main.cc b/content/app/content_main.cc
index 33e0970..302d32d9 100644
--- a/content/app/content_main.cc
+++ b/content/app/content_main.cc
@@ -358,7 +358,7 @@
     // Note #2: some platforms can directly allocated shared memory in a
     // sandboxed process. The defines below must be in sync with the
     // implementation of mojo::NodeController::CreateSharedBuffer().
-#if !defined(OS_MAC) && !defined(OS_NACL_SFI) && !defined(OS_FUCHSIA)
+#if !defined(OS_MAC) && !defined(OS_NACL) && !defined(OS_FUCHSIA)
     if (sandbox::policy::IsUnsandboxedSandboxType(
             sandbox::policy::SandboxTypeFromCommandLine(
                 *base::CommandLine::ForCurrentProcess()))) {
@@ -375,7 +375,7 @@
       // allocate shared memory.
       mojo::SharedMemoryUtils::InstallBaseHooks();
     }
-#endif  // !defined(OS_MAC) && !defined(OS_NACL_SFI) && !defined(OS_FUCHSIA)
+#endif  // !defined(OS_MAC) && !defined(OS_NACL) && !defined(OS_FUCHSIA)
 
 #if defined(OS_WIN)
     // Route stdio to parent console (if any) or create one.
diff --git a/content/browser/accessibility/browser_accessibility_android.cc b/content/browser/accessibility/browser_accessibility_android.cc
index 9d61258..2b36a75 100644
--- a/content/browser/accessibility/browser_accessibility_android.cc
+++ b/content/browser/accessibility/browser_accessibility_android.cc
@@ -1060,10 +1060,6 @@
 }
 
 std::u16string BrowserAccessibilityAndroid::GetRoleDescription() const {
-  // If an element has an aria-roledescription set, use that value by default.
-  if (HasStringAttribute(ax::mojom::StringAttribute::kRoleDescription))
-    return GetString16Attribute(ax::mojom::StringAttribute::kRoleDescription);
-
   content::ContentClient* content_client = content::GetContentClient();
 
   // As a special case, if we have a heading level return a string like
diff --git a/content/browser/interest_group/ad_auction_service_impl.cc b/content/browser/interest_group/ad_auction_service_impl.cc
index 99b5dda..fd46537f 100644
--- a/content/browser/interest_group/ad_auction_service_impl.cc
+++ b/content/browser/interest_group/ad_auction_service_impl.cc
@@ -336,47 +336,6 @@
   auctions_.insert(std::move(auction));
 }
 
-namespace {
-class FencedFrameURLMappingObserver
-    : public FencedFrameURLMapping::MappingResultObserver {
- public:
-  FencedFrameURLMappingObserver() = default;
-  ~FencedFrameURLMappingObserver() override = default;
-
-  void OnFencedFrameURLMappingComplete(
-      absl::optional<GURL> mapped_url,
-      absl::optional<FencedFrameURLMapping::PendingAdComponentsMap>
-          pending_ad_components_map) override {
-    mapped_url_ = mapped_url;
-    called_ = true;
-  }
-
-  bool called_;
-  absl::optional<GURL> mapped_url_;
-};
-
-}  // namespace
-
-void AdAuctionServiceImpl::DeprecatedGetURLFromURN(
-    const GURL& urn_url,
-    DeprecatedGetURLFromURNCallback callback) {
-  if (!FencedFrameURLMapping::IsValidUrnUuidURL(urn_url)) {
-    std::move(callback).Run(absl::nullopt);
-    return;
-  }
-  FencedFrameURLMappingObserver obs;
-  content::FencedFrameURLMapping& mapping =
-      static_cast<RenderFrameHostImpl*>(render_frame_host())
-          ->GetPage()
-          .fenced_frame_urls_map();
-  // FLEDGE URN URLs should already be mapped, so the observer will be called
-  // synchronously.
-  mapping.ConvertFencedFrameURNToURL(urn_url, &obs);
-  if (!obs.called_)
-    mapping.RemoveObserverForURN(urn_url, &obs);
-  std::move(callback).Run(std::move(obs.mapped_url_));
-}
-
 void AdAuctionServiceImpl::CreateAdRequest(
     blink::mojom::AdRequestConfigPtr config,
     CreateAdRequestCallback callback) {
diff --git a/content/browser/interest_group/ad_auction_service_impl.h b/content/browser/interest_group/ad_auction_service_impl.h
index 72c7d5b..772c57c 100644
--- a/content/browser/interest_group/ad_auction_service_impl.h
+++ b/content/browser/interest_group/ad_auction_service_impl.h
@@ -47,9 +47,6 @@
   void UpdateAdInterestGroups() override;
   void RunAdAuction(blink::mojom::AuctionAdConfigPtr config,
                     RunAdAuctionCallback callback) override;
-  void DeprecatedGetURLFromURN(
-      const GURL& urn_url,
-      DeprecatedGetURLFromURNCallback callback) override;
   void CreateAdRequest(blink::mojom::AdRequestConfigPtr config,
                        CreateAdRequestCallback callback) override;
   void FinalizeAd(const std::string& ads_guid,
diff --git a/content/browser/interest_group/interest_group_browsertest.cc b/content/browser/interest_group/interest_group_browsertest.cc
index 220d49e5..646ca63 100644
--- a/content/browser/interest_group/interest_group_browsertest.cc
+++ b/content/browser/interest_group/interest_group_browsertest.cc
@@ -707,23 +707,10 @@
         static_cast<RenderFrameHostImpl*>(adapter.render_frame_host())
             ->GetPage()
             .fenced_frame_urls_map();
+    absl::optional<FencedFrameURLMapping::PendingAdComponentsMap> ignored;
     fenced_frame_urls_map.ConvertFencedFrameURNToURL(urn_url, observer);
   }
 
-  absl::optional<GURL> ConvertFencedFrameURNToURLInJS(
-      const GURL& urn_url,
-      const absl::optional<ToRenderFrameHost> execution_target =
-          absl::nullopt) {
-    ToRenderFrameHost adapter(execution_target ? *execution_target : shell());
-    EvalJsResult result = EvalJs(adapter, JsReplace(R"(
-      navigator.deprecatedURNToURL($1)
-    )",
-                                                    urn_url));
-    if (!result.error.empty() || result.value.is_none())
-      return absl::nullopt;
-    return GURL(result.ExtractString());
-  }
-
   WebContentsImpl* web_contents() const {
     return static_cast<WebContentsImpl*>(shell()->web_contents());
   }
@@ -3642,10 +3629,8 @@
       TestFencedFrameURLMappingResultObserver observer;
       ConvertFencedFrameURNToURL(*maybe_url, &observer);
       EXPECT_TRUE(observer.mapped_url());
-      absl::optional<GURL> decoded_URL = observer.mapped_url();
-      EXPECT_EQ(decoded_URL, ConvertFencedFrameURNToURLInJS(*maybe_url));
       NavigateIframeAndCheckURL(web_contents(), *maybe_url,
-                                decoded_URL.value_or(GURL()));
+                                *observer.mapped_url());
       return *observer.mapped_url();
     }
     return absl::nullopt;
@@ -4701,12 +4686,6 @@
   }
 }
 
-// navigator.deprecatedURNToURL returns null for an invalid URN.
-IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest, InvalidURN) {
-  GURL invalid_urn("urn:uuid:c36973b5-e5d9-de59-e4c4-364f137b3c7a");
-  EXPECT_EQ(absl::nullopt, ConvertFencedFrameURNToURLInJS(invalid_urn));
-}
-
 }  // namespace
 
 }  // namespace content
diff --git a/content/browser/locks/lock_manager_browsertest.cc b/content/browser/locks/lock_manager_browsertest.cc
index 622efeb..eac1a325 100644
--- a/content/browser/locks/lock_manager_browsertest.cc
+++ b/content/browser/locks/lock_manager_browsertest.cc
@@ -228,8 +228,8 @@
 
 // Verify that content::FeatureObserver is notified that a frame stopped holding
 // locks when it is navigated away.
-// TODO(crbug.com/1286329): Flakes on Linux.
-#if defined(OS_LINUX)
+// TODO(crbug.com/1286329): Flakes on Linux and Chrome OS.
+#if defined(OS_LINUX) || defined(OS_CHROMEOS)
 #define MAYBE_ObserverNavigate DISABLED_ObserverNavigate
 #else
 #define MAYBE_ObserverNavigate ObserverNavigate
diff --git a/content/browser/renderer_host/page_lifecycle_state_manager.cc b/content/browser/renderer_host/page_lifecycle_state_manager.cc
index 909e722..c5ce4922 100644
--- a/content/browser/renderer_host/page_lifecycle_state_manager.cc
+++ b/content/browser/renderer_host/page_lifecycle_state_manager.cc
@@ -255,6 +255,8 @@
   if (acknowledged_state->should_dispatch_pageshow_for_debugging) {
     blink::RecordUMAEventPageShowPersisted(
         blink::EventPageShowPersisted::kYesInBrowserAck);
+    // We have received the ack, no need to track info for failures.
+    persisted_pageshow_timestamp_bug_1234634_.reset();
   }
 
   last_acknowledged_state_ = std::move(acknowledged_state);
diff --git a/content/browser/webui/shared_resources_data_source.cc b/content/browser/webui/shared_resources_data_source.cc
index 0f768fd..e9c5ced 100644
--- a/content/browser/webui/shared_resources_data_source.cc
+++ b/content/browser/webui/shared_resources_data_source.cc
@@ -8,8 +8,6 @@
 
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
-#include "content/browser/resources/media/grit/media_internals_resources.h"
-#include "content/browser/resources/media/grit/media_internals_resources_map.h"
 #include "content/grit/content_resources.h"
 #include "content/grit/content_resources_map.h"
 #include "content/public/common/url_constants.h"
@@ -114,8 +112,6 @@
   AddResources(GetContentResourceIds(), kContentResources,
                kContentResourcesSize, source);
   source->AddResourcePaths(
-      base::make_span(kMediaInternalsResources, kMediaInternalsResourcesSize));
-  source->AddResourcePaths(
       base::make_span(kWebuiResources, kWebuiResourcesSize));
   source->AddResourcePaths(
       base::make_span(kWebuiGeneratedResources, kWebuiGeneratedResourcesSize));
diff --git a/content/renderer/pepper/video_decoder_shim.cc b/content/renderer/pepper/video_decoder_shim.cc
index ce0aa886..bc350e9 100644
--- a/content/renderer/pepper/video_decoder_shim.cc
+++ b/content/renderer/pepper/video_decoder_shim.cc
@@ -119,9 +119,9 @@
   void Stop();
 
  private:
-  void OnInitDone(media::Status status);
+  void OnInitDone(media::DecoderStatus status);
   void DoDecode();
-  void OnDecodeComplete(media::Status status);
+  void OnDecodeComplete(media::DecoderStatus status);
   void OnOutputComplete(scoped_refptr<media::VideoFrame> frame);
   void OnResetComplete();
 
@@ -182,7 +182,7 @@
                           weak_ptr_factory_.GetWeakPtr()),
       base::NullCallback());
 #else
-  OnInitDone(media::StatusCode::kDecoderFailedInitialization);
+  OnInitDone(media::DecoderStatus::Codes::kUnsupportedCodec);
 #endif  // BUILDFLAG(ENABLE_LIBVPX) || BUILDFLAG(ENABLE_FFMPEG_VIDEO_DECODERS)
 }
 
@@ -227,7 +227,7 @@
   // This instance is deleted once we exit this scope.
 }
 
-void VideoDecoderShim::DecoderImpl::OnInitDone(media::Status status) {
+void VideoDecoderShim::DecoderImpl::OnInitDone(media::DecoderStatus status) {
   if (!status.is_ok()) {
     main_task_runner_->PostTask(
         FROM_HERE,
@@ -253,14 +253,15 @@
   pending_decodes_.pop();
 }
 
-void VideoDecoderShim::DecoderImpl::OnDecodeComplete(media::Status status) {
+void VideoDecoderShim::DecoderImpl::OnDecodeComplete(
+    media::DecoderStatus status) {
   DCHECK(awaiting_decoder_);
   awaiting_decoder_ = false;
 
   int32_t result;
   switch (status.code()) {
-    case media::StatusCode::kOk:
-    case media::StatusCode::kAborted:
+    case media::DecoderStatus::Codes::kOk:
+    case media::DecoderStatus::Codes::kAborted:
       result = PP_OK;
       break;
     default:
diff --git a/content/test/data/accessibility/aria/aria-region-expected-android-external.txt b/content/test/data/accessibility/aria/aria-region-expected-android-external.txt
index 2c083bc..cbf1880 100644
--- a/content/test/data/accessibility/aria/aria-region-expected-android-external.txt
+++ b/content/test/data/accessibility/aria/aria-region-expected-android-external.txt
@@ -5,4 +5,4 @@
 ++View text:"Named ARIA region#2 gets the region role." actions:[AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="region", roleDescription="region"]
 ++++TextView text:"Named ARIA region#2 gets the region role." viewIdResName:"region-name" actions:[AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="genericContainer"]
 ++View text:"Named region" actions:[AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="region", roleDescription="region"]
-++View text:"An aria-rolescription works on a nameless role=region." actions:[AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="region", roleDescription="Regioneque region"]
\ No newline at end of file
+++View text:"An aria-rolescription works on a nameless role=region." actions:[AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="region", roleDescription="region"]
\ No newline at end of file
diff --git a/content/test/data/accessibility/aria/aria-region-expected-android.txt b/content/test/data/accessibility/aria/aria-region-expected-android.txt
index b9fe622..74b5fde 100644
--- a/content/test/data/accessibility/aria/aria-region-expected-android.txt
+++ b/content/test/data/accessibility/aria/aria-region-expected-android.txt
@@ -5,4 +5,4 @@
 ++android.view.View role_description='region' name='Named ARIA region#2 gets the region role.'
 ++++android.widget.TextView name='Named ARIA region#2 gets the region role.'
 ++android.view.View role_description='region' name='Named region'
-++android.view.View role_description='Regioneque region' name='An aria-rolescription works on a nameless role=region.'
\ No newline at end of file
+++android.view.View role_description='region' name='An aria-rolescription works on a nameless role=region.'
\ No newline at end of file
diff --git a/content/test/data/accessibility/aria/aria-roledescription-expected-android-external.txt b/content/test/data/accessibility/aria/aria-roledescription-expected-android-external.txt
index 8db1948..fc1ef7e9 100644
--- a/content/test/data/accessibility/aria/aria-roledescription-expected-android-external.txt
+++ b/content/test/data/accessibility/aria/aria-roledescription-expected-android-external.txt
@@ -1,7 +1,7 @@
 WebView focusable focused scrollable actions:[CLEAR_FOCUS, AX_FOCUS] bundle:[chromeRole="rootWebArea"]
 ++Button text:"Native button" clickable focusable actions:[FOCUS, CLICK, AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="button", clickableScore="300", roleDescription="button"]
 ++Button text:"ARIA button" clickable actions:[CLICK, AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="button", roleDescription="button"]
-++Button text:"Clicky button" clickable actions:[CLICK, AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="button", roleDescription="Clicky"]
+++Button text:"Clicky button" clickable actions:[CLICK, AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="button", roleDescription="button"]
 ++TextView text:"foo" actions:[AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="genericContainer"]
 ++TextView text:"bar" focusable actions:[FOCUS, AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="genericContainer"]
-++TextView text:"baz" actions:[AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="paragraph", roleDescription="Texty"]
\ No newline at end of file
+++TextView text:"baz" actions:[AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="paragraph"]
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/svg-symbol-with-role-expected-android-external.txt b/content/test/data/accessibility/html/svg-symbol-with-role-expected-android-external.txt
index 959ced7..0bb22b2 100644
--- a/content/test/data/accessibility/html/svg-symbol-with-role-expected-android-external.txt
+++ b/content/test/data/accessibility/html/svg-symbol-with-role-expected-android-external.txt
@@ -1,5 +1,5 @@
 WebView focusable focused scrollable actions:[CLEAR_FOCUS, AX_FOCUS] bundle:[chromeRole="rootWebArea", hasImage="true"]
 ++View actions:[AX_FOCUS] bundle:[chromeRole="genericContainer", hasImage="true"]
 ++++Image actions:[AX_FOCUS] bundle:[chromeRole="svgRoot", hasImage="true", roleDescription="graphic"]
-++++++Button text:"Click me!" viewIdResName:"myRect" clickable actions:[CLICK, AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="button", roleDescription="fancy button"]
-++++++Button text:"Click me!" viewIdResName:"myRect" clickable actions:[CLICK, AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="button", roleDescription="fancy button"]
\ No newline at end of file
+++++++Button text:"Click me!" viewIdResName:"myRect" clickable actions:[CLICK, AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="button", roleDescription="button"]
+++++++Button text:"Click me!" viewIdResName:"myRect" clickable actions:[CLICK, AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="button", roleDescription="button"]
\ No newline at end of file
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
index a2443284..e3e5ac8 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
@@ -561,7 +561,8 @@
 crbug.com/angleproject/6430 [ mac passthrough angle-metal apple-angle-metal-renderer:-apple-m1 ] deqp/functional/gles3/texturespecification/basic_copyteximage2d.html [ Failure ]
 crbug.com/angleproject/6430 [ mac passthrough angle-metal apple-angle-metal-renderer:-apple-m1 ] deqp/functional/gles3/texturespecification/basic_copytexsubimage2d.html [ Failure ]
 # Post Python 3 conversion: crbug.com/1266604
-crbug.com/1271941 [ mac passthrough angle-metal apple-angle-metal-renderer:-apple-m1 ] conformance/ogles/GL/* [ RetryOnFailure ]
+# conformance/ogles/GL/* are too flaky to be retried.
+crbug.com/1271941 [ mac passthrough angle-metal apple-angle-metal-renderer:-apple-m1 ] conformance/ogles/GL/* [ Failure ]
 crbug.com/1271941 [ mac passthrough angle-metal apple-angle-metal-renderer:-apple-m1 ] conformance/textures/canvas_sub_rectangle/tex-2d-rgba-rgba-unsigned_short_5_5_5_1.html [ RetryOnFailure ]
 crbug.com/1271941 [ mac passthrough angle-metal apple-angle-metal-renderer:-apple-m1 ] deqp/functional/gles3/clipping.html [ Failure ]
 crbug.com/1271941 [ mac passthrough angle-metal apple-angle-metal-renderer:-apple-m1 ] deqp/functional/gles3/fborender/shared_colorbuffer_00.html [ Failure ]
diff --git a/extensions/browser/api/app_runtime/app_runtime_api.cc b/extensions/browser/api/app_runtime/app_runtime_api.cc
index 62031cb..3087cbd 100644
--- a/extensions/browser/api/app_runtime/app_runtime_api.cc
+++ b/extensions/browser/api/app_runtime/app_runtime_api.cc
@@ -114,17 +114,19 @@
   ASSERT_ENUM_EQUAL(kSourceArc, SOURCE_ARC);
   ASSERT_ENUM_EQUAL(kSourceIntentUrl, SOURCE_INTENT_URL);
 
-  // We don't allow extensions to launch an app specifying RunOnOSLogin
-  // or ProtocolHandler as the source. In this case we map it to
-  // SOURCE_CHROME_INTERNAL.
+  // We don't allow extensions to launch an app specifying RunOnOSLogin,
+  // ProtocolHandler or Reparenting as the source. In this case we map
+  // it to SOURCE_CHROME_INTERNAL.
   if (source == extensions::AppLaunchSource::kSourceRunOnOsLogin ||
-      source == extensions::AppLaunchSource::kSourceProtocolHandler)
+      source == extensions::AppLaunchSource::kSourceProtocolHandler ||
+      source == extensions::AppLaunchSource::kSourceReparenting)
     source = extensions::AppLaunchSource::kSourceChromeInternal;
 
-  // The +2 accounts for kSourceRunOnOsLogin and kSourceProtocolHandler not
-  // having a corresponding entry in app_runtime::LaunchSource.
+  // The +3 accounts for kSourceRunOnOsLogin, kSourceProtocolHandler and
+  // kSourceReparenting not having a corresponding entry in
+  // app_runtime::LaunchSource.
   static_assert(static_cast<int>(extensions::AppLaunchSource::kMaxValue) ==
-                    app_runtime::LaunchSource::LAUNCH_SOURCE_LAST + 2,
+                    app_runtime::LaunchSource::LAUNCH_SOURCE_LAST + 3,
                 "");
 
   return static_cast<app_runtime::LaunchSource>(source);
diff --git a/extensions/browser/api/networking_private/networking_private_api.cc b/extensions/browser/api/networking_private/networking_private_api.cc
index 57156a8..762a0fa9 100644
--- a/extensions/browser/api/networking_private/networking_private_api.cc
+++ b/extensions/browser/api/networking_private/networking_private_api.cc
@@ -11,6 +11,7 @@
 #include "base/callback_helpers.h"
 #include "base/cxx17_backports.h"
 #include "base/strings/string_util.h"
+#include "base/values.h"
 #include "build/chromeos_buildflags.h"
 #include "components/onc/onc_constants.h"
 #include "extensions/browser/api/extensions_api_client.h"
@@ -223,11 +224,10 @@
   return did_respond() ? AlreadyResponded() : RespondLater();
 }
 
-void NetworkingPrivateGetStateFunction::Success(
-    std::unique_ptr<base::DictionaryValue> result) {
-  FilterProperties(result.get(), PropertiesType::GET, extension(),
+void NetworkingPrivateGetStateFunction::Success(base::Value result) {
+  FilterProperties(&result, PropertiesType::GET, extension(),
                    source_context_type(), source_url());
-  Respond(OneArgument(base::Value::FromUniquePtrValue(std::move(result))));
+  Respond(OneArgument(std::move(result)));
 }
 
 void NetworkingPrivateGetStateFunction::Failure(const std::string& error) {
@@ -247,11 +247,11 @@
       private_api::SetProperties::Params::Create(args());
   EXTENSION_FUNCTION_VALIDATE(params);
 
-  std::unique_ptr<base::DictionaryValue> properties_dict(
-      params->properties.ToValue());
+  base::Value properties_dict(
+      std::move(*params->properties.ToValue().release()));
 
   std::vector<std::string> not_allowed_properties =
-      FilterProperties(properties_dict.get(), PropertiesType::SET, extension(),
+      FilterProperties(&properties_dict, PropertiesType::SET, extension(),
                        source_context_type(), source_url());
   if (!not_allowed_properties.empty())
     return RespondNow(Error(InvalidPropertiesError(not_allowed_properties)));
@@ -296,11 +296,11 @@
     return RespondNow(Error(networking_private::kErrorAccessToSharedConfig));
   }
 
-  std::unique_ptr<base::DictionaryValue> properties_dict(
-      params->properties.ToValue());
+  base::Value properties_dict(
+      std::move(*params->properties.ToValue().release()));
 
   std::vector<std::string> not_allowed_properties =
-      FilterProperties(properties_dict.get(), PropertiesType::SET, extension(),
+      FilterProperties(&properties_dict, PropertiesType::SET, extension(),
                        source_context_type(), source_url());
   if (!not_allowed_properties.empty())
     return RespondNow(Error(InvalidPropertiesError(not_allowed_properties)));
@@ -841,13 +841,11 @@
 
 ExtensionFunction::ResponseAction
 NetworkingPrivateGetGlobalPolicyFunction::Run() {
-  std::unique_ptr<base::DictionaryValue> policy_dict(
-      GetDelegate(browser_context())->GetGlobalPolicy());
-  DCHECK(policy_dict);
+  base::Value policy_dict(GetDelegate(browser_context())->GetGlobalPolicy());
   // private_api::GlobalPolicy is a subset of the global policy dictionary
   // (by definition), so use the api setter/getter to generate the subset.
   std::unique_ptr<private_api::GlobalPolicy> policy(
-      private_api::GlobalPolicy::FromValue(*policy_dict));
+      private_api::GlobalPolicy::FromValue(policy_dict));
   DCHECK(policy);
   return RespondNow(
       ArgumentList(private_api::GetGlobalPolicy::Results::Create(*policy)));
@@ -866,11 +864,9 @@
     return RespondNow(Error(kPrivateOnlyError));
   }
 
-  std::unique_ptr<base::DictionaryValue> certificate_lists(
+  base::Value certificate_lists(
       GetDelegate(browser_context())->GetCertificateLists());
-  DCHECK(certificate_lists);
-  return RespondNow(OneArgument(
-      base::Value::FromUniquePtrValue(std::move(certificate_lists))));
+  return RespondNow(OneArgument(std::move(certificate_lists)));
 }
 
 }  // namespace extensions
diff --git a/extensions/browser/api/networking_private/networking_private_api.h b/extensions/browser/api/networking_private/networking_private_api.h
index e44c016..4e78a50e 100644
--- a/extensions/browser/api/networking_private/networking_private_api.h
+++ b/extensions/browser/api/networking_private/networking_private_api.h
@@ -31,7 +31,7 @@
 // Implements the chrome.networkingPrivate.getProperties method.
 class NetworkingPrivateGetPropertiesFunction : public ExtensionFunction {
  public:
-  NetworkingPrivateGetPropertiesFunction() {}
+  NetworkingPrivateGetPropertiesFunction() = default;
 
   NetworkingPrivateGetPropertiesFunction(
       const NetworkingPrivateGetPropertiesFunction&) = delete;
@@ -55,7 +55,7 @@
 // Implements the chrome.networkingPrivate.getManagedProperties method.
 class NetworkingPrivateGetManagedPropertiesFunction : public ExtensionFunction {
  public:
-  NetworkingPrivateGetManagedPropertiesFunction() {}
+  NetworkingPrivateGetManagedPropertiesFunction() = default;
 
   NetworkingPrivateGetManagedPropertiesFunction(
       const NetworkingPrivateGetManagedPropertiesFunction&) = delete;
@@ -79,7 +79,7 @@
 // Implements the chrome.networkingPrivate.getState method.
 class NetworkingPrivateGetStateFunction : public ExtensionFunction {
  public:
-  NetworkingPrivateGetStateFunction() {}
+  NetworkingPrivateGetStateFunction() = default;
 
   NetworkingPrivateGetStateFunction(const NetworkingPrivateGetStateFunction&) =
       delete;
@@ -96,14 +96,14 @@
   ResponseAction Run() override;
 
  private:
-  void Success(std::unique_ptr<base::DictionaryValue> result);
+  void Success(base::Value result);
   void Failure(const std::string& error);
 };
 
 // Implements the chrome.networkingPrivate.setProperties method.
 class NetworkingPrivateSetPropertiesFunction : public ExtensionFunction {
  public:
-  NetworkingPrivateSetPropertiesFunction() {}
+  NetworkingPrivateSetPropertiesFunction() = default;
 
   NetworkingPrivateSetPropertiesFunction(
       const NetworkingPrivateSetPropertiesFunction&) = delete;
@@ -127,7 +127,7 @@
 // Implements the chrome.networkingPrivate.createNetwork method.
 class NetworkingPrivateCreateNetworkFunction : public ExtensionFunction {
  public:
-  NetworkingPrivateCreateNetworkFunction() {}
+  NetworkingPrivateCreateNetworkFunction() = default;
 
   NetworkingPrivateCreateNetworkFunction(
       const NetworkingPrivateCreateNetworkFunction&) = delete;
@@ -151,7 +151,7 @@
 // Implements the chrome.networkingPrivate.createNetwork method.
 class NetworkingPrivateForgetNetworkFunction : public ExtensionFunction {
  public:
-  NetworkingPrivateForgetNetworkFunction() {}
+  NetworkingPrivateForgetNetworkFunction() = default;
 
   NetworkingPrivateForgetNetworkFunction(
       const NetworkingPrivateForgetNetworkFunction&) = delete;
@@ -175,7 +175,7 @@
 // Implements the chrome.networkingPrivate.getNetworks method.
 class NetworkingPrivateGetNetworksFunction : public ExtensionFunction {
  public:
-  NetworkingPrivateGetNetworksFunction() {}
+  NetworkingPrivateGetNetworksFunction() = default;
 
   NetworkingPrivateGetNetworksFunction(
       const NetworkingPrivateGetNetworksFunction&) = delete;
@@ -199,7 +199,7 @@
 // Implements the chrome.networkingPrivate.getVisibleNetworks method.
 class NetworkingPrivateGetVisibleNetworksFunction : public ExtensionFunction {
  public:
-  NetworkingPrivateGetVisibleNetworksFunction() {}
+  NetworkingPrivateGetVisibleNetworksFunction() = default;
 
   NetworkingPrivateGetVisibleNetworksFunction(
       const NetworkingPrivateGetVisibleNetworksFunction&) = delete;
@@ -224,7 +224,7 @@
 class NetworkingPrivateGetEnabledNetworkTypesFunction
     : public ExtensionFunction {
  public:
-  NetworkingPrivateGetEnabledNetworkTypesFunction() {}
+  NetworkingPrivateGetEnabledNetworkTypesFunction() = default;
 
   NetworkingPrivateGetEnabledNetworkTypesFunction(
       const NetworkingPrivateGetEnabledNetworkTypesFunction&) = delete;
@@ -244,7 +244,7 @@
 // Implements the chrome.networkingPrivate.getDeviceStates method.
 class NetworkingPrivateGetDeviceStatesFunction : public ExtensionFunction {
  public:
-  NetworkingPrivateGetDeviceStatesFunction() {}
+  NetworkingPrivateGetDeviceStatesFunction() = default;
 
   NetworkingPrivateGetDeviceStatesFunction(
       const NetworkingPrivateGetDeviceStatesFunction&) = delete;
@@ -264,7 +264,7 @@
 // Implements the chrome.networkingPrivate.enableNetworkType method.
 class NetworkingPrivateEnableNetworkTypeFunction : public ExtensionFunction {
  public:
-  NetworkingPrivateEnableNetworkTypeFunction() {}
+  NetworkingPrivateEnableNetworkTypeFunction() = default;
 
   NetworkingPrivateEnableNetworkTypeFunction(
       const NetworkingPrivateEnableNetworkTypeFunction&) = delete;
@@ -284,7 +284,7 @@
 // Implements the chrome.networkingPrivate.disableNetworkType method.
 class NetworkingPrivateDisableNetworkTypeFunction : public ExtensionFunction {
  public:
-  NetworkingPrivateDisableNetworkTypeFunction() {}
+  NetworkingPrivateDisableNetworkTypeFunction() = default;
 
   NetworkingPrivateDisableNetworkTypeFunction(
       const NetworkingPrivateDisableNetworkTypeFunction&) = delete;
@@ -304,7 +304,7 @@
 // Implements the chrome.networkingPrivate.requestNetworkScan method.
 class NetworkingPrivateRequestNetworkScanFunction : public ExtensionFunction {
  public:
-  NetworkingPrivateRequestNetworkScanFunction() {}
+  NetworkingPrivateRequestNetworkScanFunction() = default;
 
   NetworkingPrivateRequestNetworkScanFunction(
       const NetworkingPrivateRequestNetworkScanFunction&) = delete;
@@ -324,7 +324,7 @@
 // Implements the chrome.networkingPrivate.startConnect method.
 class NetworkingPrivateStartConnectFunction : public ExtensionFunction {
  public:
-  NetworkingPrivateStartConnectFunction() {}
+  NetworkingPrivateStartConnectFunction() = default;
 
   NetworkingPrivateStartConnectFunction(
       const NetworkingPrivateStartConnectFunction&) = delete;
@@ -348,7 +348,7 @@
 // Implements the chrome.networkingPrivate.startDisconnect method.
 class NetworkingPrivateStartDisconnectFunction : public ExtensionFunction {
  public:
-  NetworkingPrivateStartDisconnectFunction() {}
+  NetworkingPrivateStartDisconnectFunction() = default;
 
   NetworkingPrivateStartDisconnectFunction(
       const NetworkingPrivateStartDisconnectFunction&) = delete;
@@ -372,7 +372,7 @@
 // Implements the chrome.networkingPrivate.startActivate method.
 class NetworkingPrivateStartActivateFunction : public ExtensionFunction {
  public:
-  NetworkingPrivateStartActivateFunction() {}
+  NetworkingPrivateStartActivateFunction() = default;
 
   NetworkingPrivateStartActivateFunction(
       const NetworkingPrivateStartActivateFunction&) = delete;
@@ -396,7 +396,7 @@
 class NetworkingPrivateGetCaptivePortalStatusFunction
     : public ExtensionFunction {
  public:
-  NetworkingPrivateGetCaptivePortalStatusFunction() {}
+  NetworkingPrivateGetCaptivePortalStatusFunction() = default;
 
   NetworkingPrivateGetCaptivePortalStatusFunction(
       const NetworkingPrivateGetCaptivePortalStatusFunction&) = delete;
@@ -419,7 +419,7 @@
 
 class NetworkingPrivateUnlockCellularSimFunction : public ExtensionFunction {
  public:
-  NetworkingPrivateUnlockCellularSimFunction() {}
+  NetworkingPrivateUnlockCellularSimFunction() = default;
 
   NetworkingPrivateUnlockCellularSimFunction(
       const NetworkingPrivateUnlockCellularSimFunction&) = delete;
@@ -442,7 +442,7 @@
 
 class NetworkingPrivateSetCellularSimStateFunction : public ExtensionFunction {
  public:
-  NetworkingPrivateSetCellularSimStateFunction() {}
+  NetworkingPrivateSetCellularSimStateFunction() = default;
 
   NetworkingPrivateSetCellularSimStateFunction(
       const NetworkingPrivateSetCellularSimStateFunction&) = delete;
@@ -466,7 +466,7 @@
 class NetworkingPrivateSelectCellularMobileNetworkFunction
     : public ExtensionFunction {
  public:
-  NetworkingPrivateSelectCellularMobileNetworkFunction() {}
+  NetworkingPrivateSelectCellularMobileNetworkFunction() = default;
 
   NetworkingPrivateSelectCellularMobileNetworkFunction(
       const NetworkingPrivateSelectCellularMobileNetworkFunction&) = delete;
@@ -489,7 +489,7 @@
 
 class NetworkingPrivateGetGlobalPolicyFunction : public ExtensionFunction {
  public:
-  NetworkingPrivateGetGlobalPolicyFunction() {}
+  NetworkingPrivateGetGlobalPolicyFunction() = default;
 
   NetworkingPrivateGetGlobalPolicyFunction(
       const NetworkingPrivateGetGlobalPolicyFunction&) = delete;
@@ -508,7 +508,7 @@
 
 class NetworkingPrivateGetCertificateListsFunction : public ExtensionFunction {
  public:
-  NetworkingPrivateGetCertificateListsFunction() {}
+  NetworkingPrivateGetCertificateListsFunction() = default;
 
   NetworkingPrivateGetCertificateListsFunction(
       const NetworkingPrivateGetCertificateListsFunction&) = delete;
diff --git a/extensions/browser/api/networking_private/networking_private_chromeos.cc b/extensions/browser/api/networking_private/networking_private_chromeos.cc
index e3e36be..daf8dc3d 100644
--- a/extensions/browser/api/networking_private/networking_private_chromeos.cc
+++ b/extensions/browser/api/networking_private/networking_private_chromeos.cc
@@ -339,16 +339,16 @@
     return;
   }
 
-  std::unique_ptr<base::DictionaryValue> network_properties =
-      chromeos::network_util::TranslateNetworkStateToONC(network_state);
-  AppendThirdPartyProviderName(network_properties.get());
+  base::Value network_properties = base::Value::FromUniquePtrValue(
+      chromeos::network_util::TranslateNetworkStateToONC(network_state));
+  AppendThirdPartyProviderName(&network_properties);
 
   std::move(success_callback).Run(std::move(network_properties));
 }
 
 void NetworkingPrivateChromeOS::SetProperties(
     const std::string& guid,
-    std::unique_ptr<base::DictionaryValue> properties,
+    base::Value properties,
     bool allow_set_shared_config,
     VoidCallback success_callback,
     FailureCallback failure_callback) {
@@ -383,7 +383,8 @@
   NET_LOG(USER) << "networkingPrivate.setProperties for: "
                 << NetworkId(network);
   GetManagedConfigurationHandler()->SetProperties(
-      network->path(), *properties, std::move(success_callback),
+      network->path(), static_cast<base::DictionaryValue&>(properties),
+      std::move(success_callback),
       base::BindOnce(&NetworkHandlerFailureCallback,
                      std::move(failure_callback)));
 }
@@ -397,7 +398,7 @@
 
 void NetworkingPrivateChromeOS::CreateNetwork(
     bool shared,
-    std::unique_ptr<base::DictionaryValue> properties,
+    base::Value properties,
     StringCallback success_callback,
     FailureCallback failure_callback) {
   std::string user_id_hash, error;
@@ -409,10 +410,10 @@
   }
 
   const std::string guid =
-      GetStringFromDictionary(*properties, ::onc::network_config::kGUID);
+      GetStringFromDictionary(properties, ::onc::network_config::kGUID);
   NET_LOG(USER) << "networkingPrivate.CreateNetwork. GUID=" << guid;
   GetManagedConfigurationHandler()->CreateConfiguration(
-      user_id_hash, *properties,
+      user_id_hash, static_cast<base::DictionaryValue&>(properties),
       base::BindOnce(&NetworkHandlerCreateCallback,
                      std::move(success_callback)),
       base::BindOnce(&NetworkHandlerFailureCallback,
@@ -721,19 +722,17 @@
   return device_state_list;
 }
 
-std::unique_ptr<base::DictionaryValue>
-NetworkingPrivateChromeOS::GetGlobalPolicy() {
-  auto result = std::make_unique<base::DictionaryValue>();
+base::Value NetworkingPrivateChromeOS::GetGlobalPolicy() {
+  base::Value result(base::Value::Type::DICTIONARY);
   const base::DictionaryValue* global_network_config =
       GetManagedConfigurationHandler()->GetGlobalConfigFromPolicy(
           std::string() /* no username hash, device policy */);
   if (global_network_config)
-    result->MergeDictionary(global_network_config);
+    result.MergeDictionary(global_network_config);
   return result;
 }
 
-std::unique_ptr<base::DictionaryValue>
-NetworkingPrivateChromeOS::GetCertificateLists() {
+base::Value NetworkingPrivateChromeOS::GetCertificateLists() {
   private_api::CertificateLists result;
   const std::vector<NetworkCertificateHandler::Certificate>& server_cas =
       NetworkHandler::Get()
@@ -750,7 +749,7 @@
   for (const auto& cert : user_certs)
     result.user_certificates.push_back(GetCertDictionary(cert));
 
-  return result.ToValue();
+  return base::Value::FromUniquePtrValue(result.ToValue());
 }
 
 bool NetworkingPrivateChromeOS::EnableNetworkType(const std::string& type) {
diff --git a/extensions/browser/api/networking_private/networking_private_chromeos.h b/extensions/browser/api/networking_private/networking_private_chromeos.h
index 52a2c1bb..3ebb677 100644
--- a/extensions/browser/api/networking_private/networking_private_chromeos.h
+++ b/extensions/browser/api/networking_private/networking_private_chromeos.h
@@ -41,12 +41,12 @@
                 DictionaryCallback success_callback,
                 FailureCallback failure_callback) override;
   void SetProperties(const std::string& guid,
-                     std::unique_ptr<base::DictionaryValue> properties,
+                     base::Value properties,
                      bool allow_set_shared_config,
                      VoidCallback success_callback,
                      FailureCallback failure_callback) override;
   void CreateNetwork(bool shared,
-                     std::unique_ptr<base::DictionaryValue> properties,
+                     base::Value properties,
                      StringCallback success_callback,
                      FailureCallback failure_callback) override;
   void ForgetNetwork(const std::string& guid,
@@ -89,8 +89,8 @@
                                    FailureCallback failure_callback) override;
   base::Value GetEnabledNetworkTypes() override;
   std::unique_ptr<DeviceStateList> GetDeviceStateList() override;
-  std::unique_ptr<base::DictionaryValue> GetGlobalPolicy() override;
-  std::unique_ptr<base::DictionaryValue> GetCertificateLists() override;
+  base::Value GetGlobalPolicy() override;
+  base::Value GetCertificateLists() override;
   bool EnableNetworkType(const std::string& type) override;
   bool DisableNetworkType(const std::string& type) override;
   bool RequestScan(const std::string& type) override;
diff --git a/extensions/browser/api/networking_private/networking_private_delegate.h b/extensions/browser/api/networking_private/networking_private_delegate.h
index 30ce0a5..3d605d7 100644
--- a/extensions/browser/api/networking_private/networking_private_delegate.h
+++ b/extensions/browser/api/networking_private/networking_private_delegate.h
@@ -24,8 +24,7 @@
 // networking_private.idl for descriptions of the expected inputs and results.
 class NetworkingPrivateDelegate : public KeyedService {
  public:
-  using DictionaryCallback =
-      base::OnceCallback<void(std::unique_ptr<base::DictionaryValue>)>;
+  using DictionaryCallback = base::OnceCallback<void(base::Value)>;
   using VoidCallback = base::OnceCallback<void()>;
   using BoolCallback = base::OnceCallback<void(bool)>;
   using StringCallback = base::OnceCallback<void(const std::string&)>;
@@ -78,12 +77,12 @@
                         DictionaryCallback success_callback,
                         FailureCallback failure_callback) = 0;
   virtual void SetProperties(const std::string& guid,
-                             std::unique_ptr<base::DictionaryValue> properties,
+                             base::Value properties,
                              bool allow_set_shared_config,
                              VoidCallback success_callback,
                              FailureCallback failure_callback) = 0;
   virtual void CreateNetwork(bool shared,
-                             std::unique_ptr<base::DictionaryValue> properties,
+                             base::Value properties,
                              StringCallback success_callback,
                              FailureCallback failure_callback) = 0;
   virtual void ForgetNetwork(const std::string& guid,
@@ -138,10 +137,10 @@
   // dictionary is expected to be a superset of the networkingPrivate
   // GlobalPolicy dictionary. Any properties not in GlobalPolicy will be
   // ignored.
-  virtual std::unique_ptr<base::DictionaryValue> GetGlobalPolicy() = 0;
+  virtual base::Value GetGlobalPolicy() = 0;
 
   // Returns a dictionary of certificate lists.
-  virtual std::unique_ptr<base::DictionaryValue> GetCertificateLists() = 0;
+  virtual base::Value GetCertificateLists() = 0;
 
   // Returns true if the ONC network type |type| is enabled.
   virtual bool EnableNetworkType(const std::string& type) = 0;
diff --git a/extensions/browser/api/networking_private/networking_private_linux.cc b/extensions/browser/api/networking_private/networking_private_linux.cc
index 80e65de..8a90826d 100644
--- a/extensions/browser/api/networking_private/networking_private_linux.cc
+++ b/extensions/browser/api/networking_private/networking_private_linux.cc
@@ -6,6 +6,7 @@
 
 #include <stddef.h>
 
+#include <memory>
 #include <string>
 #include <utility>
 
@@ -15,6 +16,7 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/strings/string_split.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
 #include "components/onc/onc_constants.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
@@ -85,7 +87,7 @@
   auto network_list = std::make_unique<base::ListValue>();
 
   for (const auto& network : network_map) {
-    network_list->Append(network.second->CreateDeepCopy());
+    network_list->Append(network.second.Clone());
   }
 
   return network_list;
@@ -125,21 +127,21 @@
 // from the |dbus_thread_|.
 void GetCachedNetworkPropertiesCallback(
     std::unique_ptr<std::string> error,
-    std::unique_ptr<base::DictionaryValue> properties,
+    std::unique_ptr<base::Value> properties,
     NetworkingPrivateDelegate::DictionaryCallback success_callback,
     NetworkingPrivateDelegate::FailureCallback failure_callback) {
   if (!error->empty()) {
     std::move(failure_callback).Run(*error);
     return;
   }
-  std::move(success_callback).Run(std::move(properties));
+  std::move(success_callback).Run(std::move(*properties));
 }
 
 // Fires the appropriate callback when the network properties are returned
 // from the |dbus_thread_|.
 void GetCachedNetworkPropertiesResultCallback(
     std::unique_ptr<std::string> error,
-    std::unique_ptr<base::DictionaryValue> properties,
+    std::unique_ptr<base::Value> properties,
     NetworkingPrivateDelegate::PropertiesCallback callback) {
   if (!error->empty()) {
     LOG(ERROR) << "GetCachedNetworkProperties failed: " << *error;
@@ -193,7 +195,7 @@
     LOG(ERROR) << "Platform does not support NetworkManager over DBUS";
   }
 
-  network_map_.reset(new NetworkMap());
+  network_map_ = std::make_unique<NetworkMap>();
 }
 
 bool NetworkingPrivateLinux::CheckNetworkManagerSupported() {
@@ -210,17 +212,16 @@
   }
 
   std::unique_ptr<std::string> error(new std::string);
-  std::unique_ptr<base::DictionaryValue> network_properties(
-      new base::DictionaryValue);
+  auto network_properties =
+      std::make_unique<base::Value>(base::Value::Type::DICTIONARY);
 
   // Runs GetCachedNetworkProperties on |dbus_thread|.
   std::string* error_ptr = error.get();
-  base::DictionaryValue* network_prop_ptr = network_properties.get();
   dbus_thread_.task_runner()->PostTaskAndReply(
       FROM_HERE,
       base::BindOnce(&NetworkingPrivateLinux::GetCachedNetworkProperties,
                      base::Unretained(this), guid,
-                     base::Unretained(network_prop_ptr),
+                     base::Unretained(network_properties.get()),
                      base::Unretained(error_ptr)),
       base::BindOnce(&GetCachedNetworkPropertiesResultCallback,
                      std::move(error), std::move(network_properties),
@@ -243,27 +244,25 @@
   }
 
   std::unique_ptr<std::string> error(new std::string);
-  std::unique_ptr<base::DictionaryValue> network_properties(
-      new base::DictionaryValue);
+  auto network_properties =
+      std::make_unique<base::Value>(base::Value::Type::DICTIONARY);
 
   // Runs GetCachedNetworkProperties on |dbus_thread|.
   std::string* error_ptr = error.get();
-  base::DictionaryValue* network_prop_ptr = network_properties.get();
   dbus_thread_.task_runner()->PostTaskAndReply(
       FROM_HERE,
       base::BindOnce(&NetworkingPrivateLinux::GetCachedNetworkProperties,
                      base::Unretained(this), guid,
-                     base::Unretained(network_prop_ptr),
+                     base::Unretained(network_properties.get()),
                      base::Unretained(error_ptr)),
       base::BindOnce(&GetCachedNetworkPropertiesCallback, std::move(error),
                      std::move(network_properties), std::move(success_callback),
                      std::move(failure_callback)));
 }
 
-void NetworkingPrivateLinux::GetCachedNetworkProperties(
-    const std::string& guid,
-    base::DictionaryValue* properties,
-    std::string* error) {
+void NetworkingPrivateLinux::GetCachedNetworkProperties(const std::string& guid,
+                                                        base::Value* properties,
+                                                        std::string* error) {
   AssertOnDBusThread();
   std::string ssid;
 
@@ -279,28 +278,21 @@
     return;
   }
 
-  // Make a copy of the properties out of the cached map.
-  std::unique_ptr<base::DictionaryValue> temp_properties(
-      network_iter->second->DeepCopy());
-
-  // Swap the new copy into the dictionary that is shared with the reply.
-  properties->Swap(temp_properties.get());
+  *properties = network_iter->second.Clone();
 }
 
-void NetworkingPrivateLinux::SetProperties(
-    const std::string& guid,
-    std::unique_ptr<base::DictionaryValue> properties,
-    bool allow_set_shared_config,
-    VoidCallback success_callback,
-    FailureCallback failure_callback) {
+void NetworkingPrivateLinux::SetProperties(const std::string& guid,
+                                           base::Value properties,
+                                           bool allow_set_shared_config,
+                                           VoidCallback success_callback,
+                                           FailureCallback failure_callback) {
   ReportNotSupported("SetProperties", std::move(failure_callback));
 }
 
-void NetworkingPrivateLinux::CreateNetwork(
-    bool shared,
-    std::unique_ptr<base::DictionaryValue> properties,
-    StringCallback success_callback,
-    FailureCallback failure_callback) {
+void NetworkingPrivateLinux::CreateNetwork(bool shared,
+                                           base::Value properties,
+                                           StringCallback success_callback,
+                                           FailureCallback failure_callback) {
   ReportNotSupported("CreateNetwork", std::move(failure_callback));
 }
 
@@ -510,9 +502,8 @@
     return;
   }
 
-  std::string connection_state;
-  network_iter->second->GetString(kAccessPointInfoConnectionState,
-                                  &connection_state);
+  std::string connection_state =
+      *network_iter->second.FindStringPath(kAccessPointInfoConnectionState);
   if (connection_state == ::onc::connection_state::kNotConnected) {
     // Already disconnected so nothing to do.
     return;
@@ -627,14 +618,12 @@
   return device_state_list;
 }
 
-std::unique_ptr<base::DictionaryValue>
-NetworkingPrivateLinux::GetGlobalPolicy() {
-  return std::make_unique<base::DictionaryValue>();
+base::Value NetworkingPrivateLinux::GetGlobalPolicy() {
+  return {};
 }
 
-std::unique_ptr<base::DictionaryValue>
-NetworkingPrivateLinux ::GetCertificateLists() {
-  return std::make_unique<base::DictionaryValue>();
+base::Value NetworkingPrivateLinux ::GetCertificateLists() {
+  return {};
 }
 
 bool NetworkingPrivateLinux::EnableNetworkType(const std::string& type) {
@@ -681,7 +670,7 @@
 }
 
 void NetworkingPrivateLinux::SendNetworkListChangedEvent(
-    const base::ListValue& network_list) {
+    const base::Value& network_list) {
   GuidList guidsForEventCallback;
 
   for (const auto& network : network_list.GetList()) {
@@ -801,7 +790,7 @@
 
 bool NetworkingPrivateLinux::GetAccessPointInfo(
     const dbus::ObjectPath& access_point_path,
-    const std::unique_ptr<base::DictionaryValue>& access_point_info) {
+    base::Value* access_point_info) {
   AssertOnDBusThread();
   dbus::ObjectProxy* access_point_proxy = dbus_->GetObjectProxy(
       networking_private::kNetworkManagerNamespace, access_point_path);
@@ -835,7 +824,7 @@
     std::string ssidUTF8(ssid_bytes, ssid_bytes + ssid_length);
     std::u16string ssid = base::UTF8ToUTF16(ssidUTF8);
 
-    access_point_info->SetString(kAccessPointInfoName, ssid);
+    access_point_info->SetStringKey(kAccessPointInfoName, ssid);
   }
 
   // Read signal strength.
@@ -855,8 +844,8 @@
       return false;
     }
 
-    access_point_info->SetInteger(kAccessPointInfoWifiSignalStrengthDotted,
-                                  strength);
+    access_point_info->SetIntKey(kAccessPointInfoWifiSignalStrengthDotted,
+                                 strength);
   }
 
   // Read the security type. This is from the WpaFlags and RsnFlags property
@@ -901,9 +890,10 @@
 
   std::string security;
   MapSecurityFlagsToString(rsn_security_flags | wpa_security_flags, &security);
-  access_point_info->SetString(kAccessPointInfoWifiSecurityDotted, security);
-  access_point_info->SetString(kAccessPointInfoType, kAccessPointInfoTypeWifi);
-  access_point_info->SetBoolean(kAccessPointInfoConnectable, true);
+  access_point_info->SetStringKey(kAccessPointInfoWifiSecurityDotted, security);
+  access_point_info->SetStringKey(kAccessPointInfoType,
+                                  kAccessPointInfoTypeWifi);
+  access_point_info->SetBoolKey(kAccessPointInfoConnectable, true);
   return true;
 }
 
@@ -939,19 +929,17 @@
   }
 
   for (const auto& access_point_path : access_point_paths) {
-    std::unique_ptr<base::DictionaryValue> access_point(
-        new base::DictionaryValue);
+    base::Value access_point(base::Value::Type::DICTIONARY);
 
-    if (GetAccessPointInfo(access_point_path, access_point)) {
+    if (GetAccessPointInfo(access_point_path, &access_point)) {
       std::string connection_state =
           (access_point_path == connected_access_point)
               ? ::onc::connection_state::kConnected
               : ::onc::connection_state::kNotConnected;
 
-      access_point->SetString(kAccessPointInfoConnectionState,
-                              connection_state);
-      std::string ssid;
-      access_point->GetString(kAccessPointInfoName, &ssid);
+      access_point.SetStringKey(kAccessPointInfoConnectionState,
+                                connection_state);
+      std::string ssid = *access_point.FindStringPath(kAccessPointInfoName);
 
       std::string network_guid =
           ConstructNetworkGuid(device_path, access_point_path, ssid);
@@ -960,7 +948,7 @@
       // access point paths, this consolidates them. If it is already
       // in the map it updates the signal strength and GUID paths if this
       // network is stronger or the one that is connected.
-      AddOrUpdateAccessPoint(network_map, network_guid, access_point);
+      AddOrUpdateAccessPoint(network_map, network_guid, &access_point);
     }
   }
 
@@ -970,44 +958,39 @@
 void NetworkingPrivateLinux::AddOrUpdateAccessPoint(
     NetworkMap* network_map,
     const std::string& network_guid,
-    std::unique_ptr<base::DictionaryValue>& access_point) {
-  std::u16string ssid;
-  std::string connection_state;
-  int signal_strength;
-
-  access_point->GetString(kAccessPointInfoConnectionState, &connection_state);
-  access_point->GetInteger(kAccessPointInfoWifiSignalStrengthDotted,
-                           &signal_strength);
-  access_point->GetString(kAccessPointInfoName, &ssid);
-  access_point->SetString(kAccessPointInfoGuid, network_guid);
+    base::Value* access_point) {
+  std::string connection_state =
+      *access_point->FindStringPath(kAccessPointInfoConnectionState);
+  int signal_strength =
+      *access_point->FindIntPath(kAccessPointInfoWifiSignalStrengthDotted);
+  std::u16string ssid =
+      base::UTF8ToUTF16(*access_point->FindStringPath(kAccessPointInfoName));
+  access_point->SetStringPath(kAccessPointInfoGuid, network_guid);
 
   auto existing_access_point_iter = network_map->find(ssid);
 
   if (existing_access_point_iter == network_map->end()) {
     // Unseen access point. Add it to the map.
-    network_map->insert(NetworkMap::value_type(ssid, std::move(access_point)));
+    network_map->insert(NetworkMap::value_type(ssid, std::move(*access_point)));
   } else {
     // Already seen access point. Update the record if this is the connected
     // record or if the signal strength is higher. But don't override a weaker
     // access point if that is the one that is connected.
-    int existing_signal_strength;
-    base::DictionaryValue* existing_access_point =
-        existing_access_point_iter->second.get();
-    existing_access_point->GetInteger(kAccessPointInfoWifiSignalStrengthDotted,
-                                      &existing_signal_strength);
+    base::Value& existing_access_point = existing_access_point_iter->second;
+    int existing_signal_strength = *existing_access_point.FindIntPath(
+        kAccessPointInfoWifiSignalStrengthDotted);
 
-    std::string existing_connection_state;
-    existing_access_point->GetString(kAccessPointInfoConnectionState,
-                                     &existing_connection_state);
+    std::string existing_connection_state =
+        *existing_access_point.FindStringPath(kAccessPointInfoConnectionState);
 
     if ((connection_state == ::onc::connection_state::kConnected) ||
         (!(existing_connection_state == ::onc::connection_state::kConnected) &&
          signal_strength > existing_signal_strength)) {
-      existing_access_point->SetString(kAccessPointInfoConnectionState,
-                                       connection_state);
-      existing_access_point->SetInteger(
-          kAccessPointInfoWifiSignalStrengthDotted, signal_strength);
-      existing_access_point->SetString(kAccessPointInfoGuid, network_guid);
+      existing_access_point.SetStringPath(kAccessPointInfoConnectionState,
+                                          connection_state);
+      existing_access_point.SetIntPath(kAccessPointInfoWifiSignalStrengthDotted,
+                                       signal_strength);
+      existing_access_point.SetStringPath(kAccessPointInfoGuid, network_guid);
     }
   }
 }
@@ -1187,33 +1170,33 @@
   // If setting this network to connected, find the previously connected network
   // and disconnect that one. Also retain the guid of that network to fire a
   // changed event.
-  std::string connected_network_guid;
+  std::string* connected_network_guid = nullptr;
   if (connection_state == ::onc::connection_state::kConnected) {
     for (auto& network : *network_map_) {
-      std::string other_connection_state;
-      if (network.second->GetString(kAccessPointInfoConnectionState,
-                                    &other_connection_state)) {
-        if (other_connection_state == ::onc::connection_state::kConnected) {
-          network.second->GetString(kAccessPointInfoGuid,
-                                    &connected_network_guid);
-          network.second->SetString(kAccessPointInfoConnectionState,
-                                    ::onc::connection_state::kNotConnected);
+      if (std::string* other_connection_state =
+              network.second.FindStringPath(kAccessPointInfoConnectionState)) {
+        if (*other_connection_state == ::onc::connection_state::kConnected) {
+          connected_network_guid =
+              network.second.FindStringPath(kAccessPointInfoGuid);
+          network.second.SetStringPath(kAccessPointInfoConnectionState,
+                                       ::onc::connection_state::kNotConnected);
         }
       }
     }
   }
 
   // Set the status.
-  network_iter->second->SetString(kAccessPointInfoConnectionState,
-                                  connection_state);
+  network_iter->second.SetStringPath(kAccessPointInfoConnectionState,
+                                     connection_state);
 
   std::unique_ptr<GuidList> changed_networks(new GuidList());
   changed_networks->push_back(guid);
 
   // Only add a second network if it exists and it is not the same as the
   // network already being added to the list.
-  if (!connected_network_guid.empty() && connected_network_guid != guid) {
-    changed_networks->push_back(connected_network_guid);
+  if (connected_network_guid && !connected_network_guid->empty() &&
+      *connected_network_guid != guid) {
+    changed_networks->push_back(*connected_network_guid);
   }
 
   PostOnNetworksChangedToUIThread(std::move(changed_networks));
diff --git a/extensions/browser/api/networking_private/networking_private_linux.h b/extensions/browser/api/networking_private/networking_private_linux.h
index 2fffeaf2..3d94778 100644
--- a/extensions/browser/api/networking_private/networking_private_linux.h
+++ b/extensions/browser/api/networking_private/networking_private_linux.h
@@ -27,8 +27,7 @@
 // Linux NetworkingPrivateDelegate implementation.
 class NetworkingPrivateLinux : public NetworkingPrivateDelegate {
  public:
-  using NetworkMap =
-      std::map<std::u16string, std::unique_ptr<base::DictionaryValue>>;
+  using NetworkMap = std::map<std::u16string, base::Value>;
 
   typedef std::vector<std::string> GuidList;
 
@@ -46,12 +45,12 @@
                 DictionaryCallback success_callback,
                 FailureCallback failure_callback) override;
   void SetProperties(const std::string& guid,
-                     std::unique_ptr<base::DictionaryValue> properties,
+                     base::Value properties,
                      bool allow_set_shared_config,
                      VoidCallback success_callback,
                      FailureCallback failure_callback) override;
   void CreateNetwork(bool shared,
-                     std::unique_ptr<base::DictionaryValue> properties,
+                     base::Value properties,
                      StringCallback success_callback,
                      FailureCallback failure_callback) override;
   void ForgetNetwork(const std::string& guid,
@@ -90,8 +89,8 @@
                                    FailureCallback failure_callback) override;
   base::Value GetEnabledNetworkTypes() override;
   std::unique_ptr<DeviceStateList> GetDeviceStateList() override;
-  std::unique_ptr<base::DictionaryValue> GetGlobalPolicy() override;
-  std::unique_ptr<base::DictionaryValue> GetCertificateLists() override;
+  base::Value GetGlobalPolicy() override;
+  base::Value GetCertificateLists() override;
   bool EnableNetworkType(const std::string& type) override;
   bool DisableNetworkType(const std::string& type) override;
   bool RequestScan(const std::string& type) override;
@@ -184,14 +183,13 @@
 
   // Helper function for OnAccessPointsFound and OnAccessPointsFoundViaScan to
   // fire the OnNetworkListChangedEvent.
-  void SendNetworkListChangedEvent(const base::ListValue& network_list);
+  void SendNetworkListChangedEvent(const base::Value& network_list);
 
   // Gets a dictionary of information about the access point.
   // Returns false if there is an error getting information about the
   // supplied |access_point_path|.
-  bool GetAccessPointInfo(
-      const dbus::ObjectPath& access_point_path,
-      const std::unique_ptr<base::DictionaryValue>& access_point_info);
+  bool GetAccessPointInfo(const dbus::ObjectPath& access_point_path,
+                          base::Value* access_point_info);
 
   // Helper function to extract a property from a device.
   // Returns the dbus::Response object from calling Get on the supplied
@@ -203,10 +201,9 @@
   // If the access_point is not already in the map it is added. Otherwise
   // the access point is updated (eg. with the max of the signal
   // strength).
-  void AddOrUpdateAccessPoint(
-      NetworkMap* network_map,
-      const std::string& network_guid,
-      std::unique_ptr<base::DictionaryValue>& access_point);
+  void AddOrUpdateAccessPoint(NetworkMap* network_map,
+                              const std::string& network_guid,
+                              base::Value* access_point);
 
   // Maps the WPA security flags to a human readable string.
   void MapSecurityFlagsToString(uint32_t securityFlags, std::string* security);
@@ -246,7 +243,7 @@
   void OnNetworksChangedEventTask(std::unique_ptr<GuidList> guid_list);
 
   void GetCachedNetworkProperties(const std::string& guid,
-                                  base::DictionaryValue* properties,
+                                  base::Value* properties,
                                   std::string* error);
 
   void OnNetworksChangedEventOnUIThread(const GuidList& network_guids);
diff --git a/extensions/browser/api/networking_private/networking_private_service_client.cc b/extensions/browser/api/networking_private/networking_private_service_client.cc
index 3f5da9d5..204bfde 100644
--- a/extensions/browser/api/networking_private/networking_private_service_client.cc
+++ b/extensions/browser/api/networking_private/networking_private_service_client.cc
@@ -177,7 +177,7 @@
 
 void NetworkingPrivateServiceClient::SetProperties(
     const std::string& guid,
-    std::unique_ptr<base::DictionaryValue> properties,
+    base::Value properties,
     bool allow_set_shared_config,
     VoidCallback success_callback,
     FailureCallback failure_callback) {
@@ -193,7 +193,9 @@
       FROM_HERE,
       base::BindOnce(&WiFiService::SetProperties,
                      base::Unretained(wifi_service_.get()), guid,
-                     std::move(properties), error),
+                     base::DictionaryValue::From(
+                         base::Value::ToUniquePtrValue(std::move(properties))),
+                     error),
       base::BindOnce(&NetworkingPrivateServiceClient::AfterSetProperties,
                      weak_factory_.GetWeakPtr(), service_callbacks->id,
                      base::Owned(error)));
@@ -201,7 +203,7 @@
 
 void NetworkingPrivateServiceClient::CreateNetwork(
     bool shared,
-    std::unique_ptr<base::DictionaryValue> properties,
+    base::Value properties,
     StringCallback success_callback,
     FailureCallback failure_callback) {
   ServiceCallbacks* service_callbacks = AddServiceCallbacks();
@@ -215,7 +217,9 @@
       FROM_HERE,
       base::BindOnce(&WiFiService::CreateNetwork,
                      base::Unretained(wifi_service_.get()), shared,
-                     std::move(properties), network_guid, error),
+                     base::DictionaryValue::From(
+                         base::Value::ToUniquePtrValue(std::move(properties))),
+                     network_guid, error),
       base::BindOnce(&NetworkingPrivateServiceClient::AfterCreateNetwork,
                      weak_factory_.GetWeakPtr(), service_callbacks->id,
                      base::Owned(network_guid), base::Owned(error)));
@@ -346,14 +350,12 @@
   return device_state_list;
 }
 
-std::unique_ptr<base::DictionaryValue>
-NetworkingPrivateServiceClient::GetGlobalPolicy() {
-  return std::make_unique<base::DictionaryValue>();
+base::Value NetworkingPrivateServiceClient::GetGlobalPolicy() {
+  return base::Value(base::Value::Type::DICTIONARY);
 }
 
-std::unique_ptr<base::DictionaryValue>
-NetworkingPrivateServiceClient::GetCertificateLists() {
-  return std::make_unique<base::DictionaryValue>();
+base::Value NetworkingPrivateServiceClient::GetCertificateLists() {
+  return base::Value(base::Value::Type::DICTIONARY);
 }
 
 bool NetworkingPrivateServiceClient::EnableNetworkType(
@@ -401,7 +403,7 @@
   } else {
     DCHECK(!service_callbacks->get_properties_callback.is_null());
     std::move(service_callbacks->get_properties_callback)
-        .Run(std::move(properties));
+        .Run(std::move(*properties));
   }
   RemoveServiceCallbacks(callback_id);
 }
diff --git a/extensions/browser/api/networking_private/networking_private_service_client.h b/extensions/browser/api/networking_private/networking_private_service_client.h
index 66bf5940..90fcdfa 100644
--- a/extensions/browser/api/networking_private/networking_private_service_client.h
+++ b/extensions/browser/api/networking_private/networking_private_service_client.h
@@ -57,12 +57,12 @@
                 DictionaryCallback success_callback,
                 FailureCallback failure_callback) override;
   void SetProperties(const std::string& guid,
-                     std::unique_ptr<base::DictionaryValue> properties_dict,
+                     base::Value properties_dict,
                      bool allow_set_shared_config,
                      VoidCallback success_callback,
                      FailureCallback failure_callback) override;
   void CreateNetwork(bool shared,
-                     std::unique_ptr<base::DictionaryValue> properties_dict,
+                     base::Value properties_dict,
                      StringCallback success_callback,
                      FailureCallback failure_callback) override;
   void ForgetNetwork(const std::string& guid,
@@ -101,8 +101,8 @@
                                    FailureCallback failure_callback) override;
   base::Value GetEnabledNetworkTypes() override;
   std::unique_ptr<DeviceStateList> GetDeviceStateList() override;
-  std::unique_ptr<base::DictionaryValue> GetGlobalPolicy() override;
-  std::unique_ptr<base::DictionaryValue> GetCertificateLists() override;
+  base::Value GetGlobalPolicy() override;
+  base::Value GetCertificateLists() override;
   bool EnableNetworkType(const std::string& type) override;
   bool DisableNetworkType(const std::string& type) override;
   bool RequestScan(const std::string& type) override;
diff --git a/extensions/browser/disable_reason.h b/extensions/browser/disable_reason.h
index 622b56d..6b3175df 100644
--- a/extensions/browser/disable_reason.h
+++ b/extensions/browser/disable_reason.h
@@ -51,8 +51,10 @@
   DISABLE_REINSTALL = 1 << 19,
   // Disabled by Safe Browsing extension allowlist enforcement.
   DISABLE_NOT_ALLOWLISTED = 1 << 20,
+  // Disabled by Ash extension keep-list enforcement.
+  DISABLE_NOT_ASH_KEEPLISTED = 1 << 21,
   // This should always be the last value.
-  DISABLE_REASON_LAST = 1LL << 21,
+  DISABLE_REASON_LAST = 1LL << 22,
 };
 
 static_assert(DISABLE_REASON_LAST - 1 <= std::numeric_limits<int>::max(),
diff --git a/extensions/common/constants.h b/extensions/common/constants.h
index e664beb..29aa595 100644
--- a/extensions/common/constants.h
+++ b/extensions/common/constants.h
@@ -173,10 +173,11 @@
   kSourceIntentUrl = 23,        // App launch triggered by a URL.
   kSourceRunOnOsLogin = 24,     // App launched during OS login.
   kSourceProtocolHandler = 25,  // App launch via protocol handler.
+  kSourceReparenting = 26,      // APP launch via reparenting.
 
   // Add any new values above this one, and update kMaxValue to the highest
   // enumerator value.
-  kMaxValue = kSourceProtocolHandler,
+  kMaxValue = kSourceReparenting,
 };
 
 // This enum is used for the launch type the user wants to use for an
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
index 84990aa..fe0c333 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-89ba9b634f50f974c54d114a7548d8c043870144
\ No newline at end of file
+6ac8818431d10aaf0462a145cd2fefae1a0575f6
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
index 4ea7110..6d89c93 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-4a413dd6d650cde748ee73abc77d52bb6034e9cc
\ No newline at end of file
+763576587bb863e5b8d7da0a28adeb0f888b0e5e
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
index 161ef21..5e16a612 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-6d6a744a9c94fe19a936202d6a74d48b0ffc7962
\ No newline at end of file
+d87293f396192b540e6168c136fb7537a74fcf84
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
index b4caaf94..02e3f903 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-ac80c785736ba77853ded9a80e60f270f167f22d
\ No newline at end of file
+a4baf0add99eaf58311d8ee4ffee6ca8b08b0f5b
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1
index 7e3b2c4..19c34e8 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-53fd39d3fb9b5f9e166485ae14a54882e2af9881
\ No newline at end of file
+a812241f30d4e58f3640044604bb51f5782c7d98
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1
index 911788d..bb8324e 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-c74acccb77b44199b91d20b9fc194698d6455600
\ No newline at end of file
+9f097a6caba590741df3bb1ef890b39f8d828ad0
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
index 9720c84..443867d8 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-c2b1248388325b67feed59e65a225a879baa075c
\ No newline at end of file
+ba2fdc7374942f92b3269e8d2217c0fd546620f4
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
index ee831f6..e1852539 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-ccb3852b2f5719e96b86b7c5774f5c8b8ec45249
\ No newline at end of file
+703aea367f839b9c0a752e8cbe5e9c7b3c9b7b2b
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
index a47ccec..c6aad7b 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-906a4084db29f7aa629d585e8f8db721cc33af8b
\ No newline at end of file
+b0d22bad112ceeebb5d3a1abab07dbcb32433a79
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
index 996c547..a9eda4a6 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-2da0638d73f0f36a6a2e4ee3d17d8da7d6517e74
\ No newline at end of file
+09954c7f2aef48cc6d5e131bcc3482b147bf996c
\ No newline at end of file
diff --git a/ipc/ipc_channel.h b/ipc/ipc_channel.h
index 0fb3c1e6..aa93dd3 100644
--- a/ipc/ipc_channel.h
+++ b/ipc/ipc_channel.h
@@ -231,7 +231,7 @@
   // deleted once the contents of the Message have been sent.
   bool Send(Message* message) override = 0;
 
-#if !defined(OS_NACL_SFI)
+#if !defined(OS_NACL)
   // Generates a channel ID that's non-predictable and unique.
   static std::string GenerateUniqueRandomChannelID();
 #endif
diff --git a/ipc/ipc_channel_common.cc b/ipc/ipc_channel_common.cc
index 8990eee6b..c02a071 100644
--- a/ipc/ipc_channel_common.cc
+++ b/ipc/ipc_channel_common.cc
@@ -33,7 +33,7 @@
     const IPC::ChannelHandle& channel_handle,
     Listener* listener,
     const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner) {
-#if defined(OS_NACL_SFI)
+#if defined(OS_NACL)
   return Channel::Create(channel_handle, Channel::MODE_CLIENT, listener);
 #else
   DCHECK(channel_handle.is_mojo_channel_handle());
@@ -50,7 +50,7 @@
     const IPC::ChannelHandle& channel_handle,
     Listener* listener,
     const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner) {
-#if defined(OS_NACL_SFI)
+#if defined(OS_NACL)
   return Channel::Create(channel_handle, Channel::MODE_SERVER, listener);
 #else
   DCHECK(channel_handle.is_mojo_channel_handle());
diff --git a/ipc/ipc_channel_factory.cc b/ipc/ipc_channel_factory.cc
index 8d7291b..d5587ae 100644
--- a/ipc/ipc_channel_factory.cc
+++ b/ipc/ipc_channel_factory.cc
@@ -3,7 +3,9 @@
 // found in the LICENSE file.
 
 #include "ipc/ipc_channel_factory.h"
+
 #include "base/memory/ptr_util.h"
+#include "build/build_config.h"
 #include "ipc/ipc_channel_mojo.h"
 #include "mojo/public/cpp/bindings/lib/message_quota_checker.h"
 
@@ -26,7 +28,7 @@
   PlatformChannelFactory& operator=(const PlatformChannelFactory&) = delete;
 
   std::unique_ptr<Channel> BuildChannel(Listener* listener) override {
-#if defined(OS_NACL_SFI)
+#if defined(OS_NACL)
     return Channel::Create(handle_, mode_, listener);
 #else
     DCHECK(handle_.is_mojo_channel_handle());
diff --git a/ipc/ipc_channel_handle.h b/ipc/ipc_channel_handle.h
index 58904ac..c68d784b 100644
--- a/ipc/ipc_channel_handle.h
+++ b/ipc/ipc_channel_handle.h
@@ -8,15 +8,15 @@
 #include "build/build_config.h"
 #include "mojo/public/cpp/system/message_pipe.h"
 
-#if defined(OS_NACL_SFI)
+#if defined(OS_NACL)
 #include "base/file_descriptor_posix.h"
-#endif  // defined (OS_NACL_SFI)
+#endif  // defined (OS_NACL)
 
 namespace IPC {
 
 // Note that serialization for this object is defined in the ParamTraits
 // template specialization in ipc_message_utils.h.
-#if defined(OS_NACL_SFI)
+#if defined(OS_NACL)
 struct ChannelHandle {
   ChannelHandle() {}
   explicit ChannelHandle(const base::FileDescriptor& s) : socket(s) {}
@@ -32,7 +32,7 @@
 
   mojo::MessagePipeHandle mojo_handle;
 };
-#endif  // defined(OS_NACL_SFI)
+#endif  // defined(OS_NACL)
 
 }  // namespace IPC
 
diff --git a/ipc/ipc_message_utils.cc b/ipc/ipc_message_utils.cc
index 147793e..5d1ed41 100644
--- a/ipc/ipc_message_utils.cc
+++ b/ipc/ipc_message_utils.cc
@@ -1326,7 +1326,7 @@
 
 void ParamTraits<IPC::ChannelHandle>::Write(base::Pickle* m,
                                             const param_type& p) {
-#if defined(OS_NACL_SFI)
+#if defined(OS_NACL)
   WriteParam(m, p.socket);
 #else
   WriteParam(m, p.mojo_handle);
@@ -1336,7 +1336,7 @@
 bool ParamTraits<IPC::ChannelHandle>::Read(const base::Pickle* m,
                                            base::PickleIterator* iter,
                                            param_type* r) {
-#if defined(OS_NACL_SFI)
+#if defined(OS_NACL)
   return ReadParam(m, iter, &r->socket);
 #else
   return ReadParam(m, iter, &r->mojo_handle);
@@ -1346,7 +1346,7 @@
 void ParamTraits<IPC::ChannelHandle>::Log(const param_type& p,
                                           std::string* l) {
   l->append("ChannelHandle(");
-#if defined(OS_NACL_SFI)
+#if defined(OS_NACL)
   ParamTraits<base::FileDescriptor>::Log(p.socket, l);
 #else
   LogParam(p.mojo_handle, l);
diff --git a/media/base/BUILD.gn b/media/base/BUILD.gn
index 2c5c2e06..3a544df 100644
--- a/media/base/BUILD.gn
+++ b/media/base/BUILD.gn
@@ -136,8 +136,6 @@
     "data_buffer.h",
     "data_source.cc",
     "data_source.h",
-    "decode_status.cc",
-    "decode_status.h",
     "decoder.cc",
     "decoder.h",
     "decoder_buffer.cc",
@@ -146,6 +144,8 @@
     "decoder_buffer_queue.h",
     "decoder_factory.cc",
     "decoder_factory.h",
+    "decoder_status.cc",
+    "decoder_status.h",
     "decrypt_config.cc",
     "decrypt_config.h",
     "decryptor.cc",
diff --git a/media/base/android/media_codec_loop.h b/media/base/android/media_codec_loop.h
index 0928d8a..7427df5 100644
--- a/media/base/android/media_codec_loop.h
+++ b/media/base/android/media_codec_loop.h
@@ -17,7 +17,7 @@
 #include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "media/base/android/media_codec_bridge.h"
-#include "media/base/decode_status.h"
+#include "media/base/decoder_status.h"
 #include "media/base/encryption_scheme.h"
 #include "media/base/media_export.h"
 #include "media/base/subsample_entry.h"
diff --git a/media/base/audio_decoder.h b/media/base/audio_decoder.h
index 252f356..fea0a13 100644
--- a/media/base/audio_decoder.h
+++ b/media/base/audio_decoder.h
@@ -9,12 +9,11 @@
 #include "base/memory/ref_counted.h"
 #include "media/base/audio_decoder_config.h"
 #include "media/base/channel_layout.h"
-#include "media/base/decode_status.h"
 #include "media/base/decoder.h"
 #include "media/base/decoder_buffer.h"
+#include "media/base/decoder_status.h"
 #include "media/base/media_export.h"
 #include "media/base/pipeline_status.h"
-#include "media/base/status.h"
 #include "media/base/waiting.h"
 
 namespace media {
@@ -25,7 +24,7 @@
 class MEDIA_EXPORT AudioDecoder : public Decoder {
  public:
   // Callback for Decoder initialization.
-  using InitCB = base::OnceCallback<void(Status)>;
+  using InitCB = base::OnceCallback<void(DecoderStatus)>;
 
   // Callback for AudioDecoder to return a decoded frame whenever it becomes
   // available. Only non-EOS frames should be returned via this callback.
@@ -37,7 +36,7 @@
   // decode was aborted, which does not necessarily indicate an error.  For
   // example, a Reset() can trigger this.  Any other status code indicates that
   // the decoder encountered an error, and must be reset.
-  using DecodeCB = base::OnceCallback<void(Status)>;
+  using DecodeCB = base::OnceCallback<void(DecoderStatus)>;
 
   AudioDecoder();
 
diff --git a/media/base/decode_status.cc b/media/base/decode_status.cc
deleted file mode 100644
index b6f46f3..0000000
--- a/media/base/decode_status.cc
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "media/base/decode_status.h"
-
-#include <ostream>
-
-#include "base/trace_event/trace_event.h"
-#include "media/base/status.h"
-
-namespace media {
-
-const char* GetDecodeStatusString(DecodeStatus status) {
-  switch (status) {
-    case DecodeStatus::OK:
-      return "DecodeStatus::OK";
-    case DecodeStatus::ABORTED:
-      return "DecodeStatus::ABORTED";
-    case DecodeStatus::DECODE_ERROR:
-      return "DecodeStatus::DECODE_ERROR";
-    default:
-      // TODO(liberato): Temporary while converting to media::Status.  This
-      // fn should go away.
-      return "DecodeStatus::UNKNOWN_ERROR";
-  }
-
-  NOTREACHED();
-  return "";
-}
-
-// static
-bool ScopedDecodeTrace::IsEnabled() {
-  bool enable_decode_traces = false;
-  TRACE_EVENT_CATEGORY_GROUP_ENABLED("media", &enable_decode_traces);
-  return enable_decode_traces;
-}
-
-ScopedDecodeTrace::ScopedDecodeTrace(const char* trace_name,
-                                     bool is_key_frame,
-                                     base::TimeDelta timestamp)
-    : trace_name_(trace_name) {
-  DCHECK(trace_name_);
-  TRACE_EVENT_NESTABLE_ASYNC_BEGIN2("media", trace_name_, TRACE_ID_LOCAL(this),
-                                    "is_key_frame", is_key_frame,
-                                    "timestamp_us", timestamp.InMicroseconds());
-}
-
-ScopedDecodeTrace::ScopedDecodeTrace(const char* trace_name,
-                                     const DecoderBuffer& buffer)
-    : trace_name_(trace_name) {
-  DCHECK(trace_name_);
-  TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(
-      "media", trace_name_, TRACE_ID_LOCAL(this), "decoder_buffer",
-      buffer.AsHumanReadableString(/*verbose=*/true));
-}
-
-ScopedDecodeTrace::~ScopedDecodeTrace() {
-  if (!closed_)
-    EndTrace(DecodeStatus::ABORTED);
-}
-
-void ScopedDecodeTrace::EndTrace(const Status& status) {
-  DCHECK(!closed_);
-  closed_ = true;
-  TRACE_EVENT_NESTABLE_ASYNC_END1("media", trace_name_, TRACE_ID_LOCAL(this),
-                                  "status",
-                                  GetDecodeStatusString(status.code()));
-}
-
-}  // namespace media
diff --git a/media/base/decode_status.h b/media/base/decode_status.h
deleted file mode 100644
index 0f05be2..0000000
--- a/media/base/decode_status.h
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_BASE_DECODE_STATUS_H_
-#define MEDIA_BASE_DECODE_STATUS_H_
-
-#include <iosfwd>
-
-#include "media/base/decoder_buffer.h"
-#include "media/base/media_export.h"
-#include "media/base/status.h"
-#include "media/base/status_codes.h"
-
-namespace media {
-
-// TODO(crbug.com/1129662): This is temporary, to allow DecodeStatus::OK to
-// work, while we replace DecodeStatus with actual status codes.
-using DecodeStatus = StatusCode;
-
-MEDIA_EXPORT const char* GetDecodeStatusString(DecodeStatus status);
-
-// Helper class for ensuring that Decode() traces are properly unique and closed
-// if the Decode is aborted via a WeakPtr invalidation. We use the |this|
-// pointer of the ScopedDecodeTrace object itself as the id. Since the callback
-// owns the class it's guaranteed to be unique.
-class MEDIA_EXPORT ScopedDecodeTrace {
- public:
-  // Returns true if tracing is enabled for the media category. If false,
-  // clients should avoid creating ScopedDecodeTrace objects.
-  static bool IsEnabled();
-
-  // Begins an asynchronous trace with the given name and properties. Providing
-  // the DecoderBuffer itself yields the most information in the trace.
-  ScopedDecodeTrace(const char* trace_name, const DecoderBuffer& buffer);
-  ScopedDecodeTrace(const char* trace_name,
-                    bool is_key_frame,
-                    base::TimeDelta timestamp);
-
-  ScopedDecodeTrace(const ScopedDecodeTrace&) = delete;
-  ScopedDecodeTrace& operator=(const ScopedDecodeTrace&) = delete;
-
-  ~ScopedDecodeTrace();
-
-  // Completes the Decode() trace with the given status.
-  void EndTrace(const Status& status);
-
- private:
-  const char* trace_name_;
-  bool closed_ = false;
-};
-
-}  // namespace media
-
-#endif  // MEDIA_BASE_DECODE_STATUS_H_
diff --git a/media/base/decoder_status.cc b/media/base/decoder_status.cc
new file mode 100644
index 0000000..869b3bc
--- /dev/null
+++ b/media/base/decoder_status.cc
@@ -0,0 +1,85 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/base/decoder_status.h"
+
+#include <sstream>
+
+#include "base/trace_event/trace_event.h"
+#include "media/base/status.h"
+
+namespace media {
+namespace {
+
+const std::string GetDecodeStatusString(const DecoderStatus& status) {
+#define STRINGIFY(V) \
+  case V:            \
+    return #V
+  switch (status.code()) {
+    STRINGIFY(DecoderStatus::Codes::kOk);
+    STRINGIFY(DecoderStatus::Codes::kFailed);
+    STRINGIFY(DecoderStatus::Codes::kAborted);
+    STRINGIFY(DecoderStatus::Codes::kInvalidArgument);
+    STRINGIFY(DecoderStatus::Codes::kInterrupted);
+    STRINGIFY(DecoderStatus::Codes::kNotInitialized);
+    STRINGIFY(DecoderStatus::Codes::kMissingCDM);
+    STRINGIFY(DecoderStatus::Codes::kFailedToGetVideoFrame);
+    STRINGIFY(DecoderStatus::Codes::kPlatformDecodeFailure);
+    STRINGIFY(DecoderStatus::Codes::kMalformedBitstream);
+    STRINGIFY(DecoderStatus::Codes::kFailedToGetDecoderBuffer);
+    STRINGIFY(DecoderStatus::Codes::kDecoderStreamInErrorState);
+    STRINGIFY(DecoderStatus::Codes::kDecoderStreamReinitFailed);
+    STRINGIFY(DecoderStatus::Codes::kDecoderStreamDemuxerError);
+    STRINGIFY(DecoderStatus::Codes::kUnsupportedProfile);
+    STRINGIFY(DecoderStatus::Codes::kUnsupportedCodec);
+    STRINGIFY(DecoderStatus::Codes::kUnsupportedConfig);
+    STRINGIFY(DecoderStatus::Codes::kUnsupportedEncryptionMode);
+    STRINGIFY(DecoderStatus::Codes::kCantChangeCodec);
+    STRINGIFY(DecoderStatus::Codes::kFailedToCreateDecoder);
+    STRINGIFY(DecoderStatus::Codes::kKeyFrameRequired);
+  }
+#undef STRINGIFY
+}
+
+}  // namespace
+
+// static
+bool ScopedDecodeTrace::IsEnabled() {
+  bool enable_decode_traces = false;
+  TRACE_EVENT_CATEGORY_GROUP_ENABLED("media", &enable_decode_traces);
+  return enable_decode_traces;
+}
+
+ScopedDecodeTrace::ScopedDecodeTrace(const char* trace_name,
+                                     bool is_key_frame,
+                                     base::TimeDelta timestamp)
+    : trace_name_(trace_name) {
+  DCHECK(trace_name_);
+  TRACE_EVENT_NESTABLE_ASYNC_BEGIN2("media", trace_name_, TRACE_ID_LOCAL(this),
+                                    "is_key_frame", is_key_frame,
+                                    "timestamp_us", timestamp.InMicroseconds());
+}
+
+ScopedDecodeTrace::ScopedDecodeTrace(const char* trace_name,
+                                     const DecoderBuffer& buffer)
+    : trace_name_(trace_name) {
+  DCHECK(trace_name_);
+  TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(
+      "media", trace_name_, TRACE_ID_LOCAL(this), "decoder_buffer",
+      buffer.AsHumanReadableString(/*verbose=*/true));
+}
+
+ScopedDecodeTrace::~ScopedDecodeTrace() {
+  if (!closed_)
+    EndTrace(DecoderStatus::Codes::kAborted);
+}
+
+void ScopedDecodeTrace::EndTrace(const DecoderStatus& status) {
+  DCHECK(!closed_);
+  closed_ = true;
+  TRACE_EVENT_NESTABLE_ASYNC_END1("media", trace_name_, TRACE_ID_LOCAL(this),
+                                  "status", GetDecodeStatusString(status));
+}
+
+}  // namespace media
diff --git a/media/base/decoder_status.h b/media/base/decoder_status.h
new file mode 100644
index 0000000..5618777
--- /dev/null
+++ b/media/base/decoder_status.h
@@ -0,0 +1,80 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_BASE_DECODER_STATUS_H_
+#define MEDIA_BASE_DECODER_STATUS_H_
+
+#include "media/base/decoder_buffer.h"
+#include "media/base/status.h"
+
+namespace media {
+
+struct DecoderStatusTraits {
+  enum class Codes : StatusCodeType {
+    // Shared & General errors
+    kOk = 0,
+    kFailed = 1,
+    kAborted = 2,  // TODO(*) document _why_ aborted is a thing
+    kInvalidArgument = 3,
+    kInterrupted = 4,
+
+    // Reasons for failing to decode
+    kNotInitialized = 100,
+    kMissingCDM = 101,
+    kFailedToGetVideoFrame = 102,
+    kPlatformDecodeFailure = 103,
+    kMalformedBitstream = 104,
+    kFailedToGetDecoderBuffer = 107,
+    kDecoderStreamInErrorState = 108,
+    kDecoderStreamReinitFailed = 109,
+    kDecoderStreamDemuxerError = 110,
+    kKeyFrameRequired = 111,
+
+    // Reasons for failing to initialize
+    kUnsupportedProfile = 200,
+    kUnsupportedCodec = 201,
+    kUnsupportedConfig = 202,
+    kUnsupportedEncryptionMode = 203,
+    kCantChangeCodec = 204,
+    kFailedToCreateDecoder = 205,
+  };
+  static constexpr StatusGroupType Group() { return "DecoderStatusCodes"; }
+  static constexpr Codes DefaultEnumValue() { return Codes::kOk; }
+};
+
+using DecoderStatus = TypedStatus<DecoderStatusTraits>;
+
+// Helper class for ensuring that Decode() traces are properly unique and closed
+// if the Decode is aborted via a WeakPtr invalidation. We use the |this|
+// pointer of the ScopedDecodeTrace object itself as the id. Since the callback
+// owns the class it's guaranteed to be unique.
+class MEDIA_EXPORT ScopedDecodeTrace {
+ public:
+  // Returns true if tracing is enabled for the media category. If false,
+  // clients should avoid creating ScopedDecodeTrace objects.
+  static bool IsEnabled();
+
+  // Begins an asynchronous trace with the given name and properties. Providing
+  // the DecoderBuffer itself yields the most information in the trace.
+  ScopedDecodeTrace(const char* trace_name, const DecoderBuffer& buffer);
+  ScopedDecodeTrace(const char* trace_name,
+                    bool is_key_frame,
+                    base::TimeDelta timestamp);
+
+  ScopedDecodeTrace(const ScopedDecodeTrace&) = delete;
+  ScopedDecodeTrace& operator=(const ScopedDecodeTrace&) = delete;
+
+  ~ScopedDecodeTrace();
+
+  // Completes the Decode() trace with the given status.
+  void EndTrace(const DecoderStatus& status);
+
+ private:
+  const char* trace_name_;
+  bool closed_ = false;
+};
+
+}  // namespace media
+
+#endif  // MEDIA_BASE_DECODER_STATUS_H_
diff --git a/media/base/ipc/media_param_traits_macros.h b/media/base/ipc/media_param_traits_macros.h
index 633635de..1fb3d2d 100644
--- a/media/base/ipc/media_param_traits_macros.h
+++ b/media/base/ipc/media_param_traits_macros.h
@@ -16,8 +16,8 @@
 #include "media/base/channel_layout.h"
 #include "media/base/container_names.h"
 #include "media/base/content_decryption_module.h"
-#include "media/base/decode_status.h"
 #include "media/base/decoder.h"
+#include "media/base/decoder_status.h"
 #include "media/base/decrypt_config.h"
 #include "media/base/decryptor.h"
 #include "media/base/demuxer_stream.h"
diff --git a/media/base/status.h b/media/base/status.h
index 965001b..aec1fe8 100644
--- a/media/base/status.h
+++ b/media/base/status.h
@@ -224,7 +224,7 @@
   }
 
   const std::string group() const {
-    return data_ ? data_->group : Traits::Group();
+    return data_ ? data_->group : std::string(Traits::Group());
   }
 
   const std::string& message() const {
diff --git a/media/base/status_codes.h b/media/base/status_codes.h
index 2c9bff8..98b2434 100644
--- a/media/base/status_codes.h
+++ b/media/base/status_codes.h
@@ -29,29 +29,9 @@
   // General errors: 0x00
   kAborted = 0x0001,
   kInvalidArgument = 0x0002,
-  kKeyFrameRequired = 0x0003,
   kWrappedError = 0x0004,
 
   // Decoder Errors: 0x01
-  kDecoderInitializeNeverCompleted = 0x0101,
-  kDecoderFailedDecode = 0x0102,
-  kDecoderUnsupportedProfile = 0x0103,
-  kDecoderUnsupportedCodec = 0x0104,
-  kDecoderUnsupportedConfig = 0x0105,
-  kEncryptedContentUnsupported = 0x0106,
-  kClearContentUnsupported = 0x0107,
-  kDecoderMissingCdmForEncryptedContent = 0x0108,
-  kDecoderInitializationFailed = 0x0109,  // Prefer this one.
-  kDecoderFailedInitialization = kDecoderInitializationFailed,  // Do not use.
-  kDecoderCantChangeCodec = 0x010A,
-  kDecoderCreationFailed = 0x010B,                  // Prefer this one.
-  kDecoderFailedCreation = kDecoderCreationFailed,  // Do not use.
-  kInitializationUnspecifiedFailure = 0x010C,
-  kDecoderVideoFrameConstructionFailed = 0x010D,
-  kMakeContextCurrentFailed = 0x010E,
-  // This is a temporary error for use only by existing code during the
-  // DecodeStatus => Status conversion.
-  kDecodeErrorDoNotUse = 0x010F,
 
   // MojoDecoder Errors: 0x04
   kMojoDecoderNoWrappedDecoder = 0x0401,
@@ -110,19 +90,6 @@
   // proper status.
   kDecoderStreamDemuxerError = 0x0B02,
 
-  // DecodeStatus temporary codes.  These names were chosen to match the
-  // DecodeStatus enum, so that un-converted code can DecodeStatus::OK/etc.
-  // Note that OK must result in Status::is_ok(), since converted code will
-  // check for it.  These will be removed when the conversion is complete.
-  //
-  // DO NOT ADD NEW USES OF OK/ABORTED/DECODE_ERROR.
-  OK = kOk,  // Everything went as planned.
-  // Read aborted due to Reset() during pending read.
-  ABORTED = kAborted,  // Read aborted due to Reset() during pending read.
-  // Decoder returned decode error. Note: Prefixed by DECODE_
-  // since ERROR is a reserved name (special macro) on Windows.
-  DECODE_ERROR = kDecodeErrorDoNotUse,
-
   // Special codes
   kGenericErrorPleaseRemove = 0x7999,
   kCodeOnlyForTesting = std::numeric_limits<StatusCodeType>::max(),
diff --git a/media/base/test_helpers.h b/media/base/test_helpers.h
index 04da5a9b..f257a1e 100644
--- a/media/base/test_helpers.h
+++ b/media/base/test_helpers.h
@@ -15,6 +15,7 @@
 #include "base/strings/stringprintf.h"
 #include "media/base/audio_parameters.h"
 #include "media/base/channel_layout.h"
+#include "media/base/decoder_status.h"
 #include "media/base/demuxer_stream.h"
 #include "media/base/media_log.h"
 #include "media/base/pipeline_status.h"
@@ -261,7 +262,7 @@
 // True if and only if the Status would be interpreted as an error from a decode
 // callback (not okay, not aborted).
 MATCHER(IsDecodeErrorStatus, "") {
-  return !arg.is_ok() && arg.code() != StatusCode::kAborted;
+  return !arg.is_ok() && arg.code() != DecoderStatus::Codes::kAborted;
 }
 
 // Compares two {Audio|Video}DecoderConfigs
diff --git a/media/base/video_decoder.h b/media/base/video_decoder.h
index 892bce6..7eb5a98f 100644
--- a/media/base/video_decoder.h
+++ b/media/base/video_decoder.h
@@ -6,8 +6,8 @@
 #define MEDIA_BASE_VIDEO_DECODER_H_
 
 #include "base/memory/ref_counted.h"
-#include "media/base/decode_status.h"
 #include "media/base/decoder.h"
+#include "media/base/decoder_status.h"
 #include "media/base/media_export.h"
 #include "media/base/pipeline_status.h"
 #include "media/base/waiting.h"
@@ -23,7 +23,7 @@
 class MEDIA_EXPORT VideoDecoder : public Decoder {
  public:
   // Callback for Decoder initialization.
-  using InitCB = base::OnceCallback<void(Status)>;
+  using InitCB = base::OnceCallback<void(DecoderStatus)>;
 
   // Callback for VideoDecoder to return a decoded frame whenever it becomes
   // available. Only non-EOS frames should be returned via this callback.
@@ -35,7 +35,7 @@
   // decode was aborted, which does not necessarily indicate an error.  For
   // example, a Reset() can trigger this.  Any other status code indicates that
   // the decoder encountered an error, and must be reset.
-  using DecodeCB = base::OnceCallback<void(Status)>;
+  using DecodeCB = base::OnceCallback<void(DecoderStatus)>;
 
   VideoDecoder();
   VideoDecoder(const VideoDecoder&) = delete;
diff --git a/media/base/video_thumbnail_decoder.cc b/media/base/video_thumbnail_decoder.cc
index ab3033cc..11cd0cac 100644
--- a/media/base/video_thumbnail_decoder.cc
+++ b/media/base/video_thumbnail_decoder.cc
@@ -36,7 +36,7 @@
       base::DoNothing());
 }
 
-void VideoThumbnailDecoder::OnVideoDecoderInitialized(Status status) {
+void VideoThumbnailDecoder::OnVideoDecoderInitialized(DecoderStatus status) {
   if (!status.is_ok()) {
     NotifyComplete(nullptr);
     return;
@@ -50,7 +50,7 @@
                                   weak_factory_.GetWeakPtr()));
 }
 
-void VideoThumbnailDecoder::OnVideoBufferDecoded(Status status) {
+void VideoThumbnailDecoder::OnVideoBufferDecoded(DecoderStatus status) {
   if (!status.is_ok()) {
     NotifyComplete(nullptr);
     return;
@@ -62,7 +62,7 @@
                                   weak_factory_.GetWeakPtr()));
 }
 
-void VideoThumbnailDecoder::OnEosBufferDecoded(Status status) {
+void VideoThumbnailDecoder::OnEosBufferDecoded(DecoderStatus status) {
   if (!status.is_ok())
     NotifyComplete(nullptr);
 }
diff --git a/media/base/video_thumbnail_decoder.h b/media/base/video_thumbnail_decoder.h
index af70aaf2..4d83e76 100644
--- a/media/base/video_thumbnail_decoder.h
+++ b/media/base/video_thumbnail_decoder.h
@@ -39,9 +39,9 @@
   void Start(VideoFrameCallback video_frame_callback);
 
  private:
-  void OnVideoDecoderInitialized(Status status);
-  void OnVideoBufferDecoded(Status status);
-  void OnEosBufferDecoded(Status status);
+  void OnVideoDecoderInitialized(DecoderStatus status);
+  void OnVideoBufferDecoded(DecoderStatus status);
+  void OnEosBufferDecoded(DecoderStatus status);
 
   // Called when the output frame is generated.
   void OnVideoFrameDecoded(scoped_refptr<VideoFrame> frame);
diff --git a/media/base/video_thumbnail_decoder_unittest.cc b/media/base/video_thumbnail_decoder_unittest.cc
index be746fc..ed7b4ef 100644
--- a/media/base/video_thumbnail_decoder_unittest.cc
+++ b/media/base/video_thumbnail_decoder_unittest.cc
@@ -88,11 +88,11 @@
 TEST_F(VideoThumbnailDecoderTest, Success) {
   auto expected_frame = CreateFrame();
   EXPECT_CALL(*mock_video_decoder(), Initialize_(_, _, _, _, _, _))
-      .WillOnce(DoAll(RunOnceCallback<3>(OkStatus()),
+      .WillOnce(DoAll(RunOnceCallback<3>(DecoderStatus::Codes::kOk),
                       RunCallback<4>(expected_frame)));
   EXPECT_CALL(*mock_video_decoder(), Decode_(_, _))
       .Times(2)
-      .WillRepeatedly(RunOnceCallback<1>(DecodeStatus::OK));
+      .WillRepeatedly(RunOnceCallback<1>(DecoderStatus::Codes::kOk));
 
   Start();
   EXPECT_TRUE(frame());
@@ -102,7 +102,7 @@
 TEST_F(VideoThumbnailDecoderTest, InitializationFailed) {
   auto expected_frame = CreateFrame();
   EXPECT_CALL(*mock_video_decoder(), Initialize_(_, _, _, _, _, _))
-      .WillOnce(RunOnceCallback<3>(StatusCode::kCodeOnlyForTesting));
+      .WillOnce(RunOnceCallback<3>(DecoderStatus::Codes::kFailed));
 
   Start();
   EXPECT_FALSE(frame());
@@ -112,9 +112,9 @@
 TEST_F(VideoThumbnailDecoderTest, DecodingFailed) {
   auto expected_frame = CreateFrame();
   EXPECT_CALL(*mock_video_decoder(), Initialize_(_, _, _, _, _, _))
-      .WillOnce(RunOnceCallback<3>(OkStatus()));
+      .WillOnce(RunOnceCallback<3>(DecoderStatus::Codes::kOk));
   EXPECT_CALL(*mock_video_decoder(), Decode_(_, _))
-      .WillOnce(RunOnceCallback<1>(DecodeStatus::DECODE_ERROR));
+      .WillOnce(RunOnceCallback<1>(DecoderStatus::Codes::kFailed));
 
   Start();
   EXPECT_FALSE(frame());
diff --git a/media/cast/sender/h264_vt_encoder_unittest.cc b/media/cast/sender/h264_vt_encoder_unittest.cc
index 6cfa165..80a349b 100644
--- a/media/cast/sender/h264_vt_encoder_unittest.cc
+++ b/media/cast/sender/h264_vt_encoder_unittest.cc
@@ -59,7 +59,7 @@
 // See comment in end2end_unittest.cc for details on this value.
 const double kVideoAcceptedPSNR = 38.0;
 
-void SaveDecoderInitResult(bool* out_result, ::media::Status in_result) {
+void SaveDecoderInitResult(bool* out_result, DecoderStatus in_result) {
   *out_result = in_result.is_ok();
 }
 
@@ -160,7 +160,7 @@
     ++count_frames_checked_;
   }
 
-  void DecodeDone(Status status) { EXPECT_TRUE(status.is_ok()); }
+  void DecodeDone(DecoderStatus status) { EXPECT_TRUE(status.is_ok()); }
 
   int count_frames_checked() const { return count_frames_checked_; }
 
diff --git a/media/cdm/library_cdm/clear_key_cdm/cdm_video_decoder.cc b/media/cdm/library_cdm/clear_key_cdm/cdm_video_decoder.cc
index bbcd531..6c80b2d5 100644
--- a/media/cdm/library_cdm/clear_key_cdm/cdm_video_decoder.cc
+++ b/media/cdm/library_cdm/clear_key_cdm/cdm_video_decoder.cc
@@ -23,7 +23,7 @@
 #include "base/run_loop.h"
 #include "base/task/single_thread_task_executor.h"
 #include "base/threading/thread_task_runner_handle.h"
-#include "media/base/decode_status.h"
+#include "media/base/decoder_status.h"
 #include "media/base/media_switches.h"
 #include "media/base/media_util.h"
 #include "media/cdm/cdm_type_conversion.h"
@@ -186,7 +186,7 @@
   ~VideoDecoderAdapter() final = default;
 
   // CdmVideoDecoder implementation.
-  Status Initialize(const cdm::VideoDecoderConfig_3& config) final {
+  DecoderStatus Initialize(const cdm::VideoDecoderConfig_3& config) final {
     auto clear_config = ToClearMediaVideoDecoderConfig(config);
     DVLOG(1) << __func__ << ": " << clear_config.AsHumanReadableString();
     DCHECK(!last_init_result_.has_value());
@@ -244,7 +244,7 @@
 
     // "kAborted" shouldn't happen during a sync decode, so treat it as an
     // error.
-    DCHECK_NE(decode_status.code(), StatusCode::kAborted);
+    DCHECK_NE(decode_status.code(), DecoderStatus::Codes::kAborted);
 
     if (!decode_status.is_ok())
       return cdm::kDecodeError;
@@ -261,7 +261,7 @@
   }
 
  private:
-  void OnInitialized(base::OnceClosure quit_closure, Status status) {
+  void OnInitialized(base::OnceClosure quit_closure, DecoderStatus status) {
     DVLOG(1) << __func__ << " success = " << status.is_ok();
     DCHECK(!last_init_result_.has_value());
     last_init_result_ = std::move(status);
@@ -282,7 +282,7 @@
     std::move(quit_closure).Run();
   }
 
-  void OnDecoded(base::OnceClosure quit_closure, Status decode_status) {
+  void OnDecoded(base::OnceClosure quit_closure, DecoderStatus decode_status) {
     DCHECK(!last_decode_status_.has_value());
     last_decode_status_ = std::move(decode_status);
     std::move(quit_closure).Run();
@@ -293,8 +293,8 @@
 
   // Results of |video_decoder_| operations. Set iff the callback of the
   // operation has been called.
-  absl::optional<Status> last_init_result_;
-  absl::optional<Status> last_decode_status_;
+  absl::optional<DecoderStatus> last_init_result_;
+  absl::optional<DecoderStatus> last_decode_status_;
 
   // Queue of decoded video frames.
   using VideoFrameQueue = base::queue<scoped_refptr<VideoFrame>>;
diff --git a/media/cdm/library_cdm/clear_key_cdm/cdm_video_decoder.h b/media/cdm/library_cdm/clear_key_cdm/cdm_video_decoder.h
index 7346d330..133af1fc 100644
--- a/media/cdm/library_cdm/clear_key_cdm/cdm_video_decoder.h
+++ b/media/cdm/library_cdm/clear_key_cdm/cdm_video_decoder.h
@@ -11,7 +11,7 @@
 
 #include "base/memory/ref_counted.h"
 #include "media/base/decoder_buffer.h"
-#include "media/base/status.h"
+#include "media/base/decoder_status.h"
 #include "media/cdm/api/content_decryption_module.h"
 
 namespace media {
@@ -22,8 +22,8 @@
  public:
   using CdmVideoFrame = cdm::VideoFrame_2;
 
-  virtual ~CdmVideoDecoder() {}
-  virtual Status Initialize(const cdm::VideoDecoderConfig_3& config) = 0;
+  virtual ~CdmVideoDecoder() = default;
+  virtual DecoderStatus Initialize(const cdm::VideoDecoderConfig_3& config) = 0;
   virtual void Deinitialize() = 0;
   virtual void Reset() = 0;
   virtual cdm::Status Decode(scoped_refptr<DecoderBuffer> buffer,
diff --git a/media/filters/android/media_codec_audio_decoder.cc b/media/filters/android/media_codec_audio_decoder.cc
index e48a737e..66652fc 100644
--- a/media/filters/android/media_codec_audio_decoder.cc
+++ b/media/filters/android/media_codec_audio_decoder.cc
@@ -46,7 +46,7 @@
   if (media_crypto_context_)
     media_crypto_context_->SetMediaCryptoReadyCB(base::NullCallback());
 
-  ClearInputQueue(DecodeStatus::ABORTED);
+  ClearInputQueue(DecoderStatus::Codes::kAborted);
 }
 
 AudioDecoderType MediaCodecAudioDecoder::GetDecoderType() const {
@@ -66,7 +66,7 @@
   // Initialization and reinitialization should not be called during pending
   // decode.
   DCHECK(input_queue_.empty());
-  ClearInputQueue(DecodeStatus::ABORTED);
+  ClearInputQueue(DecoderStatus::Codes::kAborted);
 
   is_passthrough_ = MediaCodecUtil::IsPassthroughAudioFormat(config.codec());
   sample_format_ = kSampleFormatS16;
@@ -80,8 +80,7 @@
 
   if (state_ == STATE_ERROR) {
     DVLOG(1) << "Decoder is in error state.";
-    BindToCurrentLoop(std::move(init_cb))
-        .Run(StatusCode::kDecoderFailedInitialization);
+    BindToCurrentLoop(std::move(init_cb)).Run(DecoderStatus::Codes::kFailed);
     return;
   }
 
@@ -96,7 +95,7 @@
   if (!is_codec_supported) {
     DVLOG(1) << "Unsuported codec " << GetCodecName(config.codec());
     BindToCurrentLoop(std::move(init_cb))
-        .Run(StatusCode::kDecoderUnsupportedCodec);
+        .Run(DecoderStatus::Codes::kUnsupportedCodec);
     return;
   }
 
@@ -114,7 +113,7 @@
                     "MediaCryptoContext is not supported";
       SetState(STATE_ERROR);
       BindToCurrentLoop(std::move(init_cb))
-          .Run(StatusCode::kDecoderMissingCdmForEncryptedContent);
+          .Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
       return;
     }
 
@@ -126,13 +125,12 @@
   }
 
   if (!CreateMediaCodecLoop()) {
-    BindToCurrentLoop(std::move(init_cb))
-        .Run(StatusCode::kDecoderFailedInitialization);
+    BindToCurrentLoop(std::move(init_cb)).Run(DecoderStatus::Codes::kFailed);
     return;
   }
 
   SetState(STATE_READY);
-  BindToCurrentLoop(std::move(init_cb)).Run(OkStatus());
+  BindToCurrentLoop(std::move(init_cb)).Run(DecoderStatus::Codes::kOk);
 }
 
 bool MediaCodecAudioDecoder::CreateMediaCodecLoop() {
@@ -171,7 +169,7 @@
   if (!buffer->end_of_stream() && buffer->timestamp() == kNoTimestamp) {
     DVLOG(2) << __func__ << " " << buffer->AsHumanReadableString()
              << ": no timestamp, skipping this buffer";
-    std::move(bound_decode_cb).Run(DecodeStatus::DECODE_ERROR);
+    std::move(bound_decode_cb).Run(DecoderStatus::Codes::kFailed);
     return;
   }
 
@@ -180,8 +178,8 @@
     // We get here if an error happens in DequeueOutput() or Reset().
     DVLOG(2) << __func__ << " " << buffer->AsHumanReadableString()
              << ": Error state, returning decode error for all buffers";
-    ClearInputQueue(DecodeStatus::DECODE_ERROR);
-    std::move(bound_decode_cb).Run(DecodeStatus::DECODE_ERROR);
+    ClearInputQueue(DecoderStatus::Codes::kFailed);
+    std::move(bound_decode_cb).Run(DecoderStatus::Codes::kFailed);
     return;
   }
 
@@ -204,7 +202,7 @@
 void MediaCodecAudioDecoder::Reset(base::OnceClosure closure) {
   DVLOG(2) << __func__;
 
-  ClearInputQueue(DecodeStatus::ABORTED);
+  ClearInputQueue(DecoderStatus::Codes::kAborted);
 
   // Flush if we can, otherwise completely recreate and reconfigure the codec.
   bool success = codec_loop_->TryFlush();
@@ -267,7 +265,7 @@
   if (media_crypto->is_null()) {
     LOG(ERROR) << "MediaCrypto is not available, can't play encrypted stream.";
     SetState(STATE_UNINITIALIZED);
-    std::move(init_cb).Run(StatusCode::kDecoderMissingCdmForEncryptedContent);
+    std::move(init_cb).Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
     return;
   }
 
@@ -280,12 +278,12 @@
   // After receiving |media_crypto_| we can configure MediaCodec.
   if (!CreateMediaCodecLoop()) {
     SetState(STATE_UNINITIALIZED);
-    std::move(init_cb).Run(StatusCode::kDecoderFailedInitialization);
+    std::move(init_cb).Run(DecoderStatus::Codes::kFailed);
     return;
   }
 
   SetState(STATE_READY);
-  std::move(init_cb).Run(OkStatus());
+  std::move(init_cb).Run(DecoderStatus::Codes::kOk);
 }
 
 bool MediaCodecAudioDecoder::IsAnyInputPending() const {
@@ -332,11 +330,11 @@
     return;
 
   std::move(input_queue_.front().second)
-      .Run(success ? DecodeStatus::OK : DecodeStatus::DECODE_ERROR);
+      .Run(success ? DecoderStatus::Codes::kOk : DecoderStatus::Codes::kFailed);
   input_queue_.pop_front();
 }
 
-void MediaCodecAudioDecoder::ClearInputQueue(DecodeStatus decode_status) {
+void MediaCodecAudioDecoder::ClearInputQueue(DecoderStatus decode_status) {
   DVLOG(2) << __func__;
 
   for (auto& entry : input_queue_)
@@ -354,7 +352,7 @@
 void MediaCodecAudioDecoder::OnCodecLoopError() {
   // If the codec transitions into the error state, then so should we.
   SetState(STATE_ERROR);
-  ClearInputQueue(DecodeStatus::DECODE_ERROR);
+  ClearInputQueue(DecoderStatus::Codes::kFailed);
 }
 
 bool MediaCodecAudioDecoder::OnDecodedEos(
@@ -376,7 +374,7 @@
   // So, we shouldn't be in that state.  So, just DCHECK here.
   DCHECK_NE(state_, STATE_ERROR);
 
-  std::move(input_queue_.front()).second.Run(DecodeStatus::OK);
+  std::move(input_queue_.front()).second.Run(DecoderStatus::Codes::kOk);
   input_queue_.pop_front();
 
   return true;
diff --git a/media/filters/android/media_codec_audio_decoder.h b/media/filters/android/media_codec_audio_decoder.h
index 0f1c6c3..7971cf36 100644
--- a/media/filters/android/media_codec_audio_decoder.h
+++ b/media/filters/android/media_codec_audio_decoder.h
@@ -142,7 +142,7 @@
 
   // Calls DecodeCB with |decode_status| for every frame in |input_queue| and
   // then clears it.
-  void ClearInputQueue(DecodeStatus decode_status);
+  void ClearInputQueue(DecoderStatus decode_status);
 
   // Helper method to change the state.
   void SetState(State new_state);
diff --git a/media/filters/audio_decoder_stream_unittest.cc b/media/filters/audio_decoder_stream_unittest.cc
index 0f9e4bb9..7040c14 100644
--- a/media/filters/audio_decoder_stream_unittest.cc
+++ b/media/filters/audio_decoder_stream_unittest.cc
@@ -98,7 +98,8 @@
                 config.channel_layout(), config.channels(),
                 config.samples_per_second(), 1221, last_timestamp_)));
     base::SequencedTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE, base::BindOnce(std::move(decode_cb), DecodeStatus::OK));
+        FROM_HERE,
+        base::BindOnce(std::move(decode_cb), DecoderStatus::Codes::kOk));
   }
 
   void RunUntilIdle() { task_environment_.RunUntilIdle(); }
@@ -109,7 +110,7 @@
     EXPECT_CALL(*decoder, Initialize_(_, _, _, _, _))
         .Times(AnyNumber())
         .WillRepeatedly(DoAll(SaveArg<3>(&decoder_output_cb_),
-                              RunOnceCallback<2>(OkStatus())));
+                              RunOnceCallback<2>(DecoderStatus::Codes::kOk)));
     decoder_ = decoder.get();
 
     std::vector<std::unique_ptr<AudioDecoder>> result;
diff --git a/media/filters/audio_decoder_unittest.cc b/media/filters/audio_decoder_unittest.cc
index 06f22b1..5718906 100644
--- a/media/filters/audio_decoder_unittest.cc
+++ b/media/filters/audio_decoder_unittest.cc
@@ -124,7 +124,7 @@
         params_(std::get<1>(GetParam())),
         pending_decode_(false),
         pending_reset_(false),
-        last_decode_status_(DecodeStatus::DECODE_ERROR) {
+        last_decode_status_(DecoderStatus::Codes::kFailed) {
     switch (decoder_type_) {
       case FFMPEG:
         decoder_ = std::make_unique<FFmpegAudioDecoder>(
@@ -151,7 +151,7 @@
   void DecodeBuffer(scoped_refptr<DecoderBuffer> buffer) {
     ASSERT_FALSE(pending_decode_);
     pending_decode_ = true;
-    last_decode_status_ = DecodeStatus::DECODE_ERROR;
+    last_decode_status_ = DecoderStatus::Codes::kFailed;
 
     base::RunLoop run_loop;
     decoder_->Decode(
@@ -227,7 +227,7 @@
                                    bool success) {
     decoder_->Initialize(config, nullptr,
                          base::BindOnce(
-                             [](bool success, Status status) {
+                             [](bool success, DecoderStatus status) {
                                EXPECT_EQ(status.is_ok(), success);
                              },
                              success),
@@ -280,7 +280,7 @@
     decoded_audio_.push_back(std::move(buffer));
   }
 
-  void DecodeFinished(base::OnceClosure quit_closure, Status status) {
+  void DecodeFinished(base::OnceClosure quit_closure, DecoderStatus status) {
     EXPECT_TRUE(pending_decode_);
     EXPECT_FALSE(pending_reset_);
     pending_decode_ = false;
@@ -353,7 +353,9 @@
   const scoped_refptr<AudioBuffer>& decoded_audio(size_t i) {
     return decoded_audio_[i];
   }
-  const Status& last_decode_status() const { return last_decode_status_; }
+  const DecoderStatus& last_decode_status() const {
+    return last_decode_status_;
+  }
 
  private:
   const TestAudioDecoderType decoder_type_;
@@ -373,7 +375,7 @@
   std::unique_ptr<AudioDecoder> decoder_;
   bool pending_decode_;
   bool pending_reset_;
-  Status last_decode_status_;
+  DecoderStatus last_decode_status_ = DecoderStatus::Codes::kOk;
 
   base::circular_deque<scoped_refptr<AudioBuffer>> decoded_audio_;
   base::TimeDelta start_timestamp_;
diff --git a/media/filters/dav1d_video_decoder.cc b/media/filters/dav1d_video_decoder.cc
index 1633956..830b6bd 100644
--- a/media/filters/dav1d_video_decoder.cc
+++ b/media/filters/dav1d_video_decoder.cc
@@ -169,13 +169,14 @@
   InitCB bound_init_cb = bind_callbacks_ ? BindToCurrentLoop(std::move(init_cb))
                                          : std::move(init_cb);
   if (config.is_encrypted()) {
-    std::move(bound_init_cb).Run(StatusCode::kEncryptedContentUnsupported);
+    std::move(bound_init_cb)
+        .Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
     return;
   }
 
   if (config.codec() != VideoCodec::kAV1) {
     std::move(bound_init_cb)
-        .Run(Status(StatusCode::kDecoderUnsupportedCodec)
+        .Run(DecoderStatus(DecoderStatus::Codes::kUnsupportedCodec)
                  .WithData("codec", config.codec()));
     return;
   }
@@ -211,14 +212,14 @@
 
   // TODO(tmathmeyer) write the dav1d error into the data for the media error.
   if (dav1d_open(&dav1d_decoder_, &s) < 0) {
-    std::move(bound_init_cb).Run(StatusCode::kDecoderFailedInitialization);
+    std::move(bound_init_cb).Run(DecoderStatus::Codes::kFailedToCreateDecoder);
     return;
   }
 
   config_ = config;
   state_ = DecoderState::kNormal;
   output_cb_ = output_cb;
-  std::move(bound_init_cb).Run(OkStatus());
+  std::move(bound_init_cb).Run(DecoderStatus::Codes::kOk);
 }
 
 void Dav1dVideoDecoder::Decode(scoped_refptr<DecoderBuffer> buffer,
@@ -234,18 +235,18 @@
                                  : std::move(decode_cb);
 
   if (state_ == DecoderState::kError) {
-    std::move(bound_decode_cb).Run(DecodeStatus::DECODE_ERROR);
+    std::move(bound_decode_cb).Run(DecoderStatus::Codes::kFailed);
     return;
   }
 
   if (!DecodeBuffer(std::move(buffer))) {
     state_ = DecoderState::kError;
-    std::move(bound_decode_cb).Run(DecodeStatus::DECODE_ERROR);
+    std::move(bound_decode_cb).Run(DecoderStatus::Codes::kFailed);
     return;
   }
 
   // VideoDecoderShim expects |decode_cb| call after |output_cb_|.
-  std::move(bound_decode_cb).Run(DecodeStatus::OK);
+  std::move(bound_decode_cb).Run(DecoderStatus::Codes::kOk);
 }
 
 void Dav1dVideoDecoder::Reset(base::OnceClosure reset_cb) {
diff --git a/media/filters/dav1d_video_decoder_unittest.cc b/media/filters/dav1d_video_decoder_unittest.cc
index 61f51fc5..676a02b8 100644
--- a/media/filters/dav1d_video_decoder_unittest.cc
+++ b/media/filters/dav1d_video_decoder_unittest.cc
@@ -57,7 +57,7 @@
         config, true,  // Use low delay so we get 1 frame out for each frame in.
         nullptr,
         base::BindOnce(
-            [](bool success, Status status) {
+            [](bool success, DecoderStatus status) {
               EXPECT_EQ(status.is_ok(), success);
             },
             success),
@@ -105,14 +105,14 @@
   // Decodes all buffers in |input_buffers| and push all successfully decoded
   // output frames into |output_frames|. Returns the last decode status returned
   // by the decoder.
-  Status DecodeMultipleFrames(const InputBuffers& input_buffers) {
+  DecoderStatus DecodeMultipleFrames(const InputBuffers& input_buffers) {
     for (auto iter = input_buffers.begin(); iter != input_buffers.end();
          ++iter) {
-      Status status = Decode(*iter);
+      DecoderStatus status = Decode(*iter);
       switch (status.code()) {
-        case StatusCode::kOk:
+        case DecoderStatus::Codes::kOk:
           break;
-        case StatusCode::kAborted:
+        case DecoderStatus::Codes::kAborted:
           NOTREACHED();
           [[fallthrough]];
         default:
@@ -120,11 +120,11 @@
           return status;
       }
     }
-    return StatusCode::kOk;
+    return DecoderStatus::Codes::kOk;
   }
 
   // Decodes the single compressed frame in |buffer|.
-  Status DecodeSingleFrame(scoped_refptr<DecoderBuffer> buffer) {
+  DecoderStatus DecodeSingleFrame(scoped_refptr<DecoderBuffer> buffer) {
     InputBuffers input_buffers;
     input_buffers.push_back(std::move(buffer));
     return DecodeMultipleFrames(input_buffers);
@@ -143,7 +143,7 @@
     input_buffers.push_back(buffer);
     input_buffers.push_back(DecoderBuffer::CreateEOSBuffer());
 
-    Status status = DecodeMultipleFrames(input_buffers);
+    DecoderStatus status = DecodeMultipleFrames(input_buffers);
 
     EXPECT_TRUE(status.is_ok());
     ASSERT_EQ(2U, output_frames_.size());
@@ -159,8 +159,8 @@
               output_frames_[1]->visible_rect().size().height());
   }
 
-  Status Decode(scoped_refptr<DecoderBuffer> buffer) {
-    Status status;
+  DecoderStatus Decode(scoped_refptr<DecoderBuffer> buffer) {
+    DecoderStatus status;
     EXPECT_CALL(*this, DecodeDone(_)).WillOnce(testing::SaveArg<0>(&status));
 
     decoder_->Decode(std::move(buffer),
@@ -185,7 +185,7 @@
     return base::MD5DigestToBase16(digest);
   }
 
-  MOCK_METHOD1(DecodeDone, void(Status));
+  MOCK_METHOD1(DecodeDone, void(DecoderStatus));
 
   testing::StrictMock<MockMediaLog> media_log_;
 
diff --git a/media/filters/decoder_selector.cc b/media/filters/decoder_selector.cc
index aa9973d1..3cbaaf3 100644
--- a/media/filters/decoder_selector.cc
+++ b/media/filters/decoder_selector.cc
@@ -321,9 +321,10 @@
 }
 
 template <DemuxerStream::Type StreamType>
-void DecoderSelector<StreamType>::OnDecoderInitializeDone(Status status) {
+void DecoderSelector<StreamType>::OnDecoderInitializeDone(
+    DecoderStatus status) {
   DVLOG(2) << __func__ << ": " << decoder_->GetDecoderType()
-           << " success=" << std::hex << status.code();
+           << " success=" << static_cast<int>(status.code());
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (!status.is_ok()) {
diff --git a/media/filters/decoder_selector.h b/media/filters/decoder_selector.h
index 9e73a52..152785b 100644
--- a/media/filters/decoder_selector.h
+++ b/media/filters/decoder_selector.h
@@ -132,7 +132,7 @@
  private:
   void CreateDecoders();
   void InitializeDecoder();
-  void OnDecoderInitializeDone(Status status);
+  void OnDecoderInitializeDone(DecoderStatus status);
   void ReturnNullDecoder();
   void InitializeDecryptingDemuxerStream();
   void OnDecryptingDemuxerStreamInitializeDone(PipelineStatus status);
diff --git a/media/filters/decoder_selector_unittest.cc b/media/filters/decoder_selector_unittest.cc
index 8bfbf8b1..4856e5d 100644
--- a/media/filters/decoder_selector_unittest.cc
+++ b/media/filters/decoder_selector_unittest.cc
@@ -68,16 +68,19 @@
   }
 }
 
-Status IsConfigSupported(DecoderCapability capability, bool is_encrypted) {
+DecoderStatus IsConfigSupported(DecoderCapability capability,
+                                bool is_encrypted) {
   switch (capability) {
     case kAlwaysFail:
-      return StatusCode::kCodeOnlyForTesting;
+      return DecoderStatus::Codes::kFailed;
     case kClearOnly:
-      return is_encrypted ? StatusCode::kCodeOnlyForTesting : StatusCode::kOk;
+      return is_encrypted ? DecoderStatus::Codes::kUnsupportedEncryptionMode
+                          : DecoderStatus::Codes::kOk;
     case kEncryptedOnly:
-      return is_encrypted ? StatusCode::kOk : StatusCode::kCodeOnlyForTesting;
+      return is_encrypted ? DecoderStatus::Codes::kOk
+                          : DecoderStatus::Codes::kUnsupportedEncryptionMode;
     case kAlwaysSucceed:
-      return OkStatus();
+      return DecoderStatus::Codes::kOk;
   }
 }
 
diff --git a/media/filters/decoder_stream.cc b/media/filters/decoder_stream.cc
index 09bc37f..b67fb512 100644
--- a/media/filters/decoder_stream.cc
+++ b/media/filters/decoder_stream.cc
@@ -78,12 +78,12 @@
   return "AudioDecoderStream::PrepareOutput";
 }
 
-const char* GetStatusString(const Status& status) {
+const char* GetStatusString(const DecoderStatus& status) {
   // TODO(crbug.com/1129662): Replace this with generic Status-to-string.
   switch (status.code()) {
-    case StatusCode::kOk:
+    case DecoderStatus::Codes::kOk:
       return "okay";
-    case StatusCode::kAborted:
+    case DecoderStatus::Codes::kAborted:
       return "aborted";
     default:
       return "decode_error";
@@ -127,7 +127,7 @@
   }
   if (read_cb_) {
     read_cb_ = BindToCurrentLoop(std::move(read_cb_));
-    SatisfyRead(StatusCode::kAborted);
+    SatisfyRead(DecoderStatus::Codes::kAborted);
   }
   if (reset_cb_)
     task_runner_->PostTask(FROM_HERE, std::move(reset_cb_));
@@ -188,7 +188,7 @@
     read_cb_ = BindToCurrentLoop(std::move(read_cb));
     // TODO(crbug.com/1129662): Consider attaching a caused-by of the original
     // error as well.
-    SatisfyRead(StatusCode::kDecoderStreamInErrorState);
+    SatisfyRead(DecoderStatus::Codes::kDecoderStreamInErrorState);
     return;
   }
 
@@ -223,7 +223,7 @@
 
   if (read_cb_) {
     read_cb_ = BindToCurrentLoop(std::move(read_cb_));
-    SatisfyRead(StatusCode::kAborted);
+    SatisfyRead(DecoderStatus::Codes::kAborted);
   }
 
   ClearOutputs();
@@ -519,8 +519,9 @@
     int buffer_size,
     bool end_of_stream,
     std::unique_ptr<ScopedDecodeTrace> trace_event,
-    Status status) {
-  FUNCTION_DVLOG(status.is_ok() ? 3 : 1) << ": " << status.code();
+    DecoderStatus status) {
+  FUNCTION_DVLOG(status.is_ok() ? 3 : 1)
+      << ": " << static_cast<int>(status.code());
   DCHECK(state_ == STATE_NORMAL || state_ == STATE_FLUSHING_DECODER ||
          state_ == STATE_ERROR)
       << state_;
@@ -552,11 +553,11 @@
     return;
 
   switch (status.code()) {
-    case StatusCode::kAborted:
+    case DecoderStatus::Codes::kAborted:
       // Decoder can return kAborted during Reset() or during destruction.
       return;
 
-    case StatusCode::kOk:
+    case DecoderStatus::Codes::kOk:
       // Any successful decode counts!
       if (buffer_size > 0)
         traits_->ReportStatistics(statistics_cb_, buffer_size);
@@ -742,7 +743,7 @@
     pending_buffers_.clear();
     ClearOutputs();
     if (read_cb_)
-      SatisfyRead(StatusCode::kDecoderStreamDemuxerError);
+      SatisfyRead(DecoderStatus::Codes::kDecoderStreamDemuxerError);
   }
 
   // Decoding has been stopped.
@@ -818,7 +819,7 @@
 
   if (status == DemuxerStream::kAborted) {
     if (read_cb_)
-      SatisfyRead(StatusCode::kAborted);
+      SatisfyRead(DecoderStatus::Codes::kAborted);
     return;
   }
 
@@ -870,7 +871,7 @@
   if (state_ == STATE_ERROR) {
     MEDIA_LOG(ERROR, media_log_)
         << GetStreamTypeString() << " decoder reinitialization failed";
-    SatisfyRead(StatusCode::kDecoderStreamReinitFailed);
+    SatisfyRead(DecoderStatus::Codes::kDecoderStreamReinitFailed);
     return;
   }
 
diff --git a/media/filters/decoder_stream.h b/media/filters/decoder_stream.h
index cbe5750..0c669bd 100644
--- a/media/filters/decoder_stream.h
+++ b/media/filters/decoder_stream.h
@@ -18,6 +18,7 @@
 #include "base/types/pass_key.h"
 #include "media/base/audio_decoder.h"
 #include "media/base/audio_timestamp_helper.h"
+#include "media/base/decoder_status.h"
 #include "media/base/demuxer_stream.h"
 #include "media/base/media_export.h"
 #include "media/base/media_log.h"
@@ -55,7 +56,7 @@
   using InitCB = base::OnceCallback<void(bool success)>;
 
   // Indicates completion of a DecoderStream read.
-  using ReadResult = StatusOr<scoped_refptr<Output>>;
+  using ReadResult = DecoderStatus::Or<scoped_refptr<Output>>;
   using ReadCB = base::OnceCallback<void(ReadResult)>;
 
   DecoderStream(std::unique_ptr<DecoderStreamTraits<StreamType>> traits,
@@ -199,7 +200,7 @@
   void OnDecodeDone(int buffer_size,
                     bool end_of_stream,
                     std::unique_ptr<ScopedDecodeTrace> trace_event,
-                    media::Status status);
+                    DecoderStatus status);
 
   // Output callback passed to Decoder::Initialize().
   void OnDecodeOutputReady(scoped_refptr<Output> output);
diff --git a/media/filters/decoder_stream_traits.cc b/media/filters/decoder_stream_traits.cc
index a7b3a82..322134fa 100644
--- a/media/filters/decoder_stream_traits.cc
+++ b/media/filters/decoder_stream_traits.cc
@@ -102,7 +102,7 @@
 void DecoderStreamTraits<DemuxerStream::AUDIO>::OnDecoderInitialized(
     DecoderType* decoder,
     InitCB cb,
-    Status result) {
+    DecoderStatus result) {
   if (result.is_ok())
     stats_.audio_pipeline_info.decoder_type = decoder->GetDecoderType();
   std::move(cb).Run(result);
@@ -227,7 +227,7 @@
 void DecoderStreamTraits<DemuxerStream::VIDEO>::OnDecoderInitialized(
     DecoderType* decoder,
     InitCB cb,
-    Status result) {
+    DecoderStatus result) {
   if (result.is_ok()) {
     stats_.video_pipeline_info.decoder_type = decoder->GetDecoderType();
     DVLOG(2) << stats_.video_pipeline_info.decoder_type;
diff --git a/media/filters/decoder_stream_traits.h b/media/filters/decoder_stream_traits.h
index ea5369d..d3d3afba 100644
--- a/media/filters/decoder_stream_traits.h
+++ b/media/filters/decoder_stream_traits.h
@@ -67,7 +67,9 @@
                          InitCB init_cb,
                          const OutputCB& output_cb,
                          const WaitingCB& waiting_cb);
-  void OnDecoderInitialized(DecoderType* decoder, InitCB cb, Status status);
+  void OnDecoderInitialized(DecoderType* decoder,
+                            InitCB cb,
+                            DecoderStatus status);
   DecoderConfigType GetDecoderConfig(DemuxerStream* stream);
   void OnDecode(const DecoderBuffer& buffer);
   PostDecodeAction OnDecodeDone(OutputType* buffer);
@@ -126,7 +128,9 @@
                          InitCB init_cb,
                          const OutputCB& output_cb,
                          const WaitingCB& waiting_cb);
-  void OnDecoderInitialized(DecoderType* decoder, InitCB cb, Status status);
+  void OnDecoderInitialized(DecoderType* decoder,
+                            InitCB cb,
+                            DecoderStatus status);
   void OnDecode(const DecoderBuffer& buffer);
   PostDecodeAction OnDecodeDone(OutputType* buffer);
   void OnStreamReset(DemuxerStream* stream);
diff --git a/media/filters/decrypting_audio_decoder.cc b/media/filters/decrypting_audio_decoder.cc
index c86740c..67035105 100644
--- a/media/filters/decrypting_audio_decoder.cc
+++ b/media/filters/decrypting_audio_decoder.cc
@@ -61,12 +61,12 @@
   if (!cdm_context) {
     // Once we have a CDM context, one should always be present.
     DCHECK(!support_clear_content_);
-    std::move(init_cb_).Run(StatusCode::kDecoderMissingCdmForEncryptedContent);
+    std::move(init_cb_).Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
     return;
   }
 
   if (!config.is_encrypted() && !support_clear_content_) {
-    std::move(init_cb_).Run(StatusCode::kClearContentUnsupported);
+    std::move(init_cb_).Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
     return;
   }
 
@@ -82,7 +82,7 @@
   // TODO(xhwang): We should be able to DCHECK config.IsValidConfig().
   if (!config.IsValidConfig()) {
     DLOG(ERROR) << "Invalid audio stream config.";
-    std::move(init_cb_).Run(StatusCode::kDecoderUnsupportedCodec);
+    std::move(init_cb_).Run(DecoderStatus::Codes::kUnsupportedCodec);
     return;
   }
 
@@ -91,7 +91,7 @@
   if (state_ == kUninitialized) {
     if (!cdm_context->GetDecryptor()) {
       DVLOG(1) << __func__ << ": no decryptor";
-      std::move(init_cb_).Run(StatusCode::kDecoderFailedInitialization);
+      std::move(init_cb_).Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
       return;
     }
 
@@ -121,7 +121,7 @@
   // Return empty (end-of-stream) frames if decoding has finished.
   if (state_ == kDecodeFinished) {
     output_cb_.Run(AudioBuffer::CreateEOSBuffer());
-    std::move(decode_cb_).Run(DecodeStatus::OK);
+    std::move(decode_cb_).Run(DecoderStatus::Codes::kOk);
     return;
   }
 
@@ -162,7 +162,7 @@
   if (state_ == kWaitingForKey) {
     DCHECK(decode_cb_);
     pending_buffer_to_decode_.reset();
-    std::move(decode_cb_).Run(DecodeStatus::ABORTED);
+    std::move(decode_cb_).Run(DecoderStatus::Codes::kAborted);
   }
 
   DCHECK(!decode_cb_);
@@ -182,9 +182,9 @@
   }
   pending_buffer_to_decode_.reset();
   if (init_cb_)
-    std::move(init_cb_).Run(StatusCode::kDecoderInitializeNeverCompleted);
+    std::move(init_cb_).Run(DecoderStatus::Codes::kInterrupted);
   if (decode_cb_)
-    std::move(decode_cb_).Run(DecodeStatus::ABORTED);
+    std::move(decode_cb_).Run(DecoderStatus::Codes::kAborted);
   if (reset_cb_)
     std::move(reset_cb_).Run();
 }
@@ -207,7 +207,7 @@
 
   if (!success) {
     DVLOG(1) << __func__ << ": failed to init audio decoder on decryptor";
-    std::move(init_cb_).Run(StatusCode::kDecoderInitializeNeverCompleted);
+    std::move(init_cb_).Run(DecoderStatus::Codes::kFailedToCreateDecoder);
     decryptor_ = nullptr;
     event_cb_registration_.reset();
     state_ = kError;
@@ -219,7 +219,7 @@
       std::make_unique<AudioTimestampHelper>(config_.samples_per_second());
 
   state_ = kIdle;
-  std::move(init_cb_).Run(OkStatus());
+  std::move(init_cb_).Run(DecoderStatus::Codes::kOk);
 }
 
 void DecryptingAudioDecoder::DecodePendingBuffer() {
@@ -254,7 +254,7 @@
       std::move(pending_buffer_to_decode_);
 
   if (reset_cb_) {
-    std::move(decode_cb_).Run(DecodeStatus::ABORTED);
+    std::move(decode_cb_).Run(DecoderStatus::Codes::kAborted);
     DoReset();
     return;
   }
@@ -265,7 +265,7 @@
     DVLOG(2) << "DeliverFrame() - kError";
     MEDIA_LOG(ERROR, media_log_) << GetDecoderType() << ": decode error";
     state_ = kDecodeFinished;  // TODO add kError state
-    std::move(decode_cb_).Run(DecodeStatus::DECODE_ERROR);
+    std::move(decode_cb_).Run(DecoderStatus::Codes::kFailed);
     return;
   }
 
@@ -299,7 +299,7 @@
     DVLOG(2) << "DeliverFrame() - kNeedMoreData";
     state_ = scoped_pending_buffer_to_decode->end_of_stream() ? kDecodeFinished
                                                               : kIdle;
-    std::move(decode_cb_).Run(DecodeStatus::OK);
+    std::move(decode_cb_).Run(DecoderStatus::Codes::kOk);
     return;
   }
 
@@ -316,7 +316,7 @@
   }
 
   state_ = kIdle;
-  std::move(decode_cb_).Run(DecodeStatus::OK);
+  std::move(decode_cb_).Run(DecoderStatus::Codes::kOk);
 }
 
 void DecryptingAudioDecoder::OnCdmContextEvent(CdmContext::Event event) {
diff --git a/media/filters/decrypting_audio_decoder_unittest.cc b/media/filters/decrypting_audio_decoder_unittest.cc
index a55e5d5f..0774de1 100644
--- a/media/filters/decrypting_audio_decoder_unittest.cc
+++ b/media/filters/decrypting_audio_decoder_unittest.cc
@@ -89,7 +89,7 @@
     decoder_->Initialize(
         config, cdm_context_.get(),
         base::BindOnce(
-            [](bool success, Status status) {
+            [](bool success, DecoderStatus status) {
               EXPECT_EQ(status.is_ok(), success);
             },
             success),
@@ -132,7 +132,8 @@
         .WillOnce(RunOnceCallback<1>(true));
     decoder_->Initialize(
         new_config, cdm_context_.get(),
-        base::BindOnce([](Status status) { EXPECT_TRUE(status.is_ok()); }),
+        base::BindOnce(
+            [](DecoderStatus status) { EXPECT_TRUE(status.is_ok()); }),
         base::BindRepeating(&DecryptingAudioDecoderTest::FrameReady,
                             base::Unretained(this)),
         base::BindRepeating(&DecryptingAudioDecoderTest::OnWaiting,
@@ -140,7 +141,8 @@
   }
 
   // Decode |buffer| and expect DecodeDone to get called with |status|.
-  void DecodeAndExpect(scoped_refptr<DecoderBuffer> buffer, StatusCode status) {
+  void DecodeAndExpect(scoped_refptr<DecoderBuffer> buffer,
+                       DecoderStatus status) {
     EXPECT_CALL(*this, DecodeDone(HasStatusCode(status)));
     decoder_->Decode(buffer,
                      base::BindOnce(&DecryptingAudioDecoderTest::DecodeDone,
@@ -176,7 +178,7 @@
             Invoke(this, &DecryptingAudioDecoderTest::DecryptAndDecodeAudio));
     EXPECT_CALL(*this, FrameReady(decoded_frame_));
     for (int i = 0; i < kDecodingDelay + 1; ++i)
-      DecodeAndExpect(encrypted_buffer_, DecodeStatus::OK);
+      DecodeAndExpect(encrypted_buffer_, DecoderStatus::Codes::kOk);
   }
 
   // Sets up expectations and actions to put DecryptingAudioDecoder in an end
@@ -185,7 +187,8 @@
   void EnterEndOfStreamState() {
     // The codec in the |decryptor_| will be flushed.
     EXPECT_CALL(*this, FrameReady(decoded_frame_)).Times(kDecodingDelay);
-    DecodeAndExpect(DecoderBuffer::CreateEOSBuffer(), DecodeStatus::OK);
+    DecodeAndExpect(DecoderBuffer::CreateEOSBuffer(),
+                    DecoderStatus::Codes::kOk);
     EXPECT_EQ(0, num_frames_in_decryptor_);
   }
 
@@ -252,7 +255,7 @@
   }
 
   MOCK_METHOD1(FrameReady, void(scoped_refptr<AudioBuffer>));
-  MOCK_METHOD1(DecodeDone, void(Status));
+  MOCK_METHOD1(DecodeDone, void(DecoderStatus));
 
   MOCK_METHOD1(OnWaiting, void(WaitingReason));
 
@@ -329,7 +332,7 @@
       .WillRepeatedly(
           RunOnceCallback<1>(Decryptor::kError, Decryptor::AudioFrames()));
 
-  DecodeAndExpect(encrypted_buffer_, DecodeStatus::DECODE_ERROR);
+  DecodeAndExpect(encrypted_buffer_, DecoderStatus::Codes::kFailed);
 }
 
 // Test the case where the decryptor returns multiple decoded frames.
@@ -353,7 +356,7 @@
   EXPECT_CALL(*this, FrameReady(decoded_frame_));
   EXPECT_CALL(*this, FrameReady(frame_a));
   EXPECT_CALL(*this, FrameReady(frame_b));
-  DecodeAndExpect(encrypted_buffer_, DecodeStatus::OK);
+  DecodeAndExpect(encrypted_buffer_, DecoderStatus::Codes::kOk);
 }
 
 // Test the case where the decryptor receives end-of-stream buffer.
@@ -461,7 +464,7 @@
   Initialize();
   EnterPendingDecodeState();
 
-  EXPECT_CALL(*this, DecodeDone(HasStatusCode(StatusCode::kAborted)));
+  EXPECT_CALL(*this, DecodeDone(HasStatusCode(DecoderStatus::Codes::kAborted)));
 
   Reset();
 }
@@ -471,7 +474,7 @@
   Initialize();
   EnterWaitingForKeyState();
 
-  EXPECT_CALL(*this, DecodeDone(HasStatusCode(StatusCode::kAborted)));
+  EXPECT_CALL(*this, DecodeDone(HasStatusCode(DecoderStatus::Codes::kAborted)));
 
   Reset();
 }
diff --git a/media/filters/decrypting_video_decoder.cc b/media/filters/decrypting_video_decoder.cc
index fc1f044..a98e206b 100644
--- a/media/filters/decrypting_video_decoder.cc
+++ b/media/filters/decrypting_video_decoder.cc
@@ -53,12 +53,12 @@
   if (!cdm_context) {
     // Once we have a CDM context, one should always be present.
     DCHECK(!support_clear_content_);
-    std::move(init_cb_).Run(StatusCode::kDecoderMissingCdmForEncryptedContent);
+    std::move(init_cb_).Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
     return;
   }
 
   if (!config.is_encrypted() && !support_clear_content_) {
-    std::move(init_cb_).Run(StatusCode::kClearContentUnsupported);
+    std::move(init_cb_).Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
     return;
   }
 
@@ -75,7 +75,7 @@
   if (state_ == kUninitialized) {
     if (!cdm_context->GetDecryptor()) {
       DVLOG(1) << __func__ << ": no decryptor";
-      std::move(init_cb_).Run(StatusCode::kDecoderFailedInitialization);
+      std::move(init_cb_).Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
       return;
     }
 
@@ -112,13 +112,13 @@
   decode_cb_ = BindToCurrentLoop(std::move(decode_cb));
 
   if (state_ == kError) {
-    std::move(decode_cb_).Run(DecodeStatus::DECODE_ERROR);
+    std::move(decode_cb_).Run(DecoderStatus::Codes::kPlatformDecodeFailure);
     return;
   }
 
   // Return empty frames if decoding has finished.
   if (state_ == kDecodeFinished) {
-    std::move(decode_cb_).Run(DecodeStatus::OK);
+    std::move(decode_cb_).Run(DecoderStatus::Codes::kOk);
     return;
   }
 
@@ -154,7 +154,7 @@
     CompleteWaitingForDecryptionKey();
     DCHECK(decode_cb_);
     pending_buffer_to_decode_.reset();
-    std::move(decode_cb_).Run(DecodeStatus::ABORTED);
+    std::move(decode_cb_).Run(DecoderStatus::Codes::kAborted);
   }
 
   DCHECK(!decode_cb_);
@@ -178,9 +178,9 @@
   }
   pending_buffer_to_decode_.reset();
   if (init_cb_)
-    std::move(init_cb_).Run(StatusCode::kDecoderInitializeNeverCompleted);
+    std::move(init_cb_).Run(DecoderStatus::Codes::kInterrupted);
   if (decode_cb_)
-    std::move(decode_cb_).Run(DecodeStatus::ABORTED);
+    std::move(decode_cb_).Run(DecoderStatus::Codes::kAborted);
   if (reset_cb_)
     std::move(reset_cb_).Run();
 }
@@ -195,7 +195,9 @@
 
   if (!success) {
     DVLOG(1) << __func__ << ": failed to init video decoder on decryptor";
-    std::move(init_cb_).Run(StatusCode::kDecoderInitializeNeverCompleted);
+    // TODO(*) Is there a better reason? Should this method itself take a
+    // status?
+    std::move(init_cb_).Run(DecoderStatus::Codes::kFailed);
     decryptor_ = nullptr;
     event_cb_registration_.reset();
     state_ = kError;
@@ -204,7 +206,7 @@
 
   // Success!
   state_ = kIdle;
-  std::move(init_cb_).Run(OkStatus());
+  std::move(init_cb_).Run(DecoderStatus::Codes::kOk);
 }
 
 void DecryptingVideoDecoder::DecodePendingBuffer() {
@@ -243,7 +245,7 @@
       std::move(pending_buffer_to_decode_);
 
   if (reset_cb_) {
-    std::move(decode_cb_).Run(DecodeStatus::ABORTED);
+    std::move(decode_cb_).Run(DecoderStatus::Codes::kAborted);
     DoReset();
     return;
   }
@@ -254,7 +256,7 @@
     DVLOG(2) << "DeliverFrame() - kError";
     MEDIA_LOG(ERROR, media_log_) << GetDecoderType() << ": decode error";
     state_ = kError;
-    std::move(decode_cb_).Run(DecodeStatus::DECODE_ERROR);
+    std::move(decode_cb_).Run(DecoderStatus::Codes::kPlatformDecodeFailure);
     return;
   }
 
@@ -290,7 +292,7 @@
     DVLOG(2) << "DeliverFrame() - kNeedMoreData";
     state_ = scoped_pending_buffer_to_decode->end_of_stream() ? kDecodeFinished
                                                               : kIdle;
-    std::move(decode_cb_).Run(DecodeStatus::OK);
+    std::move(decode_cb_).Run(DecoderStatus::Codes::kOk);
     return;
   }
 
@@ -318,7 +320,7 @@
   }
 
   state_ = kIdle;
-  std::move(decode_cb_).Run(DecodeStatus::OK);
+  std::move(decode_cb_).Run(DecoderStatus::Codes::kOk);
 }
 
 void DecryptingVideoDecoder::OnCdmContextEvent(CdmContext::Event event) {
diff --git a/media/filters/decrypting_video_decoder_unittest.cc b/media/filters/decrypting_video_decoder_unittest.cc
index 69c1b424..74c11b4 100644
--- a/media/filters/decrypting_video_decoder_unittest.cc
+++ b/media/filters/decrypting_video_decoder_unittest.cc
@@ -84,7 +84,7 @@
     decoder_->Initialize(
         config, false, cdm_context_.get(),
         base::BindOnce(
-            [](bool success, Status status) {
+            [](bool success, DecoderStatus status) {
               EXPECT_EQ(status.is_ok(), success);
             },
             success),
@@ -118,7 +118,8 @@
   }
 
   // Decode |buffer| and expect DecodeDone to get called with |status|.
-  void DecodeAndExpect(scoped_refptr<DecoderBuffer> buffer, StatusCode status) {
+  void DecodeAndExpect(scoped_refptr<DecoderBuffer> buffer,
+                       DecoderStatus::Codes status) {
     EXPECT_CALL(*this, DecodeDone(HasStatusCode(status)));
     decoder_->Decode(buffer,
                      base::BindOnce(&DecryptingVideoDecoderTest::DecodeDone,
@@ -162,7 +163,7 @@
             Invoke(this, &DecryptingVideoDecoderTest::DecryptAndDecodeVideo));
     EXPECT_CALL(*this, FrameReady(decoded_video_frame_));
     for (int i = 0; i < kDecodingDelay + 1; ++i)
-      DecodeAndExpect(encrypted_buffer_, DecodeStatus::OK);
+      DecodeAndExpect(encrypted_buffer_, DecoderStatus::Codes::kOk);
   }
 
   // Sets up expectations and actions to put DecryptingVideoDecoder in an end
@@ -171,7 +172,8 @@
   void EnterEndOfStreamState() {
     // The codec in the |decryptor_| will be flushed.
     EXPECT_CALL(*this, FrameReady(decoded_video_frame_)).Times(kDecodingDelay);
-    DecodeAndExpect(DecoderBuffer::CreateEOSBuffer(), DecodeStatus::OK);
+    DecodeAndExpect(DecoderBuffer::CreateEOSBuffer(),
+                    DecoderStatus::Codes::kOk);
     EXPECT_EQ(0, num_frames_in_decryptor_);
   }
 
@@ -237,7 +239,7 @@
   }
 
   MOCK_METHOD1(FrameReady, void(scoped_refptr<VideoFrame>));
-  MOCK_METHOD1(DecodeDone, void(Status));
+  MOCK_METHOD1(DecodeDone, void(DecoderStatus));
 
   MOCK_METHOD1(OnWaiting, void(WaitingReason));
 
@@ -389,7 +391,7 @@
   Initialize();
   EnterPendingDecodeState();
 
-  EXPECT_CALL(*this, DecodeDone(HasStatusCode(StatusCode::kAborted)));
+  EXPECT_CALL(*this, DecodeDone(HasStatusCode(DecoderStatus::Codes::kAborted)));
 
   Reset();
 }
@@ -399,7 +401,7 @@
   Initialize();
   EnterWaitingForKeyState();
 
-  EXPECT_CALL(*this, DecodeDone(HasStatusCode(StatusCode::kAborted)));
+  EXPECT_CALL(*this, DecodeDone(HasStatusCode(DecoderStatus::Codes::kAborted)));
 
   Reset();
 }
@@ -459,7 +461,7 @@
   Initialize();
   EnterPendingDecodeState();
 
-  EXPECT_CALL(*this, DecodeDone(HasStatusCode(StatusCode::kAborted)));
+  EXPECT_CALL(*this, DecodeDone(HasStatusCode(DecoderStatus::Codes::kAborted)));
 
   Destroy();
 }
@@ -469,7 +471,7 @@
   Initialize();
   EnterWaitingForKeyState();
 
-  EXPECT_CALL(*this, DecodeDone(HasStatusCode(StatusCode::kAborted)));
+  EXPECT_CALL(*this, DecodeDone(HasStatusCode(DecoderStatus::Codes::kAborted)));
 
   Destroy();
 }
@@ -491,7 +493,7 @@
   EnterPendingDecodeState();
 
   EXPECT_CALL(*decryptor_, ResetDecoder(Decryptor::kVideo));
-  EXPECT_CALL(*this, DecodeDone(HasStatusCode(StatusCode::kAborted)));
+  EXPECT_CALL(*this, DecodeDone(HasStatusCode(DecoderStatus::Codes::kAborted)));
 
   decoder_->Reset(NewExpectedClosure());
   Destroy();
diff --git a/media/filters/fake_video_decoder.cc b/media/filters/fake_video_decoder.cc
index 5803459..d37f69d 100644
--- a/media/filters/fake_video_decoder.cc
+++ b/media/filters/fake_video_decoder.cc
@@ -96,18 +96,18 @@
   if (config.is_encrypted() && (!supports_encrypted_config_ || !cdm_context)) {
     DVLOG(1) << "Encrypted config not supported.";
     state_ = STATE_NORMAL;
-    init_cb_.RunOrHold(StatusCode::kEncryptedContentUnsupported);
+    init_cb_.RunOrHold(DecoderStatus::Codes::kUnsupportedEncryptionMode);
     return;
   }
 
   if (fail_to_initialize_) {
     DVLOG(1) << decoder_id_ << ": Initialization failed.";
     state_ = STATE_ERROR;
-    init_cb_.RunOrHold(StatusCode::kDecoderInitializeNeverCompleted);
+    init_cb_.RunOrHold(DecoderStatus::Codes::kFailed);
   } else {
     DVLOG(1) << decoder_id_ << ": Initialization succeeded.";
     state_ = STATE_NORMAL;
-    init_cb_.RunOrHold(OkStatus());
+    init_cb_.RunOrHold(DecoderStatus::Codes::kOk);
   }
 }
 
@@ -127,7 +127,7 @@
       buffer_size, BindToCurrentLoop(std::move(decode_cb)));
 
   if (state_ == STATE_ERROR) {
-    std::move(wrapped_decode_cb).Run(DecodeStatus::DECODE_ERROR);
+    std::move(wrapped_decode_cb).Run(DecoderStatus::Codes::kFailed);
     return;
   }
 
@@ -218,7 +218,8 @@
 
   state_ = STATE_ERROR;
   while (!held_decode_callbacks_.empty()) {
-    std::move(held_decode_callbacks_.front()).Run(DecodeStatus::DECODE_ERROR);
+    std::move(held_decode_callbacks_.front())
+        .Run(DecoderStatus::Codes::kFailed);
     held_decode_callbacks_.pop_front();
   }
   decoded_frames_.clear();
@@ -234,7 +235,7 @@
 
 void FakeVideoDecoder::OnFrameDecoded(int buffer_size,
                                       DecodeCB decode_cb,
-                                      Status status) {
+                                      DecoderStatus status) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (status.is_ok()) {
@@ -261,7 +262,7 @@
 
   if (!reset_cb_.IsNull()) {
     DCHECK(decoded_frames_.empty());
-    std::move(decode_cb).Run(DecodeStatus::ABORTED);
+    std::move(decode_cb).Run(DecoderStatus::Codes::kAborted);
     return;
   }
 
@@ -286,7 +287,7 @@
     }
   }
 
-  std::move(decode_cb).Run(DecodeStatus::OK);
+  std::move(decode_cb).Run(DecoderStatus::Codes::kOk);
 }
 
 void FakeVideoDecoder::DoReset() {
diff --git a/media/filters/fake_video_decoder.h b/media/filters/fake_video_decoder.h
index aa80fda8..52ac03f 100644
--- a/media/filters/fake_video_decoder.h
+++ b/media/filters/fake_video_decoder.h
@@ -101,7 +101,9 @@
   virtual scoped_refptr<VideoFrame> MakeVideoFrame(const DecoderBuffer& buffer);
 
   // Callback for updating |total_bytes_decoded_|.
-  void OnFrameDecoded(int buffer_size, DecodeCB decode_cb, Status status);
+  void OnFrameDecoded(int buffer_size,
+                      DecodeCB decode_cb,
+                      DecoderStatus status);
 
   // Runs |decode_cb| or puts it to |held_decode_callbacks_| depending on
   // current value of |hold_decode_|.
diff --git a/media/filters/fake_video_decoder_unittest.cc b/media/filters/fake_video_decoder_unittest.cc
index 1e8b46e..18063e9 100644
--- a/media/filters/fake_video_decoder_unittest.cc
+++ b/media/filters/fake_video_decoder_unittest.cc
@@ -45,7 +45,7 @@
         num_decoded_frames_(0),
         num_bytes_decoded_(0),
         total_bytes_in_buffers_(0),
-        last_decode_status_(DecodeStatus::OK),
+        last_decode_status_(DecoderStatus::Codes::kOk),
         pending_decode_requests_(0),
         is_reset_pending_(false) {}
 
@@ -60,7 +60,7 @@
                                            bool success) {
     decoder_->Initialize(config, false, nullptr,
                          base::BindOnce(
-                             [](bool success, Status status) {
+                             [](bool success, DecoderStatus status) {
                                EXPECT_EQ(status.is_ok(), success);
                              },
                              success),
@@ -86,7 +86,7 @@
   }
 
   // Callback for VideoDecoder::Decode().
-  void DecodeDone(Status status) {
+  void DecodeDone(DecoderStatus status) {
     DCHECK_GT(pending_decode_requests_, 0);
     --pending_decode_requests_;
     last_decode_status_ = std::move(status);
@@ -126,7 +126,7 @@
         break;
       case ABORTED:
         EXPECT_EQ(0, pending_decode_requests_);
-        ASSERT_EQ(StatusCode::kAborted, last_decode_status_.code());
+        ASSERT_EQ(DecoderStatus::Codes::kAborted, last_decode_status_.code());
         EXPECT_FALSE(last_decoded_frame_.get());
         break;
     }
@@ -241,7 +241,7 @@
   int total_bytes_in_buffers_;
 
   // Callback result/status.
-  Status last_decode_status_;
+  DecoderStatus last_decode_status_;
   scoped_refptr<VideoFrame> last_decoded_frame_;
   int pending_decode_requests_;
   bool is_reset_pending_;
diff --git a/media/filters/ffmpeg_audio_decoder.cc b/media/filters/ffmpeg_audio_decoder.cc
index d94bfe6..6a56c67 100644
--- a/media/filters/ffmpeg_audio_decoder.cc
+++ b/media/filters/ffmpeg_audio_decoder.cc
@@ -82,15 +82,16 @@
 
   if (config.is_encrypted()) {
     std::move(bound_init_cb)
-        .Run(Status(StatusCode::kEncryptedContentUnsupported,
-                    "FFmpegAudioDecoder does not support encrypted content"));
+        .Run(DecoderStatus(
+            DecoderStatus::Codes::kUnsupportedEncryptionMode,
+            "FFmpegAudioDecoder does not support encrypted content"));
     return;
   }
 
   // TODO(dalecurtis): Remove this if ffmpeg ever gets xHE-AAC support.
   if (config.profile() == AudioCodecProfile::kXHE_AAC) {
     std::move(bound_init_cb)
-        .Run(Status(StatusCode::kDecoderUnsupportedProfile)
+        .Run(DecoderStatus(DecoderStatus::Codes::kUnsupportedProfile)
                  .WithData("decoder", "FFmpegAudioDecoder")
                  .WithData("profile", config.profile()));
     return;
@@ -98,7 +99,7 @@
 
   if (!ConfigureDecoder(config)) {
     av_sample_format_ = 0;
-    std::move(bound_init_cb).Run(StatusCode::kDecoderFailedInitialization);
+    std::move(bound_init_cb).Run(DecoderStatus::Codes::kUnsupportedConfig);
     return;
   }
 
@@ -106,7 +107,7 @@
   config_ = config;
   output_cb_ = BindToCurrentLoop(output_cb);
   state_ = DecoderState::kNormal;
-  std::move(bound_init_cb).Run(OkStatus());
+  std::move(bound_init_cb).Run(DecoderStatus::Codes::kOk);
 }
 
 void FFmpegAudioDecoder::Decode(scoped_refptr<DecoderBuffer> buffer,
@@ -117,13 +118,13 @@
   DecodeCB decode_cb_bound = BindToCurrentLoop(std::move(decode_cb));
 
   if (state_ == DecoderState::kError) {
-    std::move(decode_cb_bound).Run(DecodeStatus::DECODE_ERROR);
+    std::move(decode_cb_bound).Run(DecoderStatus::Codes::kFailed);
     return;
   }
 
   // Do nothing if decoding has finished.
   if (state_ == DecoderState::kDecodeFinished) {
-    std::move(decode_cb_bound).Run(DecodeStatus::OK);
+    std::move(decode_cb_bound).Run(DecoderStatus::Codes::kOk);
     return;
   }
 
@@ -150,20 +151,20 @@
   // occurs with some damaged files.
   if (!buffer.end_of_stream() && buffer.timestamp() == kNoTimestamp) {
     DVLOG(1) << "Received a buffer without timestamps!";
-    std::move(decode_cb).Run(DecodeStatus::DECODE_ERROR);
+    std::move(decode_cb).Run(DecoderStatus::Codes::kFailed);
     return;
   }
 
   if (!FFmpegDecode(buffer)) {
     state_ = DecoderState::kError;
-    std::move(decode_cb).Run(DecodeStatus::DECODE_ERROR);
+    std::move(decode_cb).Run(DecoderStatus::Codes::kFailed);
     return;
   }
 
   if (buffer.end_of_stream())
     state_ = DecoderState::kDecodeFinished;
 
-  std::move(decode_cb).Run(DecodeStatus::OK);
+  std::move(decode_cb).Run(DecoderStatus::Codes::kOk);
 }
 
 bool FFmpegAudioDecoder::FFmpegDecode(const DecoderBuffer& buffer) {
diff --git a/media/filters/ffmpeg_video_decoder.cc b/media/filters/ffmpeg_video_decoder.cc
index 9d028bf..99f5231 100644
--- a/media/filters/ffmpeg_video_decoder.cc
+++ b/media/filters/ffmpeg_video_decoder.cc
@@ -248,12 +248,13 @@
   InitCB bound_init_cb = BindToCurrentLoop(std::move(init_cb));
 
   if (config.is_encrypted()) {
-    std::move(bound_init_cb).Run(StatusCode::kEncryptedContentUnsupported);
+    std::move(bound_init_cb)
+        .Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
     return;
   }
 
   if (!ConfigureDecoder(config, low_delay)) {
-    std::move(bound_init_cb).Run(StatusCode::kDecoderFailedInitialization);
+    std::move(bound_init_cb).Run(DecoderStatus::Codes::kUnsupportedConfig);
     return;
   }
 
@@ -261,7 +262,7 @@
   config_ = config;
   output_cb_ = output_cb;
   state_ = DecoderState::kNormal;
-  std::move(bound_init_cb).Run(OkStatus());
+  std::move(bound_init_cb).Run(DecoderStatus::Codes::kOk);
 }
 
 void FFmpegVideoDecoder::Decode(scoped_refptr<DecoderBuffer> buffer,
@@ -275,12 +276,12 @@
   DecodeCB decode_cb_bound = BindToCurrentLoop(std::move(decode_cb));
 
   if (state_ == DecoderState::kError) {
-    std::move(decode_cb_bound).Run(DecodeStatus::DECODE_ERROR);
+    std::move(decode_cb_bound).Run(DecoderStatus::Codes::kFailed);
     return;
   }
 
   if (state_ == DecoderState::kDecodeFinished) {
-    std::move(decode_cb_bound).Run(DecodeStatus::OK);
+    std::move(decode_cb_bound).Run(DecoderStatus::Codes::kOk);
     return;
   }
 
@@ -306,7 +307,7 @@
 
   if (!FFmpegDecode(*buffer)) {
     state_ = DecoderState::kError;
-    std::move(decode_cb_bound).Run(DecodeStatus::DECODE_ERROR);
+    std::move(decode_cb_bound).Run(DecoderStatus::Codes::kFailed);
     return;
   }
 
@@ -315,7 +316,7 @@
 
   // VideoDecoderShim expects that |decode_cb| is called only after
   // |output_cb_|.
-  std::move(decode_cb_bound).Run(DecodeStatus::OK);
+  std::move(decode_cb_bound).Run(DecoderStatus::Codes::kOk);
 }
 
 void FFmpegVideoDecoder::Reset(base::OnceClosure closure) {
diff --git a/media/filters/ffmpeg_video_decoder_unittest.cc b/media/filters/ffmpeg_video_decoder_unittest.cc
index 76b30ab..6f11af4 100644
--- a/media/filters/ffmpeg_video_decoder_unittest.cc
+++ b/media/filters/ffmpeg_video_decoder_unittest.cc
@@ -78,7 +78,7 @@
     decoder_->Initialize(
         config, false, nullptr,
         base::BindOnce(
-            [](bool success, Status status) {
+            [](bool success, DecoderStatus status) {
               EXPECT_EQ(status.is_ok(), success);
             },
             success),
@@ -126,14 +126,14 @@
   // Decodes all buffers in |input_buffers| and push all successfully decoded
   // output frames into |output_frames|.
   // Returns the last decode status returned by the decoder.
-  Status DecodeMultipleFrames(const InputBuffers& input_buffers) {
+  DecoderStatus DecodeMultipleFrames(const InputBuffers& input_buffers) {
     for (auto iter = input_buffers.begin(); iter != input_buffers.end();
          ++iter) {
-      Status status = Decode(*iter);
+      DecoderStatus status = Decode(*iter);
       switch (status.code()) {
-        case StatusCode::kOk:
+        case DecoderStatus::Codes::kOk:
           break;
-        case StatusCode::kAborted:
+        case DecoderStatus::Codes::kAborted:
           NOTREACHED();
           [[fallthrough]];
         default:
@@ -141,14 +141,14 @@
           return status;
       }
     }
-    return StatusCode::kOk;
+    return DecoderStatus::Codes::kOk;
   }
 
   // Decodes the single compressed frame in |buffer| and writes the
   // uncompressed output to |video_frame|. This method works with single
   // and multithreaded decoders. End of stream buffers are used to trigger
   // the frame to be returned in the multithreaded decoder case.
-  Status DecodeSingleFrame(scoped_refptr<DecoderBuffer> buffer) {
+  DecoderStatus DecodeSingleFrame(scoped_refptr<DecoderBuffer> buffer) {
     InputBuffers input_buffers;
     input_buffers.push_back(buffer);
     input_buffers.push_back(end_of_stream_buffer_);
@@ -170,7 +170,7 @@
     input_buffers.push_back(buffer);
     input_buffers.push_back(end_of_stream_buffer_);
 
-    Status status = DecodeMultipleFrames(input_buffers);
+    DecoderStatus status = DecodeMultipleFrames(input_buffers);
 
     EXPECT_TRUE(status.is_ok());
     ASSERT_EQ(2U, output_frames_.size());
@@ -186,8 +186,8 @@
               output_frames_[1]->visible_rect().size().height());
   }
 
-  Status Decode(scoped_refptr<DecoderBuffer> buffer) {
-    Status status;
+  DecoderStatus Decode(scoped_refptr<DecoderBuffer> buffer) {
+    DecoderStatus status;
     EXPECT_CALL(*this, DecodeDone(_)).WillOnce(SaveArg<0>(&status));
 
     decoder_->Decode(buffer, base::BindOnce(&FFmpegVideoDecoderTest::DecodeDone,
@@ -203,7 +203,7 @@
     output_frames_.push_back(std::move(frame));
   }
 
-  MOCK_METHOD1(DecodeDone, void(Status));
+  MOCK_METHOD1(DecodeDone, void(DecoderStatus));
 
   StrictMock<MockMediaLog> media_log_;
 
@@ -281,7 +281,7 @@
   EXPECT_TRUE(output_frames_.empty());
 
   // After a decode error occurred, all following decodes will return
-  // DecodeStatus::DECODE_ERROR.
+  // DecoderStatus::Codes::kFailed.
   EXPECT_THAT(Decode(i_frame_buffer_), IsDecodeErrorStatus());
   EXPECT_TRUE(output_frames_.empty());
 }
diff --git a/media/filters/fuchsia/fuchsia_video_decoder.cc b/media/filters/fuchsia/fuchsia_video_decoder.cc
index db80b84..9669b80 100644
--- a/media/filters/fuchsia/fuchsia_video_decoder.cc
+++ b/media/filters/fuchsia/fuchsia_video_decoder.cc
@@ -242,7 +242,7 @@
 
   // There should be no pending decode request, so DropInputQueue() is not
   // expected to fail.
-  bool result = DropInputQueue(DecodeStatus::ABORTED);
+  bool result = DropInputQueue(DecoderStatus::Codes::kAborted);
   DCHECK(result);
 
   output_cb_ = output_cb;
@@ -252,7 +252,7 @@
   // Keep decoder and decryptor if the configuration hasn't changed.
   if (decoder_ && current_config_.codec() == config.codec() &&
       current_config_.is_encrypted() == config.is_encrypted()) {
-    std::move(done_callback).Run(OkStatus());
+    std::move(done_callback).Run(DecoderStatus::Codes::kOk);
     return;
   }
 
@@ -261,10 +261,10 @@
 
   // Initialize the stream.
   bool secure_mode = false;
-  StatusCode status = InitializeSysmemBufferStream(config.is_encrypted(),
-                                                   cdm_context, &secure_mode);
-  if (status != StatusCode::kOk) {
-    std::move(done_callback).Run(StatusCode::kOk);
+  DecoderStatus status = InitializeSysmemBufferStream(
+      config.is_encrypted(), cdm_context, &secure_mode);
+  if (!status.is_ok()) {
+    std::move(done_callback).Run(status);
     return;
   }
 
@@ -292,7 +292,7 @@
       break;
 
     default:
-      std::move(done_callback).Run(StatusCode::kDecoderUnsupportedCodec);
+      std::move(done_callback).Run(DecoderStatus::Codes::kUnsupportedCodec);
       return;
   }
 
@@ -320,7 +320,7 @@
 
   current_config_ = config;
 
-  std::move(done_callback).Run(OkStatus());
+  std::move(done_callback).Run(DecoderStatus::Codes::kOk);
 }
 
 void FuchsiaVideoDecoder::Decode(scoped_refptr<DecoderBuffer> buffer,
@@ -330,7 +330,7 @@
     // Decode() to complete synchronously.
     base::SequencedTaskRunnerHandle::Get()->PostTask(
         FROM_HERE,
-        base::BindOnce(std::move(decode_cb), DecodeStatus::DECODE_ERROR));
+        base::BindOnce(std::move(decode_cb), DecoderStatus::Codes::kFailed));
     return;
   }
 
@@ -340,7 +340,7 @@
 }
 
 void FuchsiaVideoDecoder::Reset(base::OnceClosure closure) {
-  DropInputQueue(DecodeStatus::ABORTED);
+  DropInputQueue(DecoderStatus::Codes::kAborted);
   base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE,
                                                    std::move(closure));
 }
@@ -357,7 +357,7 @@
   return max_decoder_requests_;
 }
 
-StatusCode FuchsiaVideoDecoder::InitializeSysmemBufferStream(
+DecoderStatus FuchsiaVideoDecoder::InitializeSysmemBufferStream(
     bool is_encrypted,
     CdmContext* cdm_context,
     bool* out_secure_mode) {
@@ -373,7 +373,7 @@
     // Caller makes sure |cdm_context| is available if the stream is encrypted.
     if (!cdm_context) {
       DLOG(ERROR) << "No cdm context for encrypted stream.";
-      return StatusCode::kDecoderMissingCdmForEncryptedContent;
+      return DecoderStatus::Codes::kUnsupportedEncryptionMode;
     }
 
     // Use FuchsiaStreamDecryptor with FuchsiaCdm (it doesn't support
@@ -400,7 +400,7 @@
 
   sysmem_buffer_stream_->Initialize(this, kInputBufferSize, kNumInputBuffers);
 
-  return StatusCode::kOk;
+  return DecoderStatus::Codes::kOk;
 }
 
 void FuchsiaVideoDecoder::OnSysmemBufferStreamBufferCollectionToken(
@@ -597,10 +597,10 @@
   auto cb = std::move(decode_callbacks_.front());
   decode_callbacks_.pop_front();
 
-  std::move(cb).Run(DecodeStatus::OK);
+  std::move(cb).Run(DecoderStatus::Codes::kOk);
 }
 
-bool FuchsiaVideoDecoder::DropInputQueue(DecodeStatus status) {
+bool FuchsiaVideoDecoder::DropInputQueue(DecoderStatus status) {
   // Invalidate callbacks for CallNextDecodeCallback(), so the callbacks are not
   // called when the |decoder_| is dropped below. The callbacks are called
   // explicitly later.
@@ -617,7 +617,7 @@
   auto weak_this = weak_this_;
 
   for (auto& cb : decode_callbacks_) {
-    std::move(cb).Run(status);
+    std::move(cb).Run(std::move(status));
 
     // DecodeCB may destroy |this|.
     if (!weak_this)
@@ -634,7 +634,7 @@
 
   ReleaseOutputBuffers();
 
-  DropInputQueue(DecodeStatus::DECODE_ERROR);
+  DropInputQueue(DecoderStatus::Codes::kFailed);
 }
 
 void FuchsiaVideoDecoder::SetBufferCollectionTokenForGpu(
diff --git a/media/filters/fuchsia/fuchsia_video_decoder.h b/media/filters/fuchsia/fuchsia_video_decoder.h
index 95889cf..bdabdb9 100644
--- a/media/filters/fuchsia/fuchsia_video_decoder.h
+++ b/media/filters/fuchsia/fuchsia_video_decoder.h
@@ -74,9 +74,9 @@
  private:
   class OutputMailbox;
 
-  StatusCode InitializeSysmemBufferStream(bool is_encrypted,
-                                          CdmContext* cdm_context,
-                                          bool* secure_mode);
+  DecoderStatus InitializeSysmemBufferStream(bool is_encrypted,
+                                             CdmContext* cdm_context,
+                                             bool* secure_mode);
 
   // SysmemBufferStream::Sink implementation.
   void OnSysmemBufferStreamBufferCollectionToken(
@@ -104,7 +104,7 @@
 
   // Drops all pending input buffers and then calls all pending DecodeCB with
   // |status|. Returns true if the decoder still exists.
-  bool DropInputQueue(DecodeStatus status);
+  bool DropInputQueue(DecoderStatus status);
 
   // Called on errors to shutdown the decoder and notify the client.
   void OnError();
diff --git a/media/filters/fuchsia/fuchsia_video_decoder_unittest.cc b/media/filters/fuchsia/fuchsia_video_decoder_unittest.cc
index 213af7e..1277788 100644
--- a/media/filters/fuchsia/fuchsia_video_decoder_unittest.cc
+++ b/media/filters/fuchsia/fuchsia_video_decoder_unittest.cc
@@ -308,7 +308,8 @@
     decoder_->Initialize(
         config, true, /*cdm_context=*/nullptr,
         base::BindRepeating(
-            [](bool* init_cb_result, base::RunLoop* run_loop, Status status) {
+            [](bool* init_cb_result, base::RunLoop* run_loop,
+               DecoderStatus status) {
               *init_cb_result = status.is_ok();
               run_loop->Quit();
             },
@@ -351,7 +352,7 @@
     DecodeBuffer(ReadTestDataFile(name));
   }
 
-  void OnFrameDecoded(size_t frame_pos, Status status) {
+  void OnFrameDecoded(size_t frame_pos, DecoderStatus status) {
     EXPECT_EQ(frame_pos, num_decoded_buffers_);
     num_decoded_buffers_ += 1;
     last_decode_status_ = std::move(status);
@@ -388,7 +389,7 @@
   std::list<scoped_refptr<VideoFrame>> output_frames_;
   size_t num_output_frames_ = 0;
 
-  Status last_decode_status_;
+  DecoderStatus last_decode_status_;
   base::RunLoop* run_loop_ = nullptr;
 
   // Number of frames that OnVideoFrame() should keep in |output_frames_|.
diff --git a/media/filters/gav1_video_decoder.cc b/media/filters/gav1_video_decoder.cc
index ae2901a..56b2c69 100644
--- a/media/filters/gav1_video_decoder.cc
+++ b/media/filters/gav1_video_decoder.cc
@@ -269,7 +269,8 @@
   InitCB bound_init_cb = bind_callbacks_ ? BindToCurrentLoop(std::move(init_cb))
                                          : std::move(init_cb);
   if (config.is_encrypted() || config.codec() != VideoCodec::kAV1) {
-    std::move(bound_init_cb).Run(StatusCode::kEncryptedContentUnsupported);
+    std::move(bound_init_cb)
+        .Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
     return;
   }
 
@@ -295,7 +296,7 @@
   if (status != kLibgav1StatusOk) {
     MEDIA_LOG(ERROR, media_log_) << "libgav1::Decoder::Init() failed, "
                                  << "status=" << status;
-    std::move(bound_init_cb).Run(StatusCode::kDecoderFailedInitialization);
+    std::move(bound_init_cb).Run(DecoderStatus::Codes::kFailedToCreateDecoder);
     return;
   }
 
@@ -303,7 +304,7 @@
   state_ = DecoderState::kDecoding;
   color_space_ = config.color_space_info();
   aspect_ratio_ = config.aspect_ratio();
-  std::move(bound_init_cb).Run(OkStatus());
+  std::move(bound_init_cb).Run(DecoderStatus::Codes::kOk);
 }
 
 void Gav1VideoDecoder::Decode(scoped_refptr<DecoderBuffer> buffer,
@@ -320,18 +321,18 @@
                                  : std::move(decode_cb);
 
   if (state_ == DecoderState::kError) {
-    std::move(bound_decode_cb).Run(DecodeStatus::DECODE_ERROR);
+    std::move(bound_decode_cb).Run(DecoderStatus::Codes::kFailed);
     return;
   }
 
   if (!DecodeBuffer(std::move(buffer))) {
     state_ = DecoderState::kError;
-    std::move(bound_decode_cb).Run(DecodeStatus::DECODE_ERROR);
+    std::move(bound_decode_cb).Run(DecoderStatus::Codes::kFailed);
     return;
   }
 
   // VideoDecoderShim expects |decode_cb| call after |output_cb_|.
-  std::move(bound_decode_cb).Run(DecodeStatus::OK);
+  std::move(bound_decode_cb).Run(DecoderStatus::Codes::kOk);
 }
 
 bool Gav1VideoDecoder::DecodeBuffer(scoped_refptr<DecoderBuffer> buffer) {
diff --git a/media/filters/gav1_video_decoder_unittest.cc b/media/filters/gav1_video_decoder_unittest.cc
index ee67114f..b2d57be 100644
--- a/media/filters/gav1_video_decoder_unittest.cc
+++ b/media/filters/gav1_video_decoder_unittest.cc
@@ -82,7 +82,7 @@
                                       bool success) {
     decoder_->Initialize(config, false, nullptr,
                          base::BindOnce(
-                             [](bool success, Status status) {
+                             [](bool success, DecoderStatus status) {
                                EXPECT_EQ(status.is_ok(), success);
                              },
                              success),
@@ -130,14 +130,14 @@
   // Decodes all buffers in |input_buffers| and push all successfully decoded
   // output frames into |output_frames|. Returns the last decode status returned
   // by the decoder.
-  Status DecodeMultipleFrames(const InputBuffers& input_buffers) {
+  DecoderStatus DecodeMultipleFrames(const InputBuffers& input_buffers) {
     for (auto iter = input_buffers.begin(); iter != input_buffers.end();
          ++iter) {
-      Status status = Decode(*iter);
+      DecoderStatus status = Decode(*iter);
       switch (status.code()) {
-        case StatusCode::kOk:
+        case DecoderStatus::Codes::kOk:
           break;
-        case StatusCode::kAborted:
+        case DecoderStatus::Codes::kAborted:
           NOTREACHED();
           [[fallthrough]];
         default:
@@ -145,11 +145,11 @@
           return status;
       }
     }
-    return StatusCode::kOk;
+    return DecoderStatus::Codes::kOk;
   }
 
   // Decodes the single compressed frame in |buffer|.
-  Status DecodeSingleFrame(scoped_refptr<DecoderBuffer> buffer) {
+  DecoderStatus DecodeSingleFrame(scoped_refptr<DecoderBuffer> buffer) {
     InputBuffers input_buffers;
     input_buffers.push_back(std::move(buffer));
     return DecodeMultipleFrames(input_buffers);
@@ -168,9 +168,7 @@
     input_buffers.push_back(buffer);
     input_buffers.push_back(DecoderBuffer::CreateEOSBuffer());
 
-    Status status = DecodeMultipleFrames(input_buffers);
-
-    EXPECT_TRUE(status.is_ok());
+    EXPECT_TRUE(DecodeMultipleFrames(input_buffers).is_ok());
     ASSERT_EQ(2U, output_frames_.size());
 
     gfx::Size original_size = TestVideoConfig::NormalCodedSize();
@@ -184,8 +182,8 @@
               output_frames_[1]->visible_rect().size().height());
   }
 
-  Status Decode(scoped_refptr<DecoderBuffer> buffer) {
-    Status status;
+  DecoderStatus Decode(scoped_refptr<DecoderBuffer> buffer) {
+    DecoderStatus status;
     EXPECT_CALL(*this, DecodeDone(_)).WillOnce(testing::SaveArg<0>(&status));
 
     decoder_->Decode(std::move(buffer),
@@ -210,7 +208,7 @@
     return base::MD5DigestToBase16(digest);
   }
 
-  MOCK_METHOD1(DecodeDone, void(Status));
+  MOCK_METHOD1(DecodeDone, void(DecoderStatus));
 
   testing::StrictMock<MockMediaLog> media_log_;
 
diff --git a/media/filters/offloading_video_decoder.cc b/media/filters/offloading_video_decoder.cc
index 7197ad5..daba407 100644
--- a/media/filters/offloading_video_decoder.cc
+++ b/media/filters/offloading_video_decoder.cc
@@ -35,7 +35,7 @@
   void Decode(scoped_refptr<DecoderBuffer> buffer,
               VideoDecoder::DecodeCB decode_cb) {
     if (cancellation_flag_->IsSet()) {
-      std::move(decode_cb).Run(DecodeStatus::ABORTED);
+      std::move(decode_cb).Run(DecoderStatus::Codes::kAborted);
       return;
     }
 
diff --git a/media/filters/offloading_video_decoder_unittest.cc b/media/filters/offloading_video_decoder_unittest.cc
index 12cad0e..8fcda25 100644
--- a/media/filters/offloading_video_decoder_unittest.cc
+++ b/media/filters/offloading_video_decoder_unittest.cc
@@ -87,7 +87,7 @@
     EXPECT_CALL(*this, InitDone(success))
         .WillOnce(VerifyOn(task_env_.GetMainThreadTaskRunner()));
     return base::BindOnce(
-        [](base::OnceCallback<void(bool)> cb, Status status) {
+        [](base::OnceCallback<void(bool)> cb, DecoderStatus status) {
           std::move(cb).Run(status.is_ok());
         },
         base::BindOnce(&OffloadingVideoDecoderTest::InitDone,
@@ -101,7 +101,7 @@
                                base::Unretained(this));
   }
 
-  VideoDecoder::DecodeCB ExpectDecodeCB(StatusCode status) {
+  VideoDecoder::DecodeCB ExpectDecodeCB(DecoderStatus status) {
     EXPECT_CALL(*this, DecodeDone(HasStatusCode(status)))
         .WillOnce(VerifyOn(task_env_.GetMainThreadTaskRunner()));
     return base::BindOnce(&OffloadingVideoDecoderTest::DecodeDone,
@@ -128,7 +128,7 @@
     VideoDecoder::OutputCB output_cb;
     EXPECT_CALL(*decoder_, Initialize_(_, false, nullptr, _, _, _))
         .WillOnce(DoAll(VerifyOn(task_env_.GetMainThreadTaskRunner()),
-                        RunOnceCallback<3>(OkStatus()),
+                        RunOnceCallback<3>(DecoderStatus::Codes::kOk),
                         SaveArg<4>(&output_cb)));
     offloading_decoder_->Initialize(config, false, nullptr, ExpectInitCB(true),
                                     ExpectOutputCB(), base::NullCallback());
@@ -138,9 +138,9 @@
     EXPECT_CALL(*decoder_, Decode_(_, _))
         .WillOnce(DoAll(VerifyOn(task_env_.GetMainThreadTaskRunner()),
                         RunOnceClosure(base::BindOnce(output_cb, nullptr)),
-                        RunOnceCallback<1>(DecodeStatus::OK)));
+                        RunOnceCallback<1>(DecoderStatus::Codes::kOk)));
     offloading_decoder_->Decode(DecoderBuffer::CreateEOSBuffer(),
-                                ExpectDecodeCB(DecodeStatus::OK));
+                                ExpectDecodeCB(DecoderStatus::Codes::kOk));
     task_env_.RunUntilIdle();
 
     // Reset so we can call Initialize() again.
@@ -170,7 +170,7 @@
                                     ExpectOutputCB(), base::NullCallback());
     EXPECT_CALL(*decoder_, Initialize_(_, false, nullptr, _, _, _))
         .WillOnce(DoAll(VerifyNotOn(task_env_.GetMainThreadTaskRunner()),
-                        RunOnceCallback<3>(OkStatus()),
+                        RunOnceCallback<3>(DecoderStatus::Codes::kOk),
                         SaveArg<4>(&output_cb)));
     task_env_.RunUntilIdle();
 
@@ -179,11 +179,11 @@
 
     // Verify decode works and is called on the right thread.
     offloading_decoder_->Decode(DecoderBuffer::CreateEOSBuffer(),
-                                ExpectDecodeCB(DecodeStatus::OK));
+                                ExpectDecodeCB(DecoderStatus::Codes::kOk));
     EXPECT_CALL(*decoder_, Decode_(_, _))
         .WillOnce(DoAll(VerifyNotOn(task_env_.GetMainThreadTaskRunner()),
                         RunOnceClosure(base::BindOnce(output_cb, nullptr)),
-                        RunOnceCallback<1>(DecodeStatus::OK)));
+                        RunOnceCallback<1>(DecoderStatus::Codes::kOk)));
     task_env_.RunUntilIdle();
 
     // Reset so we can call Initialize() again.
@@ -196,7 +196,7 @@
 
   MOCK_METHOD1(InitDone, void(bool));
   MOCK_METHOD1(OutputDone, void(scoped_refptr<VideoFrame>));
-  MOCK_METHOD1(DecodeDone, void(Status));
+  MOCK_METHOD1(DecodeDone, void(DecoderStatus));
   MOCK_METHOD0(ResetDone, void(void));
 
   base::test::TaskEnvironment task_env_;
@@ -253,7 +253,8 @@
       .WillOnce(VerifyNotOn(task_env_.GetMainThreadTaskRunner()));
   EXPECT_CALL(*decoder_, Initialize_(_, false, nullptr, _, _, _))
       .WillOnce(DoAll(VerifyOn(task_env_.GetMainThreadTaskRunner()),
-                      RunOnceCallback<3>(OkStatus()), SaveArg<4>(&output_cb)));
+                      RunOnceCallback<3>(DecoderStatus::Codes::kOk),
+                      SaveArg<4>(&output_cb)));
   task_env_.RunUntilIdle();
 }
 
@@ -280,7 +281,8 @@
       base::NullCallback());
   EXPECT_CALL(*decoder_, Initialize_(_, false, nullptr, _, _, _))
       .WillOnce(DoAll(VerifyNotOn(task_env_.GetMainThreadTaskRunner()),
-                      RunOnceCallback<3>(OkStatus()), SaveArg<4>(&output_cb)));
+                      RunOnceCallback<3>(DecoderStatus::Codes::kOk),
+                      SaveArg<4>(&output_cb)));
   task_env_.RunUntilIdle();
 
   // When offloading decodes should be parallelized.
@@ -300,7 +302,7 @@
       .Times(2)
       .WillRepeatedly(DoAll(VerifyNotOn(task_env_.GetMainThreadTaskRunner()),
                             RunClosure(base::BindRepeating(output_cb, nullptr)),
-                            RunOnceCallback<1>(DecodeStatus::OK)));
+                            RunOnceCallback<1>(DecoderStatus::Codes::kOk)));
   EXPECT_CALL(*this, DecodeDone(IsOkStatus()))
       .Times(2)
       .WillRepeatedly(VerifyOn(task_env_.GetMainThreadTaskRunner()));
@@ -331,7 +333,8 @@
       base::NullCallback());
   EXPECT_CALL(*decoder_, Initialize_(_, false, nullptr, _, _, _))
       .WillOnce(DoAll(VerifyNotOn(task_env_.GetMainThreadTaskRunner()),
-                      RunOnceCallback<3>(OkStatus()), SaveArg<4>(&output_cb)));
+                      RunOnceCallback<3>(DecoderStatus::Codes::kOk),
+                      SaveArg<4>(&output_cb)));
   task_env_.RunUntilIdle();
 
   // When offloading decodes should be parallelized.
@@ -348,7 +351,7 @@
                      base::Unretained(this)));
 
   EXPECT_CALL(*decoder_, Decode_(_, _)).Times(0);
-  EXPECT_CALL(*this, DecodeDone(HasStatusCode(StatusCode::kAborted)))
+  EXPECT_CALL(*this, DecodeDone(HasStatusCode(DecoderStatus::Codes::kAborted)))
       .Times(2)
       .WillRepeatedly(VerifyOn(task_env_.GetMainThreadTaskRunner()));
   offloading_decoder_->Reset(ExpectResetCB());
diff --git a/media/filters/video_decoder_stream_unittest.cc b/media/filters/video_decoder_stream_unittest.cc
index e1bea9b..f913a66 100644
--- a/media/filters/video_decoder_stream_unittest.cc
+++ b/media/filters/video_decoder_stream_unittest.cc
@@ -369,9 +369,10 @@
   void FrameReady(VideoDecoderStream::ReadResult result) {
     DCHECK(pending_read_);
     last_read_status_code_ = result.code();
-    scoped_refptr<VideoFrame> frame = last_read_status_code_ == StatusCode::kOk
-                                          ? std::move(result).value()
-                                          : nullptr;
+    scoped_refptr<VideoFrame> frame =
+        last_read_status_code_ == DecoderStatus::Codes::kOk
+            ? std::move(result).value()
+            : nullptr;
     frame_read_ = frame;
     if (frame && !frame->metadata().end_of_stream) {
       EXPECT_EQ(*frame->metadata().frame_duration, demuxer_stream_->duration());
@@ -555,7 +556,7 @@
   bool pending_stop_;
   int num_decoded_bytes_unreported_;
   scoped_refptr<VideoFrame> frame_read_;
-  StatusCode last_read_status_code_;
+  DecoderStatus::Codes last_read_status_code_;
 
   // Decryptor has no key to decrypt a frame.
   bool has_no_key_;
@@ -824,7 +825,7 @@
   decoder_->SatisfySingleDecode();
   base::RunLoop().RunUntilIdle();
   ASSERT_FALSE(pending_read_);
-  EXPECT_EQ(last_read_status_code_, StatusCode::kOk);
+  EXPECT_EQ(last_read_status_code_, DecoderStatus::Codes::kOk);
 
   // The read output should indicate end of stream.
   ASSERT_TRUE(frame_read_.get());
@@ -847,8 +848,8 @@
   base::RunLoop().RunUntilIdle();
 
   ASSERT_FALSE(pending_read_);
-  EXPECT_NE(last_read_status_code_, StatusCode::kOk);
-  EXPECT_NE(last_read_status_code_, StatusCode::kAborted);
+  EXPECT_NE(last_read_status_code_, DecoderStatus::Codes::kOk);
+  EXPECT_NE(last_read_status_code_, DecoderStatus::Codes::kAborted);
 }
 
 // No Reset() before initialization is successfully completed.
@@ -1037,7 +1038,7 @@
   ASSERT_EQ(GetDecoderId(1), decoder_->GetDecoderId());
 
   ASSERT_FALSE(pending_read_);
-  ASSERT_EQ(last_read_status_code_, StatusCode::kOk);
+  ASSERT_EQ(last_read_status_code_, DecoderStatus::Codes::kOk);
 
   // Check that we fell back to Decoder2.
   ASSERT_GT(decoder_->total_bytes_decoded(), 0);
@@ -1077,7 +1078,7 @@
 
   // A frame should have been emitted.
   EXPECT_FALSE(pending_read_);
-  EXPECT_EQ(last_read_status_code_, StatusCode::kOk);
+  EXPECT_EQ(last_read_status_code_, DecoderStatus::Codes::kOk);
   EXPECT_FALSE(frame_read_->metadata().end_of_stream);
   EXPECT_GT(decoder_->total_bytes_decoded(), 0);
 
@@ -1151,8 +1152,8 @@
   // No decoders left, expect failure.
   EXPECT_EQ(decoder_, nullptr);
   EXPECT_FALSE(pending_read_);
-  EXPECT_NE(last_read_status_code_, StatusCode::kOk);
-  EXPECT_NE(last_read_status_code_, StatusCode::kAborted);
+  EXPECT_NE(last_read_status_code_, DecoderStatus::Codes::kOk);
+  EXPECT_NE(last_read_status_code_, DecoderStatus::Codes::kAborted);
 }
 
 // This tests verifies that we properly fallback to a new decoder if the first
@@ -1173,7 +1174,7 @@
   // Verify that the first frame was decoded successfully.
   EXPECT_FALSE(pending_read_);
   EXPECT_GT(decoder_->total_bytes_decoded(), 0);
-  EXPECT_EQ(last_read_status_code_, StatusCode::kOk);
+  EXPECT_EQ(last_read_status_code_, DecoderStatus::Codes::kOk);
 
   // Continue up to the point of reinitialization.
   EnterPendingState(DEMUXER_READ_CONFIG_CHANGE);
@@ -1197,7 +1198,7 @@
   // Verify that fallback happened.
   EXPECT_EQ(GetDecoderId(0), decoder_->GetDecoderId());
   EXPECT_FALSE(pending_read_);
-  EXPECT_EQ(last_read_status_code_, StatusCode::kOk);
+  EXPECT_EQ(last_read_status_code_, DecoderStatus::Codes::kOk);
   EXPECT_GT(decoder_->total_bytes_decoded(), 0);
 }
 
@@ -1234,8 +1235,8 @@
   // No decoders left.
   EXPECT_EQ(decoder_, nullptr);
   EXPECT_FALSE(pending_read_);
-  EXPECT_NE(last_read_status_code_, StatusCode::kOk);
-  EXPECT_NE(last_read_status_code_, StatusCode::kAborted);
+  EXPECT_NE(last_read_status_code_, DecoderStatus::Codes::kOk);
+  EXPECT_NE(last_read_status_code_, DecoderStatus::Codes::kAborted);
 }
 
 TEST_P(VideoDecoderStreamTest,
@@ -1388,7 +1389,7 @@
   ASSERT_EQ(GetDecoderId(2), decoder_->GetDecoderId());
 
   ASSERT_FALSE(pending_read_);
-  ASSERT_EQ(last_read_status_code_, StatusCode::kOk);
+  ASSERT_EQ(last_read_status_code_, DecoderStatus::Codes::kOk);
 
   // Can't check previously selected decoder(s) right now, they might have been
   // destroyed already.
@@ -1413,7 +1414,7 @@
   ASSERT_EQ(GetDecoderId(2), decoder_->GetDecoderId());
 
   ASSERT_FALSE(pending_read_);
-  ASSERT_EQ(last_read_status_code_, StatusCode::kOk);
+  ASSERT_EQ(last_read_status_code_, DecoderStatus::Codes::kOk);
 
   // Can't check previously selected decoder(s) right now, they might have been
   // destroyed already.
@@ -1435,7 +1436,7 @@
   decoder_->SimulateError();
 
   // The error must surface from Read() as DECODE_ERROR.
-  while (last_read_status_code_ == StatusCode::kOk) {
+  while (last_read_status_code_ == DecoderStatus::Codes::kOk) {
     ReadOneFrame();
     base::RunLoop().RunUntilIdle();
     EXPECT_FALSE(pending_read_);
@@ -1444,8 +1445,8 @@
   // Verify the error was surfaced, rather than falling back to other decoders.
   ASSERT_EQ(GetDecoderId(0), decoder_->GetDecoderId());
   EXPECT_FALSE(pending_read_);
-  EXPECT_NE(last_read_status_code_, StatusCode::kOk);
-  EXPECT_NE(last_read_status_code_, StatusCode::kAborted);
+  EXPECT_NE(last_read_status_code_, DecoderStatus::Codes::kOk);
+  EXPECT_NE(last_read_status_code_, DecoderStatus::Codes::kAborted);
 }
 
 TEST_P(VideoDecoderStreamTest, DecoderErrorWhenNotReading) {
@@ -1464,13 +1465,13 @@
   decoder_->SimulateError();
 
   // The error must surface from Read() as DECODE_ERROR.
-  while (last_read_status_code_ == StatusCode::kOk) {
+  while (last_read_status_code_ == DecoderStatus::Codes::kOk) {
     ReadOneFrame();
     base::RunLoop().RunUntilIdle();
     EXPECT_FALSE(pending_read_);
   }
-  EXPECT_NE(last_read_status_code_, StatusCode::kOk);
-  EXPECT_NE(last_read_status_code_, StatusCode::kAborted);
+  EXPECT_NE(last_read_status_code_, DecoderStatus::Codes::kOk);
+  EXPECT_NE(last_read_status_code_, DecoderStatus::Codes::kAborted);
 }
 
 TEST_P(VideoDecoderStreamTest, ReinitializeFailure_Once) {
@@ -1531,13 +1532,13 @@
   ReadUntilDecoderReinitialized();
 
   // The error will surface from Read() as DECODE_ERROR.
-  while (last_read_status_code_ == StatusCode::kOk) {
+  while (last_read_status_code_ == DecoderStatus::Codes::kOk) {
     ReadOneFrame();
     base::RunLoop().RunUntilIdle();
     EXPECT_FALSE(pending_read_);
   }
-  EXPECT_NE(last_read_status_code_, StatusCode::kOk);
-  EXPECT_NE(last_read_status_code_, StatusCode::kAborted);
+  EXPECT_NE(last_read_status_code_, DecoderStatus::Codes::kOk);
+  EXPECT_NE(last_read_status_code_, DecoderStatus::Codes::kAborted);
 }
 
 TEST_P(VideoDecoderStreamTest, Destroy_DuringFallbackDecoderSelection) {
diff --git a/media/filters/vpx_video_decoder.cc b/media/filters/vpx_video_decoder.cc
index f11d265e..a444b8e7 100644
--- a/media/filters/vpx_video_decoder.cc
+++ b/media/filters/vpx_video_decoder.cc
@@ -146,12 +146,13 @@
   InitCB bound_init_cb = bind_callbacks_ ? BindToCurrentLoop(std::move(init_cb))
                                          : std::move(init_cb);
   if (config.is_encrypted()) {
-    std::move(bound_init_cb).Run(StatusCode::kEncryptedContentUnsupported);
+    std::move(bound_init_cb)
+        .Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
     return;
   }
 
   if (!ConfigureDecoder(config)) {
-    std::move(bound_init_cb).Run(StatusCode::kDecoderFailedInitialization);
+    std::move(bound_init_cb).Run(DecoderStatus::Codes::kUnsupportedConfig);
     return;
   }
 
@@ -159,7 +160,7 @@
   config_ = config;
   state_ = DecoderState::kNormal;
   output_cb_ = output_cb;
-  std::move(bound_init_cb).Run(OkStatus());
+  std::move(bound_init_cb).Run(DecoderStatus::Codes::kOk);
 }
 
 void VpxVideoDecoder::Decode(scoped_refptr<DecoderBuffer> buffer,
@@ -176,25 +177,25 @@
                                  : std::move(decode_cb);
 
   if (state_ == DecoderState::kError) {
-    std::move(bound_decode_cb).Run(DecodeStatus::DECODE_ERROR);
+    std::move(bound_decode_cb).Run(DecoderStatus::Codes::kFailed);
     return;
   }
 
   if (state_ == DecoderState::kDecodeFinished) {
-    std::move(bound_decode_cb).Run(DecodeStatus::OK);
+    std::move(bound_decode_cb).Run(DecoderStatus::Codes::kOk);
     return;
   }
 
   if (state_ == DecoderState::kNormal && buffer->end_of_stream()) {
     state_ = DecoderState::kDecodeFinished;
-    std::move(bound_decode_cb).Run(DecodeStatus::OK);
+    std::move(bound_decode_cb).Run(DecoderStatus::Codes::kOk);
     return;
   }
 
   scoped_refptr<VideoFrame> video_frame;
   if (!VpxDecode(buffer.get(), &video_frame)) {
     state_ = DecoderState::kError;
-    std::move(bound_decode_cb).Run(DecodeStatus::DECODE_ERROR);
+    std::move(bound_decode_cb).Run(DecoderStatus::Codes::kFailed);
     return;
   }
 
@@ -206,7 +207,7 @@
   }
 
   // VideoDecoderShim expects |decode_cb| call after |output_cb_|.
-  std::move(bound_decode_cb).Run(DecodeStatus::OK);
+  std::move(bound_decode_cb).Run(DecoderStatus::Codes::kOk);
 }
 
 void VpxVideoDecoder::Reset(base::OnceClosure reset_cb) {
diff --git a/media/filters/vpx_video_decoder_fuzzertest.cc b/media/filters/vpx_video_decoder_fuzzertest.cc
index afeba4f..b0f8194 100644
--- a/media/filters/vpx_video_decoder_fuzzertest.cc
+++ b/media/filters/vpx_video_decoder_fuzzertest.cc
@@ -30,13 +30,14 @@
   base::test::SingleThreadTaskEnvironment task_environment;
 };
 
-void OnDecodeComplete(base::OnceClosure quit_closure, media::Status status) {
+void OnDecodeComplete(base::OnceClosure quit_closure,
+                      media::DecoderStatus status) {
   std::move(quit_closure).Run();
 }
 
 void OnInitDone(base::OnceClosure quit_closure,
                 bool* success_dest,
-                media::Status status) {
+                media::DecoderStatus status) {
   *success_dest = status.is_ok();
   std::move(quit_closure).Run();
 }
diff --git a/media/filters/vpx_video_decoder_unittest.cc b/media/filters/vpx_video_decoder_unittest.cc
index 8103eae..05729bd08 100644
--- a/media/filters/vpx_video_decoder_unittest.cc
+++ b/media/filters/vpx_video_decoder_unittest.cc
@@ -44,7 +44,7 @@
                                       bool success) {
     decoder_->Initialize(config, false, nullptr,
                          base::BindOnce(
-                             [](bool success, Status status) {
+                             [](bool success, DecoderStatus status) {
                                EXPECT_EQ(status.is_ok(), success);
                              },
                              success),
@@ -92,14 +92,14 @@
   // Decodes all buffers in |input_buffers| and push all successfully decoded
   // output frames into |output_frames|.
   // Returns the last decode status returned by the decoder.
-  Status DecodeMultipleFrames(const InputBuffers& input_buffers) {
+  DecoderStatus DecodeMultipleFrames(const InputBuffers& input_buffers) {
     for (auto iter = input_buffers.begin(); iter != input_buffers.end();
          ++iter) {
-      Status status = Decode(*iter);
+      DecoderStatus status = Decode(*iter);
       switch (status.code()) {
-        case StatusCode::kOk:
+        case DecoderStatus::Codes::kOk:
           break;
-        case StatusCode::kAborted:
+        case DecoderStatus::Codes::kAborted:
           NOTREACHED();
           [[fallthrough]];
         default:
@@ -107,14 +107,14 @@
           return status;
       }
     }
-    return OkStatus();
+    return DecoderStatus::Codes::kOk;
   }
 
   // Decodes the single compressed frame in |buffer| and writes the
   // uncompressed output to |video_frame|. This method works with single
   // and multithreaded decoders. End of stream buffers are used to trigger
   // the frame to be returned in the multithreaded decoder case.
-  Status DecodeSingleFrame(scoped_refptr<DecoderBuffer> buffer) {
+  DecoderStatus DecodeSingleFrame(scoped_refptr<DecoderBuffer> buffer) {
     InputBuffers input_buffers;
     input_buffers.push_back(std::move(buffer));
     input_buffers.push_back(DecoderBuffer::CreateEOSBuffer());
@@ -135,7 +135,7 @@
     input_buffers.push_back(buffer);
     input_buffers.push_back(DecoderBuffer::CreateEOSBuffer());
 
-    Status status = DecodeMultipleFrames(input_buffers);
+    DecoderStatus status = DecodeMultipleFrames(input_buffers);
 
     EXPECT_TRUE(status.is_ok());
     ASSERT_EQ(2U, output_frames_.size());
@@ -151,8 +151,8 @@
               output_frames_[1]->visible_rect().size().height());
   }
 
-  Status Decode(scoped_refptr<DecoderBuffer> buffer) {
-    Status status;
+  DecoderStatus Decode(scoped_refptr<DecoderBuffer> buffer) {
+    DecoderStatus status;
     EXPECT_CALL(*this, DecodeDone(_)).WillOnce(testing::SaveArg<0>(&status));
 
     decoder_->Decode(std::move(buffer),
@@ -168,7 +168,7 @@
     output_frames_.push_back(std::move(frame));
   }
 
-  MOCK_METHOD1(DecodeDone, void(Status));
+  MOCK_METHOD1(DecodeDone, void(DecoderStatus));
 
   base::test::TaskEnvironment task_env_;
   std::unique_ptr<VideoDecoder> decoder_;
@@ -326,7 +326,7 @@
 
   AVPacket packet = {};
   while (av_read_frame(glue.format_context(), &packet) >= 0) {
-    Status decode_status =
+    DecoderStatus decode_status =
         Decode(DecoderBuffer::CopyFrom(packet.data, packet.size));
     av_packet_unref(&packet);
     if (!decode_status.is_ok())
diff --git a/media/gpu/android/media_codec_video_decoder.cc b/media/gpu/android/media_codec_video_decoder.cc
index 577bdd3..b3604b3 100644
--- a/media/gpu/android/media_codec_video_decoder.cc
+++ b/media/gpu/android/media_codec_video_decoder.cc
@@ -309,7 +309,7 @@
 
   // Cancel callbacks we no longer want.
   self->codec_allocator_weak_factory_.InvalidateWeakPtrs();
-  self->CancelPendingDecodes(DecodeStatus::ABORTED);
+  self->CancelPendingDecodes(DecoderStatus::Codes::kAborted);
   self->StartDrainingCodec(DrainType::kForDestroy);
 
   // Per the WARNING above. Validate that no draining work remains.
@@ -336,7 +336,7 @@
                                 << config.AsHumanReadableString();
     DVLOG(1) << "Invalid configuration.";
     BindToCurrentLoop(std::move(init_cb))
-        .Run(StatusCode::kDecoderUnsupportedConfig);
+        .Run(DecoderStatus::Codes::kUnsupportedConfig);
     return;
   }
 
@@ -351,7 +351,7 @@
     MEDIA_LOG(INFO, media_log_) << "Video configuration is not valid: "
                                 << config.AsHumanReadableString();
     BindToCurrentLoop(std::move(init_cb))
-        .Run(StatusCode::kDecoderUnsupportedConfig);
+        .Run(DecoderStatus::Codes::kUnsupportedConfig);
     return;
   }
 
@@ -362,7 +362,7 @@
                                 << decoder_config_.AsHumanReadableString()
                                 << " -> " << config.AsHumanReadableString();
     BindToCurrentLoop(std::move(init_cb))
-        .Run(StatusCode::kDecoderCantChangeCodec);
+        .Run(DecoderStatus::Codes::kCantChangeCodec);
     return;
   }
   decoder_config_ = config;
@@ -391,12 +391,12 @@
     DVLOG(1) << "No MediaCrypto to handle encrypted config";
     MEDIA_LOG(INFO, media_log_) << "No MediaCrypto to handle encrypted config";
     BindToCurrentLoop(std::move(init_cb))
-        .Run(StatusCode::kDecoderMissingCdmForEncryptedContent);
+        .Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
     return;
   }
 
   // Do the rest of the initialization lazily on the first decode.
-  BindToCurrentLoop(std::move(init_cb)).Run(OkStatus());
+  BindToCurrentLoop(std::move(init_cb)).Run(DecoderStatus::Codes::kOk);
 
   const int width = decoder_config_.coded_size().width();
   // On re-init, reallocate the codec if the size has changed too much.
@@ -451,14 +451,14 @@
     if (decoder_config_.is_encrypted()) {
       LOG(ERROR) << "MediaCrypto is not available";
       EnterTerminalState(State::kError, "MediaCrypto is not available");
-      std::move(init_cb).Run(StatusCode::kDecoderMissingCdmForEncryptedContent);
+      std::move(init_cb).Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
       return;
     }
 
     // MediaCrypto is not available, but the stream is clear. So we can still
     // play the current stream. But if we switch to an encrypted stream playback
     // will fail.
-    std::move(init_cb).Run(OkStatus());
+    std::move(init_cb).Run(DecoderStatus::Codes::kOk);
     return;
   }
 
@@ -473,7 +473,7 @@
           : SurfaceChooserHelper::SecureSurfaceMode::kRequested);
 
   // Signal success, and create the codec lazily on the first decode.
-  std::move(init_cb).Run(OkStatus());
+  std::move(init_cb).Run(DecoderStatus::Codes::kOk);
 }
 
 void MediaCodecVideoDecoder::OnCdmContextEvent(CdmContext::Event event) {
@@ -736,7 +736,7 @@
                                     DecodeCB decode_cb) {
   DVLOG(3) << __func__ << ": " << buffer->AsHumanReadableString();
   if (state_ == State::kError) {
-    std::move(decode_cb).Run(DecodeStatus::DECODE_ERROR);
+    std::move(decode_cb).Run(DecoderStatus::Codes::kFailed);
     return;
   }
   pending_decodes_.emplace_back(std::move(buffer), std::move(decode_cb));
@@ -932,7 +932,7 @@
     DCHECK(!eos_decode_cb_);
     eos_decode_cb_ = std::move(pending_decode.decode_cb);
   } else {
-    std::move(pending_decode.decode_cb).Run(DecodeStatus::OK);
+    std::move(pending_decode.decode_cb).Run(DecoderStatus::Codes::kOk);
   }
   pending_decodes_.pop_front();
   return true;
@@ -1039,7 +1039,7 @@
   //  * After a Reset(), the reset generations won't match, but we might already
   //    have a new |eos_decode_cb_| for the new generation.
   if (reset_generation == reset_generation_ && eos_decode_cb_)
-    std::move(eos_decode_cb_).Run(DecodeStatus::OK);
+    std::move(eos_decode_cb_).Run(DecoderStatus::Codes::kOk);
 }
 
 void MediaCodecVideoDecoder::ForwardVideoFrame(
@@ -1080,7 +1080,7 @@
   DCHECK(!reset_cb_);
   reset_generation_++;
   reset_cb_ = std::move(closure);
-  CancelPendingDecodes(DecodeStatus::ABORTED);
+  CancelPendingDecodes(DecoderStatus::Codes::kAborted);
   StartDrainingCodec(DrainType::kForReset);
 }
 
@@ -1163,7 +1163,7 @@
   target_surface_bundle_ = nullptr;
   texture_owner_bundle_ = nullptr;
   if (state == State::kError)
-    CancelPendingDecodes(DecodeStatus::DECODE_ERROR);
+    CancelPendingDecodes(DecoderStatus::Codes::kFailed);
   if (drain_type_)
     OnCodecDrained();
 }
@@ -1172,7 +1172,7 @@
   return state_ == State::kSurfaceDestroyed || state_ == State::kError;
 }
 
-void MediaCodecVideoDecoder::CancelPendingDecodes(DecodeStatus status) {
+void MediaCodecVideoDecoder::CancelPendingDecodes(DecoderStatus status) {
   for (auto& pending_decode : pending_decodes_)
     std::move(pending_decode.decode_cb).Run(status);
   pending_decodes_.clear();
diff --git a/media/gpu/android/media_codec_video_decoder.h b/media/gpu/android/media_codec_video_decoder.h
index 73ec387..1c8740517 100644
--- a/media/gpu/android/media_codec_video_decoder.h
+++ b/media/gpu/android/media_codec_video_decoder.h
@@ -211,7 +211,7 @@
   // if possible.
   void StartDrainingCodec(DrainType drain_type);
   void OnCodecDrained();
-  void CancelPendingDecodes(DecodeStatus status);
+  void CancelPendingDecodes(DecoderStatus status);
 
   // Sets |state_| and does common teardown for the terminal states. |state_|
   // must be either kSurfaceDestroyed or kError.  |reason| will be logged to
diff --git a/media/gpu/android/media_codec_video_decoder_unittest.cc b/media/gpu/android/media_codec_video_decoder_unittest.cc
index ff467c0..6481bee 100644
--- a/media/gpu/android/media_codec_video_decoder_unittest.cc
+++ b/media/gpu/android/media_codec_video_decoder_unittest.cc
@@ -185,7 +185,7 @@
     if (!mcvd_)
       CreateMcvd();
     bool result = false;
-    auto init_cb = [](bool* result_out, Status result) {
+    auto init_cb = [](bool* result_out, DecoderStatus result) {
       *result_out = result.is_ok();
     };
     mcvd_->Initialize(
@@ -602,7 +602,7 @@
 
 TEST_P(MediaCodecVideoDecoderTest, ResetAbortsPendingDecodes) {
   InitializeWithTextureOwner_OneDecodePending(TestVideoConfig::Large(codec_));
-  EXPECT_CALL(decode_cb_, Run(HasStatusCode(StatusCode::kAborted)));
+  EXPECT_CALL(decode_cb_, Run(HasStatusCode(DecoderStatus::Codes::kAborted)));
   DoReset();
   testing::Mock::VerifyAndClearExpectations(&decode_cb_);
 }
@@ -620,7 +620,8 @@
   codec->AcceptOneInput(MockMediaCodecBridge::kEos);
   PumpCodec();
 
-  EXPECT_CALL(eos_decode_cb, Run(HasStatusCode(StatusCode::kAborted)));
+  EXPECT_CALL(eos_decode_cb,
+              Run(HasStatusCode(DecoderStatus::Codes::kAborted)));
   DoReset();
   // Should be run before |mcvd_| is destroyed.
   testing::Mock::VerifyAndClearExpectations(&eos_decode_cb);
diff --git a/media/gpu/chromeos/decoder_buffer_transcryptor.cc b/media/gpu/chromeos/decoder_buffer_transcryptor.cc
index 29f3bc8..e3cd31d 100644
--- a/media/gpu/chromeos/decoder_buffer_transcryptor.cc
+++ b/media/gpu/chromeos/decoder_buffer_transcryptor.cc
@@ -36,7 +36,7 @@
 
 DecoderBufferTranscryptor::~DecoderBufferTranscryptor() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  Reset(DecodeStatus::ABORTED);
+  Reset(DecoderStatus::Codes::kAborted);
 }
 
 void DecoderBufferTranscryptor::EnqueueBuffer(
@@ -47,7 +47,7 @@
   DecryptPendingBuffer();
 }
 
-void DecoderBufferTranscryptor::Reset(DecodeStatus status) {
+void DecoderBufferTranscryptor::Reset(DecoderStatus status) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (current_transcrypt_task_) {
     std::move(current_transcrypt_task_->decode_done_cb).Run(status);
@@ -150,4 +150,4 @@
     DecryptPendingBuffer();
 }
 
-}  // namespace media
\ No newline at end of file
+}  // namespace media
diff --git a/media/gpu/chromeos/decoder_buffer_transcryptor.h b/media/gpu/chromeos/decoder_buffer_transcryptor.h
index 9bb1a85..f5c157b3c 100644
--- a/media/gpu/chromeos/decoder_buffer_transcryptor.h
+++ b/media/gpu/chromeos/decoder_buffer_transcryptor.h
@@ -13,8 +13,8 @@
 #include "base/sequence_checker.h"
 #include "media/base/callback_registry.h"
 #include "media/base/cdm_context.h"
-#include "media/base/decode_status.h"
 #include "media/base/decoder_buffer.h"
+#include "media/base/decoder_status.h"
 #include "media/base/decryptor.h"
 #include "media/base/video_decoder.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -48,7 +48,7 @@
 
   // Removes all pending tasks and invokes all pending VideoDecoder::DecodeCB
   // callbacks with the passed in |status|.
-  void Reset(DecodeStatus status);
+  void Reset(DecoderStatus status);
 
  private:
   // Transcrypt task holding single transcrypt request.
diff --git a/media/gpu/chromeos/vd_video_decode_accelerator.cc b/media/gpu/chromeos/vd_video_decode_accelerator.cc
index 7799dffb..f777cad 100644
--- a/media/gpu/chromeos/vd_video_decode_accelerator.cc
+++ b/media/gpu/chromeos/vd_video_decode_accelerator.cc
@@ -270,7 +270,7 @@
   return true;
 }
 
-void VdVideoDecodeAccelerator::OnInitializeDone(Status status) {
+void VdVideoDecodeAccelerator::OnInitializeDone(DecoderStatus status) {
   DVLOGF(3) << "success: " << status.is_ok();
   DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
   DCHECK(client_);
@@ -313,12 +313,13 @@
 }
 
 void VdVideoDecodeAccelerator::OnDecodeDone(int32_t bitstream_buffer_id,
-                                            Status status) {
-  DVLOGF(4) << "status: " << status.code();
+                                            DecoderStatus status) {
+  DVLOGF(4) << "status: " << status.group() << ":"
+            << static_cast<int>(status.code());
   DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
   DCHECK(client_);
 
-  if (!status.is_ok() && status.code() != StatusCode::kAborted) {
+  if (!status.is_ok() && status.code() != DecoderStatus::Codes::kAborted) {
     OnError(FROM_HERE, PLATFORM_FAILURE);
     return;
   }
@@ -364,16 +365,16 @@
       base::BindOnce(&VdVideoDecodeAccelerator::OnFlushDone, weak_this_));
 }
 
-void VdVideoDecodeAccelerator::OnFlushDone(Status status) {
-  DVLOGF(3) << "status: " << status.code();
+void VdVideoDecodeAccelerator::OnFlushDone(DecoderStatus status) {
+  DVLOGF(3) << "status: " << static_cast<int>(status.code());
   DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
   DCHECK(client_);
 
   switch (status.code()) {
-    case StatusCode::kOk:
+    case DecoderStatus::Codes::kOk:
       client_->NotifyFlushDone();
       break;
-    case StatusCode::kAborted:
+    case DecoderStatus::Codes::kAborted:
       // Do nothing.
       break;
     default:
diff --git a/media/gpu/chromeos/vd_video_decode_accelerator.h b/media/gpu/chromeos/vd_video_decode_accelerator.h
index 7aa2470a..79f0da64 100644
--- a/media/gpu/chromeos/vd_video_decode_accelerator.h
+++ b/media/gpu/chromeos/vd_video_decode_accelerator.h
@@ -93,10 +93,10 @@
       scoped_refptr<base::SequencedTaskRunner> task_runner);
 
   // Callback methods of |vd_|.
-  void OnInitializeDone(Status status);
-  void OnDecodeDone(int32_t bitstream_buffer_id, Status status);
+  void OnInitializeDone(DecoderStatus status);
+  void OnDecodeDone(int32_t bitstream_buffer_id, DecoderStatus status);
   void OnFrameReady(scoped_refptr<VideoFrame> frame);
-  void OnFlushDone(Status status);
+  void OnFlushDone(DecoderStatus status);
   void OnResetDone();
 
   // Get Picture instance that represents the same buffer as |frame|. Return
diff --git a/media/gpu/chromeos/video_decoder_pipeline.cc b/media/gpu/chromeos/video_decoder_pipeline.cc
index 33a7f5b..cd002a24a 100644
--- a/media/gpu/chromeos/video_decoder_pipeline.cc
+++ b/media/gpu/chromeos/video_decoder_pipeline.cc
@@ -265,24 +265,24 @@
 
   if (!config.IsValidConfig()) {
     VLOGF(1) << "config is not valid";
-    std::move(init_cb).Run(StatusCode::kDecoderUnsupportedConfig);
+    std::move(init_cb).Run(DecoderStatus::Codes::kUnsupportedConfig);
     return;
   }
 #if BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA)
   if (config.is_encrypted() && !cdm_context) {
     VLOGF(1) << "Encrypted streams require a CdmContext";
-    std::move(init_cb).Run(StatusCode::kDecoderUnsupportedConfig);
+    std::move(init_cb).Run(DecoderStatus::Codes::kUnsupportedConfig);
     return;
   }
 #else   // BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA)
   if (config.is_encrypted() && !allow_encrypted_content_for_testing_) {
     VLOGF(1) << "Encrypted streams are not supported for this VD";
-    std::move(init_cb).Run(StatusCode::kEncryptedContentUnsupported);
+    std::move(init_cb).Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
     return;
   }
   if (cdm_context && !allow_encrypted_content_for_testing_) {
     VLOGF(1) << "cdm_context is not supported.";
-    std::move(init_cb).Run(StatusCode::kEncryptedContentUnsupported);
+    std::move(init_cb).Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
     return;
   }
 #endif  // !BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA)
@@ -320,7 +320,8 @@
     OnError("|decoder_| creation failed.");
     client_task_runner_->PostTask(
         FROM_HERE,
-        base::BindOnce(std::move(init_cb), StatusCode::kDecoderFailedCreation));
+        base::BindOnce(std::move(init_cb),
+                       DecoderStatus::Codes::kFailedToCreateDecoder));
     return;
   }
 
@@ -336,14 +337,14 @@
 
 void VideoDecoderPipeline::OnInitializeDone(InitCB init_cb,
                                             CdmContext* cdm_context,
-                                            Status status) {
+                                            DecoderStatus status) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
-  DVLOGF(4) << "Initialization status = " << status.code();
+  DVLOGF(4) << "Initialization status = " << static_cast<int>(status.code());
 
   if (!status.is_ok()) {
     MEDIA_LOG(ERROR, media_log_)
         << "VideoDecoderPipeline |decoder_| Initialize() failed, status: "
-        << status.code();
+        << static_cast<int>(status.code());
     decoder_ = nullptr;
   }
   MEDIA_LOG(INFO, media_log_)
@@ -354,7 +355,7 @@
     if (!cdm_context) {
       VLOGF(1) << "CdmContext required for transcryption";
       decoder_ = nullptr;
-      status = Status(StatusCode::kDecoderMissingCdmForEncryptedContent);
+      status = DecoderStatus::Codes::kUnsupportedEncryptionMode;
     } else {
       // We need to enable transcryption for protected content.
       buffer_transcryptor_ = std::make_unique<DecoderBufferTranscryptor>(
@@ -402,10 +403,10 @@
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   if (buffer_transcryptor_)
-    buffer_transcryptor_->Reset(DecodeStatus::ABORTED);
+    buffer_transcryptor_->Reset(DecoderStatus::Codes::kAborted);
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
-  CallFlushCbIfNeeded(DecodeStatus::ABORTED);
+  CallFlushCbIfNeeded(DecoderStatus::Codes::kAborted);
 
   if (need_frame_pool_rebuild_) {
     need_frame_pool_rebuild_ = false;
@@ -437,8 +438,8 @@
 
   if (has_error_) {
     client_task_runner_->PostTask(
-        FROM_HERE, base::BindOnce(std::move(decode_cb),
-                                  Status(DecodeStatus::DECODE_ERROR)));
+        FROM_HERE,
+        base::BindOnce(std::move(decode_cb), DecoderStatus::Codes::kFailed));
     return;
   }
 
@@ -461,16 +462,17 @@
 
 void VideoDecoderPipeline::OnDecodeDone(bool is_flush,
                                         DecodeCB decode_cb,
-                                        Status status) {
+                                        DecoderStatus status) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
-  DVLOGF(4) << "is_flush: " << is_flush << ", status: " << status.code();
+  DVLOGF(4) << "is_flush: " << is_flush
+            << ", status: " << static_cast<int>(status.code());
 
   if (has_error_)
-    status = Status(DecodeStatus::DECODE_ERROR);
+    status = DecoderStatus::Codes::kFailed;
 
   if (is_flush && status.is_ok()) {
     client_flush_cb_ = std::move(decode_cb);
-    CallFlushCbIfNeeded(DecodeStatus::OK);
+    CallFlushCbIfNeeded(DecoderStatus::Codes::kOk);
     return;
   }
 
@@ -526,7 +528,7 @@
       FROM_HERE, base::BindOnce(client_output_cb_, std::move(frame)));
 
   // After outputting a frame, flush might be completed.
-  CallFlushCbIfNeeded(DecodeStatus::OK);
+  CallFlushCbIfNeeded(DecoderStatus::Codes::kOk);
   CallApplyResolutionChangeIfNeeded();
 }
 
@@ -555,20 +557,20 @@
   has_error_ = true;
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   if (buffer_transcryptor_)
-    buffer_transcryptor_->Reset(DecodeStatus::DECODE_ERROR);
+    buffer_transcryptor_->Reset(DecoderStatus::Codes::kFailed);
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
-  CallFlushCbIfNeeded(DecodeStatus::DECODE_ERROR);
+  CallFlushCbIfNeeded(DecoderStatus::Codes::kFailed);
 }
 
-void VideoDecoderPipeline::CallFlushCbIfNeeded(DecodeStatus status) {
+void VideoDecoderPipeline::CallFlushCbIfNeeded(DecoderStatus status) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
-  DVLOGF(3) << "status: " << status;
+  DVLOGF(3) << "status: " << static_cast<int>(status.code());
 
   if (!client_flush_cb_)
     return;
 
   // Flush is not completed yet.
-  if (status == DecodeStatus::OK && HasPendingFrames())
+  if (status == DecoderStatus::Codes::kOk && HasPendingFrames())
     return;
 
   client_task_runner_->PostTask(
@@ -782,7 +784,7 @@
   DCHECK(!has_error_);
   if (!transcrypted_buffer) {
     OnError("Error in buffer transcryption");
-    std::move(decode_callback).Run(DecodeStatus::DECODE_ERROR);
+    std::move(decode_callback).Run(DecoderStatus::Codes::kFailed);
     return;
   }
 
diff --git a/media/gpu/chromeos/video_decoder_pipeline.h b/media/gpu/chromeos/video_decoder_pipeline.h
index f798c98..61560d3 100644
--- a/media/gpu/chromeos/video_decoder_pipeline.h
+++ b/media/gpu/chromeos/video_decoder_pipeline.h
@@ -198,9 +198,11 @@
   void ResetTask(base::OnceClosure reset_cb);
   void DecodeTask(scoped_refptr<DecoderBuffer> buffer, DecodeCB decode_cb);
 
-  void OnInitializeDone(InitCB init_cb, CdmContext* cdm_context, Status status);
+  void OnInitializeDone(InitCB init_cb,
+                        CdmContext* cdm_context,
+                        DecoderStatus status);
 
-  void OnDecodeDone(bool eos_buffer, DecodeCB decode_cb, Status status);
+  void OnDecodeDone(bool eos_buffer, DecodeCB decode_cb, DecoderStatus status);
   void OnResetDone(base::OnceClosure reset_cb);
   void OnError(const std::string& msg);
 
@@ -222,7 +224,7 @@
   void CallApplyResolutionChangeIfNeeded();
 
   // Call |client_flush_cb_| with |status|.
-  void CallFlushCbIfNeeded(DecodeStatus status);
+  void CallFlushCbIfNeeded(DecoderStatus status);
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   // Callback for when transcryption of a buffer completes.
diff --git a/media/gpu/chromeos/video_decoder_pipeline_unittest.cc b/media/gpu/chromeos/video_decoder_pipeline_unittest.cc
index 87514407..40b6e510 100644
--- a/media/gpu/chromeos/video_decoder_pipeline_unittest.cc
+++ b/media/gpu/chromeos/video_decoder_pipeline_unittest.cc
@@ -144,7 +144,7 @@
   using RepeatingCreateDecoderFunctionCB = base::RepeatingCallback<
       VideoDecoderPipeline::CreateDecoderFunctionCB::RunType>;
   RepeatingCreateDecoderFunctionCB create_decoder_function_cb;
-  StatusCode status_code;
+  DecoderStatus::Codes status_code;
 };
 
 class VideoDecoderPipelineTest
@@ -176,10 +176,10 @@
     VideoDecoderPipeline::DestroyAsync(std::move(decoder_));
     task_environment_.RunUntilIdle();
   }
-  MOCK_METHOD1(OnInit, void(Status));
+  MOCK_METHOD1(OnInit, void(DecoderStatus));
   MOCK_METHOD1(OnOutput, void(scoped_refptr<VideoFrame>));
   MOCK_METHOD0(OnResetDone, void());
-  MOCK_METHOD1(OnDecodeDone, void(Status));
+  MOCK_METHOD1(OnDecodeDone, void(DecoderStatus));
   MOCK_METHOD1(OnWaiting, void(WaitingReason));
 
   void SetCreateDecoderFunctionCB(VideoDecoderPipeline::CreateDecoderFunctionCB
@@ -197,7 +197,7 @@
   // verifying |status_code| is received back in OnInit().
   void InitializeDecoder(
       VideoDecoderPipeline::CreateDecoderFunctionCB create_decoder_function_cb,
-      StatusCode status_code,
+      DecoderStatus::Codes status_code,
       CdmContext* cdm_context = nullptr) {
     SetCreateDecoderFunctionCB(std::move(create_decoder_function_cb));
 
@@ -234,7 +234,7 @@
     InitializeDecoder(
         base::BindOnce(
             &VideoDecoderPipelineTest::CreateGoodMockTranscryptDecoder),
-        StatusCode::kOk, &cdm_context_);
+        DecoderStatus::Codes::kOk, &cdm_context_);
     testing::Mock::VerifyAndClearExpectations(&chromeos_cdm_context_);
     testing::Mock::VerifyAndClearExpectations(&cdm_context_);
     // GetDecryptor() will be called again, so set that expectation.
@@ -262,7 +262,7 @@
     std::unique_ptr<MockDecoder> decoder(new MockDecoder());
     EXPECT_CALL(*decoder, Initialize(_, _, _, _, _, _))
         .WillOnce(::testing::WithArgs<3>([](VideoDecoder::InitCB init_cb) {
-          std::move(init_cb).Run(OkStatus());
+          std::move(init_cb).Run(DecoderStatus::Codes::kOk);
         }));
     EXPECT_CALL(*decoder, NeedsTranscryption()).WillRepeatedly(Return(false));
     return std::move(decoder);
@@ -277,7 +277,7 @@
     std::unique_ptr<MockDecoder> decoder(new MockDecoder());
     EXPECT_CALL(*decoder, Initialize(_, _, _, _, _, _))
         .WillOnce(::testing::WithArgs<3>([](VideoDecoder::InitCB init_cb) {
-          std::move(init_cb).Run(OkStatus());
+          std::move(init_cb).Run(DecoderStatus::Codes::kOk);
         }));
     EXPECT_CALL(*decoder, NeedsTranscryption()).WillRepeatedly(Return(true));
     return std::move(decoder);
@@ -291,7 +291,7 @@
     std::unique_ptr<MockDecoder> decoder(new MockDecoder());
     EXPECT_CALL(*decoder, Initialize(_, _, _, _, _, _))
         .WillOnce(::testing::WithArgs<3>([](VideoDecoder::InitCB init_cb) {
-          std::move(init_cb).Run(StatusCode::kDecoderInitializationFailed);
+          std::move(init_cb).Run(DecoderStatus::Codes::kFailed);
         }));
     EXPECT_CALL(*decoder, NeedsTranscryption()).WillRepeatedly(Return(false));
     return std::move(decoder);
@@ -355,7 +355,7 @@
   InitializeDecoder(base::BindOnce(GetParam().create_decoder_function_cb),
                     GetParam().status_code);
 
-  EXPECT_EQ(GetParam().status_code == StatusCode::kOk,
+  EXPECT_EQ(GetParam().status_code == DecoderStatus::Codes::kOk,
             !!GetUnderlyingDecoder());
 }
 
@@ -363,12 +363,12 @@
     // A CreateDecoderFunctionCB that fails to Create() (i.e. returns a
     // null Decoder)
     {base::BindRepeating(&VideoDecoderPipelineTest::CreateNullMockDecoder),
-     StatusCode::kDecoderFailedCreation},
+     DecoderStatus::Codes::kFailedToCreateDecoder},
 
     // A CreateDecoderFunctionCB that works fine, i.e. Create()s and
     // Initialize()s correctly.
     {base::BindRepeating(&VideoDecoderPipelineTest::CreateGoodMockDecoder),
-     StatusCode::kOk},
+     DecoderStatus::Codes::kOk},
 
     // A CreateDecoderFunctionCB for transcryption, where Create() is ok, and
     // the decoder will Initialize OK, but then the pipeline will not create the
@@ -376,12 +376,12 @@
     // through InitializeForTranscrypt where a CdmContext is set.
     {base::BindRepeating(
          &VideoDecoderPipelineTest::CreateGoodMockTranscryptDecoder),
-     StatusCode::kDecoderMissingCdmForEncryptedContent},
+     DecoderStatus::Codes::kUnsupportedEncryptionMode},
 
     // A CreateDecoderFunctionCB that Create()s ok but fails to Initialize()
     // correctly.
     {base::BindRepeating(&VideoDecoderPipelineTest::CreateBadMockDecoder),
-     StatusCode::kDecoderInitializationFailed},
+     DecoderStatus::Codes::kFailed},
 };
 
 INSTANTIATE_TEST_SUITE_P(All,
@@ -392,7 +392,7 @@
 TEST_F(VideoDecoderPipelineTest, Reset) {
   InitializeDecoder(
       base::BindOnce(&VideoDecoderPipelineTest::CreateGoodMockDecoder),
-      StatusCode::kOk);
+      DecoderStatus::Codes::kOk);
 
   // When we call Reset(), we expect GetUnderlyingDecoder()'s Reset() method to
   // be called, and when that method Run()s its argument closure, then
@@ -427,9 +427,10 @@
                 Decode(transcrypted_buffer_, _))
         .WillOnce([](scoped_refptr<DecoderBuffer> transcrypted,
                      VideoDecoderMixin::DecodeCB decode_cb) {
-          std::move(decode_cb).Run(OkStatus());
+          std::move(decode_cb).Run(DecoderStatus::Codes::kOk);
         });
-    EXPECT_CALL(*this, OnDecodeDone(MatchesStatusCode(StatusCode::kOk)));
+    EXPECT_CALL(*this,
+                OnDecodeDone(MatchesStatusCode(DecoderStatus::Codes::kOk)));
   }
   decoder_->Decode(encrypted_buffer_,
                    base::BindOnce(&VideoDecoderPipelineTest::OnDecodeDone,
@@ -449,9 +450,10 @@
                 Decode(eos_buffer, _))
         .WillOnce([](scoped_refptr<DecoderBuffer> transcrypted,
                      VideoDecoderMixin::DecodeCB decode_cb) {
-          std::move(decode_cb).Run(OkStatus());
+          std::move(decode_cb).Run(DecoderStatus::Codes::kOk);
         });
-    EXPECT_CALL(*this, OnDecodeDone(MatchesStatusCode(StatusCode::kOk)));
+    EXPECT_CALL(*this,
+                OnDecodeDone(MatchesStatusCode(DecoderStatus::Codes::kOk)));
   }
   decoder_->Decode(eos_buffer,
                    base::BindOnce(&VideoDecoderPipelineTest::OnDecodeDone,
@@ -486,7 +488,8 @@
     EXPECT_CALL(*reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()),
                 Reset(_))
         .WillOnce([](base::OnceClosure closure) { std::move(closure).Run(); });
-    EXPECT_CALL(*this, OnDecodeDone(MatchesStatusCode(DecodeStatus::ABORTED)))
+    EXPECT_CALL(*this,
+                OnDecodeDone(MatchesStatusCode(DecoderStatus::Codes::kAborted)))
         .Times(3);
     EXPECT_CALL(*this, OnResetDone()).Times(1);
   }
@@ -533,9 +536,10 @@
                 Decode(transcrypted_buffer_, _))
         .WillOnce([](scoped_refptr<DecoderBuffer> transcrypted,
                      VideoDecoderMixin::DecodeCB decode_cb) {
-          std::move(decode_cb).Run(OkStatus());
+          std::move(decode_cb).Run(DecoderStatus::Codes::kOk);
         });
-    EXPECT_CALL(*this, OnDecodeDone(MatchesStatusCode(StatusCode::kOk)));
+    EXPECT_CALL(*this,
+                OnDecodeDone(MatchesStatusCode(DecoderStatus::Codes::kOk)));
   }
   EXPECT_CALL(*this, OnWaiting(_)).Times(0);
   std::move(saved_decrypt_cb).Run(Decryptor::kNoKey, nullptr);
@@ -580,9 +584,10 @@
                 Decode(transcrypted_buffer_, _))
         .WillOnce([](scoped_refptr<DecoderBuffer> transcrypted,
                      VideoDecoderMixin::DecodeCB decode_cb) {
-          std::move(decode_cb).Run(OkStatus());
+          std::move(decode_cb).Run(DecoderStatus::Codes::kOk);
         });
-    EXPECT_CALL(*this, OnDecodeDone(MatchesStatusCode(StatusCode::kOk)));
+    EXPECT_CALL(*this,
+                OnDecodeDone(MatchesStatusCode(DecoderStatus::Codes::kOk)));
   }
   event_callbacks_.Notify(CdmContext::Event::kHasAdditionalUsableKey);
   task_environment_.RunUntilIdle();
@@ -599,7 +604,7 @@
           std::move(decrypt_cb).Run(Decryptor::kError, nullptr);
         });
     EXPECT_CALL(*this,
-                OnDecodeDone(MatchesStatusCode(StatusCode::DECODE_ERROR)));
+                OnDecodeDone(MatchesStatusCode(DecoderStatus::Codes::kFailed)));
   }
   decoder_->Decode(encrypted_buffer_,
                    base::BindOnce(&VideoDecoderPipelineTest::OnDecodeDone,
@@ -771,7 +776,7 @@
 TEST_F(VideoDecoderPipelineTest, RebuildFramePoolsOnStateLost) {
   InitializeDecoder(
       base::BindOnce(&VideoDecoderPipelineTest::CreateGoodMockDecoder),
-      StatusCode::kOk);
+      DecoderStatus::Codes::kOk);
 
   // Simulate the waiting callback from the decoder for kDecoderStateLost.
   EXPECT_CALL(*this, OnWaiting(media::WaitingReason::kDecoderStateLost));
diff --git a/media/gpu/ipc/client/gpu_video_decode_accelerator_host.cc b/media/gpu/ipc/client/gpu_video_decode_accelerator_host.cc
index fa668ac..bdbbd77c 100644
--- a/media/gpu/ipc/client/gpu_video_decode_accelerator_host.cc
+++ b/media/gpu/ipc/client/gpu_video_decode_accelerator_host.cc
@@ -172,7 +172,7 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (client_) {
     client_->NotifyInitializationComplete(
-        success ? OkStatus() : StatusCode::kInitializationUnspecifiedFailure);
+        success ? DecoderStatus::Codes::kOk : DecoderStatus::Codes::kFailed);
   }
 }
 
diff --git a/media/gpu/ipc/service/gpu_video_decode_accelerator.cc b/media/gpu/ipc/service/gpu_video_decode_accelerator.cc
index 4ac2f6a..3ab9547 100644
--- a/media/gpu/ipc/service/gpu_video_decode_accelerator.cc
+++ b/media/gpu/ipc/service/gpu_video_decode_accelerator.cc
@@ -312,7 +312,8 @@
       gpu_preferences, workarounds);
 }
 
-void GpuVideoDecodeAccelerator::NotifyInitializationComplete(Status status) {
+void GpuVideoDecodeAccelerator::NotifyInitializationComplete(
+    DecoderStatus status) {
   decoder_client_->OnInitializationComplete(status.is_ok());
 }
 
diff --git a/media/gpu/ipc/service/gpu_video_decode_accelerator.h b/media/gpu/ipc/service/gpu_video_decode_accelerator.h
index d37bc17f..683939e 100644
--- a/media/gpu/ipc/service/gpu_video_decode_accelerator.h
+++ b/media/gpu/ipc/service/gpu_video_decode_accelerator.h
@@ -60,7 +60,7 @@
       const gpu::GpuDriverBugWorkarounds& workarounds);
 
   // VideoDecodeAccelerator::Client implementation.
-  void NotifyInitializationComplete(Status status) override;
+  void NotifyInitializationComplete(DecoderStatus status) override;
   void ProvidePictureBuffers(uint32_t requested_num_of_buffers,
                              VideoPixelFormat format,
                              uint32_t textures_per_buffer,
diff --git a/media/gpu/ipc/service/vda_video_decoder.cc b/media/gpu/ipc/service/vda_video_decoder.cc
index 90a26a2..ccf7b1f 100644
--- a/media/gpu/ipc/service/vda_video_decoder.cc
+++ b/media/gpu/ipc/service/vda_video_decoder.cc
@@ -232,10 +232,9 @@
   DCHECK(decode_cbs_.empty());
 
   if (has_error_) {
-    // TODO(tmathmeyer) generic error, please remove.
     parent_task_runner_->PostTask(
-        FROM_HERE, base::BindOnce(std::move(init_cb),
-                                  StatusCode::kGenericErrorPleaseRemove));
+        FROM_HERE,
+        base::BindOnce(std::move(init_cb), DecoderStatus::Codes::kFailed));
     return;
   }
 
@@ -306,8 +305,9 @@
                                     gpu_weak_this_));
     } else {
       parent_task_runner_->PostTask(
-          FROM_HERE, base::BindOnce(&VdaVideoDecoder::InitializeDone,
-                                    parent_weak_this_, OkStatus()));
+          FROM_HERE,
+          base::BindOnce(&VdaVideoDecoder::InitializeDone, parent_weak_this_,
+                         DecoderStatus::Codes::kOk));
     }
     return;
   }
@@ -344,7 +344,7 @@
       parent_task_runner_->PostTask(
           FROM_HERE,
           base::BindOnce(&VdaVideoDecoder::InitializeDone, parent_weak_this_,
-                         StatusCode::kDecoderInitializeNeverCompleted));
+                         DecoderStatus::Codes::kFailed));
       return;
     }
 
@@ -375,7 +375,7 @@
     parent_task_runner_->PostTask(
         FROM_HERE,
         base::BindOnce(&VdaVideoDecoder::InitializeDone, parent_weak_this_,
-                       StatusCode::kDecoderInitializeNeverCompleted));
+                       DecoderStatus::Codes::kFailedToCreateDecoder));
     return;
   }
 
@@ -388,13 +388,14 @@
   decode_on_parent_thread_ = vda_->TryToSetupDecodeOnSeparateThread(
       parent_weak_this_, parent_task_runner_);
 
-  parent_task_runner_->PostTask(FROM_HERE,
-                                base::BindOnce(&VdaVideoDecoder::InitializeDone,
-                                               parent_weak_this_, OkStatus()));
+  parent_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&VdaVideoDecoder::InitializeDone,
+                                parent_weak_this_, DecoderStatus::Codes::kOk));
 }
 
-void VdaVideoDecoder::InitializeDone(Status status) {
-  DVLOG(1) << __func__ << " success = (" << status.code() << ")";
+void VdaVideoDecoder::InitializeDone(DecoderStatus status) {
+  DVLOG(1) << __func__ << " success = (" << static_cast<int>(status.code())
+           << ")";
   DCHECK(parent_task_runner_->BelongsToCurrentThread());
 
   if (has_error_)
@@ -422,7 +423,7 @@
   if (has_error_) {
     parent_task_runner_->PostTask(
         FROM_HERE,
-        base::BindOnce(std::move(decode_cb), DecodeStatus::DECODE_ERROR));
+        base::BindOnce(std::move(decode_cb), DecoderStatus::Codes::kFailed));
     return;
   }
 
@@ -504,8 +505,8 @@
   return 4;
 }
 
-void VdaVideoDecoder::NotifyInitializationComplete(Status status) {
-  DVLOG(2) << __func__ << "(" << status.code() << ")";
+void VdaVideoDecoder::NotifyInitializationComplete(DecoderStatus status) {
+  DVLOG(2) << __func__ << "(" << static_cast<int>(status.code()) << ")";
   DCHECK(gpu_task_runner_->BelongsToCurrentThread());
   DCHECK(vda_initialized_);
 
@@ -686,7 +687,7 @@
   // Run a local copy in case the decode callback modifies |decode_cbs_|.
   DecodeCB decode_cb = std::move(decode_cb_it->second);
   decode_cbs_.erase(decode_cb_it);
-  std::move(decode_cb).Run(DecodeStatus::OK);
+  std::move(decode_cb).Run(DecoderStatus::Codes::kOk);
 }
 
 void VdaVideoDecoder::NotifyFlushDone() {
@@ -711,7 +712,7 @@
     return;
 
   DCHECK(decode_cbs_.empty());
-  std::move(flush_cb_).Run(DecodeStatus::OK);
+  std::move(flush_cb_).Run(DecoderStatus::Codes::kOk);
 }
 
 void VdaVideoDecoder::NotifyResetDone() {
@@ -744,13 +745,13 @@
   std::map<int32_t, DecodeCB> local_decode_cbs = std::move(decode_cbs_);
   decode_cbs_.clear();
   for (auto& it : local_decode_cbs) {
-    std::move(it.second).Run(DecodeStatus::ABORTED);
+    std::move(it.second).Run(DecoderStatus::Codes::kAborted);
     if (!weak_this)
       return;
   }
 
   if (weak_this && flush_cb_)
-    std::move(flush_cb_).Run(DecodeStatus::ABORTED);
+    std::move(flush_cb_).Run(DecoderStatus::Codes::kAborted);
 
   if (weak_this)
     std::move(reset_cb_).Run();
@@ -828,13 +829,13 @@
   std::map<int32_t, DecodeCB> local_decode_cbs = std::move(decode_cbs_);
   decode_cbs_.clear();
   for (auto& it : local_decode_cbs) {
-    std::move(it.second).Run(DecodeStatus::DECODE_ERROR);
+    std::move(it.second).Run(DecoderStatus::Codes::kFailed);
     if (!weak_this)
       return;
   }
 
   if (weak_this && flush_cb_)
-    std::move(flush_cb_).Run(DecodeStatus::DECODE_ERROR);
+    std::move(flush_cb_).Run(DecoderStatus::Codes::kFailed);
 
   // Note: |reset_cb_| cannot return failure, so the client won't actually find
   // out about the error until another operation is attempted.
@@ -842,7 +843,7 @@
     std::move(reset_cb_).Run();
 
   if (weak_this && init_cb_)
-    std::move(init_cb_).Run(StatusCode::kDecoderInitializeNeverCompleted);
+    std::move(init_cb_).Run(DecoderStatus::Codes::kFailed);
 }
 
 }  // namespace media
diff --git a/media/gpu/ipc/service/vda_video_decoder.h b/media/gpu/ipc/service/vda_video_decoder.h
index a8afdf9..83b8653 100644
--- a/media/gpu/ipc/service/vda_video_decoder.h
+++ b/media/gpu/ipc/service/vda_video_decoder.h
@@ -117,7 +117,7 @@
       const VideoDecodeAccelerator::Capabilities& vda_capabilities);
 
   // media::VideoDecodeAccelerator::Client implementation.
-  void NotifyInitializationComplete(Status status) override;
+  void NotifyInitializationComplete(DecoderStatus status) override;
   void ProvidePictureBuffers(uint32_t requested_num_of_buffers,
                              VideoPixelFormat format,
                              uint32_t textures_per_buffer,
@@ -136,7 +136,7 @@
   static void CleanupOnGpuThread(std::unique_ptr<VdaVideoDecoder>);
   void InitializeOnGpuThread();
   void ReinitializeOnGpuThread();
-  void InitializeDone(Status status);
+  void InitializeDone(DecoderStatus status);
   void DecodeOnGpuThread(scoped_refptr<DecoderBuffer> buffer,
                          int32_t bitstream_id);
   void DismissPictureBufferOnParentThread(int32_t picture_buffer_id);
diff --git a/media/gpu/ipc/service/vda_video_decoder_unittest.cc b/media/gpu/ipc/service/vda_video_decoder_unittest.cc
index 656e8be..111ea51 100644
--- a/media/gpu/ipc/service/vda_video_decoder_unittest.cc
+++ b/media/gpu/ipc/service/vda_video_decoder_unittest.cc
@@ -18,8 +18,8 @@
 #include "base/time/time.h"
 #include "gpu/command_buffer/common/sync_token.h"
 #include "media/base/async_destroy_video_decoder.h"
-#include "media/base/decode_status.h"
 #include "media/base/decoder_buffer.h"
+#include "media/base/decoder_status.h"
 #include "media/base/media_util.h"
 #include "media/base/mock_media_log.h"
 #include "media/base/simple_sync_token_client.h"
@@ -179,7 +179,7 @@
   }
 
   void NotifyEndOfBitstreamBuffer(int32_t bitstream_id) {
-    EXPECT_CALL(decode_cb_, Run(HasStatusCode(DecodeStatus::OK)));
+    EXPECT_CALL(decode_cb_, Run(HasStatusCode(DecoderStatus::Codes::kOk)));
     if (GetParam()) {
       // TODO(sandersd): The VDA could notify on either thread. Test both.
       client_->NotifyEndOfBitstreamBuffer(bitstream_id);
@@ -328,8 +328,7 @@
       VideoDecoderConfig::AlphaMode::kIsOpaque, VideoColorSpace::REC601(),
       kNoTransformation, gfx::Size(320, 240), gfx::Rect(320, 240),
       gfx::Size(320, 240), EmptyExtraData(), EncryptionScheme::kUnencrypted));
-  EXPECT_CALL(init_cb_,
-              Run(HasStatusCode(StatusCode::kDecoderInitializeNeverCompleted)));
+  EXPECT_CALL(init_cb_, Run(HasStatusCode(DecoderStatus::Codes::kFailed)));
   RunUntilIdle();
 }
 
@@ -339,8 +338,7 @@
       VideoDecoderConfig::AlphaMode::kIsOpaque, VideoColorSpace::REC709(),
       kNoTransformation, gfx::Size(1920, 1088), gfx::Rect(1920, 1080),
       gfx::Size(1920, 1080), EmptyExtraData(), EncryptionScheme::kUnencrypted));
-  EXPECT_CALL(init_cb_,
-              Run(HasStatusCode(StatusCode::kDecoderInitializeNeverCompleted)));
+  EXPECT_CALL(init_cb_, Run(HasStatusCode(DecoderStatus::Codes::kFailed)));
   RunUntilIdle();
 }
 
@@ -351,8 +349,7 @@
       VideoDecoderConfig::AlphaMode::kIsOpaque, VideoColorSpace::REC709(),
       kNoTransformation, gfx::Size(1920, 1088), gfx::Rect(1920, 1080),
       gfx::Size(1920, 1080), EmptyExtraData(), EncryptionScheme::kUnencrypted));
-  EXPECT_CALL(init_cb_,
-              Run(HasStatusCode(StatusCode::kDecoderInitializeNeverCompleted)));
+  EXPECT_CALL(init_cb_, Run(HasStatusCode(DecoderStatus::Codes::kFailed)));
   RunUntilIdle();
 }
 
@@ -376,7 +373,7 @@
   vdavd_->Reset(reset_cb_.Get());
   RunUntilIdle();
 
-  EXPECT_CALL(decode_cb_, Run(HasStatusCode(DecodeStatus::ABORTED)));
+  EXPECT_CALL(decode_cb_, Run(HasStatusCode(DecoderStatus::Codes::kAborted)));
   EXPECT_CALL(reset_cb_, Run());
   NotifyResetDone();
 }
diff --git a/media/gpu/test/video.cc b/media/gpu/test/video.cc
index bb9d23a7..2674443 100644
--- a/media/gpu/test/video.cc
+++ b/media/gpu/test/video.cc
@@ -553,12 +553,12 @@
   }
 
   // Setup the VP9 decoder.
-  media::Status init_result;
+  DecoderStatus init_result;
   VpxVideoDecoder decoder(
       media::OffloadableVideoDecoder::OffloadState::kOffloaded);
   media::VideoDecoder::InitCB init_cb =
-      base::BindOnce([](media::Status* save_to,
-                        media::Status save_from) { *save_to = save_from; },
+      base::BindOnce([](DecoderStatus* save_to,
+                        DecoderStatus save_from) { *save_to = save_from; },
                      &init_result);
   decoder.Initialize(config, false, nullptr, std::move(init_cb),
                      base::BindRepeating(&Video::OnFrameDecoded, resolution,
@@ -576,7 +576,7 @@
          num_decoded_frames < num_frames) {
     if (packet.stream_index == stream_index) {
       media::VideoDecoder::DecodeCB decode_cb = base::BindOnce(
-          [](bool* success, media::Status status) {
+          [](bool* success, DecoderStatus status) {
             *success = (status.is_ok());
           },
           success);
diff --git a/media/gpu/test/video_encoder/bitstream_validator.cc b/media/gpu/test/video_encoder/bitstream_validator.cc
index 187148d..4a0c828 100644
--- a/media/gpu/test/video_encoder/bitstream_validator.cc
+++ b/media/gpu/test/video_encoder/bitstream_validator.cc
@@ -87,7 +87,7 @@
   bool success = false;
   base::WaitableEvent initialized;
   VideoDecoder::InitCB init_done = base::BindOnce(
-      [](bool* result, base::WaitableEvent* initialized, Status status) {
+      [](bool* result, base::WaitableEvent* initialized, DecoderStatus status) {
         *result = true;
         if (!status.is_ok()) {
           LOG(ERROR) << "Failed decoder initialization ("
@@ -264,14 +264,14 @@
   }
 }
 
-void BitstreamValidator::DecodeDone(int64_t timestamp, Status status) {
+void BitstreamValidator::DecodeDone(int64_t timestamp, DecoderStatus status) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(validator_thread_sequence_checker_);
   if (!status.is_ok()) {
     base::AutoLock lock(validator_lock_);
     if (!decode_error_) {
       decode_error_ = true;
       LOG(ERROR) << "DecodeStatus is not OK, status="
-                 << GetDecodeStatusString(status.code());
+                 << static_cast<int>(status.code());
     }
   }
   if (timestamp == kEOSTimeStamp) {
diff --git a/media/gpu/test/video_encoder/bitstream_validator.h b/media/gpu/test/video_encoder/bitstream_validator.h
index e589415..500aff53 100644
--- a/media/gpu/test/video_encoder/bitstream_validator.h
+++ b/media/gpu/test/video_encoder/bitstream_validator.h
@@ -17,6 +17,7 @@
 #include "base/thread_annotations.h"
 #include "base/threading/thread.h"
 #include "media/base/bitstream_buffer.h"
+#include "media/base/decoder_status.h"
 #include "media/base/video_decoder.h"
 #include "media/gpu/test/bitstream_helpers.h"
 #include "media/gpu/test/video_frame_helpers.h"
@@ -77,7 +78,7 @@
   void OutputFrameProcessed();
 
   // Functions for media::VideoDecoder.
-  void DecodeDone(int64_t timestamp, Status status);
+  void DecodeDone(int64_t timestamp, DecoderStatus status);
   void VerifyOutputFrame(scoped_refptr<VideoFrame> frame);
 
   // Construct the spatial index conversion table |original_spatial_indices_|
diff --git a/media/gpu/test/video_player/test_vda_video_decoder.cc b/media/gpu/test/video_player/test_vda_video_decoder.cc
index 140a58d..60cce08c 100644
--- a/media/gpu/test/video_player/test_vda_video_decoder.cc
+++ b/media/gpu/test/video_player/test_vda_video_decoder.cc
@@ -110,7 +110,7 @@
 
   if (!decoder_factory) {
     ASSERT_TRUE(decoder_) << "Failed to create VideoDecodeAccelerator factory";
-    std::move(init_cb).Run(StatusCode::kCodeOnlyForTesting);
+    std::move(init_cb).Run(DecoderStatus::Codes::kFailed);
     return;
   }
 
@@ -142,17 +142,17 @@
 
   if (!decoder_) {
     ASSERT_TRUE(decoder_) << "Failed to create VideoDecodeAccelerator factory";
-    std::move(init_cb).Run(StatusCode::kCodeOnlyForTesting);
+    std::move(init_cb).Run(DecoderStatus::Codes::kFailed);
     return;
   }
 
   if (!vda_config.is_deferred_initialization_allowed)
-    std::move(init_cb).Run(OkStatus());
+    std::move(init_cb).Run(DecoderStatus::Codes::kOk);
   else
     init_cb_ = std::move(init_cb);
 }
 
-void TestVDAVideoDecoder::NotifyInitializationComplete(Status status) {
+void TestVDAVideoDecoder::NotifyInitializationComplete(DecoderStatus status) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(vda_wrapper_sequence_checker_);
   DCHECK(init_cb_);
 
@@ -373,7 +373,7 @@
       << "Couldn't find decode callback for picture buffer with id "
       << bitstream_buffer_id;
 
-  std::move(it->second).Run(DecodeStatus::OK);
+  std::move(it->second).Run(DecoderStatus::Codes::kOk);
   decode_cbs_.erase(it);
 }
 
@@ -381,7 +381,7 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(vda_wrapper_sequence_checker_);
   DCHECK(flush_cb_);
 
-  std::move(flush_cb_).Run(DecodeStatus::OK);
+  std::move(flush_cb_).Run(DecoderStatus::Codes::kOk);
 }
 
 void TestVDAVideoDecoder::NotifyResetDone() {
diff --git a/media/gpu/test/video_player/test_vda_video_decoder.h b/media/gpu/test/video_player/test_vda_video_decoder.h
index 362089dd..791b037a 100644
--- a/media/gpu/test/video_player/test_vda_video_decoder.h
+++ b/media/gpu/test/video_player/test_vda_video_decoder.h
@@ -62,7 +62,7 @@
 
  private:
   // media::VideoDecodeAccelerator::Client implementation
-  void NotifyInitializationComplete(Status status) override;
+  void NotifyInitializationComplete(DecoderStatus status) override;
   void ProvidePictureBuffers(uint32_t requested_num_of_buffers,
                              VideoPixelFormat format,
                              uint32_t textures_per_buffer,
diff --git a/media/gpu/test/video_player/video_decoder_client.cc b/media/gpu/test/video_player/video_decoder_client.cc
index b2ea827..faa0abba 100644
--- a/media/gpu/test/video_player/video_decoder_client.cc
+++ b/media/gpu/test/video_player/video_decoder_client.cc
@@ -234,7 +234,7 @@
 
   VideoDecoder::InitCB init_cb = base::BindOnce(
       CallbackThunk<decltype(&VideoDecoderClient::DecoderInitializedTask),
-                    Status>,
+                    DecoderStatus>,
       weak_this_, decoder_client_thread_.task_runner(),
       &VideoDecoderClient::DecoderInitializedTask);
   VideoDecoder::OutputCB output_cb = base::BindRepeating(
@@ -320,7 +320,7 @@
 
   VideoDecoder::DecodeCB decode_cb = base::BindOnce(
       CallbackThunk<decltype(&VideoDecoderClient::DecodeDoneTask),
-                    media::Status>,
+                    DecoderStatus>,
       weak_this_, decoder_client_thread_.task_runner(),
       &VideoDecoderClient::DecodeDoneTask);
   decoder_->Decode(std::move(bitstream_buffer), std::move(decode_cb));
@@ -341,7 +341,7 @@
 
   VideoDecoder::DecodeCB flush_done_cb =
       base::BindOnce(CallbackThunk<decltype(&VideoDecoderClient::FlushDoneTask),
-                                   media::Status>,
+                                   DecoderStatus>,
                      weak_this_, decoder_client_thread_.task_runner(),
                      &VideoDecoderClient::FlushDoneTask);
   decoder_->Decode(DecoderBuffer::CreateEOSBuffer(), std::move(flush_done_cb));
@@ -365,7 +365,7 @@
   FireEvent(VideoPlayerEvent::kResetting);
 }
 
-void VideoDecoderClient::DecoderInitializedTask(Status status) {
+void VideoDecoderClient::DecoderInitializedTask(DecoderStatus status) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_client_sequence_checker_);
   DCHECK(decoder_client_state_ == VideoDecoderClientState::kUninitialized ||
          decoder_client_state_ == VideoDecoderClientState::kIdle);
@@ -375,10 +375,10 @@
   FireEvent(VideoPlayerEvent::kInitialized);
 }
 
-void VideoDecoderClient::DecodeDoneTask(media::Status status) {
+void VideoDecoderClient::DecodeDoneTask(DecoderStatus status) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_client_sequence_checker_);
   DCHECK_NE(VideoDecoderClientState::kIdle, decoder_client_state_);
-  ASSERT_TRUE(status.code() != media::StatusCode::kAborted ||
+  ASSERT_TRUE(status != DecoderStatus::Codes::kAborted ||
               decoder_client_state_ == VideoDecoderClientState::kResetting);
   DVLOGF(4);
 
@@ -407,7 +407,7 @@
   current_frame_index_++;
 }
 
-void VideoDecoderClient::FlushDoneTask(media::Status status) {
+void VideoDecoderClient::FlushDoneTask(DecoderStatus status) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_client_sequence_checker_);
   DCHECK_EQ(0u, num_outstanding_decode_requests_);
 
diff --git a/media/gpu/test/video_player/video_decoder_client.h b/media/gpu/test/video_player/video_decoder_client.h
index bc04c114..c0ee013 100644
--- a/media/gpu/test/video_player/video_decoder_client.h
+++ b/media/gpu/test/video_player/video_decoder_client.h
@@ -14,7 +14,7 @@
 #include "base/sequence_checker.h"
 #include "base/threading/thread.h"
 #include "gpu/ipc/service/gpu_memory_buffer_factory.h"
-#include "media/base/decode_status.h"
+#include "media/base/decoder_status.h"
 #include "media/base/video_decoder.h"
 #include "media/base/video_decoder_config.h"
 #include "media/gpu/test/video_player/video_player.h"
@@ -142,13 +142,13 @@
   // The below functions are callbacks provided to the video decoder. They are
   // all executed on the |decoder_client_thread_|.
   // Called by the decoder when initialization has completed.
-  void DecoderInitializedTask(Status status);
+  void DecoderInitializedTask(DecoderStatus status);
   // Called by the decoder when a fragment has been decoded.
-  void DecodeDoneTask(media::Status status);
+  void DecodeDoneTask(DecoderStatus status);
   // Called by the decoder when a video frame is ready.
   void FrameReadyTask(scoped_refptr<VideoFrame> video_frame);
   // Called by the decoder when flushing has completed.
-  void FlushDoneTask(media::Status status);
+  void FlushDoneTask(DecoderStatus status);
   // Called by the decoder when resetting has completed.
   void ResetDoneTask();
   // Called by the decoder when a resolution change was requested, returns
diff --git a/media/gpu/v4l2/v4l2_video_decoder.cc b/media/gpu/v4l2/v4l2_video_decoder.cc
index c75a914..fc5e58c9 100644
--- a/media/gpu/v4l2/v4l2_video_decoder.cc
+++ b/media/gpu/v4l2/v4l2_video_decoder.cc
@@ -115,7 +115,7 @@
 
   // Call all pending decode callback.
   if (backend_) {
-    backend_->ClearPendingRequests(DecodeStatus::ABORTED);
+    backend_->ClearPendingRequests(DecoderStatus::Codes::kAborted);
     backend_ = nullptr;
   }
 
@@ -156,14 +156,13 @@
     case State::kError:
       VLOGF(1) << "V4L2 decoder should not be initialized at state: "
                << static_cast<int>(state_);
-      std::move(init_cb).Run(
-          Status(Status::Codes::kDecoderInitializationFailed));
+      std::move(init_cb).Run(DecoderStatus::Codes::kFailed);
       return;
   }
 
   if (cdm_context || config.is_encrypted()) {
     VLOGF(1) << "V4L2 decoder does not support encrypted stream";
-    std::move(init_cb).Run(StatusCode::kEncryptedContentUnsupported);
+    std::move(init_cb).Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
     return;
   }
 
@@ -176,7 +175,7 @@
       // TODO(crbug/1103510): Make StopStreamV4L2Queue return a StatusOr, and
       // pipe that back instead.
       std::move(init_cb).Run(
-          Status(Status::Codes::kDecoderInitializeNeverCompleted)
+          DecoderStatus(DecoderStatus::Codes::kNotInitialized)
               .AddCause(
                   V4L2Status(V4L2Status::Codes::kFailedToStopStreamQueue)));
       return;
@@ -199,7 +198,7 @@
       // TODO(crbug/1103510): Make V4L2Device::Create return a StatusOr, and
       // pipe that back instead.
       std::move(init_cb).Run(
-          Status(Status::Codes::kDecoderInitializeNeverCompleted)
+          DecoderStatus(DecoderStatus::Codes::kNotInitialized)
               .AddCause(V4L2Status(V4L2Status::Codes::kNoDevice)));
       return;
     }
@@ -219,7 +218,7 @@
     VLOGF(1) << "Unknown profile.";
     SetState(State::kError);
     std::move(init_cb).Run(
-        Status(Status::Codes::kDecoderInitializeNeverCompleted)
+        DecoderStatus(DecoderStatus::Codes::kNotInitialized)
             .AddCause(V4L2Status(V4L2Status::Codes::kNoProfile)));
     return;
   }
@@ -227,7 +226,7 @@
   // Call init_cb
   output_cb_ = std::move(output_cb);
   SetState(State::kInitialized);
-  std::move(init_cb).Run(::media::OkStatus());
+  std::move(init_cb).Run(DecoderStatus::Codes::kOk);
 }
 
 bool V4L2VideoDecoder::NeedsBitstreamConversion() const {
@@ -524,7 +523,7 @@
   }
 
   // Call all pending decode callback.
-  backend_->ClearPendingRequests(DecodeStatus::ABORTED);
+  backend_->ClearPendingRequests(DecoderStatus::Codes::kAborted);
 
   // Streamoff V4L2 queues to drop input and output buffers.
   // If the queues are streaming before reset, then we need to start streaming
@@ -554,14 +553,14 @@
   // VideoDecoder interface: |decode_cb| can't be called from within Decode().
   auto trampoline_decode_cb = base::BindOnce(
       [](const scoped_refptr<base::SequencedTaskRunner>& this_sequence_runner,
-         DecodeCB decode_cb, Status status) {
+         DecodeCB decode_cb, DecoderStatus status) {
         this_sequence_runner->PostTask(
             FROM_HERE, base::BindOnce(std::move(decode_cb), status));
       },
       base::SequencedTaskRunnerHandle::Get(), std::move(decode_cb));
 
   if (state_ == State::kError) {
-    std::move(trampoline_decode_cb).Run(DecodeStatus::DECODE_ERROR);
+    std::move(trampoline_decode_cb).Run(DecoderStatus::Codes::kFailed);
     return;
   }
 
@@ -570,7 +569,7 @@
     if (status != V4L2Status::Codes::kOk) {
       SetState(State::kError);
       std::move(trampoline_decode_cb)
-          .Run(Status(Status::Codes::kDecoderFailedDecode)
+          .Run(DecoderStatus(DecoderStatus::Codes::kFailed)
                    .AddCause(std::move(status)));
       return;
     }
@@ -890,7 +889,7 @@
     VLOGF(1) << "Error occurred, stopping queues.";
     StopStreamV4L2Queue(true);
     if (backend_)
-      backend_->ClearPendingRequests(DecodeStatus::DECODE_ERROR);
+      backend_->ClearPendingRequests(DecoderStatus::Codes::kFailed);
     return;
   }
   state_ = new_state;
diff --git a/media/gpu/v4l2/v4l2_video_decoder_backend.h b/media/gpu/v4l2/v4l2_video_decoder_backend.h
index 4d8e1d8..d120ef1 100644
--- a/media/gpu/v4l2/v4l2_video_decoder_backend.h
+++ b/media/gpu/v4l2/v4l2_video_decoder_backend.h
@@ -6,7 +6,7 @@
 #define MEDIA_GPU_V4L2_V4L2_VIDEO_DECODER_BACKEND_H_
 
 #include "base/memory/scoped_refptr.h"
-#include "media/base/decode_status.h"
+#include "media/base/decoder_status.h"
 #include "media/base/video_decoder.h"
 #include "media/gpu/chromeos/chromeos_status.h"
 #include "media/gpu/chromeos/dmabuf_video_frame_pool.h"
@@ -94,7 +94,7 @@
   virtual void OnChangeResolutionDone(CroStatus status) = 0;
   // Clear all pending decoding tasks and call all pending decode callbacks
   // with |status| as argument.
-  virtual void ClearPendingRequests(DecodeStatus status) = 0;
+  virtual void ClearPendingRequests(DecoderStatus status) = 0;
 
   // Whether we should stop the input queue when changing resolution. Stateless
   // decoders require this, but stateful ones need the input queue to keep
diff --git a/media/gpu/v4l2/v4l2_video_decoder_backend_stateful.cc b/media/gpu/v4l2/v4l2_video_decoder_backend_stateful.cc
index 892f522c..b98f92f 100644
--- a/media/gpu/v4l2/v4l2_video_decoder_backend_stateful.cc
+++ b/media/gpu/v4l2/v4l2_video_decoder_backend_stateful.cc
@@ -199,7 +199,7 @@
   if (!frame_splitter_->AdvanceFrameFragment(data, data_size, &bytes_to_copy)) {
     VLOGF(1) << "Invalid H.264 stream detected.";
     std::move(current_decode_request_->decode_cb)
-        .Run(DecodeStatus::DECODE_ERROR);
+        .Run(DecoderStatus::Codes::kFailed);
     current_decode_request_.reset();
     current_input_buffer_.reset();
     client_->OnBackendError();
@@ -210,7 +210,7 @@
   if (bytes_used + bytes_to_copy > current_input_buffer_->GetPlaneSize(0)) {
     VLOGF(1) << "V4L2 buffer size is too small to contain a whole frame.";
     std::move(current_decode_request_->decode_cb)
-        .Run(DecodeStatus::DECODE_ERROR);
+        .Run(DecoderStatus::Codes::kFailed);
     current_decode_request_.reset();
     current_input_buffer_.reset();
     client_->OnBackendError();
@@ -226,7 +226,8 @@
 
   // Release current_input_request_ if we reached its end.
   if (current_decode_request_->IsCompleted()) {
-    std::move(current_decode_request_->decode_cb).Run(DecodeStatus::OK);
+    std::move(current_decode_request_->decode_cb)
+        .Run(DecoderStatus::Codes::kOk);
     current_decode_request_.reset();
   }
 
@@ -520,7 +521,7 @@
   DCHECK(flush_cb_);
 
   // Signal that flush has properly been completed.
-  std::move(flush_cb_).Run(DecodeStatus::OK);
+  std::move(flush_cb_).Run(DecoderStatus::Codes::kOk);
 
   // If CAPTURE queue is streaming, send the START command to the V4L2 device
   // to signal that we are resuming decoding with the same state.
@@ -530,7 +531,7 @@
     cmd.cmd = V4L2_DEC_CMD_START;
     if (device_->Ioctl(VIDIOC_DECODER_CMD, &cmd) != 0) {
       LOG(ERROR) << "Failed to issue START command";
-      std::move(flush_cb_).Run(DecodeStatus::DECODE_ERROR);
+      std::move(flush_cb_).Run(DecoderStatus::Codes::kFailed);
       client_->OnBackendError();
       return false;
     }
@@ -661,7 +662,7 @@
 }
 
 void V4L2StatefulVideoDecoderBackend::ClearPendingRequests(
-    DecodeStatus status) {
+    DecoderStatus status) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DVLOGF(3);
 
diff --git a/media/gpu/v4l2/v4l2_video_decoder_backend_stateful.h b/media/gpu/v4l2/v4l2_video_decoder_backend_stateful.h
index deaec95c..70205b6f 100644
--- a/media/gpu/v4l2/v4l2_video_decoder_backend_stateful.h
+++ b/media/gpu/v4l2/v4l2_video_decoder_backend_stateful.h
@@ -49,7 +49,7 @@
                        const gfx::Rect& visible_rect,
                        const size_t num_output_frames) override;
   void OnChangeResolutionDone(CroStatus status) override;
-  void ClearPendingRequests(DecodeStatus status) override;
+  void ClearPendingRequests(DecoderStatus status) override;
   bool StopInputQueueOnResChange() const override;
 
  private:
diff --git a/media/gpu/v4l2/v4l2_video_decoder_backend_stateless.cc b/media/gpu/v4l2/v4l2_video_decoder_backend_stateless.cc
index 99905806..fc6ad774 100644
--- a/media/gpu/v4l2/v4l2_video_decoder_backend_stateless.cc
+++ b/media/gpu/v4l2/v4l2_video_decoder_backend_stateless.cc
@@ -18,7 +18,7 @@
 #include "base/posix/eintr_wrapper.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/trace_event/trace_event.h"
-#include "media/base/decode_status.h"
+#include "media/base/decoder_status.h"
 #include "media/base/video_codecs.h"
 #include "media/base/video_frame.h"
 #include "media/gpu/accelerated_video_decoder.h"
@@ -422,7 +422,8 @@
           enqueuing_timestamps_[current_decode_request_->buffer->timestamp()
                                     .InMilliseconds()] = base::TimeTicks::Now();
 
-          std::move(current_decode_request_->decode_cb).Run(DecodeStatus::OK);
+          std::move(current_decode_request_->decode_cb)
+              .Run(DecoderStatus::Codes::kOk);
           current_decode_request_ = absl::nullopt;
         }
 
@@ -498,7 +499,7 @@
       case OutputRequest::kFlushFence:
         DCHECK(output_request_queue_.empty());
         DVLOGF(2) << "Flush finished.";
-        std::move(flush_cb_).Run(DecodeStatus::OK);
+        std::move(flush_cb_).Run(DecoderStatus::Codes::kOk);
         resume_decode = true;
         client_->CompleteFlush();
         break;
@@ -617,7 +618,7 @@
 }
 
 void V4L2StatelessVideoDecoderBackend::ClearPendingRequests(
-    DecodeStatus status) {
+    DecoderStatus status) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DVLOGF(3);
 
diff --git a/media/gpu/v4l2/v4l2_video_decoder_backend_stateless.h b/media/gpu/v4l2/v4l2_video_decoder_backend_stateless.h
index 58c6478..1f77bbb6 100644
--- a/media/gpu/v4l2/v4l2_video_decoder_backend_stateless.h
+++ b/media/gpu/v4l2/v4l2_video_decoder_backend_stateless.h
@@ -11,7 +11,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
 #include "base/task/sequenced_task_runner.h"
-#include "media/base/decode_status.h"
+#include "media/base/decoder_status.h"
 #include "media/base/video_decoder.h"
 #include "media/gpu/chromeos/dmabuf_video_frame_pool.h"
 #include "media/gpu/v4l2/v4l2_decode_surface_handler.h"
@@ -54,7 +54,7 @@
                        const gfx::Rect& visible_rect,
                        const size_t num_output_frames) override;
   void OnChangeResolutionDone(CroStatus status) override;
-  void ClearPendingRequests(DecodeStatus status) override;
+  void ClearPendingRequests(DecoderStatus status) override;
   bool StopInputQueueOnResChange() const override;
 
   // V4L2DecodeSurfaceHandler implementation.
diff --git a/media/gpu/vaapi/vaapi_video_decode_accelerator_unittest.cc b/media/gpu/vaapi/vaapi_video_decode_accelerator_unittest.cc
index cc8a347c0..b6ea013 100644
--- a/media/gpu/vaapi/vaapi_video_decode_accelerator_unittest.cc
+++ b/media/gpu/vaapi/vaapi_video_decode_accelerator_unittest.cc
@@ -380,7 +380,7 @@
   }
 
   // VideoDecodeAccelerator::Client methods.
-  MOCK_METHOD1(NotifyInitializationComplete, void(Status));
+  MOCK_METHOD1(NotifyInitializationComplete, void(DecoderStatus));
   MOCK_METHOD5(
       ProvidePictureBuffers,
       void(uint32_t, VideoPixelFormat, uint32_t, const gfx::Size&, uint32_t));
diff --git a/media/gpu/vaapi/vaapi_video_decoder.cc b/media/gpu/vaapi/vaapi_video_decoder.cc
index 548fcdb..4a94baa4 100644
--- a/media/gpu/vaapi/vaapi_video_decoder.cc
+++ b/media/gpu/vaapi/vaapi_video_decoder.cc
@@ -152,7 +152,7 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   // Abort all currently scheduled decode tasks.
-  ClearDecodeTaskQueue(DecodeStatus::ABORTED);
+  ClearDecodeTaskQueue(DecoderStatus::Codes::kAborted);
 
   weak_this_factory_.InvalidateWeakPtrs();
 
@@ -192,7 +192,7 @@
       state_ == State::kExpectingReset) {
     LOG(ERROR)
         << "Don't call Initialize() while there are pending decode tasks";
-    std::move(init_cb).Run(StatusCode::kDecoderInitializationFailed);
+    std::move(init_cb).Run(DecoderStatus::Codes::kFailed);
     return;
   }
 
@@ -239,12 +239,12 @@
   if (config.is_encrypted()) {
 #if !BUILDFLAG(IS_CHROMEOS_ASH)
     SetErrorState("encrypted content is not supported");
-    std::move(init_cb).Run(StatusCode::kEncryptedContentUnsupported);
+    std::move(init_cb).Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
     return;
 #else
     if (!cdm_context || !cdm_context->GetChromeOsCdmContext()) {
       SetErrorState("cannot support encrypted stream w/out ChromeOsCdmContext");
-      std::move(init_cb).Run(StatusCode::kDecoderMissingCdmForEncryptedContent);
+      std::move(init_cb).Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
       return;
     }
     bool encrypted_av1_support = false;
@@ -261,7 +261,7 @@
       SetErrorState(
           base::StringPrintf("%s is not supported for encrypted content",
                              GetCodecName(config.codec()).c_str()));
-      std::move(init_cb).Run(StatusCode::kEncryptedContentUnsupported);
+      std::move(init_cb).Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
       return;
     }
     cdm_event_cb_registration_ = cdm_context->RegisterEventCB(
@@ -278,7 +278,7 @@
              !base::CommandLine::ForCurrentProcess()->HasSwitch(
                  switches::kEnableClearHevcForTesting)) {
     SetErrorState("clear HEVC content is not supported");
-    std::move(init_cb).Run(StatusCode::kClearContentUnsupported);
+    std::move(init_cb).Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
     return;
 #endif
   }
@@ -303,7 +303,7 @@
     SetErrorState(
         base::StringPrintf("failed initializing VaapiWrapper for profile %s, ",
                            GetProfileName(profile).c_str()));
-    std::move(init_cb).Run(StatusCode::kDecoderUnsupportedProfile);
+    std::move(init_cb).Run(DecoderStatus::Codes::kUnsupportedProfile);
     return;
   }
 
@@ -315,7 +315,7 @@
   auto accel_status = CreateAcceleratedVideoDecoder();
   if (!accel_status.is_ok()) {
     SetErrorState("failed to create decoder delegate");
-    std::move(init_cb).Run(Status(Status::Codes::kDecoderInitializationFailed)
+    std::move(init_cb).Run(DecoderStatus(DecoderStatus::Codes::kFailed)
                                .AddCause(std::move(accel_status)));
     return;
   }
@@ -327,7 +327,7 @@
   SetState(State::kWaitingForInput);
 
   // Notify client initialization was successful.
-  std::move(init_cb).Run(OkStatus());
+  std::move(init_cb).Run(DecoderStatus::Codes::kOk);
 }
 
 void VaapiVideoDecoder::OnCdmContextEvent(CdmContext::Event event) {
@@ -352,7 +352,7 @@
     // VideoDecoder interface: |decode_cb| can't be called from within Decode().
     base::SequencedTaskRunnerHandle::Get()->PostTask(
         FROM_HERE,
-        base::BindOnce(std::move(decode_cb), DecodeStatus::DECODE_ERROR));
+        base::BindOnce(std::move(decode_cb), DecoderStatus::Codes::kFailed));
     return;
   }
 
@@ -417,7 +417,8 @@
     case AcceleratedVideoDecoder::kRanOutOfStreamData:
       // Decoding was successful, notify client and try to schedule the next
       // task. Switch to the idle state if we ran out of buffers to decode.
-      std::move(current_decode_task_->decode_done_cb_).Run(DecodeStatus::OK);
+      std::move(current_decode_task_->decode_done_cb_)
+          .Run(DecoderStatus::Codes::kOk);
       current_decode_task_ = absl::nullopt;
       if (!decode_task_queue_.empty()) {
         ScheduleNextDecodeTask();
@@ -458,7 +459,7 @@
   }
 }
 
-void VaapiVideoDecoder::ClearDecodeTaskQueue(DecodeStatus status) {
+void VaapiVideoDecoder::ClearDecodeTaskQueue(DecoderStatus status) {
   DVLOGF(4);
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
@@ -1033,7 +1034,8 @@
   DCHECK(output_frames_.empty());
 
   // Notify the client flushing is done.
-  std::move(current_decode_task_->decode_done_cb_).Run(DecodeStatus::OK);
+  std::move(current_decode_task_->decode_done_cb_)
+      .Run(DecoderStatus::Codes::kOk);
   current_decode_task_ = absl::nullopt;
 
   // Wait for new decodes, no decode tasks should be queued while flushing.
@@ -1192,7 +1194,7 @@
              state_ == State::kWaitingForProtected ||
              state_ == State::kChangingResolution ||
              state_ == State::kExpectingReset);
-      ClearDecodeTaskQueue(DecodeStatus::ABORTED);
+      ClearDecodeTaskQueue(DecoderStatus::Codes::kAborted);
       break;
     case State::kChangingResolution:
       DCHECK_EQ(state_, State::kDecoding);
@@ -1201,7 +1203,7 @@
       DCHECK_EQ(state_, State::kChangingResolution);
       break;
     case State::kError:
-      ClearDecodeTaskQueue(DecodeStatus::DECODE_ERROR);
+      ClearDecodeTaskQueue(DecoderStatus::Codes::kFailed);
       break;
   }
 
diff --git a/media/gpu/vaapi/vaapi_video_decoder.h b/media/gpu/vaapi/vaapi_video_decoder.h
index d6bdb19..d58d2845 100644
--- a/media/gpu/vaapi/vaapi_video_decoder.h
+++ b/media/gpu/vaapi/vaapi_video_decoder.h
@@ -134,7 +134,7 @@
   void HandleDecodeTask();
   // Clear the decode task queue. This is done when resetting or destroying the
   // decoder, or encountering an error.
-  void ClearDecodeTaskQueue(DecodeStatus status);
+  void ClearDecodeTaskQueue(DecoderStatus status);
 
   // Releases the local reference to the VideoFrame associated with the
   // specified |surface_id| on the decoder thread. This is called when
diff --git a/media/gpu/video_decode_accelerator_tests.cc b/media/gpu/video_decode_accelerator_tests.cc
index 9ea3b77..bce49fb 100644
--- a/media/gpu/video_decode_accelerator_tests.cc
+++ b/media/gpu/video_decode_accelerator_tests.cc
@@ -214,7 +214,7 @@
 
     bool init_success = false;
     VideoDecoder::InitCB init_cb = base::BindOnce(
-        [](bool* init_success, media::Status result) {
+        [](bool* init_success, DecoderStatus result) {
           *init_success = result.is_ok();
         },
         &init_success);
@@ -231,7 +231,7 @@
     while (!encoded_data_helper->ReachEndOfStream()) {
       bool decode_success = false;
       media::VideoDecoder::DecodeCB decode_cb = base::BindOnce(
-          [](bool* decode_success, media::Status status) {
+          [](bool* decode_success, DecoderStatus status) {
             *decode_success = status.is_ok();
           },
           &decode_success);
@@ -247,7 +247,7 @@
     }
     bool flush_success = false;
     media::VideoDecoder::DecodeCB flush_cb = base::BindOnce(
-        [](bool* flush_success, media::Status status) {
+        [](bool* flush_success, DecoderStatus status) {
           *flush_success = status.is_ok();
         },
         &flush_success);
diff --git a/media/gpu/windows/d3d11_video_decoder.cc b/media/gpu/windows/d3d11_video_decoder.cc
index 34a8c9d..2c624f3e 100644
--- a/media/gpu/windows/d3d11_video_decoder.cc
+++ b/media/gpu/windows/d3d11_video_decoder.cc
@@ -497,7 +497,7 @@
   release_mailbox_cb_ = std::move(release_mailbox_cb);
 
   state_ = State::kRunning;
-  std::move(init_cb_).Run(OkStatus());
+  std::move(init_cb_).Run(DecoderStatus::Codes::kOk);
 }
 
 void D3D11VideoDecoder::Decode(scoped_refptr<DecoderBuffer> buffer,
@@ -512,7 +512,7 @@
 
   if (state_ == State::kError) {
     // TODO(liberato): consider posting, though it likely doesn't matter.
-    std::move(decode_cb).Run(DecodeStatus::DECODE_ERROR);
+    std::move(decode_cb).Run(DecoderStatus::Codes::kInterrupted);
     return;
   }
 
@@ -558,7 +558,7 @@
       }
       // Pictures out output synchronously during Flush.  Signal the decode
       // cb now.
-      std::move(current_decode_cb_).Run(DecodeStatus::OK);
+      std::move(current_decode_cb_).Run(DecoderStatus::Codes::kOk);
       return;
     }
     // This must be after checking for EOS because there is no timestamp for an
@@ -601,7 +601,7 @@
     // TODO(liberato): switch + class enum.
     if (result == media::AcceleratedVideoDecoder::kRanOutOfStreamData) {
       current_buffer_ = nullptr;
-      std::move(current_decode_cb_).Run(DecodeStatus::OK);
+      std::move(current_decode_cb_).Run(DecoderStatus::Codes::kOk);
       break;
     } else if (result == media::AcceleratedVideoDecoder::kRanOutOfSurfaces) {
       // At this point, we know the picture size.
@@ -673,10 +673,10 @@
 
   current_buffer_ = nullptr;
   if (current_decode_cb_)
-    std::move(current_decode_cb_).Run(DecodeStatus::ABORTED);
+    std::move(current_decode_cb_).Run(DecoderStatus::Codes::kAborted);
 
   for (auto& queue_pair : input_buffer_queue_)
-    std::move(queue_pair.second).Run(DecodeStatus::ABORTED);
+    std::move(queue_pair.second).Run(DecoderStatus::Codes::kAborted);
   input_buffer_queue_.clear();
 
   // TODO(liberato): how do we signal an error?
@@ -908,9 +908,8 @@
     // TODO(liberato): VideoDecoder::InitCB should have either its own status
     // codes, or should use a common one that has "succeeded / didn't succeed"
     // as its only options.
-    std::move(init_cb_).Run(
-        Status(Status::Codes::kDecoderInitializeNeverCompleted)
-            .AddCause(std::move(reason)));
+    std::move(init_cb_).Run(DecoderStatus(DecoderStatus::Codes::kFailed)
+                                .AddCause(std::move(reason)));
   } else {
     // TODO(tmathmeyer) - Remove this after plumbing Status through the
     // decode_cb and input_buffer_queue cb's.
@@ -922,10 +921,10 @@
 
   current_buffer_ = nullptr;
   if (current_decode_cb_)
-    std::move(current_decode_cb_).Run(DecodeStatus::DECODE_ERROR);
+    std::move(current_decode_cb_).Run(DecoderStatus::Codes::kFailed);
 
   for (auto& queue_pair : input_buffer_queue_)
-    std::move(queue_pair.second).Run(DecodeStatus::DECODE_ERROR);
+    std::move(queue_pair.second).Run(DecoderStatus::Codes::kFailed);
   input_buffer_queue_.clear();
 }
 
diff --git a/media/gpu/windows/d3d11_video_decoder_unittest.cc b/media/gpu/windows/d3d11_video_decoder_unittest.cc
index e540bc4..897ec02 100644
--- a/media/gpu/windows/d3d11_video_decoder_unittest.cc
+++ b/media/gpu/windows/d3d11_video_decoder_unittest.cc
@@ -224,12 +224,12 @@
     base::RunLoop().RunUntilIdle();
   }
 
-  void CheckStatus(bool expectSuccess, Status actual) {
+  void CheckStatus(bool expectSuccess, DecoderStatus actual) {
     ASSERT_EQ(expectSuccess, actual.is_ok());
     MockInitCB(actual);
   }
 
-  MOCK_METHOD1(MockInitCB, void(Status));
+  MOCK_METHOD1(MockInitCB, void(DecoderStatus));
 
   base::test::TaskEnvironment task_environment_;
 
diff --git a/media/mojo/clients/mojo_audio_decoder.cc b/media/mojo/clients/mojo_audio_decoder.cc
index 4e50d022..22bdd2fc 100644
--- a/media/mojo/clients/mojo_audio_decoder.cc
+++ b/media/mojo/clients/mojo_audio_decoder.cc
@@ -51,7 +51,7 @@
   return decoder_type_;
 }
 
-void MojoAudioDecoder::FailInit(InitCB init_cb, Status err) {
+void MojoAudioDecoder::FailInit(InitCB init_cb, DecoderStatus err) {
   task_runner_->PostTask(FROM_HERE,
                          base::BindOnce(std::move(init_cb), std::move(err)));
 }
@@ -70,7 +70,7 @@
   // This could happen during reinitialization.
   if (!remote_decoder_.is_connected()) {
     DVLOG(1) << __func__ << ": Connection error happened.";
-    FailInit(std::move(init_cb), StatusCode::kMojoDecoderNoConnection);
+    FailInit(std::move(init_cb), DecoderStatus::Codes::kFailedToCreateDecoder);
     return;
   }
 
@@ -82,7 +82,7 @@
   if (config.is_encrypted() && !cdm_id) {
     DVLOG(1) << __func__ << ": Invalid CdmContext.";
     FailInit(std::move(init_cb),
-             StatusCode::kDecoderMissingCdmForEncryptedContent);
+             DecoderStatus::Codes::kUnsupportedEncryptionMode);
     return;
   }
 
@@ -105,7 +105,7 @@
   if (!remote_decoder_.is_connected()) {
     task_runner_->PostTask(
         FROM_HERE,
-        base::BindOnce(std::move(decode_cb), DecodeStatus::DECODE_ERROR));
+        base::BindOnce(std::move(decode_cb), DecoderStatus::Codes::kFailed));
     return;
   }
 
@@ -114,7 +114,7 @@
   if (!buffer) {
     task_runner_->PostTask(
         FROM_HERE,
-        base::BindOnce(std::move(decode_cb), DecodeStatus::DECODE_ERROR));
+        base::BindOnce(std::move(decode_cb), DecoderStatus::Codes::kFailed));
     return;
   }
 
@@ -134,7 +134,7 @@
     if (decode_cb_) {
       task_runner_->PostTask(
           FROM_HERE,
-          base::BindOnce(std::move(decode_cb_), DecodeStatus::DECODE_ERROR));
+          base::BindOnce(std::move(decode_cb_), DecoderStatus::Codes::kFailed));
     }
 
     task_runner_->PostTask(FROM_HERE, std::move(closure));
@@ -188,17 +188,17 @@
   DCHECK(!remote_decoder_.is_connected());
 
   if (init_cb_) {
-    FailInit(std::move(init_cb_), StatusCode::kMojoDecoderNoConnection);
+    FailInit(std::move(init_cb_), DecoderStatus::Codes::kFailedToCreateDecoder);
     return;
   }
 
   if (decode_cb_)
-    std::move(decode_cb_).Run(DecodeStatus::DECODE_ERROR);
+    std::move(decode_cb_).Run(DecoderStatus::Codes::kFailed);
   if (reset_cb_)
     std::move(reset_cb_).Run();
 }
 
-void MojoAudioDecoder::OnInitialized(const Status& status,
+void MojoAudioDecoder::OnInitialized(const DecoderStatus& status,
                                      bool needs_bitstream_conversion,
                                      AudioDecoderType decoder_type) {
   DVLOG(1) << __func__ << ": success:" << status.is_ok();
@@ -219,8 +219,8 @@
   std::move(init_cb_).Run(std::move(status));
 }
 
-void MojoAudioDecoder::OnDecodeStatus(const Status& status) {
-  DVLOG(1) << __func__ << ": status:" << status.code();
+void MojoAudioDecoder::OnDecodeStatus(const DecoderStatus& status) {
+  DVLOG(1) << __func__ << ": status:" << static_cast<int>(status.code());
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   DCHECK(decode_cb_);
diff --git a/media/mojo/clients/mojo_audio_decoder.h b/media/mojo/clients/mojo_audio_decoder.h
index 7d18d6e1..3097a9e1 100644
--- a/media/mojo/clients/mojo_audio_decoder.h
+++ b/media/mojo/clients/mojo_audio_decoder.h
@@ -66,15 +66,15 @@
   void OnConnectionError();
 
   // Fail an initialization with a Status.
-  void FailInit(InitCB init_cb, Status err);
+  void FailInit(InitCB init_cb, DecoderStatus err);
 
   // Called when |remote_decoder_| finished initialization.
-  void OnInitialized(const Status& status,
+  void OnInitialized(const DecoderStatus& status,
                      bool needs_bitstream_conversion,
                      AudioDecoderType decoder_type);
 
   // Called when |remote_decoder_| accepted or rejected DecoderBuffer.
-  void OnDecodeStatus(const Status& decode_status);
+  void OnDecodeStatus(const DecoderStatus& decode_status);
 
   // called when |remote_decoder_| finished Reset() sequence.
   void OnResetDone();
diff --git a/media/mojo/clients/mojo_audio_decoder_unittest.cc b/media/mojo/clients/mojo_audio_decoder_unittest.cc
index ed0266a..710f556 100644
--- a/media/mojo/clients/mojo_audio_decoder_unittest.cc
+++ b/media/mojo/clients/mojo_audio_decoder_unittest.cc
@@ -83,10 +83,10 @@
   }
 
   // Completion callbacks.
-  MOCK_METHOD1(OnInitialized, void(Status));
+  MOCK_METHOD1(OnInitialized, void(DecoderStatus));
   MOCK_METHOD1(OnOutput, void(scoped_refptr<AudioBuffer>));
   MOCK_METHOD1(OnWaiting, void(WaitingReason));
-  MOCK_METHOD1(OnDecoded, void(Status));
+  MOCK_METHOD1(OnDecoded, void(DecoderStatus));
   MOCK_METHOD0(OnReset, void());
 
   // Always create a new RunLoop (and destroy the old loop if it exists) before
@@ -118,12 +118,12 @@
 
     EXPECT_CALL(*mock_audio_decoder_, Initialize_(_, _, _, _, _))
         .WillRepeatedly(DoAll(SaveArg<3>(&output_cb_), SaveArg<4>(&waiting_cb_),
-                              RunOnceCallback<2>(OkStatus())));
+                              RunOnceCallback<2>(DecoderStatus::Codes::kOk)));
     EXPECT_CALL(*mock_audio_decoder_, Decode(_, _))
         .WillRepeatedly([&](scoped_refptr<DecoderBuffer> buffer,
                             AudioDecoder::DecodeCB decode_cb) {
           ReturnOutput();
-          std::move(decode_cb).Run(DecodeStatus::OK);
+          std::move(decode_cb).Run(DecoderStatus::Codes::kOk);
         });
     EXPECT_CALL(*mock_audio_decoder_, Reset_(_))
         .WillRepeatedly(RunOnceCallback<0>());
@@ -138,8 +138,8 @@
     mojo_audio_decoder_->set_writer_capacity_for_testing(capacity);
   }
 
-  void InitializeAndExpect(Status status) {
-    DVLOG(1) << __func__ << ": success=" << status.code();
+  void InitializeAndExpect(DecoderStatus status) {
+    DVLOG(1) << __func__ << ": success=" << static_cast<int>(status.code());
     EXPECT_CALL(*this, OnInitialized(SameStatusCode(status)))
         .WillOnce(InvokeWithoutArgs(this, &MojoAudioDecoderTest::QuitLoop));
 
@@ -159,7 +159,7 @@
     RunLoop();
   }
 
-  void Initialize() { InitializeAndExpect(OkStatus()); }
+  void Initialize() { InitializeAndExpect(DecoderStatus::Codes::kOk); }
 
   void Decode() {
     scoped_refptr<DecoderBuffer> buffer(new DecoderBuffer(100));
@@ -294,7 +294,7 @@
       .WillOnce([&](scoped_refptr<DecoderBuffer> buffer,
                     AudioDecoder::DecodeCB decode_cb) {
         WaitForKey();
-        std::move(decode_cb).Run(DecodeStatus::OK);
+        std::move(decode_cb).Run(DecoderStatus::Codes::kOk);
       });
   EXPECT_CALL(*this, OnWaiting(WaitingReason::kNoDecryptionKey)).Times(1);
   EXPECT_CALL(*this, OnDecoded(IsOkStatus()))
diff --git a/media/mojo/clients/mojo_video_decoder.cc b/media/mojo/clients/mojo_video_decoder.cc
index 97add281..dddfd99 100644
--- a/media/mojo/clients/mojo_video_decoder.cc
+++ b/media/mojo/clients/mojo_video_decoder.cc
@@ -154,7 +154,7 @@
   return decoder_type_;
 }
 
-void MojoVideoDecoder::FailInit(InitCB init_cb, Status err) {
+void MojoVideoDecoder::FailInit(InitCB init_cb, DecoderStatus err) {
   task_runner_->PostTask(FROM_HERE,
                          base::BindOnce(std::move(init_cb), std::move(err)));
 }
@@ -174,7 +174,7 @@
   // Fail immediately if we know that the remote side cannot support |config|.
   if (gpu_factories_ && gpu_factories_->IsDecoderConfigSupported(config) ==
                             GpuVideoAcceleratorFactories::Supported::kFalse) {
-    FailInit(std::move(init_cb), StatusCode::kDecoderUnsupportedConfig);
+    FailInit(std::move(init_cb), DecoderStatus::Codes::kUnsupportedConfig);
     return;
   }
 
@@ -189,7 +189,7 @@
   if (config.is_encrypted() && !cdm_id) {
     DVLOG(1) << __func__ << ": Invalid CdmContext.";
     FailInit(std::move(init_cb),
-             StatusCode::kDecoderMissingCdmForEncryptedContent);
+             DecoderStatus::Codes::kUnsupportedEncryptionMode);
     return;
   }
 
@@ -214,7 +214,7 @@
     absl::optional<base::UnguessableToken> cdm_id) {
   if (has_connection_error_) {
     DCHECK(init_cb_);
-    FailInit(std::move(init_cb_), StatusCode::kMojoDecoderNoConnection);
+    FailInit(std::move(init_cb_), DecoderStatus::Codes::kFailedToCreateDecoder);
     return;
   }
 
@@ -224,11 +224,12 @@
                      base::Unretained(this)));
 }
 
-void MojoVideoDecoder::OnInitializeDone(const Status& status,
+void MojoVideoDecoder::OnInitializeDone(const DecoderStatus& status,
                                         bool needs_bitstream_conversion,
                                         int32_t max_decode_requests,
                                         VideoDecoderType decoder_type) {
-  DVLOG(1) << __func__ << ": status = " << std::hex << status.code();
+  DVLOG(1) << __func__ << ": status = " << status.group() << ":"
+           << static_cast<int>(status.code());
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   initialized_ = status.is_ok();
   needs_bitstream_conversion_ = needs_bitstream_conversion;
@@ -244,8 +245,8 @@
 
   if (has_connection_error_) {
     task_runner_->PostTask(
-        FROM_HERE,
-        base::BindOnce(std::move(decode_cb), DecodeStatus::DECODE_ERROR));
+        FROM_HERE, base::BindOnce(std::move(decode_cb),
+                                  DecoderStatus::Codes::kNotInitialized));
     return;
   }
 
@@ -260,7 +261,8 @@
     ReportInitialPlaybackErrorUMA();
     task_runner_->PostTask(
         FROM_HERE,
-        base::BindOnce(std::move(decode_cb), DecodeStatus::DECODE_ERROR));
+        base::BindOnce(std::move(decode_cb),
+                       DecoderStatus::Codes::kFailedToGetDecoderBuffer));
     return;
   }
 
@@ -317,7 +319,8 @@
   }
 }
 
-void MojoVideoDecoder::OnDecodeDone(uint64_t decode_id, const Status& status) {
+void MojoVideoDecoder::OnDecodeDone(uint64_t decode_id,
+                                    const DecoderStatus& status) {
   DVLOG(3) << __func__;
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
@@ -328,7 +331,7 @@
     return;
   }
 
-  if (!status.is_ok() && status.code() != StatusCode::kAborted)
+  if (!status.is_ok() && status.code() != DecoderStatus::Codes::kAborted)
     ReportInitialPlaybackErrorUMA();
 
   DecodeCB decode_cb = std::move(it->second);
@@ -492,13 +495,14 @@
   base::WeakPtr<MojoVideoDecoder> weak_this = weak_this_;
 
   if (init_cb_)
-    std::move(init_cb_).Run(StatusCode::kMojoDecoderStoppedBeforeInitDone);
+    std::move(init_cb_).Run(DecoderStatus::Codes::kFailedToCreateDecoder);
 
   if (!weak_this)
     return;
 
   for (auto& pending_decode : pending_decodes_) {
-    std::move(pending_decode.second).Run(DecodeStatus::DECODE_ERROR);
+    // It would be ideal if we could get a reason for the interruption.
+    std::move(pending_decode.second).Run(DecoderStatus::Codes::kInterrupted);
     if (!weak_this)
       return;
   }
diff --git a/media/mojo/clients/mojo_video_decoder.h b/media/mojo/clients/mojo_video_decoder.h
index a5be6e7f..6f1df825 100644
--- a/media/mojo/clients/mojo_video_decoder.h
+++ b/media/mojo/clients/mojo_video_decoder.h
@@ -91,12 +91,12 @@
   }
 
  private:
-  void FailInit(InitCB init_cb, Status err);
-  void OnInitializeDone(const Status& status,
+  void FailInit(InitCB init_cb, DecoderStatus err);
+  void OnInitializeDone(const DecoderStatus& status,
                         bool needs_bitstream_conversion,
                         int32_t max_decode_requests,
                         VideoDecoderType decoder_type);
-  void OnDecodeDone(uint64_t decode_id, const Status& status);
+  void OnDecodeDone(uint64_t decode_id, const DecoderStatus& status);
   void OnResetDone();
 
   void InitAndBindRemoteDecoder(base::OnceClosure complete_cb);
diff --git a/media/mojo/mojom/BUILD.gn b/media/mojo/mojom/BUILD.gn
index 16e65798..b0cd457e 100644
--- a/media/mojo/mojom/BUILD.gn
+++ b/media/mojo/mojom/BUILD.gn
@@ -368,6 +368,10 @@
           mojom = "media.mojom.EncoderStatus"
           cpp = "::media::EncoderStatus"
         },
+        {
+          mojom = "media.mojom.DecoderStatus"
+          cpp = "::media::DecoderStatus"
+        },
       ]
       traits_headers = [ "status_mojom_traits.h" ]
       traits_sources = [ "status_mojom_traits.cc" ]
diff --git a/media/mojo/mojom/audio_decoder.mojom b/media/mojo/mojom/audio_decoder.mojom
index 25029a8..7c9fb8a 100644
--- a/media/mojo/mojom/audio_decoder.mojom
+++ b/media/mojo/mojom/audio_decoder.mojom
@@ -20,7 +20,7 @@
   // bitstream conversion.
   Initialize(AudioDecoderConfig config,
              mojo_base.mojom.UnguessableToken? cdm_id)
-      => (Status success,
+      => (DecoderStatus success,
           bool needs_bitstream_conversion,
           AudioDecoderType decoder_type);
 
@@ -35,7 +35,7 @@
   // pending buffers should be processed, the corresponding decoded buffers
   // should be returned to the proxy, and only then the service should return
   // DecoderStatus.
-  Decode(DecoderBuffer buffer) => (Status status);
+  Decode(DecoderBuffer buffer) => (DecoderStatus status);
 
   // Resets decoder state. Should be called only if Initialize() succeeds.
   // All pending Decode() requests will be finished or aborted, then the method
diff --git a/media/mojo/mojom/media_types.mojom b/media/mojo/mojom/media_types.mojom
index 4020d63..2769288 100644
--- a/media/mojo/mojom/media_types.mojom
+++ b/media/mojo/mojom/media_types.mojom
@@ -30,10 +30,6 @@
 [Native]
 enum ChannelLayout;
 
-// See media/base/decode_status.h for descriptions.
-[Native]
-enum DecodeStatus;
-
 // See media/base/status_codes.h for descriptions.
 [Native]
 enum StatusCode;
@@ -506,6 +502,10 @@
   StatusData? internal;
 };
 
+struct DecoderStatus {
+  StatusData? internal;
+};
+
 // Types of media stream, categorised by the media stream's source.
 // The enum values are emitted to metrics. Do not reorder.
 enum MediaStreamType {
diff --git a/media/mojo/mojom/status_mojom_traits.h b/media/mojo/mojom/status_mojom_traits.h
index 116cf34..ed8bb28 100644
--- a/media/mojo/mojom/status_mojom_traits.h
+++ b/media/mojo/mojom/status_mojom_traits.h
@@ -7,6 +7,7 @@
 
 #include "base/containers/span.h"
 #include "base/values.h"
+#include "media/base/decoder_status.h"
 #include "media/base/encoder_status.h"
 #include "media/base/ipc/media_param_traits.h"
 #include "media/base/status.h"
diff --git a/media/mojo/mojom/video_decoder.mojom b/media/mojo/mojom/video_decoder.mojom
index 3cfe796..46867aa 100644
--- a/media/mojo/mojom/video_decoder.mojom
+++ b/media/mojo/mojom/video_decoder.mojom
@@ -119,7 +119,7 @@
   // used for unencrypted streams.
   Initialize(VideoDecoderConfig config, bool low_delay,
              mojo_base.mojom.UnguessableToken? cdm_id)
-      => (Status status,
+      => (DecoderStatus status,
           bool needs_bitstream_conversion,
           int32 max_decode_requests,
           VideoDecoderType decoder_type);
@@ -136,7 +136,7 @@
   // If |buffer| is an EOS buffer, implementations must execute all other
   // pending Decode() callbacks and output all pending frames before executing
   // the Decode(EOS) callback. (That is, they must flush.)
-  Decode(DecoderBuffer buffer) => (Status status);
+  Decode(DecoderBuffer buffer) => (DecoderStatus status);
 
   // Reset the decoder. All ongoing Decode() requests must be completed or
   // aborted before executing the callback. This must not be called while there
diff --git a/media/mojo/services/mojo_audio_decoder_service.cc b/media/mojo/services/mojo_audio_decoder_service.cc
index 9cf0a5e..654c3c3d0 100644
--- a/media/mojo/services/mojo_audio_decoder_service.cc
+++ b/media/mojo/services/mojo_audio_decoder_service.cc
@@ -54,7 +54,7 @@
       // TODO(xhwang): Replace with mojo::ReportBadMessage().
       NOTREACHED() << "The caller should not switch CDM";
       OnInitialized(std::move(callback),
-                    StatusCode::kDecoderMissingCdmForEncryptedContent);
+                    DecoderStatus::Codes::kUnsupportedEncryptionMode);
       return;
     }
   }
@@ -68,7 +68,7 @@
              << CdmContext::CdmIdToString(base::OptionalOrNullptr(cdm_id))
              << " not found for encrypted audio";
     OnInitialized(std::move(callback),
-                  StatusCode::kDecoderMissingCdmForEncryptedContent);
+                  DecoderStatus::Codes::kUnsupportedEncryptionMode);
     return;
   }
 
@@ -107,7 +107,7 @@
 }
 
 void MojoAudioDecoderService::OnInitialized(InitializeCallback callback,
-                                            Status status) {
+                                            DecoderStatus status) {
   DVLOG(1) << __func__ << " success:" << status.is_ok();
 
   if (!status.is_ok()) {
@@ -132,7 +132,7 @@
   DVLOG(3) << __func__ << " success:" << !!buffer;
 
   if (!buffer) {
-    std::move(callback).Run(DecodeStatus::DECODE_ERROR);
+    std::move(callback).Run(DecoderStatus::Codes::kFailedToGetDecoderBuffer);
     return;
   }
 
@@ -147,8 +147,9 @@
 }
 
 void MojoAudioDecoderService::OnDecodeStatus(DecodeCallback callback,
-                                             const Status status) {
-  DVLOG(3) << __func__ << " status:" << status.code();
+                                             const DecoderStatus status) {
+  DVLOG(3) << __func__ << " status=" << status.group() << ":"
+           << static_cast<int>(status.code());
   std::move(callback).Run(std::move(status));
 }
 
diff --git a/media/mojo/services/mojo_audio_decoder_service.h b/media/mojo/services/mojo_audio_decoder_service.h
index 4a2a3d2..ab0c9af4 100644
--- a/media/mojo/services/mojo_audio_decoder_service.h
+++ b/media/mojo/services/mojo_audio_decoder_service.h
@@ -51,7 +51,7 @@
 
  private:
   // Called by |decoder_| upon finishing initialization.
-  void OnInitialized(InitializeCallback callback, Status status);
+  void OnInitialized(InitializeCallback callback, DecoderStatus status);
 
   // Called by |mojo_decoder_buffer_reader_| when read is finished.
   void OnReadDone(DecodeCallback callback, scoped_refptr<DecoderBuffer> buffer);
@@ -60,7 +60,7 @@
   void OnReaderFlushDone(ResetCallback callback);
 
   // Called by |decoder_| when DecoderBuffer is accepted or rejected.
-  void OnDecodeStatus(DecodeCallback callback, media::Status status);
+  void OnDecodeStatus(DecodeCallback callback, DecoderStatus status);
 
   // Called by |decoder_| when reset sequence is finished.
   void OnResetDone(ResetCallback callback);
diff --git a/media/mojo/services/mojo_video_decoder_service.cc b/media/mojo/services/mojo_video_decoder_service.cc
index 8f9a1b6..4369bc4 100644
--- a/media/mojo/services/mojo_video_decoder_service.cc
+++ b/media/mojo/services/mojo_video_decoder_service.cc
@@ -112,9 +112,7 @@
   DVLOG(1) << __func__;
 
   if (init_cb_) {
-    OnDecoderInitialized(
-        Status(StatusCode::kMojoDecoderDeletedWithoutInitialization)
-            .WithData("decoder", "MojoVideoDecoder"));
+    OnDecoderInitialized(DecoderStatus::Codes::kInterrupted);
   }
 
   if (reset_cb_)
@@ -199,7 +197,7 @@
   init_cb_ = std::move(callback);
 
   if (!decoder_) {
-    OnDecoderInitialized(StatusCode::kMojoDecoderNoWrappedDecoder);
+    OnDecoderInitialized(DecoderStatus::Codes::kFailedToCreateDecoder);
     return;
   }
 
@@ -215,7 +213,7 @@
     } else if (cdm_id != cdm_id_) {
       // TODO(xhwang): Replace with mojo::ReportBadMessage().
       NOTREACHED() << "The caller should not switch CDM";
-      OnDecoderInitialized(StatusCode::kDecoderMissingCdmForEncryptedContent);
+      OnDecoderInitialized(DecoderStatus::Codes::kUnsupportedEncryptionMode);
       return;
     }
   }
@@ -228,7 +226,7 @@
     DVLOG(1) << "CdmContext for "
              << CdmContext::CdmIdToString(base::OptionalOrNullptr(cdm_id))
              << " not found for encrypted video";
-    OnDecoderInitialized(StatusCode::kDecoderMissingCdmForEncryptedContent);
+    OnDecoderInitialized(DecoderStatus::Codes::kUnsupportedEncryptionMode);
     return;
   }
 
@@ -267,7 +265,7 @@
 
   if (!decoder_) {
     OnDecoderDecoded(std::move(callback), std::move(trace_event),
-                     DecodeStatus::DECODE_ERROR);
+                     DecoderStatus::Codes::kNotInitialized);
     return;
   }
 
@@ -310,7 +308,7 @@
       base::BindOnce(&MojoVideoDecoderService::OnReaderFlushed, weak_this_));
 }
 
-void MojoVideoDecoderService::OnDecoderInitialized(Status status) {
+void MojoVideoDecoderService::OnDecoderInitialized(DecoderStatus status) {
   DVLOG(1) << __func__;
   DCHECK(!status.is_ok() || decoder_);
   DCHECK(init_cb_);
@@ -341,7 +339,7 @@
 
   if (!buffer) {
     OnDecoderDecoded(std::move(callback), std::move(trace_event),
-                     DecodeStatus::DECODE_ERROR);
+                     DecoderStatus::Codes::kFailedToGetDecoderBuffer);
     return;
   }
 
@@ -359,7 +357,7 @@
 void MojoVideoDecoderService::OnDecoderDecoded(
     DecodeCallback callback,
     std::unique_ptr<ScopedDecodeTrace> trace_event,
-    media::Status status) {
+    media::DecoderStatus status) {
   DVLOG(3) << __func__;
   if (trace_event) {
     TRACE_EVENT_ASYNC_STEP_PAST0("media", kDecodeTraceName, trace_event.get(),
diff --git a/media/mojo/services/mojo_video_decoder_service.h b/media/mojo/services/mojo_video_decoder_service.h
index ddadc6d..df1820bf4 100644
--- a/media/mojo/services/mojo_video_decoder_service.h
+++ b/media/mojo/services/mojo_video_decoder_service.h
@@ -13,7 +13,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/unguessable_token.h"
 #include "media/base/cdm_context.h"
-#include "media/base/decode_status.h"
+#include "media/base/decoder_status.h"
 #include "media/base/overlay_info.h"
 #include "media/base/video_decoder.h"
 #include "media/mojo/mojom/video_decoder.mojom.h"
@@ -70,13 +70,13 @@
   // running mojom::VideoDecoder callbacks after connection error happens and
   // |this| is deleted. It's not safe to run the callbacks after a connection
   // error.
-  void OnDecoderInitialized(Status status);
+  void OnDecoderInitialized(DecoderStatus status);
   void OnReaderRead(DecodeCallback callback,
                     std::unique_ptr<ScopedDecodeTrace> trace_event,
                     scoped_refptr<DecoderBuffer> buffer);
   void OnDecoderDecoded(DecodeCallback callback,
                         std::unique_ptr<ScopedDecodeTrace> trace_event,
-                        media::Status status);
+                        DecoderStatus status);
 
   // Called by |mojo_decoder_buffer_reader_| when reset is finished.
   void OnReaderFlushed();
diff --git a/media/mojo/test/mojo_video_decoder_integration_test.cc b/media/mojo/test/mojo_video_decoder_integration_test.cc
index 6c45ea9..a6b6c45 100644
--- a/media/mojo/test/mojo_video_decoder_integration_test.cc
+++ b/media/mojo/test/mojo_video_decoder_integration_test.cc
@@ -21,8 +21,8 @@
 #include "base/test/task_environment.h"
 #include "base/time/time.h"
 #include "gpu/command_buffer/common/mailbox_holder.h"
-#include "media/base/decode_status.h"
 #include "media/base/decoder_buffer.h"
+#include "media/base/decoder_status.h"
 #include "media/base/decrypt_config.h"
 #include "media/base/media_log.h"
 #include "media/base/mock_media_log.h"
@@ -133,8 +133,8 @@
         // This size buffer indicates that decoder should return an error.
         // |decode_cb| must not be called from the same stack.
         base::ThreadTaskRunnerHandle::Get()->PostTask(
-            FROM_HERE,
-            base::BindOnce(std::move(decode_cb), DecodeStatus::DECODE_ERROR));
+            FROM_HERE, base::BindOnce(std::move(decode_cb),
+                                      DecoderStatus::Codes::kFailed));
         return;
       }
       if (buffer->decrypt_config()) {
@@ -154,7 +154,8 @@
 
     // |decode_cb| must not be called from the same stack.
     base::ThreadTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE, base::BindOnce(std::move(decode_cb), DecodeStatus::OK));
+        FROM_HERE,
+        base::BindOnce(std::move(decode_cb), DecoderStatus::Codes::kOk));
   }
 
   void DoReset(base::OnceClosure& reset_cb) {
@@ -249,9 +250,9 @@
     CreateClient();
 
     EXPECT_CALL(*decoder_, DoInitialize(_))
-        .WillOnce(RunOnceCallback<0>(OkStatus()));
+        .WillOnce(RunOnceCallback<0>(DecoderStatus::Codes::kOk));
 
-    Status result = OkStatus();
+    DecoderStatus result = DecoderStatus::Codes::kOk;
     StrictMock<base::MockCallback<VideoDecoder::InitCB>> init_cb;
     EXPECT_CALL(init_cb, Run(_)).WillOnce(SaveArg<0>(&result));
 
@@ -263,10 +264,10 @@
     return result.is_ok();
   }
 
-  Status Decode(scoped_refptr<DecoderBuffer> buffer,
-                VideoFrame::ReleaseMailboxCB release_cb =
-                    VideoFrame::ReleaseMailboxCB()) {
-    Status result(DecodeStatus::DECODE_ERROR);
+  DecoderStatus Decode(scoped_refptr<DecoderBuffer> buffer,
+                       VideoFrame::ReleaseMailboxCB release_cb =
+                           VideoFrame::ReleaseMailboxCB()) {
+    DecoderStatus result(DecoderStatus::Codes::kFailed);
 
     if (!buffer->end_of_stream()) {
       decoder_->release_mailbox_cb = std::move(release_cb);
@@ -397,7 +398,7 @@
 
   StrictMock<base::MockCallback<VideoDecoder::InitCB>> init_cb;
   EXPECT_CALL(init_cb,
-              Run(HasStatusCode(StatusCode::kMojoDecoderNoWrappedDecoder)));
+              Run(HasStatusCode(DecoderStatus::Codes::kFailedToCreateDecoder)));
 
   // Clear |decoder_| so that Initialize() should fail.
   decoder_owner_.reset();
@@ -412,7 +413,7 @@
   StrictMock<base::MockCallback<VideoDecoder::InitCB>> init_cb;
   EXPECT_CALL(
       init_cb,
-      Run(HasStatusCode(StatusCode::kDecoderMissingCdmForEncryptedContent)));
+      Run(HasStatusCode(DecoderStatus::Codes::kUnsupportedEncryptionMode)));
 
   // CdmContext* (3rd parameter) is not provided but the VideoDecoderConfig
   // specifies encrypted video, so Initialize() should fail.
diff --git a/media/renderers/audio_renderer_impl.cc b/media/renderers/audio_renderer_impl.cc
index b70d91be9..18497f98 100644
--- a/media/renderers/audio_renderer_impl.cc
+++ b/media/renderers/audio_renderer_impl.cc
@@ -832,7 +832,7 @@
 
 void AudioRendererImpl::DecodedAudioReady(
     AudioDecoderStream::ReadResult result) {
-  DVLOG(2) << __func__ << "(" << result.code() << ")";
+  DVLOG(2) << __func__ << "(" << static_cast<int>(result.code()) << ")";
   DCHECK(task_runner_->BelongsToCurrentThread());
 
   base::AutoLock auto_lock(lock_);
@@ -842,7 +842,8 @@
   pending_read_ = false;
 
   if (result.has_error()) {
-    HandleAbortedReadOrDecodeError(result.code() == StatusCode::kAborted
+    HandleAbortedReadOrDecodeError(result.code() ==
+                                           DecoderStatus::Codes::kAborted
                                        ? PIPELINE_OK
                                        : PIPELINE_ERROR_DECODE);
     return;
diff --git a/media/renderers/audio_renderer_impl.h b/media/renderers/audio_renderer_impl.h
index fe6d6ba..e8ca040d 100644
--- a/media/renderers/audio_renderer_impl.h
+++ b/media/renderers/audio_renderer_impl.h
@@ -116,7 +116,7 @@
   bool was_unmuted_for_testing() const { return was_unmuted_; }
 
   void decoded_audio_ready_for_testing() {
-    DecodedAudioReady(StatusCode::kCodeOnlyForTesting);
+    DecodedAudioReady(DecoderStatus::Codes::kFailed);
   }
 
  private:
diff --git a/media/renderers/audio_renderer_impl_unittest.cc b/media/renderers/audio_renderer_impl_unittest.cc
index ca882f7..d653a428 100644
--- a/media/renderers/audio_renderer_impl_unittest.cc
+++ b/media/renderers/audio_renderer_impl_unittest.cc
@@ -96,11 +96,11 @@
     auto decoder = std::make_unique<MockAudioDecoder>();
     if (!enter_pending_decoder_init_) {
       EXPECT_CALL(*decoder, Initialize_(_, _, _, _, _))
-          .WillOnce(DoAll(SaveArg<3>(&output_cb_),
-                          RunOnceCallback<2>(
-                              expected_init_result_
-                                  ? OkStatus()
-                                  : Status(StatusCode::kCodeOnlyForTesting))));
+          .WillOnce(
+              DoAll(SaveArg<3>(&output_cb_),
+                    RunOnceCallback<2>(expected_init_result_
+                                           ? DecoderStatus::Codes::kOk
+                                           : DecoderStatus::Codes::kFailed)));
     } else {
       EXPECT_CALL(*decoder, Initialize_(_, _, _, _, _))
           .WillOnce(EnterPendingDecoderInitStateAction(this));
@@ -393,7 +393,7 @@
     }
     next_timestamp_->AddFrames(frames.value);
 
-    DeliverBuffer(DecodeStatus::OK, std::move(buffer));
+    DeliverBuffer(DecoderStatus::Codes::kOk, std::move(buffer));
   }
 
   void DeliverEndOfStream() {
@@ -406,12 +406,14 @@
 
     // Satify pending |decode_cb_| to trigger a new DemuxerStream::Read().
     main_thread_task_runner_->PostTask(
-        FROM_HERE, base::BindOnce(std::move(decode_cb_), DecodeStatus::OK));
+        FROM_HERE,
+        base::BindOnce(std::move(decode_cb_), DecoderStatus::Codes::kOk));
 
     WaitForPendingRead();
 
     main_thread_task_runner_->PostTask(
-        FROM_HERE, base::BindOnce(std::move(decode_cb_), DecodeStatus::OK));
+        FROM_HERE,
+        base::BindOnce(std::move(decode_cb_), DecoderStatus::Codes::kOk));
 
     base::RunLoop().RunUntilIdle();
     EXPECT_EQ(last_statistics_.audio_memory_usage,
@@ -566,7 +568,7 @@
     main_thread_task_runner_->PostTask(FROM_HERE, std::move(reset_cb));
   }
 
-  void DeliverBuffer(DecodeStatus status, scoped_refptr<AudioBuffer> buffer) {
+  void DeliverBuffer(DecoderStatus status, scoped_refptr<AudioBuffer> buffer) {
     CHECK(decode_cb_);
 
     if (buffer.get() && !buffer->end_of_stream())
@@ -914,7 +916,7 @@
   scoped_refptr<AudioBuffer> buffer = MakeAudioBuffer<float>(
       kSampleFormat, hw_params.channel_layout(), hw_params.channels(),
       kInputSamplesPerSecond, 1.0f, 0.0f, kInputFramesChunk, base::TimeDelta());
-  DeliverBuffer(DecodeStatus::OK, std::move(buffer));
+  DeliverBuffer(DecoderStatus::Codes::kOk, std::move(buffer));
 
   // All channels should now be enabled.
   mask = channel_mask();
diff --git a/media/renderers/video_renderer_impl.cc b/media/renderers/video_renderer_impl.cc
index 39d1cd2..8caba70 100644
--- a/media/renderers/video_renderer_impl.cc
+++ b/media/renderers/video_renderer_impl.cc
@@ -565,9 +565,9 @@
 
   // Can happen when demuxers are preparing for a new Seek().
   switch (result.code()) {
-    case StatusCode::kOk:
+    case DecoderStatus::Codes::kOk:
       break;
-    case StatusCode::kAborted:
+    case DecoderStatus::Codes::kAborted:
       // TODO(liberato): This used to check specifically for the value
       // DEMUXER_READ_ABORTED, which was more specific than |kAborted|.
       // However, since it's a dcheck, this seems okay.
diff --git a/media/renderers/video_renderer_impl_unittest.cc b/media/renderers/video_renderer_impl_unittest.cc
index e3f19767..fc27abab 100644
--- a/media/renderers/video_renderer_impl_unittest.cc
+++ b/media/renderers/video_renderer_impl_unittest.cc
@@ -65,11 +65,11 @@
     std::vector<std::unique_ptr<VideoDecoder>> decoders;
     decoders.push_back(base::WrapUnique(decoder_.get()));
     ON_CALL(*decoder_, Initialize_(_, _, _, _, _, _))
-        .WillByDefault(DoAll(
-            SaveArg<4>(&output_cb_),
-            RunOnceCallback<3>(expect_init_success_
-                                   ? OkStatus()
-                                   : Status(StatusCode::kCodeOnlyForTesting))));
+        .WillByDefault(
+            DoAll(SaveArg<4>(&output_cb_),
+                  RunOnceCallback<3>(expect_init_success_
+                                         ? DecoderStatus::Codes::kOk
+                                         : DecoderStatus::Codes::kFailed)));
     // Monitor decodes from the decoder.
     ON_CALL(*decoder_, Decode_(_, _))
         .WillByDefault(Invoke(this, &VideoRendererImplTest::DecodeRequested));
@@ -220,13 +220,13 @@
                            base::SPLIT_WANT_ALL)) {
       if (token == "abort") {
         scoped_refptr<VideoFrame> null_frame;
-        QueueFrame(DecodeStatus::ABORTED, null_frame);
+        QueueFrame(DecoderStatus::Codes::kAborted, null_frame);
         continue;
       }
 
       if (token == "error") {
         scoped_refptr<VideoFrame> null_frame;
-        QueueFrame(DecodeStatus::DECODE_ERROR, null_frame);
+        QueueFrame(DecoderStatus::Codes::kFailed, null_frame);
         continue;
       }
 
@@ -236,7 +236,7 @@
         scoped_refptr<VideoFrame> frame = VideoFrame::CreateFrame(
             PIXEL_FORMAT_I420, natural_size, gfx::Rect(natural_size),
             natural_size, base::Milliseconds(timestamp_in_ms));
-        QueueFrame(DecodeStatus::OK, frame);
+        QueueFrame(DecoderStatus::Codes::kOk, frame);
         continue;
       }
 
@@ -245,7 +245,7 @@
   }
 
   // Queues video frames to be served by the decoder during rendering.
-  void QueueFrame(DecodeStatus status, scoped_refptr<VideoFrame> frame) {
+  void QueueFrame(DecoderStatus status, scoped_refptr<VideoFrame> frame) {
     decode_results_.push_back(std::make_pair(status, frame));
   }
 
@@ -312,12 +312,14 @@
 
     // Satify pending |decode_cb_| to trigger a new DemuxerStream::Read().
     task_environment_.GetMainThreadTaskRunner()->PostTask(
-        FROM_HERE, base::BindOnce(std::move(decode_cb_), DecodeStatus::OK));
+        FROM_HERE,
+        base::BindOnce(std::move(decode_cb_), DecoderStatus::Codes::kOk));
 
     WaitForPendingDecode();
 
     task_environment_.GetMainThreadTaskRunner()->PostTask(
-        FROM_HERE, base::BindOnce(std::move(decode_cb_), DecodeStatus::OK));
+        FROM_HERE,
+        base::BindOnce(std::move(decode_cb_), DecoderStatus::Codes::kOk));
   }
 
   bool HasQueuedFrames() const { return decode_results_.size() > 0; }
@@ -419,7 +421,7 @@
   // Run during DecodeRequested() to unblock WaitForPendingDecode().
   base::OnceClosure wait_for_pending_decode_cb_;
 
-  base::circular_deque<std::pair<DecodeStatus, scoped_refptr<VideoFrame>>>
+  base::circular_deque<std::pair<DecoderStatus, scoped_refptr<VideoFrame>>>
       decode_results_;
 };
 
@@ -476,7 +478,7 @@
   Initialize();
   StartPlayingFrom(10000);
   QueueFrames("0");
-  QueueFrame(DecodeStatus::OK, VideoFrame::CreateEOSFrame());
+  QueueFrame(DecoderStatus::Codes::kOk, VideoFrame::CreateEOSFrame());
   WaitForPendingDecode();
   {
     SCOPED_TRACE("Waiting for BUFFERING_HAVE_ENOUGH");
@@ -1015,19 +1017,19 @@
   gfx::Size initial_size(8, 8);
   gfx::Size larger_size(16, 16);
 
-  QueueFrame(DecodeStatus::OK,
+  QueueFrame(DecoderStatus::Codes::kOk,
              VideoFrame::CreateFrame(PIXEL_FORMAT_I420, initial_size,
                                      gfx::Rect(initial_size), initial_size,
                                      base::Milliseconds(0)));
-  QueueFrame(DecodeStatus::OK,
+  QueueFrame(DecoderStatus::Codes::kOk,
              VideoFrame::CreateFrame(PIXEL_FORMAT_I420, larger_size,
                                      gfx::Rect(larger_size), larger_size,
                                      base::Milliseconds(10)));
-  QueueFrame(DecodeStatus::OK,
+  QueueFrame(DecoderStatus::Codes::kOk,
              VideoFrame::CreateFrame(PIXEL_FORMAT_I420, larger_size,
                                      gfx::Rect(larger_size), larger_size,
                                      base::Milliseconds(20)));
-  QueueFrame(DecodeStatus::OK,
+  QueueFrame(DecoderStatus::Codes::kOk,
              VideoFrame::CreateFrame(PIXEL_FORMAT_I420, initial_size,
                                      gfx::Rect(initial_size), initial_size,
                                      base::Milliseconds(30)));
@@ -1081,20 +1083,20 @@
   VideoPixelFormat opaque_format = PIXEL_FORMAT_I420;
   VideoPixelFormat non_opaque_format = PIXEL_FORMAT_I420A;
 
-  QueueFrame(DecodeStatus::OK,
+  QueueFrame(DecoderStatus::Codes::kOk,
              VideoFrame::CreateFrame(non_opaque_format, frame_size,
                                      gfx::Rect(frame_size), frame_size,
                                      base::Milliseconds(0)));
-  QueueFrame(DecodeStatus::OK,
+  QueueFrame(DecoderStatus::Codes::kOk,
              VideoFrame::CreateFrame(non_opaque_format, frame_size,
                                      gfx::Rect(frame_size), frame_size,
                                      base::Milliseconds(10)));
   QueueFrame(
-      DecodeStatus::OK,
+      DecoderStatus::Codes::kOk,
       VideoFrame::CreateFrame(opaque_format, frame_size, gfx::Rect(frame_size),
                               frame_size, base::Milliseconds(20)));
   QueueFrame(
-      DecodeStatus::OK,
+      DecoderStatus::Codes::kOk,
       VideoFrame::CreateFrame(opaque_format, frame_size, gfx::Rect(frame_size),
                               frame_size, base::Milliseconds(30)));
 
diff --git a/media/test/pipeline_integration_test.cc b/media/test/pipeline_integration_test.cc
index 73f57b5..43f4a6d 100644
--- a/media/test/pipeline_integration_test.cc
+++ b/media/test/pipeline_integration_test.cc
@@ -363,13 +363,13 @@
                   InitCB init_cb,
                   const OutputCB& output_cb,
                   const WaitingCB& waiting_cb) override {
-    std::move(init_cb).Run(OkStatus());
+    std::move(init_cb).Run(DecoderStatus::Codes::kOk);
   }
   void Decode(scoped_refptr<DecoderBuffer> buffer,
               DecodeCB decode_cb) override {
     base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE,
-        base::BindOnce(std::move(decode_cb), DecodeStatus::DECODE_ERROR));
+        base::BindOnce(std::move(decode_cb), DecoderStatus::Codes::kFailed));
   }
   void Reset(base::OnceClosure closure) override { std::move(closure).Run(); }
   bool NeedsBitstreamConversion() const override { return true; }
diff --git a/media/video/software_video_encoder_test.cc b/media/video/software_video_encoder_test.cc
index 2041aa9..374323c 100644
--- a/media/video/software_video_encoder_test.cc
+++ b/media/video/software_video_encoder_test.cc
@@ -219,10 +219,10 @@
     auto enforcer = std::make_unique<CallEnforcer>();
     enforcer->location = loc.ToString();
     return base::BindLambdaForTesting(
-        [enforcer{std::move(enforcer)}](Status s) {
-          EXPECT_TRUE(s.is_ok())
-              << " Callback created: " << enforcer->location
-              << " Code: " << s.code() << " Error: " << s.message();
+        [enforcer{std::move(enforcer)}](DecoderStatus s) {
+          EXPECT_TRUE(s.is_ok()) << " Callback created: " << enforcer->location
+                                 << " Code: " << static_cast<int>(s.code())
+                                 << " Error: " << s.message();
           enforcer->called = true;
         });
   }
@@ -231,13 +231,14 @@
       scoped_refptr<DecoderBuffer> buffer,
       const base::Location& location = base::Location::Current()) {
     base::RunLoop run_loop;
-    decoder_->Decode(
-        std::move(buffer), base::BindLambdaForTesting([&](Status status) {
-          EXPECT_TRUE(status.is_ok())
-              << " Callback created: " << location.ToString()
-              << " Code: " << status.code() << " Error: " << status.message();
-          run_loop.Quit();
-        }));
+    decoder_->Decode(std::move(buffer),
+                     base::BindLambdaForTesting([&](DecoderStatus status) {
+                       EXPECT_TRUE(status.is_ok())
+                           << " Callback created: " << location.ToString()
+                           << " Code: " << static_cast<int>(status.code())
+                           << " Error: " << status.message();
+                       run_loop.Quit();
+                     }));
     run_loop.Run(location);
   }
 
diff --git a/media/video/video_decode_accelerator.cc b/media/video/video_decode_accelerator.cc
index 78171dc..c353138 100644
--- a/media/video/video_decode_accelerator.cc
+++ b/media/video/video_decode_accelerator.cc
@@ -28,7 +28,7 @@
 }
 
 void VideoDecodeAccelerator::Client::NotifyInitializationComplete(
-    Status status) {
+    DecoderStatus status) {
   NOTREACHED() << "By default deferred initialization is not supported.";
 }
 
diff --git a/media/video/video_decode_accelerator.h b/media/video/video_decode_accelerator.h
index 12a0c37b..2ea21479 100644
--- a/media/video/video_decode_accelerator.h
+++ b/media/video/video_decode_accelerator.h
@@ -16,9 +16,9 @@
 #include "media/base/bitstream_buffer.h"
 #include "media/base/cdm_context.h"
 #include "media/base/decoder_buffer.h"
+#include "media/base/decoder_status.h"
 #include "media/base/encryption_scheme.h"
 #include "media/base/overlay_info.h"
-#include "media/base/status.h"
 #include "media/base/video_decoder_config.h"
 #include "media/video/picture.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -207,7 +207,7 @@
     // call to VDA::Initialize returns true.
     // The default implementation is a NOTREACHED, since deferred initialization
     // is not supported by default.
-    virtual void NotifyInitializationComplete(Status status);
+    virtual void NotifyInitializationComplete(DecoderStatus status);
 
     // Callback to tell client how many and what size of buffers to provide.
     // Note that the actual count provided through AssignPictureBuffers() can be
diff --git a/mojo/core/node_channel.cc b/mojo/core/node_channel.cc
index 88d74742..430dcb86 100644
--- a/mojo/core/node_channel.cc
+++ b/mojo/core/node_channel.cc
@@ -251,7 +251,7 @@
     Channel::HandlePolicy channel_handle_policy,
     scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
     const ProcessErrorCallback& process_error_callback) {
-#if defined(OS_NACL_SFI)
+#if defined(OS_NACL)
   LOG(FATAL) << "Multi-process not yet supported on NaCl-SFI";
   return nullptr;
 #else
@@ -540,7 +540,7 @@
     : base::RefCountedDeleteOnSequence<NodeChannel>(io_task_runner),
       delegate_(delegate),
       process_error_callback_(process_error_callback)
-#if !defined(OS_NACL_SFI)
+#if !defined(OS_NACL)
       ,
       channel_(Channel::Create(this,
                                std::move(connection_params),
diff --git a/mojo/core/node_controller.cc b/mojo/core/node_controller.cc
index 2e770c6..31edf52 100644
--- a/mojo/core/node_controller.cc
+++ b/mojo/core/node_controller.cc
@@ -232,7 +232,7 @@
     ConnectionParams connection_params) {
   absl::optional<PlatformHandle> broker_host_handle;
   DCHECK(!GetConfiguration().is_broker_process);
-#if !defined(OS_APPLE) && !defined(OS_NACL_SFI) && !defined(OS_FUCHSIA)
+#if !defined(OS_APPLE) && !defined(OS_NACL) && !defined(OS_FUCHSIA)
   if (!connection_params.is_async()) {
     // Use the bootstrap channel for the broker and receive the node's channel
     // synchronously as the first message from the broker.
@@ -342,7 +342,7 @@
 
 base::WritableSharedMemoryRegion NodeController::CreateSharedBuffer(
     size_t num_bytes) {
-#if !defined(OS_APPLE) && !defined(OS_NACL_SFI) && !defined(OS_FUCHSIA)
+#if !defined(OS_APPLE) && !defined(OS_NACL) && !defined(OS_FUCHSIA)
   // Shared buffer creation failure is fatal, so always use the broker when we
   // have one; unless of course the embedder forces us not to.
   if (!GetConfiguration().force_direct_shared_memory_allocation && broker_)
diff --git a/mojo/core/node_controller.h b/mojo/core/node_controller.h
index e49e5734..4df63bde 100644
--- a/mojo/core/node_controller.h
+++ b/mojo/core/node_controller.h
@@ -361,7 +361,7 @@
   // Must only be accessed from the IO thread.
   bool destroy_on_io_thread_shutdown_ = false;
 
-#if !defined(OS_APPLE) && !defined(OS_NACL_SFI) && !defined(OS_FUCHSIA)
+#if !defined(OS_APPLE) && !defined(OS_NACL) && !defined(OS_FUCHSIA)
   // Broker for sync shared buffer creation on behalf of broker clients.
   std::unique_ptr<Broker> broker_;
 #endif
diff --git a/mojo/public/cpp/platform/platform_channel.cc b/mojo/public/cpp/platform/platform_channel.cc
index f72b1ed3a..bf619c9f 100644
--- a/mojo/public/cpp/platform/platform_channel.cc
+++ b/mojo/public/cpp/platform/platform_channel.cc
@@ -44,9 +44,9 @@
 #include "base/mac/scoped_mach_port.h"
 #endif
 
-#if defined(OS_POSIX) && !defined(OS_NACL_SFI)
+#if defined(OS_POSIX) && !defined(OS_NACL)
 #include <sys/socket.h>
-#elif defined(OS_NACL_SFI)
+#elif defined(OS_NACL)
 #include "native_client/src/public/imc_syscalls.h"
 #endif
 
@@ -143,7 +143,7 @@
 void CreateChannel(PlatformHandle* local_endpoint,
                    PlatformHandle* remote_endpoint) {
   int fds[2];
-#if defined(OS_NACL_SFI)
+#if defined(OS_NACL)
   PCHECK(imc_socketpair(fds) == 0);
 #else
   PCHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == 0);
@@ -162,7 +162,7 @@
   PCHECK(setsockopt(fds[1], SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe,
                     sizeof(no_sigpipe)) == 0);
 #endif  // defined(OS_APPLE)
-#endif  // defined(OS_NACL_SFI)
+#endif  // defined(OS_NACL)
 
   *local_endpoint = PlatformHandle(base::ScopedFD(fds[0]));
   *remote_endpoint = PlatformHandle(base::ScopedFD(fds[1]));
diff --git a/net/http/http_stream_factory_job_controller_unittest.cc b/net/http/http_stream_factory_job_controller_unittest.cc
index ecb04564..73b8ac12 100644
--- a/net/http/http_stream_factory_job_controller_unittest.cc
+++ b/net/http/http_stream_factory_job_controller_unittest.cc
@@ -78,23 +78,6 @@
 
 const char kServerHostname[] = "www.example.com";
 
-// List of errors for which fallback is expected on an HTTPS proxy.
-//
-// We omit `ERR_CONNECTION_CLOSED` because it is largely unreachable. The
-// HTTP/1.1 parser maps it to `ERR_EMPTY_RESPONSE` or
-// `ERR_RESPONSE_HEADERS_TRUNCATED` in most cases.
-//
-// TODO(davidben): Is omitting `ERR_EMPTY_RESPONSE` a bug in proxy error
-// handling?
-const int proxy_test_mock_errors[] = {
-    ERR_PROXY_CONNECTION_FAILED, ERR_NAME_NOT_RESOLVED,
-    ERR_ADDRESS_UNREACHABLE,     ERR_CONNECTION_TIMED_OUT,
-    ERR_CONNECTION_RESET,        ERR_CONNECTION_REFUSED,
-    ERR_CONNECTION_ABORTED,      ERR_TIMED_OUT,
-    ERR_SOCKS_CONNECTION_FAILED, ERR_PROXY_CERTIFICATE_INVALID,
-    ERR_SSL_PROTOCOL_ERROR,
-};
-
 class FailingProxyResolverFactory : public ProxyResolverFactory {
  public:
   FailingProxyResolverFactory() : ProxyResolverFactory(false) {}
@@ -205,6 +188,18 @@
       : TestWithTaskEnvironment(
             base::test::TaskEnvironment::TimeSource::MOCK_TIME) {
     FLAGS_quic_enable_http3_grease_randomness = false;
+    CreateSessionDeps();
+  }
+
+  // Creates / re-creates `session_deps_`, and clears test fixture fields
+  // referencing it.
+  void CreateSessionDeps() {
+    factory_ = nullptr;
+    job_controller_ = nullptr;
+    session_.reset();
+
+    session_deps_ = SpdySessionDependencies(
+        ConfiguredProxyResolutionService::CreateDirect());
     session_deps_.enable_quic = true;
     session_deps_.host_resolver->set_synchronous_mode(true);
   }
@@ -338,8 +333,7 @@
   TestJobFactory job_factory_;
   MockHttpStreamRequestDelegate request_delegate_;
   MockQuicContext quic_context_;
-  SpdySessionDependencies session_deps_{
-      ConfiguredProxyResolutionService::CreateDirect()};
+  SpdySessionDependencies session_deps_;
   std::unique_ptr<HttpNetworkSession> session_;
   raw_ptr<HttpStreamFactory> factory_ = nullptr;
   raw_ptr<HttpStreamFactory::JobController> job_controller_ = nullptr;
@@ -470,8 +464,7 @@
 }
 
 class JobControllerReconsiderProxyAfterErrorTest
-    : public HttpStreamFactoryJobControllerTest,
-      public ::testing::WithParamInterface<int> {
+    : public HttpStreamFactoryJobControllerTest {
  public:
   void Initialize(
       std::unique_ptr<ProxyResolutionService> proxy_resolution_service) {
@@ -503,104 +496,546 @@
   HttpStreamFactory::JobFactory default_job_factory_;
 };
 
-INSTANTIATE_TEST_SUITE_P(All,
-                         JobControllerReconsiderProxyAfterErrorTest,
-                         testing::ValuesIn(proxy_test_mock_errors));
-
+// Test proxy fallback logic in the case connecting through an HTTP proxy.
+//
 // TODO(eroman): The testing should be expanded to test cases where proxy
 //               fallback is NOT supposed to occur, and also vary across all of
 //               the proxy types.
-TEST_P(JobControllerReconsiderProxyAfterErrorTest, ReconsiderProxyAfterError) {
-  const int mock_error = GetParam();
-  std::unique_ptr<ConfiguredProxyResolutionService> proxy_resolution_service =
-      ConfiguredProxyResolutionService::CreateFixedFromPacResult(
-          "HTTPS badproxy:99; HTTPS badfallbackproxy:98; DIRECT",
-          TRAFFIC_ANNOTATION_FOR_TESTS);
-  auto test_proxy_delegate = std::make_unique<TestProxyDelegate>();
+TEST_F(JobControllerReconsiderProxyAfterErrorTest,
+       ReconsiderProxyAfterErrorHttpProxy) {
+  enum class ErrorPhase {
+    kHostResolution,
+    kTcpConnect,
+    kTunnelRead,
+  };
 
-  // Before starting the test, verify that there are no proxies marked as bad.
-  ASSERT_TRUE(proxy_resolution_service->proxy_retry_info().empty())
-      << mock_error;
+  const struct {
+    ErrorPhase phase;
+    net::Error error;
+  } kRetriableErrors[] = {
+      // These largely correspond to the list of errors in
+      // CanFalloverToNextProxy() which can occur with an HTTP proxy.
+      //
+      // We omit `ERR_CONNECTION_CLOSED` because it is largely unreachable. The
+      // HTTP/1.1 parser maps it to `ERR_EMPTY_RESPONSE` or
+      // `ERR_RESPONSE_HEADERS_TRUNCATED` in most cases.
+      //
+      // TODO(davidben): Is omitting `ERR_EMPTY_RESPONSE` a bug in proxy error
+      // handling?
+      {ErrorPhase::kHostResolution, ERR_NAME_NOT_RESOLVED},
+      {ErrorPhase::kTcpConnect, ERR_ADDRESS_UNREACHABLE},
+      {ErrorPhase::kTcpConnect, ERR_CONNECTION_TIMED_OUT},
+      {ErrorPhase::kTcpConnect, ERR_CONNECTION_RESET},
+      {ErrorPhase::kTcpConnect, ERR_CONNECTION_ABORTED},
+      {ErrorPhase::kTcpConnect, ERR_CONNECTION_REFUSED},
+      {ErrorPhase::kTunnelRead, ERR_TIMED_OUT},
+      {ErrorPhase::kTunnelRead, ERR_SSL_PROTOCOL_ERROR},
+  };
 
-  // Configure the HTTP CONNECT to fail with `mock_error`.
-  //
-  // TODO(crbug.com/1279685): Test this more accurately. Errors like
-  // `ERR_PROXY_CONNECTION_FAILED` or `ERR_PROXY_CERTIFICATE_INVALID` are
-  // surfaced in response to other errors in TCP or TLS connection setup.
-  SSLSocketDataProvider ssl_data(ASYNC, OK);
-  static constexpr char kHttpConnect[] =
-      "CONNECT www.example.com:443 HTTP/1.1\r\n"
-      "Host: www.example.com:443\r\n"
-      "Proxy-Connection: keep-alive\r\n\r\n";
-  const MockWrite kWrites[] = {{ASYNC, kHttpConnect}};
-  const MockRead kReads[] = {{ASYNC, mock_error}};
+  for (GURL dest_url :
+       {GURL("http://www.example.com"), GURL("https://www.example.com")}) {
+    SCOPED_TRACE(dest_url);
 
-  StaticSocketDataProvider socket_data_proxy_main_job(kReads, kWrites);
-  socket_data_proxy_main_job.set_connect_data(MockConnect(ASYNC, OK));
-  session_deps_.socket_factory->AddSocketDataProvider(
-      &socket_data_proxy_main_job);
-  session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data);
+    for (const auto& mock_error : kRetriableErrors) {
+      SCOPED_TRACE(ErrorToString(mock_error.error));
 
-  // When retrying the job using the second proxy (badfallback:98),
-  // alternative job must not be created. So, socket data for only the
-  // main job is needed.
-  StaticSocketDataProvider socket_data_proxy_main_job_2(kReads, kWrites);
-  socket_data_proxy_main_job_2.set_connect_data(MockConnect(ASYNC, OK));
-  session_deps_.socket_factory->AddSocketDataProvider(
-      &socket_data_proxy_main_job_2);
-  session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data);
+      CreateSessionDeps();
 
-  // First request would use DIRECT, and succeed. For this and the second
-  // request, `ssl_data` is a connection to the HTTPS origin. For the first two,
-  // it is a connection to the HTTPS proxy.
-  StaticSocketDataProvider socket_data_direct_first_request;
-  socket_data_direct_first_request.set_connect_data(MockConnect(ASYNC, OK));
-  session_deps_.socket_factory->AddSocketDataProvider(
-      &socket_data_direct_first_request);
-  session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data);
+      std::unique_ptr<ConfiguredProxyResolutionService>
+          proxy_resolution_service =
+              ConfiguredProxyResolutionService::CreateFixedFromPacResult(
+                  "PROXY badproxy:99; PROXY badfallbackproxy:98; DIRECT",
+                  TRAFFIC_ANNOTATION_FOR_TESTS);
+      auto test_proxy_delegate = std::make_unique<TestProxyDelegate>();
 
-  // Second request would use DIRECT, and succeed.
-  StaticSocketDataProvider socket_data_direct_second_request;
-  socket_data_direct_second_request.set_connect_data(MockConnect(ASYNC, OK));
-  session_deps_.socket_factory->AddSocketDataProvider(
-      &socket_data_direct_second_request);
-  session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data);
+      // Before starting the test, verify that there are no proxies marked as
+      // bad.
+      ASSERT_TRUE(proxy_resolution_service->proxy_retry_info().empty());
 
-  // Now request a stream. It should succeed using the DIRECT.
-  HttpRequestInfo request_info;
-  request_info.method = "GET";
-  request_info.url = GURL("https://www.example.com");
+      constexpr char kTunnelRequest[] =
+          "CONNECT www.example.com:443 HTTP/1.1\r\n"
+          "Host: www.example.com:443\r\n"
+          "Proxy-Connection: keep-alive\r\n\r\n";
+      const MockWrite kTunnelWrites[] = {{ASYNC, kTunnelRequest}};
+      std::vector<MockRead> reads;
 
-  proxy_resolution_service->SetProxyDelegate(test_proxy_delegate.get());
-  Initialize(std::move(proxy_resolution_service));
+      // Generate identical errors for both the main proxy and the fallback
+      // proxy. No alternative job is created for either, so only need one data
+      // provider for each, when the request makes it to the socket layer.
+      std::unique_ptr<StaticSocketDataProvider> socket_data_proxy_main_job;
+      std::unique_ptr<StaticSocketDataProvider> socket_data_proxy_main_job2;
+      switch (mock_error.phase) {
+        case ErrorPhase::kHostResolution:
+          // Only ERR_NAME_NOT_RESOLVED can be returned by the mock host
+          // resolver.
+          DCHECK_EQ(ERR_NAME_NOT_RESOLVED, mock_error.error);
+          session_deps_.host_resolver->rules()->AddSimulatedFailure("badproxy");
+          session_deps_.host_resolver->rules()->AddSimulatedFailure(
+              "badfallbackproxy");
+          break;
+        case ErrorPhase::kTcpConnect:
+          socket_data_proxy_main_job =
+              std::make_unique<StaticSocketDataProvider>();
+          socket_data_proxy_main_job->set_connect_data(
+              MockConnect(ASYNC, mock_error.error));
+          socket_data_proxy_main_job2 =
+              std::make_unique<StaticSocketDataProvider>();
+          socket_data_proxy_main_job2->set_connect_data(
+              MockConnect(ASYNC, mock_error.error));
+          break;
+        case ErrorPhase::kTunnelRead:
+          // Tunnels aren't established for HTTP destinations.
+          if (dest_url.SchemeIs(url::kHttpScheme))
+            continue;
+          reads.emplace_back(MockRead(ASYNC, mock_error.error));
+          socket_data_proxy_main_job =
+              std::make_unique<StaticSocketDataProvider>(reads, kTunnelWrites);
+          socket_data_proxy_main_job2 =
+              std::make_unique<StaticSocketDataProvider>(reads, kTunnelWrites);
+          break;
+      }
 
-  // Start two requests. The first request should consume data from
-  // |socket_data_proxy_main_job| and |socket_data_direct_first_request|. The
-  // second request should consume data from
-  // |socket_data_direct_second_request|.
+      if (socket_data_proxy_main_job) {
+        session_deps_.socket_factory->AddSocketDataProvider(
+            socket_data_proxy_main_job.get());
+        session_deps_.socket_factory->AddSocketDataProvider(
+            socket_data_proxy_main_job2.get());
+      }
 
-  for (size_t i = 0; i < 2; ++i) {
-    ProxyInfo used_proxy_info;
-    EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _))
-        .Times(1)
-        .WillOnce(::testing::SaveArg<1>(&used_proxy_info));
+      // After both proxies fail, the request should fall back to using DIRECT,
+      // and succeed.
+      SSLSocketDataProvider ssl_data_first_request(ASYNC, OK);
+      StaticSocketDataProvider socket_data_direct_first_request;
+      socket_data_direct_first_request.set_connect_data(MockConnect(ASYNC, OK));
+      session_deps_.socket_factory->AddSocketDataProvider(
+          &socket_data_direct_first_request);
+      // Only used in the HTTPS destination case, but harmless in the HTTP case.
+      session_deps_.socket_factory->AddSSLSocketDataProvider(
+          &ssl_data_first_request);
 
-    std::unique_ptr<HttpStreamRequest> request =
-        CreateJobController(request_info);
-    base::RunLoop().RunUntilIdle();
+      // Second request should use DIRECT, skipping the bad proxies, and
+      // succeed.
+      SSLSocketDataProvider ssl_data_second_request(ASYNC, OK);
+      StaticSocketDataProvider socket_data_direct_second_request;
+      socket_data_direct_second_request.set_connect_data(
+          MockConnect(ASYNC, OK));
+      session_deps_.socket_factory->AddSocketDataProvider(
+          &socket_data_direct_second_request);
+      // Only used in the HTTPS destination case, but harmless in the HTTP case.
+      session_deps_.socket_factory->AddSSLSocketDataProvider(
+          &ssl_data_second_request);
 
-    // Verify that request was fetched without proxy.
-    EXPECT_TRUE(used_proxy_info.is_direct());
+      // Now request a stream. It should succeed using the DIRECT fallback proxy
+      // option.
+      HttpRequestInfo request_info;
+      request_info.method = "GET";
+      request_info.url = dest_url;
 
-    // The proxies that failed should now be known to the proxy service as
-    // bad.
-    const ProxyRetryInfoMap& retry_info =
-        session_->proxy_resolution_service()->proxy_retry_info();
-    ASSERT_THAT(retry_info, SizeIs(2));
-    EXPECT_THAT(retry_info, Contains(Key("https://badproxy:99")));
-    EXPECT_THAT(retry_info, Contains(Key("https://badfallbackproxy:98")));
+      proxy_resolution_service->SetProxyDelegate(test_proxy_delegate.get());
+      Initialize(std::move(proxy_resolution_service));
+
+      // Start two requests. The first request should consume data from
+      // |socket_data_proxy_main_job| and |socket_data_direct_first_request|.
+      // The second request should consume data from
+      // |socket_data_direct_second_request|.
+
+      for (size_t i = 0; i < 2; ++i) {
+        ProxyInfo used_proxy_info;
+        EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _))
+            .Times(1)
+            .WillOnce(::testing::SaveArg<1>(&used_proxy_info));
+
+        std::unique_ptr<HttpStreamRequest> request =
+            CreateJobController(request_info);
+        RunUntilIdle();
+
+        // Verify that request was fetched without proxy.
+        EXPECT_TRUE(used_proxy_info.is_direct());
+
+        // The proxies that failed should now be known to the proxy service as
+        // bad.
+        const ProxyRetryInfoMap& retry_info =
+            session_->proxy_resolution_service()->proxy_retry_info();
+        ASSERT_THAT(retry_info, SizeIs(2));
+        EXPECT_THAT(retry_info, Contains(Key("badproxy:99")));
+        EXPECT_THAT(retry_info, Contains(Key("badfallbackproxy:98")));
+
+        // The idle socket should have been added back to the socket pool. Close
+        // it, so the next loop iteration creates a new socket instead of
+        // reusing the idle one.
+        auto* socket_pool = session_->GetSocketPool(
+            HttpNetworkSession::NORMAL_SOCKET_POOL, ProxyServer::Direct());
+        EXPECT_EQ(1, socket_pool->IdleSocketCount());
+        socket_pool->CloseIdleSockets("Close socket reason");
+      }
+      EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
+    }
   }
-  EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
+}
+
+// Test proxy fallback logic in the case connecting through an HTTPS proxy.
+TEST_F(JobControllerReconsiderProxyAfterErrorTest,
+       ReconsiderProxyAfterErrorHttpsProxy) {
+  enum class ErrorPhase {
+    kHostResolution,
+    kTcpConnect,
+    kProxySslHandshake,
+    kTunnelRead,
+  };
+
+  const struct {
+    ErrorPhase phase;
+    net::Error error;
+    // Each test case simulates a connection attempt through a proxy that fails
+    // twice, followed by two connection attempts that succeed. For most cases,
+    // this is done by having a connection attempt to the first proxy fail,
+    // triggering fallback to a second proxy, which also fails, and then
+    // fallback to the final (DIRECT) proxy option. However, SslConnectJobs have
+    // their own try logic in certain cases. This value is true for those cases,
+    // in which case there are two connection attempts to the first proxy, and
+    // then the requests fall back to the second (DIRECT) proxy.
+    bool triggers_ssl_connect_job_retry_logic = false;
+  } kRetriableErrors[] = {
+      // These largely correspond to the list of errors in
+      // CanFalloverToNextProxy() which can occur with an HTTPS proxy.
+      //
+      // We omit `ERR_CONNECTION_CLOSED` because it is largely unreachable. The
+      // HTTP/1.1 parser maps it to `ERR_EMPTY_RESPONSE` or
+      // `ERR_RESPONSE_HEADERS_TRUNCATED` in most cases.
+      //
+      // TODO(davidben): Is omitting `ERR_EMPTY_RESPONSE` a bug in proxy error
+      // handling?
+      {ErrorPhase::kHostResolution, ERR_NAME_NOT_RESOLVED},
+      {ErrorPhase::kTcpConnect, ERR_ADDRESS_UNREACHABLE},
+      {ErrorPhase::kTcpConnect, ERR_CONNECTION_TIMED_OUT},
+      {ErrorPhase::kTcpConnect, ERR_CONNECTION_RESET},
+      {ErrorPhase::kTcpConnect, ERR_CONNECTION_ABORTED},
+      {ErrorPhase::kTcpConnect, ERR_CONNECTION_REFUSED},
+      {ErrorPhase::kProxySslHandshake, ERR_CERT_COMMON_NAME_INVALID},
+      {ErrorPhase::kProxySslHandshake, ERR_SSL_PROTOCOL_ERROR,
+       /*triggers_ssl_connect_job_retry_logic=*/true},
+      {ErrorPhase::kTunnelRead, ERR_TIMED_OUT},
+      {ErrorPhase::kTunnelRead, ERR_SSL_PROTOCOL_ERROR},
+  };
+
+  for (GURL dest_url :
+       {GURL("http://www.example.com"), GURL("https://www.example.com")}) {
+    SCOPED_TRACE(dest_url);
+
+    for (const auto& mock_error : kRetriableErrors) {
+      SCOPED_TRACE(ErrorToString(mock_error.error));
+
+      CreateSessionDeps();
+
+      std::unique_ptr<ConfiguredProxyResolutionService>
+          proxy_resolution_service =
+              ConfiguredProxyResolutionService::CreateFixedFromPacResult(
+                  "HTTPS badproxy:99; HTTPS badfallbackproxy:98; DIRECT",
+                  TRAFFIC_ANNOTATION_FOR_TESTS);
+      if (mock_error.triggers_ssl_connect_job_retry_logic) {
+        proxy_resolution_service =
+            ConfiguredProxyResolutionService::CreateFixedFromPacResult(
+                "HTTPS badproxy:99; DIRECT", TRAFFIC_ANNOTATION_FOR_TESTS);
+      }
+      auto test_proxy_delegate = std::make_unique<TestProxyDelegate>();
+
+      // Before starting the test, verify that there are no proxies marked as
+      // bad.
+      ASSERT_TRUE(proxy_resolution_service->proxy_retry_info().empty());
+
+      constexpr char kTunnelRequest[] =
+          "CONNECT www.example.com:443 HTTP/1.1\r\n"
+          "Host: www.example.com:443\r\n"
+          "Proxy-Connection: keep-alive\r\n\r\n";
+      const MockWrite kTunnelWrites[] = {{ASYNC, kTunnelRequest}};
+      std::vector<MockRead> reads;
+
+      // Generate identical errors for both the main proxy and the fallback
+      // proxy. No alternative job is created for either, so only need one data
+      // provider for each, when the request makes it to the socket layer.
+      std::unique_ptr<StaticSocketDataProvider> socket_data_proxy_main_job;
+      std::unique_ptr<SSLSocketDataProvider> ssl_data_proxy_main_job;
+      std::unique_ptr<StaticSocketDataProvider> socket_data_proxy_main_job2;
+      std::unique_ptr<SSLSocketDataProvider> ssl_data_proxy_main_job2;
+      switch (mock_error.phase) {
+        case ErrorPhase::kHostResolution:
+          // Only ERR_NAME_NOT_RESOLVED can be returned by the mock host
+          // resolver.
+          DCHECK_EQ(ERR_NAME_NOT_RESOLVED, mock_error.error);
+          session_deps_.host_resolver->rules()->AddSimulatedFailure("badproxy");
+          session_deps_.host_resolver->rules()->AddSimulatedFailure(
+              "badfallbackproxy");
+          break;
+        case ErrorPhase::kTcpConnect:
+          socket_data_proxy_main_job =
+              std::make_unique<StaticSocketDataProvider>();
+          socket_data_proxy_main_job->set_connect_data(
+              MockConnect(ASYNC, mock_error.error));
+          socket_data_proxy_main_job2 =
+              std::make_unique<StaticSocketDataProvider>();
+          socket_data_proxy_main_job2->set_connect_data(
+              MockConnect(ASYNC, mock_error.error));
+          break;
+        case ErrorPhase::kProxySslHandshake:
+          socket_data_proxy_main_job =
+              std::make_unique<StaticSocketDataProvider>();
+          ssl_data_proxy_main_job =
+              std::make_unique<SSLSocketDataProvider>(ASYNC, mock_error.error);
+          socket_data_proxy_main_job2 =
+              std::make_unique<StaticSocketDataProvider>();
+          ssl_data_proxy_main_job2 =
+              std::make_unique<SSLSocketDataProvider>(ASYNC, mock_error.error);
+          break;
+        case ErrorPhase::kTunnelRead:
+          // Tunnels aren't established for HTTP destinations.
+          if (dest_url.SchemeIs(url::kHttpScheme))
+            continue;
+          reads.emplace_back(MockRead(ASYNC, mock_error.error));
+          socket_data_proxy_main_job =
+              std::make_unique<StaticSocketDataProvider>(reads, kTunnelWrites);
+          ssl_data_proxy_main_job =
+              std::make_unique<SSLSocketDataProvider>(ASYNC, OK);
+          socket_data_proxy_main_job2 =
+              std::make_unique<StaticSocketDataProvider>(reads, kTunnelWrites);
+          ssl_data_proxy_main_job2 =
+              std::make_unique<SSLSocketDataProvider>(ASYNC, OK);
+          break;
+      }
+
+      if (socket_data_proxy_main_job) {
+        session_deps_.socket_factory->AddSocketDataProvider(
+            socket_data_proxy_main_job.get());
+        session_deps_.socket_factory->AddSocketDataProvider(
+            socket_data_proxy_main_job2.get());
+      }
+      if (ssl_data_proxy_main_job) {
+        session_deps_.socket_factory->AddSSLSocketDataProvider(
+            ssl_data_proxy_main_job.get());
+        session_deps_.socket_factory->AddSSLSocketDataProvider(
+            ssl_data_proxy_main_job2.get());
+      }
+
+      // After both proxies fail, the request should fall back to using DIRECT,
+      // and succeed.
+      SSLSocketDataProvider ssl_data_first_request(ASYNC, OK);
+      StaticSocketDataProvider socket_data_direct_first_request;
+      socket_data_direct_first_request.set_connect_data(MockConnect(ASYNC, OK));
+      session_deps_.socket_factory->AddSocketDataProvider(
+          &socket_data_direct_first_request);
+      // Only used in the HTTPS destination case, but harmless in the HTTP case.
+      session_deps_.socket_factory->AddSSLSocketDataProvider(
+          &ssl_data_first_request);
+
+      // Second request should use DIRECT, skipping the bad proxies, and
+      // succeed.
+      SSLSocketDataProvider ssl_data_second_request(ASYNC, OK);
+      StaticSocketDataProvider socket_data_direct_second_request;
+      socket_data_direct_second_request.set_connect_data(
+          MockConnect(ASYNC, OK));
+      session_deps_.socket_factory->AddSocketDataProvider(
+          &socket_data_direct_second_request);
+      // Only used in the HTTPS destination case, but harmless in the HTTP case.
+      session_deps_.socket_factory->AddSSLSocketDataProvider(
+          &ssl_data_second_request);
+
+      // Now request a stream. It should succeed using the DIRECT fallback proxy
+      // option.
+      HttpRequestInfo request_info;
+      request_info.method = "GET";
+      request_info.url = dest_url;
+
+      proxy_resolution_service->SetProxyDelegate(test_proxy_delegate.get());
+      Initialize(std::move(proxy_resolution_service));
+
+      // Start two requests. The first request should consume data from
+      // |socket_data_proxy_main_job| and |socket_data_direct_first_request|.
+      // The second request should consume data from
+      // |socket_data_direct_second_request|.
+
+      for (size_t i = 0; i < 2; ++i) {
+        ProxyInfo used_proxy_info;
+        EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _))
+            .Times(1)
+            .WillOnce(::testing::SaveArg<1>(&used_proxy_info));
+
+        std::unique_ptr<HttpStreamRequest> request =
+            CreateJobController(request_info);
+        RunUntilIdle();
+
+        // Verify that request was fetched without proxy.
+        EXPECT_TRUE(used_proxy_info.is_direct());
+
+        // The proxies that failed should now be known to the proxy service as
+        // bad.
+        const ProxyRetryInfoMap& retry_info =
+            session_->proxy_resolution_service()->proxy_retry_info();
+        if (!mock_error.triggers_ssl_connect_job_retry_logic) {
+          ASSERT_THAT(retry_info, SizeIs(2));
+          EXPECT_THAT(retry_info, Contains(Key("https://badproxy:99")));
+          EXPECT_THAT(retry_info, Contains(Key("https://badfallbackproxy:98")));
+        } else {
+          ASSERT_THAT(retry_info, SizeIs(1));
+          EXPECT_THAT(retry_info, Contains(Key("https://badproxy:99")));
+        }
+
+        // The idle socket should have been added back to the socket pool. Close
+        // it, so the next loop iteration creates a new socket instead of
+        // reusing the idle one.
+        auto* socket_pool = session_->GetSocketPool(
+            HttpNetworkSession::NORMAL_SOCKET_POOL, ProxyServer::Direct());
+        EXPECT_EQ(1, socket_pool->IdleSocketCount());
+        socket_pool->CloseIdleSockets("Close socket reason");
+      }
+      EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
+    }
+  }
+}
+
+// Test proxy fallback logic in the case connecting through socks5 proxy.
+TEST_F(JobControllerReconsiderProxyAfterErrorTest,
+       ReconsiderProxyAfterErrorSocks5Proxy) {
+  enum class ErrorPhase {
+    kHostResolution,
+    kTcpConnect,
+    kTunnelRead,
+  };
+
+  const struct {
+    ErrorPhase phase;
+    net::Error error;
+  } kRetriableErrors[] = {
+      // These largely correspond to the list of errors in
+      // CanFalloverToNextProxy() which can occur with an HTTPS proxy.
+      //
+      // Unlike HTTP/HTTPS proxies, SOCKS proxies are retried in response to
+      // `ERR_CONNECTION_CLOSED`.
+      {ErrorPhase::kHostResolution, ERR_NAME_NOT_RESOLVED},
+      {ErrorPhase::kTcpConnect, ERR_ADDRESS_UNREACHABLE},
+      {ErrorPhase::kTcpConnect, ERR_CONNECTION_TIMED_OUT},
+      {ErrorPhase::kTcpConnect, ERR_CONNECTION_RESET},
+      {ErrorPhase::kTcpConnect, ERR_CONNECTION_ABORTED},
+      {ErrorPhase::kTcpConnect, ERR_CONNECTION_REFUSED},
+      {ErrorPhase::kTunnelRead, ERR_TIMED_OUT},
+      {ErrorPhase::kTunnelRead, ERR_CONNECTION_CLOSED},
+  };
+
+  // "host" on port 80 matches the kSOCK5GreetRequest.
+  const GURL kDestUrl = GURL("http://host:80/");
+
+  for (const auto& mock_error : kRetriableErrors) {
+    SCOPED_TRACE(ErrorToString(mock_error.error));
+
+    CreateSessionDeps();
+
+    std::unique_ptr<ConfiguredProxyResolutionService> proxy_resolution_service =
+        ConfiguredProxyResolutionService::CreateFixedFromPacResult(
+            "SOCKS5 badproxy:99; SOCKS5 badfallbackproxy:98; DIRECT",
+            TRAFFIC_ANNOTATION_FOR_TESTS);
+    auto test_proxy_delegate = std::make_unique<TestProxyDelegate>();
+
+    // Before starting the test, verify that there are no proxies marked as bad.
+    ASSERT_TRUE(proxy_resolution_service->proxy_retry_info().empty());
+    const MockWrite kTunnelWrites[] = {
+        {ASYNC, kSOCKS5GreetRequest, kSOCKS5GreetRequestLength}};
+    std::vector<MockRead> reads;
+
+    // Generate identical errors for both the main proxy and the fallback proxy.
+    // No alternative job is created for either, so only need one data provider
+    // for each, when the request makes it to the socket layer.
+    std::unique_ptr<StaticSocketDataProvider> socket_data_proxy_main_job;
+    std::unique_ptr<StaticSocketDataProvider> socket_data_proxy_main_job2;
+    switch (mock_error.phase) {
+      case ErrorPhase::kHostResolution:
+        // Only ERR_NAME_NOT_RESOLVED can be returned by the mock host resolver.
+        DCHECK_EQ(ERR_NAME_NOT_RESOLVED, mock_error.error);
+        session_deps_.host_resolver->rules()->AddSimulatedFailure("badproxy");
+        session_deps_.host_resolver->rules()->AddSimulatedFailure(
+            "badfallbackproxy");
+        break;
+      case ErrorPhase::kTcpConnect:
+        socket_data_proxy_main_job =
+            std::make_unique<StaticSocketDataProvider>();
+        socket_data_proxy_main_job->set_connect_data(
+            MockConnect(ASYNC, mock_error.error));
+        socket_data_proxy_main_job2 =
+            std::make_unique<StaticSocketDataProvider>();
+        socket_data_proxy_main_job2->set_connect_data(
+            MockConnect(ASYNC, mock_error.error));
+        break;
+      case ErrorPhase::kTunnelRead:
+        reads.emplace_back(MockRead(ASYNC, mock_error.error));
+        socket_data_proxy_main_job =
+            std::make_unique<StaticSocketDataProvider>(reads, kTunnelWrites);
+        socket_data_proxy_main_job2 =
+            std::make_unique<StaticSocketDataProvider>(reads, kTunnelWrites);
+        break;
+    }
+
+    if (socket_data_proxy_main_job) {
+      session_deps_.socket_factory->AddSocketDataProvider(
+          socket_data_proxy_main_job.get());
+      session_deps_.socket_factory->AddSocketDataProvider(
+          socket_data_proxy_main_job2.get());
+    }
+
+    // After both proxies fail, the request should fall back to using DIRECT,
+    // and succeed.
+    StaticSocketDataProvider socket_data_direct_first_request;
+    socket_data_direct_first_request.set_connect_data(MockConnect(ASYNC, OK));
+    session_deps_.socket_factory->AddSocketDataProvider(
+        &socket_data_direct_first_request);
+
+    // Second request should use DIRECT, skipping the bad proxies, and succeed.
+    StaticSocketDataProvider socket_data_direct_second_request;
+    socket_data_direct_second_request.set_connect_data(MockConnect(ASYNC, OK));
+    session_deps_.socket_factory->AddSocketDataProvider(
+        &socket_data_direct_second_request);
+
+    // Now request a stream. It should succeed using the DIRECT fallback proxy
+    // option.
+    HttpRequestInfo request_info;
+    request_info.method = "GET";
+    request_info.url = kDestUrl;
+
+    proxy_resolution_service->SetProxyDelegate(test_proxy_delegate.get());
+    Initialize(std::move(proxy_resolution_service));
+
+    // Start two requests. The first request should consume data from
+    // |socket_data_proxy_main_job| and |socket_data_direct_first_request|. The
+    // second request should consume data from
+    // |socket_data_direct_second_request|.
+
+    for (size_t i = 0; i < 2; ++i) {
+      ProxyInfo used_proxy_info;
+      EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _))
+          .Times(1)
+          .WillOnce(::testing::SaveArg<1>(&used_proxy_info));
+
+      std::unique_ptr<HttpStreamRequest> request =
+          CreateJobController(request_info);
+      RunUntilIdle();
+
+      // Verify that request was fetched without proxy.
+      EXPECT_TRUE(used_proxy_info.is_direct());
+
+      // The proxies that failed should now be known to the proxy service as
+      // bad.
+      const ProxyRetryInfoMap& retry_info =
+          session_->proxy_resolution_service()->proxy_retry_info();
+      ASSERT_THAT(retry_info, SizeIs(2));
+      EXPECT_THAT(retry_info, Contains(Key("socks5://badproxy:99")));
+      EXPECT_THAT(retry_info, Contains(Key("socks5://badfallbackproxy:98")));
+
+      // The idle socket should have been added back to the socket pool. Close
+      // it, so the next loop iteration creates a new socket instead of reusing
+      // the idle one.
+      auto* socket_pool = session_->GetSocketPool(
+          HttpNetworkSession::NORMAL_SOCKET_POOL, ProxyServer::Direct());
+      EXPECT_EQ(1, socket_pool->IdleSocketCount());
+      socket_pool->CloseIdleSockets("Close socket reason");
+    }
+    EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
+  }
 }
 
 // Tests that ERR_MSG_TOO_BIG is retryable for QUIC proxy.
@@ -622,7 +1057,7 @@
   socket_data_direct.set_connect_data(MockConnect(ASYNC, OK));
   session_deps_.socket_factory->AddSocketDataProvider(&socket_data_direct);
 
-  // Now request a stream. It should fallback to DIRECT on ERR_MSG_TOO_BIG.
+  // Now request a stream. It should fall back to DIRECT on ERR_MSG_TOO_BIG.
   HttpRequestInfo request_info;
   request_info.method = "GET";
   request_info.url = GURL("http://www.example.com");
diff --git a/net/spdy/spdy_test_util_common.cc b/net/spdy/spdy_test_util_common.cc
index c58c95e..130c9ecf 100644
--- a/net/spdy/spdy_test_util_common.cc
+++ b/net/spdy/spdy_test_util_common.cc
@@ -328,8 +328,14 @@
       kDefaultInitialWindowSize;
 }
 
+SpdySessionDependencies::SpdySessionDependencies(SpdySessionDependencies&&) =
+    default;
+
 SpdySessionDependencies::~SpdySessionDependencies() = default;
 
+SpdySessionDependencies& SpdySessionDependencies::operator=(
+    SpdySessionDependencies&&) = default;
+
 // static
 std::unique_ptr<HttpNetworkSession> SpdySessionDependencies::SpdyCreateSession(
     SpdySessionDependencies* session_deps) {
diff --git a/net/spdy/spdy_test_util_common.h b/net/spdy/spdy_test_util_common.h
index fbf480e0..b1f2156 100644
--- a/net/spdy/spdy_test_util_common.h
+++ b/net/spdy/spdy_test_util_common.h
@@ -152,8 +152,12 @@
   explicit SpdySessionDependencies(
       std::unique_ptr<ProxyResolutionService> proxy_resolution_service);
 
+  SpdySessionDependencies(SpdySessionDependencies&&);
+
   ~SpdySessionDependencies();
 
+  SpdySessionDependencies& operator=(SpdySessionDependencies&&);
+
   HostResolver* GetHostResolver() {
     return alternate_host_resolver ? alternate_host_resolver.get()
                                    : host_resolver.get();
diff --git a/printing/backend/printing_info_win.cc b/printing/backend/printing_info_win.cc
index 6dd01ef..35e099ba 100644
--- a/printing/backend/printing_info_win.cc
+++ b/printing/backend/printing_info_win.cc
@@ -12,36 +12,37 @@
 
 namespace internal {
 
-uint8_t* GetDriverInfo(HANDLE printer, int level) {
+std::unique_ptr<uint8_t[]> GetDriverInfo(HANDLE printer, int level) {
   DWORD size = 0;
-  ::GetPrinterDriver(printer, NULL, level, NULL, 0, &size);
-  if (size == 0) {
-    return NULL;
-  }
-  std::unique_ptr<uint8_t[]> buffer(new uint8_t[size]);
+  ::GetPrinterDriver(printer, nullptr, level, nullptr, 0, &size);
+  if (size == 0)
+    return nullptr;
+
+  auto buffer = std::make_unique<uint8_t[]>(size);
   memset(buffer.get(), 0, size);
-  if (!::GetPrinterDriver(printer, NULL, level, buffer.get(), size, &size)) {
-    return NULL;
-  }
-  return buffer.release();
+  if (!::GetPrinterDriver(printer, nullptr, level, buffer.get(), size, &size))
+    return nullptr;
+
+  return buffer;
 }
 
-uint8_t* GetPrinterInfo(HANDLE printer, int level) {
+std::unique_ptr<uint8_t[]> GetPrinterInfo(HANDLE printer, int level) {
   DWORD size = 0;
-  ::GetPrinter(printer, level, NULL, 0, &size);
+  ::GetPrinter(printer, level, nullptr, 0, &size);
   if (size == 0) {
     LOG(WARNING) << "Failed to get size of PRINTER_INFO_" << level
                  << ", error = " << GetLastError();
-    return NULL;
+    return nullptr;
   }
-  std::unique_ptr<uint8_t[]> buffer(new uint8_t[size]);
+
+  auto buffer = std::make_unique<uint8_t[]>(size);
   memset(buffer.get(), 0, size);
   if (!::GetPrinter(printer, level, buffer.get(), size, &size)) {
     LOG(WARNING) << "Failed to get PRINTER_INFO_" << level
                  << ", error = " << GetLastError();
-    return NULL;
+    return nullptr;
   }
-  return buffer.release();
+  return buffer;
 }
 
 }  // namespace internal
diff --git a/printing/backend/printing_info_win.h b/printing/backend/printing_info_win.h
index 4aad87c..9b9298d 100644
--- a/printing/backend/printing_info_win.h
+++ b/printing/backend/printing_info_win.h
@@ -18,9 +18,9 @@
 namespace internal {
 
 COMPONENT_EXPORT(PRINT_BACKEND)
-uint8_t* GetDriverInfo(HANDLE printer, int level);
+std::unique_ptr<uint8_t[]> GetDriverInfo(HANDLE printer, int level);
 COMPONENT_EXPORT(PRINT_BACKEND)
-uint8_t* GetPrinterInfo(HANDLE printer, int level);
+std::unique_ptr<uint8_t[]> GetPrinterInfo(HANDLE printer, int level);
 
 // This class is designed to work with PRINTER_INFO_X structures
 // and calls GetPrinter internally with correctly allocated buffer.
@@ -28,7 +28,7 @@
 class PrinterInfo {
  public:
   bool Init(HANDLE printer) {
-    buffer_.reset(GetPrinterInfo(printer, level));
+    buffer_ = GetPrinterInfo(printer, level);
     return buffer_ != nullptr;
   }
 
@@ -46,7 +46,7 @@
 class DriverInfo {
  public:
   bool Init(HANDLE printer) {
-    buffer_.reset(GetDriverInfo(printer, level));
+    buffer_ = GetDriverInfo(printer, level);
     return buffer_ != nullptr;
   }
 
@@ -60,10 +60,10 @@
 
 }  // namespace internal
 
-typedef internal::PrinterInfo<PRINTER_INFO_2, 2> PrinterInfo2;
-typedef internal::PrinterInfo<PRINTER_INFO_5, 5> PrinterInfo5;
+using PrinterInfo2 = internal::PrinterInfo<PRINTER_INFO_2, 2>;
+using PrinterInfo5 = internal::PrinterInfo<PRINTER_INFO_5, 5>;
 
-typedef internal::DriverInfo<DRIVER_INFO_6, 6> DriverInfo6;
+using DriverInfo6 = internal::DriverInfo<DRIVER_INFO_6, 6>;
 
 }  // namespace printing
 
diff --git a/remoting/host/BUILD.gn b/remoting/host/BUILD.gn
index 79727ee3..afe74a2 100644
--- a/remoting/host/BUILD.gn
+++ b/remoting/host/BUILD.gn
@@ -258,6 +258,8 @@
     "desktop_display_info.cc",
     "desktop_display_info.h",
     "desktop_display_info_loader.h",
+    "desktop_display_info_monitor.cc",
+    "desktop_display_info_monitor.h",
     "desktop_process.cc",
     "desktop_process.h",
     "desktop_resizer.h",
diff --git a/remoting/host/desktop_capturer_proxy.cc b/remoting/host/desktop_capturer_proxy.cc
index 54c5f5d..f5b8698 100644
--- a/remoting/host/desktop_capturer_proxy.cc
+++ b/remoting/host/desktop_capturer_proxy.cc
@@ -133,20 +133,13 @@
     scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
     base::WeakPtr<ClientSessionControl> client_session_control)
     : capture_task_runner_(capture_task_runner),
-      ui_task_runner_(ui_task_runner),
       client_session_control_(client_session_control),
-      desktop_display_info_loader_(DesktopDisplayInfoLoader::Create()) {
+      desktop_display_info_monitor_(ui_task_runner, client_session_control) {
   core_ = std::make_unique<Core>(weak_factory_.GetWeakPtr());
-  ui_task_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&DesktopDisplayInfoLoader::Init,
-                     base::Unretained(desktop_display_info_loader_.get())));
 }
 
 DesktopCapturerProxy::~DesktopCapturerProxy() {
   capture_task_runner_->DeleteSoon(FROM_HERE, core_.release());
-  ui_task_runner_->DeleteSoon(FROM_HERE,
-                              desktop_display_info_loader_.release());
 }
 
 void DesktopCapturerProxy::CreateCapturer(
@@ -201,11 +194,7 @@
 bool DesktopCapturerProxy::SelectSource(SourceId id_index) {
   DCHECK(thread_checker_.CalledOnValidThread());
 
-  const DisplayGeometry* display =
-      desktop_display_info_.GetDisplayInfo(id_index);
-
-  SourceId id = (display ? display->id : webrtc::kFullDesktopScreenId);
-
+  SourceId id = desktop_display_info_monitor_.SourceIdFromIndex(id_index);
   capture_task_runner_->PostTask(
       FROM_HERE,
       base::BindOnce(&Core::SelectSource, base::Unretained(core_.get()), id));
@@ -220,38 +209,8 @@
   callback_->OnCaptureResult(result, std::move(frame));
 
   if (client_session_control_) {
-    ui_task_runner_->PostTaskAndReplyWithResult(
-        FROM_HERE,
-        base::BindOnce(&DesktopDisplayInfoLoader::GetCurrentDisplayInfo,
-                       base::Unretained(desktop_display_info_loader_.get())),
-        base::BindOnce(&DesktopCapturerProxy::OnDisplayInfoLoaded,
-                       weak_factory_.GetWeakPtr()));
+    desktop_display_info_monitor_.QueryDisplayInfo();
   }
 }
 
-void DesktopCapturerProxy::OnDisplayInfoLoaded(DesktopDisplayInfo info) {
-  DCHECK(thread_checker_.CalledOnValidThread());
-
-  if (!client_session_control_ || desktop_display_info_ == info) {
-    return;
-  }
-
-  desktop_display_info_ = std::move(info);
-
-  auto layout = std::make_unique<protocol::VideoLayout>();
-  LOG(INFO) << "DCP::OnDisplayInfoLoaded";
-  for (const auto& display : desktop_display_info_.displays()) {
-    protocol::VideoTrackLayout* track = layout->add_video_track();
-    track->set_position_x(display.x);
-    track->set_position_y(display.y);
-    track->set_width(display.width);
-    track->set_height(display.height);
-    track->set_x_dpi(display.dpi);
-    track->set_y_dpi(display.dpi);
-    LOG(INFO) << "   Display: " << display.x << "," << display.y << " "
-              << display.width << "x" << display.height << " @ " << display.dpi;
-  }
-  client_session_control_->OnDesktopDisplayChanged(std::move(layout));
-}
-
 }  // namespace remoting
diff --git a/remoting/host/desktop_capturer_proxy.h b/remoting/host/desktop_capturer_proxy.h
index e17796d..d621432 100644
--- a/remoting/host/desktop_capturer_proxy.h
+++ b/remoting/host/desktop_capturer_proxy.h
@@ -11,8 +11,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/threading/thread_checker.h"
-#include "remoting/host/desktop_display_info.h"
-#include "remoting/host/desktop_display_info_loader.h"
+#include "remoting/host/desktop_display_info_monitor.h"
 #include "third_party/webrtc/modules/desktop_capture/desktop_capturer.h"
 
 namespace base {
@@ -63,13 +62,11 @@
 
   void OnFrameCaptured(webrtc::DesktopCapturer::Result result,
                        std::unique_ptr<webrtc::DesktopFrame> frame);
-  void OnDisplayInfoLoaded(DesktopDisplayInfo info);
 
   base::ThreadChecker thread_checker_;
 
   std::unique_ptr<Core> core_;
   scoped_refptr<base::SingleThreadTaskRunner> capture_task_runner_;
-  scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_;
 
   raw_ptr<webrtc::DesktopCapturer::Callback> callback_;
 
@@ -78,11 +75,8 @@
   // the same process as the DesktopCapturerProxy.
   base::WeakPtr<ClientSessionControl> client_session_control_;
 
-  // Contains the most recently gathered info about the desktop displays.
-  DesktopDisplayInfo desktop_display_info_;
-
-  // Created on the calling thread, but accessed and destroyed on the UI thread.
-  std::unique_ptr<DesktopDisplayInfoLoader> desktop_display_info_loader_;
+  // Monitors and stores info about the desktop displays.
+  DesktopDisplayInfoMonitor desktop_display_info_monitor_;
 
   base::WeakPtrFactory<DesktopCapturerProxy> weak_factory_{this};
 };
diff --git a/remoting/host/desktop_display_info_monitor.cc b/remoting/host/desktop_display_info_monitor.cc
new file mode 100644
index 0000000..f3a7376
--- /dev/null
+++ b/remoting/host/desktop_display_info_monitor.cc
@@ -0,0 +1,98 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "remoting/host/desktop_display_info_monitor.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/time/time.h"
+#include "remoting/base/logging.h"
+#include "remoting/host/client_session_control.h"
+
+namespace remoting {
+
+namespace {
+
+// Polling interval for querying the OS for changes to the multi-monitor
+// configuration. Before this class was written, the DesktopCapturerProxy would
+// poll after each captured frame, which could occur up to 30x per second. The
+// value chosen here is slower than this (to reduce the load on the OS), but
+// still fast enough to be responsive to any changes.
+constexpr base::TimeDelta kPollingInterval = base::Milliseconds(100);
+
+}  // namespace
+
+DesktopDisplayInfoMonitor::DesktopDisplayInfoMonitor(
+    scoped_refptr<base::SequencedTaskRunner> ui_task_runner,
+    base::WeakPtr<ClientSessionControl> client_session_control)
+    : ui_task_runner_(ui_task_runner),
+      client_session_control_(client_session_control),
+      desktop_display_info_loader_(DesktopDisplayInfoLoader::Create()) {
+  // The loader must be initialized and used on the UI thread (though it can be
+  // created on any thread).
+  ui_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(&DesktopDisplayInfoLoader::Init,
+                     base::Unretained(desktop_display_info_loader_.get())));
+}
+
+DesktopDisplayInfoMonitor::~DesktopDisplayInfoMonitor() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  ui_task_runner_->DeleteSoon(FROM_HERE,
+                              desktop_display_info_loader_.release());
+}
+
+void DesktopDisplayInfoMonitor::Start() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  timer_.Start(FROM_HERE, kPollingInterval, this,
+               &DesktopDisplayInfoMonitor::QueryDisplayInfo);
+}
+
+void DesktopDisplayInfoMonitor::QueryDisplayInfo() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (client_session_control_) {
+    ui_task_runner_->PostTaskAndReplyWithResult(
+        FROM_HERE,
+        base::BindOnce(&DesktopDisplayInfoLoader::GetCurrentDisplayInfo,
+                       base::Unretained(desktop_display_info_loader_.get())),
+        base::BindOnce(&DesktopDisplayInfoMonitor::OnDisplayInfoLoaded,
+                       weak_factory_.GetWeakPtr()));
+  }
+}
+
+webrtc::DesktopCapturer::SourceId DesktopDisplayInfoMonitor::SourceIdFromIndex(
+    int index) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  const DisplayGeometry* display = desktop_display_info_.GetDisplayInfo(index);
+  return (display ? display->id : webrtc::kFullDesktopScreenId);
+}
+
+void DesktopDisplayInfoMonitor::OnDisplayInfoLoaded(DesktopDisplayInfo info) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (!client_session_control_ || desktop_display_info_ == info) {
+    return;
+  }
+
+  desktop_display_info_ = std::move(info);
+
+  auto layout = std::make_unique<protocol::VideoLayout>();
+  HOST_LOG << "DDIM::OnDisplayInfoLoaded";
+  for (const auto& display : desktop_display_info_.displays()) {
+    protocol::VideoTrackLayout* track = layout->add_video_track();
+    track->set_position_x(display.x);
+    track->set_position_y(display.y);
+    track->set_width(display.width);
+    track->set_height(display.height);
+    track->set_x_dpi(display.dpi);
+    track->set_y_dpi(display.dpi);
+    HOST_LOG << "   Display: " << display.x << "," << display.y << " "
+             << display.width << "x" << display.height << " @ " << display.dpi;
+  }
+  client_session_control_->OnDesktopDisplayChanged(std::move(layout));
+}
+
+}  // namespace remoting
diff --git a/remoting/host/desktop_display_info_monitor.h b/remoting/host/desktop_display_info_monitor.h
new file mode 100644
index 0000000..28515a24
--- /dev/null
+++ b/remoting/host/desktop_display_info_monitor.h
@@ -0,0 +1,88 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_HOST_DESKTOP_DISPLAY_INFO_MONITOR_H_
+#define REMOTING_HOST_DESKTOP_DISPLAY_INFO_MONITOR_H_
+
+#include <memory>
+
+#include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "base/timer/timer.h"
+#include "remoting/host/desktop_display_info.h"
+#include "remoting/host/desktop_display_info_loader.h"
+
+namespace base {
+class SequencedTaskRunner;
+}  // namespace base
+
+namespace remoting {
+
+class ClientSessionControl;
+
+// This class regularly queries the OS for any changes to the multi-monitor
+// display configuration, and reports any changes to the ClientSession.
+// This class ensures that the DisplayInfo is fetched on the UI thread, which
+// may be different from the calling thread. This is helpful on platforms where
+// REMOTING_MULTI_PROCESS == false, allowing this class to be used on the
+// network thread. When REMOTING_MULTI_PROCESS == true, this instance lives in
+// the Desktop process.
+class DesktopDisplayInfoMonitor {
+ public:
+  DesktopDisplayInfoMonitor(
+      scoped_refptr<base::SequencedTaskRunner> ui_task_runner,
+      base::WeakPtr<ClientSessionControl> client_session_control);
+
+  DesktopDisplayInfoMonitor(const DesktopDisplayInfoMonitor&) = delete;
+  DesktopDisplayInfoMonitor& operator=(const DesktopDisplayInfoMonitor&) =
+      delete;
+
+  virtual ~DesktopDisplayInfoMonitor();
+
+  // Begins continuous monitoring for changes. Any changes to the monitor layout
+  // will be reported to the ClientSessionControl.
+  void Start();
+
+  // Queries the OS immediately for the current monitor layout and reports any
+  // changed display info to the ClientSessionControl. If this instance is
+  // associated with a DesktopCapturerProxy, this method could be used to
+  // query the display info on each captured frame.
+  void QueryDisplayInfo();
+
+  // Needed by DesktopCapturerProxy to convert an index into the monitor-list
+  // to an ID that can be passed to the real capturer's SelectSource(). Returns
+  // webrtc::kFullDesktopScreenId if an out-of-bounds index is provided.
+  // TODO(lambroslambrou): Remove this method and fix
+  // DesktopCapturerProxy::SelectSource() to accept a monitor SourceId instead
+  // of an array-index.
+  webrtc::DesktopCapturer::SourceId SourceIdFromIndex(int index) const;
+
+ private:
+  void OnDisplayInfoLoaded(DesktopDisplayInfo info);
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  scoped_refptr<base::SequencedTaskRunner> ui_task_runner_;
+
+  // Object which receives DesktopDisplayInfo updates.
+  base::WeakPtr<ClientSessionControl> client_session_control_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+
+  // Contains the most recently gathered info about the desktop displays.
+  DesktopDisplayInfo desktop_display_info_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+
+  // Created on the calling thread, but accessed and destroyed on the UI thread.
+  std::unique_ptr<DesktopDisplayInfoLoader> desktop_display_info_loader_;
+
+  // Timer to regularly poll |desktop_display_info_loader_| for updates.
+  base::RepeatingTimer timer_ GUARDED_BY_CONTEXT(sequence_checker_);
+
+  base::WeakPtrFactory<DesktopDisplayInfoMonitor> weak_factory_{this};
+};
+
+}  // namespace remoting
+
+#endif  // REMOTING_HOST_DESKTOP_DISPLAY_INFO_MONITOR_H_
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index fa831b9..ba15360 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -809,6 +809,24 @@
             ]
         }
     ],
+    "ArcLogdSizeConfig": [
+        {
+            "platforms": [
+                "chromeos"
+            ],
+            "experiments": [
+                {
+                    "name": "EnabledGroup512K_20211210",
+                    "params": {
+                        "size": "512"
+                    },
+                    "enable_features": [
+                        "ArcGuestLogdConfig"
+                    ]
+                }
+            ]
+        }
+    ],
     "ArcVmBlockSize": [
         {
             "platforms": [
@@ -1171,7 +1189,7 @@
             ],
             "experiments": [
                 {
-                    "name": "Enabled_2021-12-01",
+                    "name": "Enabled",
                     "enable_features": [
                         "AutofillEnableSupportForMoreStructureInAddresses"
                     ]
@@ -1208,7 +1226,7 @@
             ],
             "experiments": [
                 {
-                    "name": "Enabled_2021-12-01",
+                    "name": "Enabled",
                     "enable_features": [
                         "AutofillEnableSupportForMoreStructureInNames"
                     ]
@@ -2165,6 +2183,25 @@
             ]
         }
     ],
+    "ChromeOSRawPSIMetrics": [
+        {
+            "platforms": [
+                "chromeos"
+            ],
+            "experiments": [
+                {
+                    "name": "EnabledGroup10s_20220107",
+                    "params": {
+                        "period": "10"
+                    },
+                    "enable_features": [
+                        "ArcVmMemoryPSIReports",
+                        "MemoryPressureMetricsDetail"
+                    ]
+                }
+            ]
+        }
+    ],
     "ChromeOSReinstallApps": [
         {
             "platforms": [
@@ -8396,6 +8433,26 @@
             ]
         }
     ],
+    "V8WasmDynamicTiering": [
+        {
+            "platforms": [
+                "android",
+                "chromeos",
+                "chromeos_lacros",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled_20211210",
+                    "enable_features": [
+                        "WebAssemblyDynamicTiering"
+                    ]
+                }
+            ]
+        }
+    ],
     "V8WasmMemoryProtection": [
         {
             "platforms": [
diff --git a/third_party/blink/public/mojom/interest_group/ad_auction_service.mojom b/third_party/blink/public/mojom/interest_group/ad_auction_service.mojom
index f4046a7..0217233d 100644
--- a/third_party/blink/public/mojom/interest_group/ad_auction_service.mojom
+++ b/third_party/blink/public/mojom/interest_group/ad_auction_service.mojom
@@ -59,12 +59,4 @@
   // response. The JSON `name`, `owner`, `userBiddingSignals` and other unknown
   // fields will be ignored.
   UpdateAdInterestGroups();
-
-  // Gets the true URL from a URN returned from RunAdAuction. This function
-  // will be removed once all FLEDGE auctions switch to using fenced frames.
-  // The uuid_url should have the format format
-  // "urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" as per RFC-4122.
-  // TODO(crbug.com/1253118): Remove this function when we remove support for
-  // showing FLEDGE ads in iframes.
-  DeprecatedGetURLFromURN(url.mojom.Url uuid_url) => (url.mojom.Url? decoded_url);
 };
diff --git a/third_party/blink/public/mojom/web_feature/web_feature.mojom b/third_party/blink/public/mojom/web_feature/web_feature.mojom
index df061b9..f8e75880 100644
--- a/third_party/blink/public/mojom/web_feature/web_feature.mojom
+++ b/third_party/blink/public/mojom/web_feature/web_feature.mojom
@@ -3413,7 +3413,7 @@
   kV8UDPSocket_Close_Method = 4104,
   kHTMLInputElementSimulatedClick = 4105,
   kRTCLocalSdpModificationIceUfragPwd = 4106,
-  kV8Navigator_DeprecatedURNToURL_Method = 4107,
+  kWebNfcNdefMakeReadOnly = 4107,
 
 
   // Add new features immediately above this line. Don't change assigned
diff --git a/third_party/blink/renderer/bindings/core/v8/script_function.cc b/third_party/blink/renderer/bindings/core/v8/script_function.cc
index 3dd61e9..2203329 100644
--- a/third_party/blink/renderer/bindings/core/v8/script_function.cc
+++ b/third_party/blink/renderer/bindings/core/v8/script_function.cc
@@ -30,45 +30,6 @@
 
 }  // namespace
 
-void ScriptFunction::Trace(Visitor* visitor) const {
-  visitor->Trace(script_state_);
-  CustomWrappableAdapter::Trace(visitor);
-}
-
-v8::Local<v8::Function> ScriptFunction::BindToV8Function(int length) {
-#if DCHECK_IS_ON()
-  DCHECK(!bind_to_v8_function_already_called_);
-  bind_to_v8_function_already_called_ = true;
-#endif
-
-  v8::Local<v8::Object> wrapper = CreateAndInitializeWrapper(script_state_);
-  // The wrapper is held alive by the CallHandlerInfo internally in V8 as long
-  // as the function is alive.
-  return v8::Function::New(script_state_->GetContext(), CallCallback, wrapper,
-                           length, v8::ConstructorBehavior::kThrow)
-      .ToLocalChecked();
-}
-
-ScriptValue ScriptFunction::Call(ScriptValue) {
-  NOTREACHED();
-  return ScriptValue();
-}
-
-void ScriptFunction::CallRaw(const v8::FunctionCallbackInfo<v8::Value>& args) {
-  ScriptValue result =
-      Call(ScriptValue(GetScriptState()->GetIsolate(), args[0]));
-  V8SetReturnValue(args, result.V8Value());
-}
-
-void ScriptFunction::CallCallback(
-    const v8::FunctionCallbackInfo<v8::Value>& args) {
-  RUNTIME_CALL_TIMER_SCOPE_DISABLED_BY_DEFAULT(args.GetIsolate(),
-                                               "Blink_CallCallback");
-  ScriptFunction* script_function = static_cast<ScriptFunction*>(
-      ToCustomWrappable(v8::Local<v8::Object>::Cast(args.Data())));
-  script_function->CallRaw(args);
-}
-
 ScriptValue NewScriptFunction::Callable::Call(ScriptState*, ScriptValue) {
   NOTREACHED();
   return ScriptValue();
diff --git a/third_party/blink/renderer/bindings/core/v8/script_function.h b/third_party/blink/renderer/bindings/core/v8/script_function.h
index 8c07471..1f39cdbb 100644
--- a/third_party/blink/renderer/bindings/core/v8/script_function.h
+++ b/third_party/blink/renderer/bindings/core/v8/script_function.h
@@ -42,43 +42,6 @@
 
 namespace blink {
 
-// DEPRECATED: Use NewScriptFunction.
-class CORE_EXPORT ScriptFunction : public CustomWrappableAdapter {
- public:
-  ~ScriptFunction() override = default;
-
-  void Trace(Visitor*) const override;
-
-  const char* NameInHeapSnapshot() const override { return "ScriptFunction"; }
-
- protected:
-  explicit ScriptFunction(ScriptState* script_state)
-      : script_state_(script_state) {}
-
-  ScriptState* GetScriptState() const { return script_state_; }
-
-  // It is not usually necessary to set |length| unless the function is exposed
-  // to JavaScript.
-  v8::Local<v8::Function> BindToV8Function(int length = 0);
-
- private:
-  // Subclasses should implement one of Call() or CallRaw(). Most will implement
-  // Call().
-  virtual ScriptValue Call(ScriptValue);
-
-  // To support more than one argument, or for low-level access to the V8 API,
-  // implement CallRaw(). The default implementation delegates to Call().
-  virtual void CallRaw(const v8::FunctionCallbackInfo<v8::Value>&);
-
-  static void CallCallback(const v8::FunctionCallbackInfo<v8::Value>&);
-
-  Member<ScriptState> script_state_;
-#if DCHECK_IS_ON()
-  // BindToV8Function() must not be called twice.
-  bool bind_to_v8_function_already_called_ = false;
-#endif
-};
-
 // A `NewScriptFunction` represents a function that can be called from scripts.
 // You can define a subclass of `Callable` and put arbitrary logic by
 // overriding `Call` or `CallRaw` methods.
diff --git a/third_party/blink/renderer/build/scripts/core/css/parser/make_proto.py b/third_party/blink/renderer/build/scripts/core/css/parser/make_proto.py
new file mode 100644
index 0000000..1b84d3e
--- /dev/null
+++ b/third_party/blink/renderer/build/scripts/core/css/parser/make_proto.py
@@ -0,0 +1,80 @@
+# Copyright (c) 2022 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+from core.css import css_properties
+import json5_generator
+import template_expander
+
+
+class CSSProtoWriter(json5_generator.Writer):
+    def __init__(self, json5_file_paths, output_dir):
+        super(CSSProtoWriter, self).__init__(None, output_dir)
+        assert len(json5_file_paths) == 4, \
+            "Needs css_properties.json5, computed_style_field_aliases.json5," \
+            " runtime_enabled_features.json5, and css_value_keywords.json5."
+        self._input_files = json5_file_paths
+        self._outputs = {
+            'css.proto': self.generate_proto,
+            'css_proto_converter_generated.h': self.generate_cc,
+        }
+        self._all_properties = css_properties.CSSProperties(
+            json5_file_paths[:3]).properties_including_aliases
+        self._keywords = sorted([
+            keyword['name']
+            for keyword in json5_generator.Json5File.load_from_files(
+                [json5_file_paths[3]]).name_dictionaries
+        ])
+
+    @template_expander.use_jinja(
+        'core/css/parser/templates/css_proto_converter_generated.h.tmpl')
+    def generate_cc(self):
+        return {
+            'input_files':
+            self._input_files,
+            'property_names':
+            '\n'.join('  "%s",' % property['name'].original
+                      for property in self._all_properties),
+            'value_keywords':
+            '\n'.join('  "%s",' % keyword.original
+                      for keyword in self._keywords),
+        }
+
+    @template_expander.use_jinja('core/css/parser/templates/css.proto.tmpl')
+    def generate_proto(self):
+        property_symbols = []
+        for i, property in enumerate(self._all_properties):
+            symbol = property['name'].to_macro_case()
+            if symbol == 'OVERFLOW':  # Conflicts with a system header
+                symbol = 'OVERFLOW_'
+            property_symbols.append('    %s = %d;' % (symbol, i + 1))
+        property_symbols.append('    INVALID_PROPERTY = %d;' %
+                                (len(self._all_properties) + 1))
+
+        keyword_symbols = []
+        for i, keyword in enumerate(self._keywords):
+            symbol = keyword.to_macro_case()
+            if keyword.original == '-infinity':
+                # Conflicts with a system header
+                symbol = 'NEGATIVE_INFINITY'
+            elif keyword.original == 'infinity':
+                # Conflicts with a system header
+                symbol = 'POSITIVE_INFINITY'
+            elif keyword.original == 'unset':
+                # Conflicts with PropertyAndValue::Prio::UNSET.
+                symbol = 'UNSET_'
+            elif keyword.original == 'nan':
+                # Conflicts with a system header
+                symbol = 'NOT_A_NUMBER'
+            keyword_symbols.append('    %s = %d;' % (symbol, i + 1))
+        keyword_symbols.append('    INVALID_VALUE = %d;' %
+                               (len(self._keywords) + 1))
+
+        return {
+            'property_proto_enums': '\n'.join(property_symbols),
+            'value_proto_enums': '\n'.join(keyword_symbols),
+        }
+
+
+if __name__ == "__main__":
+    json5_generator.Maker(CSSProtoWriter).main()
diff --git a/third_party/blink/renderer/build/scripts/core/css/parser/templates/css.proto.tmpl b/third_party/blink/renderer/build/scripts/core/css/parser/templates/css.proto.tmpl
new file mode 100644
index 0000000..f19ea1f2
--- /dev/null
+++ b/third_party/blink/renderer/build/scripts/core/css/parser/templates/css.proto.tmpl
@@ -0,0 +1,1153 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Based on the grammar provided here: https://goo.gl/svLze7
+// Most top level definitions here (mostly messages, but also the enum "h", is
+// named after a rule or token in the grammar. The messages that aren't were
+// implemented to hack around shortcomings in the protobuf format (such as
+// "StringCharOrQuote"). These can be identified because they are preceded by
+// a comment that says "Not in grammar".
+
+syntax = "proto2";
+
+package css_proto_converter;
+
+// Tokens
+// TODO(metzman): Implement CDO, CDC, UNICODERANGE, and range.
+// The following tokens are implmented in code and do not have their own
+// message: INCLUDES, DASHMATCH, HASH, STRING IMPORT_SYM, PAGE_SYM, MEDIA_SYM,
+// FONT_FACE_SYM, CHARSET_SYM, NAMESPACE_SYM, IMPORTANT_SYM, EMS, EXS, NUMBER,
+// and PERCENTAGE.
+// DIMEN not implemented since it isnt used in any production.
+
+// This is named "h" because it represents the "h" token in the grammar this is
+// based off of.
+enum H {
+  ZERO = 48;
+  ONE = 49;
+  TWO = 50;
+  THREE = 51;
+  FOUR = 52;
+  FIVE = 53;
+  SIX = 54;
+  SEVEN = 55;
+  EIGHT = 56;
+  NINE = 57;
+  A_UPPER = 65;
+  B_UPPER = 66;
+  C_UPPER = 67;
+  D_UPPER = 68;
+  E_UPPER = 69;
+  F_UPPER = 70;
+  A_LOWER = 97;
+  B_LOWER = 98;
+  C_LOWER = 99;
+  D_LOWER = 100;
+  E_LOWER = 101;
+  F_LOWER = 102;
+}
+
+// TODO(metzman): Add "nonascii" token from grammar.
+
+message Unicode {
+  required H ascii_value_1 = 1;
+  optional H ascii_value_2 = 2;
+  optional H ascii_value_3 = 3;
+  optional H ascii_value_4 = 4;
+  optional H ascii_value_5 = 5;
+  optional H ascii_value_6 = 6;
+  optional UnrepeatedW unrepeated_w = 7;
+}
+
+// unicode | '\' [#x20-#x7E#x80-#xD7FF#xE000-#xFFFD#x10000-#x10FFFF]
+message Escape {
+  // #x20-#x7E
+  enum AsciiValue {
+    SPACE = 32;
+    EXCLAMATION_POINT = 33;
+    DOUBLE_QUOTE = 34;
+    HASH = 35;
+    DOLLAR = 36;
+    PERCENT = 37;
+    AMPERSAND = 38;
+    APOSTROPHE = 39;
+    OPEN_PAREN = 40;
+    CLOSE_PAREN = 41;
+    STAR = 42;
+    PLUS = 43;
+    COMMA = 44;
+    MINUS = 45;
+    DOT = 46;
+    SLASH = 47;
+    ZERO = 48;
+    ONE = 49;
+    TWO = 50;
+    THREE = 51;
+    FOUR = 52;
+    FIVE = 53;
+    SIX = 54;
+    SEVEN = 55;
+    EIGHT = 56;
+    NINE = 57;
+    COLON = 58;
+    SEMI_COLON = 59;
+    LESS_THAN = 60;
+    EQUAL = 61;
+    GREATER_THAN = 62;
+    QUESTION = 63;
+    AT_SIGN = 64;
+    A_UPPER = 65;
+    B_UPPER = 66;
+    C_UPPER = 67;
+    D_UPPER = 68;
+    E_UPPER = 69;
+    F_UPPER = 70;
+    G_UPPER = 71;
+    H_UPPER = 72;
+    I_UPPER = 73;
+    J_UPPER = 74;
+    K_UPPER = 75;
+    L_UPPER = 76;
+    M_UPPER = 77;
+    N_UPPER = 78;
+    O_UPPER = 79;
+    P_UPPER = 80;
+    Q_UPPER = 81;
+    R_UPPER = 82;
+    S_UPPER = 83;
+    T_UPPER = 84;
+    U_UPPER = 85;
+    V_UPPER = 86;
+    W_UPPER = 87;
+    X_UPPER = 88;
+    Y_UPPER = 89;
+    Z_UPPER = 90;
+    OPEN_BRACKET = 91;
+    BACKSLASH = 92;
+    CLOSE_BRACKET = 93;
+    CARET = 94;
+    UNDERSCORE = 95;
+    BACKTICK = 96;
+    A_LOWER = 97;
+    B_LOWER = 98;
+    C_LOWER = 99;
+    D_LOWER = 100;
+    E_LOWER = 101;
+    F_LOWER = 102;
+    G_LOWER = 103;
+    H_LOWER = 104;
+    I_LOWER = 105;
+    J_LOWER = 106;
+    K_LOWER = 107;
+    L_LOWER = 108;
+    M_LOWER = 109;
+    N_LOWER = 110;
+    O_LOWER = 111;
+    P_LOWER = 112;
+    Q_LOWER = 113;
+    R_LOWER = 114;
+    S_LOWER = 115;
+    T_LOWER = 116;
+    U_LOWER = 117;
+    V_LOWER = 118;
+    W_LOWER = 119;
+    X_LOWER = 120;
+    Y_LOWER = 121;
+    Z_LOWER = 122;
+    OPEN_CURLY_BRACE = 123;
+    PIPE = 124;
+    CLOSE_CURLY_BRACE_ = 125;
+    TILDE = 126;
+  }
+
+  oneof rhs {
+    Unicode unicode = 1;
+    AsciiValue ascii_value = 2;
+  }
+  // TODO(metzman): Determine if we care about unicode points not covered here.
+}
+
+message Nmstart {
+  enum AsciiValue {
+    A_UPPER = 65;
+    B_UPPER = 66;
+    C_UPPER = 67;
+    D_UPPER = 68;
+    E_UPPER = 69;
+    F_UPPER = 70;
+    G_UPPER = 71;
+    H_UPPER = 72;
+    I_UPPER = 73;
+    J_UPPER = 74;
+    K_UPPER = 75;
+    L_UPPER = 76;
+    M_UPPER = 77;
+    N_UPPER = 78;
+    O_UPPER = 79;
+    P_UPPER = 80;
+    Q_UPPER = 81;
+    R_UPPER = 82;
+    S_UPPER = 83;
+    T_UPPER = 84;
+    U_UPPER = 85;
+    V_UPPER = 86;
+    W_UPPER = 87;
+    X_UPPER = 88;
+    Y_UPPER = 89;
+    Z_UPPER = 90;
+    A_LOWER = 97;
+    B_LOWER = 98;
+    C_LOWER = 99;
+    D_LOWER = 100;
+    E_LOWER = 101;
+    F_LOWER = 102;
+    G_LOWER = 103;
+    H_LOWER = 104;
+    I_LOWER = 105;
+    J_LOWER = 106;
+    K_LOWER = 107;
+    L_LOWER = 108;
+    M_LOWER = 109;
+    N_LOWER = 110;
+    O_LOWER = 111;
+    P_LOWER = 112;
+    Q_LOWER = 113;
+    R_LOWER = 114;
+    S_LOWER = 115;
+    T_LOWER = 116;
+    U_LOWER = 117;
+    V_LOWER = 118;
+    W_LOWER = 119;
+    X_LOWER = 120;
+    Y_LOWER = 121;
+    Z_LOWER = 122;
+  }
+
+  oneof rhs {
+    AsciiValue ascii_value = 1;
+    Escape escape = 2;
+    // TODO(metzman): Add nonascii token once (if) I implement it.
+  }
+}
+
+message Nmchar {
+  enum AsciiValue {
+    MINUS = 45;
+    ZERO = 48;
+    ONE = 49;
+    TWO = 50;
+    THREE = 51;
+    FOUR = 52;
+    FIVE = 53;
+    SIX = 54;
+    SEVEN = 55;
+    EIGHT = 56;
+    NINE = 57;
+    A_UPPER = 65;
+    B_UPPER = 66;
+    C_UPPER = 67;
+    D_UPPER = 68;
+    E_UPPER = 69;
+    F_UPPER = 70;
+    G_UPPER = 71;
+    H_UPPER = 72;
+    I_UPPER = 73;
+    J_UPPER = 74;
+    K_UPPER = 75;
+    L_UPPER = 76;
+    M_UPPER = 77;
+    N_UPPER = 78;
+    O_UPPER = 79;
+    P_UPPER = 80;
+    Q_UPPER = 81;
+    R_UPPER = 82;
+    S_UPPER = 83;
+    T_UPPER = 84;
+    U_UPPER = 85;
+    V_UPPER = 86;
+    W_UPPER = 87;
+    X_UPPER = 88;
+    Y_UPPER = 89;
+    Z_UPPER = 90;
+    A_LOWER = 97;
+    B_LOWER = 98;
+    C_LOWER = 99;
+    D_LOWER = 100;
+    E_LOWER = 101;
+    F_LOWER = 102;
+    G_LOWER = 103;
+    H_LOWER = 104;
+    I_LOWER = 105;
+    J_LOWER = 106;
+    K_LOWER = 107;
+    L_LOWER = 108;
+    M_LOWER = 109;
+    N_LOWER = 110;
+    O_LOWER = 111;
+    P_LOWER = 112;
+    Q_LOWER = 113;
+    R_LOWER = 114;
+    S_LOWER = 115;
+    T_LOWER = 116;
+    U_LOWER = 117;
+    V_LOWER = 118;
+    W_LOWER = 119;
+    X_LOWER = 120;
+    Y_LOWER = 121;
+    Z_LOWER = 122;
+  }
+  oneof rhs {
+    AsciiValue ascii_value = 1;
+    Escape escape = 2;
+    // TODO(metzman): Add nonascii token once (if) I implement it.
+  }
+}
+
+// string1, string2 and string
+message String {
+  // TODO(metzman): determine if these hacks are more efficient than enforcing
+  // constraints on string types in the actual code.
+  required bool use_single_quotes = 1;
+  repeated StringCharOrQuote string_char_quotes = 2;
+}
+
+// Not in grammar.
+message StringCharOrQuote {
+  enum QuoteChar {
+    UNSET = 0;
+    IS_SET = 1;
+  }
+  oneof rhs {
+    StringChar string_char = 2;
+    QuoteChar quote_char = 1;
+  }
+}
+
+message StringChar {
+  enum Space {
+    UNSET = 0;
+    IS_SET = 1;
+  }
+  oneof rhs {
+    UrlChar url_char = 1;
+    Space space = 2;
+    // '\' nl
+    Nl nl = 3;
+  }
+}
+
+message Ident {
+  optional bool starting_minus = 1 [default = false];  // -
+  required Nmstart nmstart = 2;
+  repeated Nmchar nmchars = 3;
+}
+
+message Num {
+  required sint64 signed_int_value = 1;
+  optional float float_value = 2;
+}
+
+message UrlChar {
+  enum AsciiValue {
+    NUL = 0;
+    // #x9
+    HT = 9;
+
+    // #x21
+    EXCLAMATION_POINT = 33;
+
+    // #x23-#x26
+    HASH = 35;
+    DOLLAR = 36;
+    PERCENT = 37;
+    AMPERSAND = 38;
+
+    // #x26-#x27
+    APOSTROPHE = 39;
+    OPEN_PAREN = 40;
+    CLOSE_PAREN = 41;
+    STAR = 42;
+    PLUS = 43;
+    COMMA = 44;
+    MINUS = 45;
+    DOT = 46;
+    SLASH = 47;
+    ZERO = 48;
+    ONE = 49;
+    TWO = 50;
+    THREE = 51;
+    FOUR = 52;
+    FIVE = 53;
+    SIX = 54;
+    SEVEN = 55;
+    EIGHT = 56;
+    NINE = 57;
+    COLON = 58;
+    SEMI_COLON = 59;
+    LESS_THAN = 60;
+    EQUAL = 61;
+    GREATER_THAN = 62;
+    QUESTION = 63;
+    AT_SIGN = 64;
+    A_UPPER = 65;
+    B_UPPER = 66;
+    C_UPPER = 67;
+    D_UPPER = 68;
+    E_UPPER = 69;
+    F_UPPER = 70;
+    G_UPPER = 71;
+    H_UPPER = 72;
+    I_UPPER = 73;
+    J_UPPER = 74;
+    K_UPPER = 75;
+    L_UPPER = 76;
+    M_UPPER = 77;
+    N_UPPER = 78;
+    O_UPPER = 79;
+    P_UPPER = 80;
+    Q_UPPER = 81;
+    R_UPPER = 82;
+    S_UPPER = 83;
+    T_UPPER = 84;
+    U_UPPER = 85;
+    V_UPPER = 86;
+    W_UPPER = 87;
+    X_UPPER = 88;
+    Y_UPPER = 89;
+    Z_UPPER = 90;
+    OPEN_BRACKET = 91;
+    BACKSLASH = 92;
+    CLOSE_BRACKET = 93;
+    CARET = 94;
+    UNDERSCORE = 95;
+    BACKTICK = 96;
+    A_LOWER = 97;
+    B_LOWER = 98;
+    C_LOWER = 99;
+    D_LOWER = 100;
+    E_LOWER = 101;
+    F_LOWER = 102;
+    G_LOWER = 103;
+    H_LOWER = 104;
+    I_LOWER = 105;
+    J_LOWER = 106;
+    K_LOWER = 107;
+    L_LOWER = 108;
+    M_LOWER = 109;
+    N_LOWER = 110;
+    O_LOWER = 111;
+    P_LOWER = 112;
+    Q_LOWER = 113;
+    R_LOWER = 114;
+    S_LOWER = 115;
+    T_LOWER = 116;
+    U_LOWER = 117;
+    V_LOWER = 118;
+    W_LOWER = 119;
+    X_LOWER = 120;
+    Y_LOWER = 121;
+    Z_LOWER = 122;
+    OPEN_CURLY_BRACE = 123;
+    PIPE = 124;
+    CLOSE_CURLY_BRACE_ = 125;
+    TILDE = 126;
+  }
+  oneof rhs {
+    AsciiValue ascii_value = 1;
+    Escape escape = 2;
+    // TODO(metzman): Support nonascii tokens.
+  }
+}
+
+message W {
+  repeated UnrepeatedW unrepeated_w = 1;
+}
+
+// Not in grammar
+message UnrepeatedW {
+  enum AsciiValue {
+    // #x9 ('\t')
+    HT = 9;
+    // #xA ('\n')
+    LF = 10;
+    // #xC ('\f')
+    FF = 12;
+    // #xD ('\r')
+    CR = 13;
+    // #x20 (' ')
+    SPACE = 32;
+  }
+  required AsciiValue ascii_value = 1;
+}
+
+message Nl {
+  enum NewlineKind {
+    // #xA ('\n')
+    LF = 10;
+    // #xD #xA ('\r\n') Pseudo value, since we don't need SOH
+    CR_LF = 1;
+    // #xD ('\r')
+    CR = 13;
+    // #xC ('\f')
+    FF = 12;
+  }
+  required NewlineKind newline_kind = 1;
+}
+
+// {num}[px|cm|mm|in|pt|pc]
+message Length {
+  enum Unit {
+    PX = 1;
+    CM = 2;
+    MM = 3;
+    IN = 4;
+    PT = 5;
+    PC = 6;
+  }
+  required Num num = 1;
+  required Unit unit = 2;
+}
+
+// {num}[deg|rad|grad]
+message Angle {
+  enum Unit {
+    DEG = 1;
+    RAD = 2;
+    GRAD = 3;
+  }
+  required Num num = 1;
+  required Unit unit = 2;
+}
+
+// {num}[ms|s]
+message Time {
+  enum Unit {
+    MS = 1;
+    S = 2;
+  }
+  required Num num = 1;
+  required Unit unit = 2;
+}
+
+// {num}[Hz|kHz]
+message Freq {
+  enum Unit {
+    // Hack around build bug since some system header #defines HZ.
+    _HZ = 1;
+    KHZ = 2;
+  }
+  required Num num = 1;
+  required Unit unit = 2;
+}
+
+message Uri {
+  // "url(" w (string | url* ) w ")"
+  // TODO(metzman): Add url token once (if) I implement it.
+  // optional String value = 1;
+}
+
+// FUNCTION. Not named Function to avoid conflict.
+message FunctionToken {
+  required Ident ident = 1;
+}
+// end tokens
+
+// rules
+// TODO(metzman): Add rules for @keyframes (including
+// -webkit-keyframes), and @supports.
+message StyleSheet {
+  optional CharsetDeclaration charset_declaration = 1;
+  repeated Import imports = 2;
+  repeated Namespace namespaces = 3;
+  repeated NestedAtRule nested_at_rules = 4;
+}
+
+// Not in grammar.
+message CharsetDeclaration {
+  enum EncodingId {
+    UTF_8 = 1;
+    UTF_16 = 2;
+    UTF_32 = 3;
+  }
+  required EncodingId encoding_id = 1;
+}
+
+// Not in grammar.
+message NestedAtRule {
+  oneof rhs {
+    Ruleset ruleset = 1;
+    Media media = 2;
+    Page page = 3;
+    FontFace font_face = 4;
+    Viewport viewport = 5;
+    SupportsRule supports_rule = 6;
+  }
+}
+
+message SupportsRule {
+  required SupportsCondition supports_condition = 1;
+  repeated AtRuleOrRulesets at_rule_or_rulesets = 2;
+}
+
+message AtRuleOrRulesets {
+  required AtRuleOrRuleset first = 1;
+  repeated AtRuleOrRuleset laters = 2;
+}
+
+message AtRuleOrRuleset {
+  required Ruleset ruleset = 1;
+  optional NestedAtRule at_rule = 2;
+}
+
+message SupportsCondition {
+  // Using PropertyAndValue rather than declaration means that there
+  // will always be a declaration. This is syntactically correct but
+  // means we may miss some error paths.
+  // TODO(metzman): Figure out what to do about generating invalid output that
+  // causes more coverage. Maybe doing so infrequently is the correct move.
+  required PropertyAndValue property_and_value = 1;  // Default.
+  // TODO(metzman): Do functions also need to be supported?
+  required bool not_condition = 2;
+  oneof rhs {
+    BinarySupportsCondition and_supports_condition = 3;
+    BinarySupportsCondition or_supports_condition = 4;
+  }
+}
+
+message BinarySupportsCondition {
+  required SupportsCondition condition_1 = 1;
+  required SupportsCondition condition_2 = 2;
+}
+
+message Viewport {
+  repeated ViewportPropertyAndValue properties_and_values = 1;
+}
+
+message ViewportPropertyAndValue {
+  required ViewportProperty property = 1;
+  required ViewportValue value = 2;
+}
+
+message ViewportProperty {
+  enum PropertyId {
+    MIN_WIDTH = 1;
+    MAX_WIDTH = 2;
+    WIDTH = 3;
+    MIN_HEIGHT = 4;
+    MAX_HEIGHT = 5;
+    HEIGHT = 6;
+    ZOOM = 7;
+    MIN_ZOOM = 8;
+    USER_ZOOM = 9;
+    MAX_ZOOM = 10;
+    ORIENTATION = 11;
+  }
+  required PropertyId id = 1;
+}
+
+message ViewportValue {
+  enum ValueId {
+    LANDSCAPE = 1;
+    PORTRAIT = 2;
+    AUTO = 3;
+    ZOOM = 4;
+    FIXED = 5;
+    NONE = 6;
+  }
+  required ValueId value_id = 1;
+  oneof rhs {
+    Length length = 2;
+    Num num = 3;
+  }
+}
+
+message Import {
+  enum SrcId {
+    RELATIVE_STRING = 1;
+    FULL_URL = 2;
+  }
+  optional SrcId src_id = 1;
+  optional MediaQueryList media_query_list = 2;
+}
+
+// Media based on https://developer.mozilla.org/en-US/docs/Web/CSS/@media
+message MediaQueryList {
+  repeated MediaQuery media_queries = 1;
+}
+
+message MediaQuery {
+  required MediaCondition media_condition = 1;
+  optional MediaQueryPartTwo media_query_part_two = 2;
+}
+
+// Not in spec.
+message MediaQueryPartTwo {
+  enum NotOrOnly {
+    NOT = 1;
+    ONLY = 2;
+  }
+  optional NotOrOnly not_or_only = 1;
+  required MediaType media_type = 2;
+  optional MediaConditionWithoutOr media_condition_without_or = 3;
+}
+
+message MediaConditionWithoutOr {
+  required MediaNot media_not = 1;
+  oneof rhs {
+    MediaAnd media_and = 2;
+    MediaInParens media_in_parens = 3;
+  }
+}
+
+message MediaCondition {
+  required MediaAnd media_and = 1;
+  oneof rhs {
+    MediaNot media_not = 2;
+    MediaOr media_or = 3;
+    MediaInParens media_in_parens = 4;
+  }
+}
+
+message MediaType {
+  enum ValueId {
+    ALL = 1;
+    BRAILLE = 2;
+    EMBOSSED = 3;
+    HANDHELD = 4;
+    PRINT = 5;
+    PROJECTION = 6;
+    SCREEN = 7;
+    SPEECH = 8;
+    TTY = 9;
+    TV = 10;
+    INVALID_MEDIA_TYPE = 11;
+  }
+  required ValueId value_id = 1;
+}
+
+message MediaNot {
+  required MediaInParens media_in_parens = 1;
+}
+
+message MediaAnd {
+  required MediaInParens first_media_in_parens = 1;
+  required MediaInParens second_media_in_parens = 2;
+  repeated MediaInParens media_in_parens_list = 3;
+}
+
+message MediaOr {
+  required MediaInParens first_media_in_parens = 1;
+  required MediaInParens second_media_in_parens = 2;
+  repeated MediaInParens media_in_parens_list = 3;
+}
+
+message MediaInParens {
+  required MediaFeature media_feature = 1;
+  optional MediaCondition media_condition = 2;
+}
+
+message MediaFeature {
+  required MfPlain mf_plain = 1;
+  optional MfBool mf_bool = 2;
+  // TODO(metzman): Implement <mf-range>
+}
+
+// TODO(metzman): implement <general-enclosed>
+
+message MfPlain {
+  required MfName property = 1;
+  required MfValue value = 2;
+}
+
+message MfBool {
+  required MfName mf_name = 1;
+}
+
+message MfName {
+  enum ValueId {
+    ANY_HOVER = 1;
+    ANY_POINTER = 2;
+    COLOR = 3;
+    COLOR_INDEX = 4;
+    COLOR_GAMUT = 5;
+    GRID = 6;
+    MONOCHROME = 7;
+    HEIGHT = 8;
+    HOVER = 9;
+    WIDTH = 10;
+    ORIENTATION = 11;
+    ASPECT_RATIO = 12;
+    DEVICE_ASPECT_RATIO = 13;
+    _WEBKIT_DEVICE_PIXEL_RATIO = 14;
+    DEVICE_HEIGHT = 15;
+    DEVICE_WIDTH = 16;
+    DISPLAY_MODE = 17;
+    MAX_COLOR = 18;
+    MAX_COLOR_INDEX = 19;
+    MAX_ASPECT_RATIO = 20;
+    MAX_DEVICE_ASPECT_RATIO = 21;
+    _WEBKIT_MAX_DEVICE_PIXEL_RATIO = 22;
+    MAX_DEVICE_HEIGHT = 23;
+    MAX_DEVICE_WIDTH = 24;
+    MAX_HEIGHT = 25;
+    MAX_MONOCHROME = 26;
+    MAX_WIDTH = 27;
+    MAX_RESOLUTION = 28;
+    MIN_COLOR = 29;
+    MIN_COLOR_INDEX = 30;
+    MIN_ASPECT_RATIO = 31;
+    MIN_DEVICE_ASPECT_RATIO = 32;
+    _WEBKIT_MIN_DEVICE_PIXEL_RATIO = 33;
+    MIN_DEVICE_HEIGHT = 34;
+    MIN_DEVICE_WIDTH = 35;
+    MIN_HEIGHT = 36;
+    MIN_MONOCHROME = 37;
+    MIN_WIDTH = 38;
+    MIN_RESOLUTION = 39;
+    POINTER = 40;
+    RESOLUTION = 41;
+    _WEBKIT_TRANSFORM_3D = 42;
+    SCAN = 43;
+    SHAPE = 44;
+    IMMERSIVE = 45;
+    DYNAMIC_RANGE = 46;
+    VIDEO_DYNAMIC_RANGE = 47;
+    INVALID_NAME = 48;
+  }
+  required ValueId id = 1;
+}
+
+message MfValue {
+  required Num num = 1;
+  oneof rhs {
+    Length length = 2;  // Called <dimension> on developer.mozilla.org
+    Ident ident = 3;
+    // TODO(metzman) implement <mf-range>
+  }
+}
+
+message Namespace {
+  optional NamespacePrefix namespace_prefix = 1;
+  oneof rhs {
+    String string = 2;
+    Uri uri = 3;
+  }
+}
+
+// TODO(metzman): Determine if this is unnecessary.
+message NamespacePrefix {
+  required Ident ident = 1;
+}
+
+message Media {
+  required MediaQueryList media_query_list = 1;
+  repeated Ruleset rulesets = 2;
+}
+
+message Page {
+  optional Ident ident = 1;
+  optional PseudoPage pseudo_page = 2;
+  required DeclarationList declaration_list = 3;
+}
+
+// Not in grammar.
+message DeclarationList {
+  required Declaration first_declaration = 1;
+  repeated Declaration later_declarations = 2;
+}
+
+// TODO(metzman): Determine if this is unnecessary.
+message PseudoPage {
+  required Ident ident = 1;
+}
+
+message FontFace {
+  // required DeclarationList declaration_list = 1;
+  // https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face#Formal_syntax
+  // repeated FontFaceContents font_face_contents = 1;
+}
+
+message Operator {
+  enum AsciiValue {
+    COMMA = 44;
+    SLASH = 47;
+  }
+  optional AsciiValue ascii_value = 1;
+}
+
+message UnaryOperator {
+  enum AsciiValue {
+    PLUS = 43;
+    MINUS = 45;
+  }
+  required AsciiValue ascii_value = 1;
+}
+
+// TODO(metzman): Determine if this is unnecessary.
+message Property {
+  enum NameId {
+{{property_proto_enums}}
+  }
+  required NameId name_id = 1;
+}
+
+message Ruleset {
+  required SelectorList selector_list = 1;
+  required DeclarationList declaration_list = 2;
+}
+
+// Not in grammar.
+message SelectorList {
+  required Selector first_selector = 1;
+  repeated Selector later_selectors = 2;
+}
+
+message Attr {
+  enum Type {
+    NONE = 1;
+    EQUAL = 6;
+    TILDE = 126;
+    PIPE = 124;
+    DOLLAR = 36;
+    STAR = 42;
+  }
+  required Type type = 1;
+  optional bool attr_i = 2;
+  // TODO(metzman): Allow values to be set instead of hardcoding.
+}
+
+enum PseudoType {
+  CLASS = 1;
+  ELEMENT = 2;
+}
+
+enum Combinator {
+  NONE = 1;
+  DOLLAR = 36;
+  COMMA = 44;
+  GREATER_THAN = 62;
+  PLUS = 43;
+  TILDE = 126;
+}
+
+message Selector {
+  enum Type {
+    ELEMENT = 1;
+    CLASS = 2;
+    ID = 3;
+    // TODO(metzman): Support the different variations of universal selectors.
+    UNIVERSAL = 4;
+    ATTR = 5;
+  }
+  required Type type = 1;
+  required Attr attr = 2;
+  // TODO(metzman): Allow the selector value to be set by fuzzer.
+  required PseudoType pseudo_type = 3;
+  enum PseudoValueId {
+    _INTERNAL_AUTOFILL_PREVIEWED = 1;
+    _INTERNAL_AUTOFILL_SELECTED = 2;
+    _INTERNAL_IS_HTML = 3;
+    _INTERNAL_LIST_BOX = 4;
+    _INTERNAL_MEDIA_CONTROLS_OVERLAY_CAST_BUTTON = 5;
+    _INTERNAL_MULTI_SELECT_FOCUS = 6;
+    _INTERNAL_POPUP_OPEN = 7;
+    _INTERNAL_SHADOW_HOST_HAS_APPEARANCE = 8;
+    _INTERNAL_SPATIAL_NAVIGATION_FOCUS = 9;
+    _INTERNAL_VIDEO_PERSISTENT = 10;
+    _INTERNAL_VIDEO_PERSISTENT_ANCESTOR = 11;
+    _WEBKIT_ANY_LINK = 12;
+    _WEBKIT_AUTOFILL = 13;
+    _WEBKIT_DRAG = 14;
+    _WEBKIT_FULL_PAGE_MEDIA = 15;
+    _WEBKIT_FULL_SCREEN = 16;
+    _WEBKIT_FULL_SCREEN_ANCESTOR = 17;
+    _WEBKIT_RESIZER = 18;
+    _WEBKIT_SCROLLBAR = 19;
+    _WEBKIT_SCROLLBAR_BUTTON = 20;
+    _WEBKIT_SCROLLBAR_CORNER = 21;
+    _WEBKIT_SCROLLBAR_THUMB = 22;
+    _WEBKIT_SCROLLBAR_TRACK = 23;
+    _WEBKIT_SCROLLBAR_TRACK_PIECE = 24;
+    ACTIVE = 25;
+    AFTER = 26;
+    AUTOFILL = 27;
+    BACKDROP = 28;
+    BEFORE = 29;
+    CHECKED = 30;
+    CORNER_PRESENT = 31;
+    CUE = 32;
+    DECREMENT = 33;
+    DEFAULT = 34;
+    DEFINED = 35;
+    DISABLED = 36;
+    DOUBLE_BUTTON = 37;
+    EMPTY = 38;
+    ENABLED = 39;
+    END = 40;
+    FIRST = 41;
+    FIRST_CHILD = 42;
+    FIRST_LETTER = 43;
+    FIRST_LINE = 44;
+    FIRST_OF_TYPE = 45;
+    FOCUS = 46;
+    FOCUS_WITHIN = 47;
+    FULLSCREEN = 48;
+    FUTURE = 49;
+    HORIZONTAL = 50;
+    HOST = 51;
+    HOVER = 52;
+    IN_RANGE = 53;
+    INCREMENT = 54;
+    INDETERMINATE = 55;
+    INVALID = 56;
+    LAST_CHILD = 57;
+    LAST_OF_TYPE = 58;
+    LEFT = 59;
+    LINK = 60;
+    NO_BUTTON = 61;
+    ONLY_CHILD = 62;
+    ONLY_OF_TYPE = 63;
+    OPTIONAL = 64;
+    OUT_OF_RANGE = 65;
+    PAST = 66;
+    PLACEHOLDER = 67;
+    PLACEHOLDER_SHOWN = 68;
+    READ_ONLY = 69;
+    READ_WRITE = 70;
+    REQUIRED = 71;
+    RIGHT = 72;
+    ROOT = 73;
+    SCOPE = 74;
+    SELECTION = 75;
+    SINGLE_BUTTON = 76;
+    START = 77;
+    TARGET = 78;
+    VALID = 79;
+    VERTICAL = 80;
+    VISITED = 81;
+    WINDOW_INACTIVE = 82;
+    _WEBKIT_ANY = 83;
+    HOST_CONTEXT = 84;
+    LANG = 85;
+    NOT = 86;
+    NTH_CHILD = 87;
+    NTH_LAST_CHILD = 88;
+    NTH_LAST_OF_TYPE = 89;
+    NTH_OF_TYPE = 90;
+    SLOTTED = 91;
+    XR_OVERLAY = 92;
+    INVALID_PSEUDO_VALUE = 93;
+  }
+  optional PseudoValueId pseudo_value_id = 4;
+  required Combinator combinator = 5;
+}
+
+message Pseudo {
+  oneof rhs {
+    Ident ident_1 = 1;
+    FunctionToken function_token = 2;
+  }
+  required Ident ident_2 = 3;
+}
+
+message Declaration {
+  // property ':' S* expr prio? | /* empty */
+  optional PropertyAndValue property_and_value = 1;
+}
+
+message PropertyAndValue {
+  required Property property = 1;
+  required Expr expr = 2;
+  enum Prio {
+    UNSET = 0;
+    IS_SET = 1;
+  }
+  optional Prio prio = 3;
+  enum ValueId {
+{{value_proto_enums}}
+  };
+
+  optional ValueId value_id = 4;
+}
+
+message Expr {
+  required Term term = 1;
+  repeated OperatorTerm operator_terms = 2;
+}
+
+// Not in grammar.
+message OperatorTerm {
+  required Operator _operator = 1;
+  required Term term = 2;
+}
+
+message Term {
+  optional UnaryOperator unary_operator = 1;
+  oneof rhs {
+    //  [ NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* | ANGLE S* |
+    // TIME S* | FREQ S* | function ]
+    TermPart term_part = 2;
+    // | STRING
+    String string = 3;
+  }
+  // S* | IDENT S* | URI S* | UNICODERANGE S* | hexcolor
+  optional Ident ident = 4;
+  optional Uri uri = 5;
+  // TODO(metzman): Add UNICODERANGE token once (if) I implement it.
+  optional Hexcolor hexcolor = 6;
+}
+
+// Not in grammar.
+message TermPart {
+  // NUMBER
+  required Num number = 1;
+  // S* | PERCENTAGE
+  optional Num percentage = 2;  // num "%"
+  // S* | LENGTH
+  optional Length length = 3;
+  optional Num ems = 4;  // {num}em
+  optional Num exs = 5;  // {num}ex
+  optional Angle angle = 6;
+  optional Time time = 7;
+  optional Freq freq = 8;
+  optional Function function = 9;
+}
+
+message Function {
+  required FunctionToken function_token = 1;
+  required Expr expr = 2;
+}
+
+message Hexcolor {
+  required HexcolorThree first_three = 1;
+  optional HexcolorThree last_three = 2;
+}
+
+// Not in grammar.
+message HexcolorThree {
+  // 0-9A-Za-z
+  required H ascii_value_1 = 1;
+  required H ascii_value_2 = 2;
+  required H ascii_value_3 = 3;
+}
+
+message Input {
+  enum CSSParserMode {
+    kHTMLStandardMode = 0;
+    kHTMLQuirksMode = 1;
+    kSVGAttributeMode = 2;
+    kCSSViewportRuleMode = 3;
+    kCSSFontFaceRuleMode = 4;
+    kUASheetMode = 5;
+  }
+  enum SecureContextMode {
+    kInsecureContext = 0;
+    kSecureContext = 1;
+  }
+  required CSSParserMode css_parser_mode = 1;
+  required bool defer_property_parsing = 2;
+  required StyleSheet style_sheet = 3;
+  required bool is_live_profile = 4;
+  required SecureContextMode secure_context_mode = 5;
+}
diff --git a/third_party/blink/renderer/build/scripts/core/css/parser/templates/css_proto_converter_generated.h.tmpl b/third_party/blink/renderer/build/scripts/core/css/parser/templates/css_proto_converter_generated.h.tmpl
new file mode 100644
index 0000000..4b1b342
--- /dev/null
+++ b/third_party/blink/renderer/build/scripts/core/css/parser/templates/css_proto_converter_generated.h.tmpl
@@ -0,0 +1,16 @@
+{% from 'templates/macros.tmpl' import license, source_files_for_generated_file %}
+{{license()}}
+
+{{source_files_for_generated_file(template_file, input_files)}}
+
+const std::string Converter::kValueLookupTable[] = {
+  "",  // This is just to fill the zeroth spot. It should not be used.
+{{value_keywords}}
+  "INVALID_VALUE",
+};
+
+const std::string Converter::kPropertyLookupTable[] = {
+  "",  // This is just to fill the zeroth spot. It should not be used.
+{{property_names}}
+  "INVALID_PROPERTY",
+};
diff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn
index 62eee2d8..a57477ed 100644
--- a/third_party/blink/renderer/core/BUILD.gn
+++ b/third_party/blink/renderer/core/BUILD.gn
@@ -1770,6 +1770,19 @@
   seed_corpus = "//testing/libfuzzer/fuzzers/content_security_policy_corpus"
 }
 
+css_properties("make_css_proto") {
+  script = "../build/scripts/core/css/parser/make_proto.py"
+  in_files = [ "css/css_value_keywords.json5" ]
+  other_inputs = [
+    "../build/scripts/core/css/parser/templates/css.proto.tmpl",
+    "../build/scripts/core/css/parser/templates/css_proto_converter_generated.h.tmpl",
+  ]
+  outputs = [
+    "$blink_core_output_dir/css/parser/css.proto",
+    "$blink_core_output_dir/css/parser/css_proto_converter_generated.h",
+  ]
+}
+
 fuzzer_test("css_parser_proto_fuzzer") {
   sources = [
     "css/parser/css_parser_proto_fuzzer.cc",
@@ -1786,7 +1799,9 @@
 }
 
 proto_library("css_proto") {
-  sources = [ "css/parser/css.proto" ]
+  sources = [ "$blink_core_output_dir/css/parser/css.proto" ]
+  proto_out_dir = "third_party/blink/renderer/core/css/parser"
+  deps = [ ":make_css_proto" ]
 }
 
 fuzzer_test("blink_html_tokenizer_fuzzer") {
diff --git a/third_party/blink/renderer/core/OWNERS b/third_party/blink/renderer/core/OWNERS
index 042050c4..f260e1df2 100644
--- a/third_party/blink/renderer/core/OWNERS
+++ b/third_party/blink/renderer/core/OWNERS
@@ -70,4 +70,3 @@
 wangxianzhu@chromium.org
 yhirano@chromium.org
 yoavweiss@chromium.org
-yutak@chromium.org
diff --git a/third_party/blink/renderer/core/css/parser/css.proto b/third_party/blink/renderer/core/css/parser/css.proto
deleted file mode 100644
index 08320f97..0000000
--- a/third_party/blink/renderer/core/css/parser/css.proto
+++ /dev/null
@@ -1,2479 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Based on the grammar provided here: https://goo.gl/svLze7
-// Most top level definitions here (mostly messages, but also the enum "h", is
-// named after a rule or token in the grammar. The messages that aren't were
-// implemented to hack around shortcomings in the protobuf format (such as
-// "StringCharOrQuote"). These can be identified because they are preceded by
-// a comment that says "Not in grammar".
-
-syntax = "proto2";
-
-package css_proto_converter;
-
-// Tokens
-// TODO(metzman): Implement CDO, CDC, UNICODERANGE, and range.
-// The following tokens are implmented in code and do not have their own
-// message: INCLUDES, DASHMATCH, HASH, STRING IMPORT_SYM, PAGE_SYM, MEDIA_SYM,
-// FONT_FACE_SYM, CHARSET_SYM, NAMESPACE_SYM, IMPORTANT_SYM, EMS, EXS, NUMBER,
-// and PERCENTAGE.
-// DIMEN not implemented since it isnt used in any production.
-
-// This is named "h" because it represents the "h" token in the grammar this is
-// based off of.
-enum H {
-  ZERO = 48;
-  ONE = 49;
-  TWO = 50;
-  THREE = 51;
-  FOUR = 52;
-  FIVE = 53;
-  SIX = 54;
-  SEVEN = 55;
-  EIGHT = 56;
-  NINE = 57;
-  A_UPPER = 65;
-  B_UPPER = 66;
-  C_UPPER = 67;
-  D_UPPER = 68;
-  E_UPPER = 69;
-  F_UPPER = 70;
-  A_LOWER = 97;
-  B_LOWER = 98;
-  C_LOWER = 99;
-  D_LOWER = 100;
-  E_LOWER = 101;
-  F_LOWER = 102;
-}
-
-// TODO(metzman): Add "nonascii" token from grammar.
-
-message Unicode {
-  required H ascii_value_1 = 1;
-  optional H ascii_value_2 = 2;
-  optional H ascii_value_3 = 3;
-  optional H ascii_value_4 = 4;
-  optional H ascii_value_5 = 5;
-  optional H ascii_value_6 = 6;
-  optional UnrepeatedW unrepeated_w = 7;
-}
-
-// unicode | '\' [#x20-#x7E#x80-#xD7FF#xE000-#xFFFD#x10000-#x10FFFF]
-message Escape {
-  // #x20-#x7E
-  enum AsciiValue {
-    SPACE = 32;
-    EXCLAMATION_POINT = 33;
-    DOUBLE_QUOTE = 34;
-    HASH = 35;
-    DOLLAR = 36;
-    PERCENT = 37;
-    AMPERSAND = 38;
-    APOSTROPHE = 39;
-    OPEN_PAREN = 40;
-    CLOSE_PAREN = 41;
-    STAR = 42;
-    PLUS = 43;
-    COMMA = 44;
-    MINUS = 45;
-    DOT = 46;
-    SLASH = 47;
-    ZERO = 48;
-    ONE = 49;
-    TWO = 50;
-    THREE = 51;
-    FOUR = 52;
-    FIVE = 53;
-    SIX = 54;
-    SEVEN = 55;
-    EIGHT = 56;
-    NINE = 57;
-    COLON = 58;
-    SEMI_COLON = 59;
-    LESS_THAN = 60;
-    EQUAL = 61;
-    GREATER_THAN = 62;
-    QUESTION = 63;
-    AT_SIGN = 64;
-    A_UPPER = 65;
-    B_UPPER = 66;
-    C_UPPER = 67;
-    D_UPPER = 68;
-    E_UPPER = 69;
-    F_UPPER = 70;
-    G_UPPER = 71;
-    H_UPPER = 72;
-    I_UPPER = 73;
-    J_UPPER = 74;
-    K_UPPER = 75;
-    L_UPPER = 76;
-    M_UPPER = 77;
-    N_UPPER = 78;
-    O_UPPER = 79;
-    P_UPPER = 80;
-    Q_UPPER = 81;
-    R_UPPER = 82;
-    S_UPPER = 83;
-    T_UPPER = 84;
-    U_UPPER = 85;
-    V_UPPER = 86;
-    W_UPPER = 87;
-    X_UPPER = 88;
-    Y_UPPER = 89;
-    Z_UPPER = 90;
-    OPEN_BRACKET = 91;
-    BACKSLASH = 92;
-    CLOSE_BRACKET = 93;
-    CARET = 94;
-    UNDERSCORE = 95;
-    BACKTICK = 96;
-    A_LOWER = 97;
-    B_LOWER = 98;
-    C_LOWER = 99;
-    D_LOWER = 100;
-    E_LOWER = 101;
-    F_LOWER = 102;
-    G_LOWER = 103;
-    H_LOWER = 104;
-    I_LOWER = 105;
-    J_LOWER = 106;
-    K_LOWER = 107;
-    L_LOWER = 108;
-    M_LOWER = 109;
-    N_LOWER = 110;
-    O_LOWER = 111;
-    P_LOWER = 112;
-    Q_LOWER = 113;
-    R_LOWER = 114;
-    S_LOWER = 115;
-    T_LOWER = 116;
-    U_LOWER = 117;
-    V_LOWER = 118;
-    W_LOWER = 119;
-    X_LOWER = 120;
-    Y_LOWER = 121;
-    Z_LOWER = 122;
-    OPEN_CURLY_BRACE = 123;
-    PIPE = 124;
-    CLOSE_CURLY_BRACE_ = 125;
-    TILDE = 126;
-  }
-
-  oneof rhs {
-    Unicode unicode = 1;
-    AsciiValue ascii_value = 2;
-  }
-  // TODO(metzman): Determine if we care about unicode points not covered here.
-}
-
-message Nmstart {
-  enum AsciiValue {
-    A_UPPER = 65;
-    B_UPPER = 66;
-    C_UPPER = 67;
-    D_UPPER = 68;
-    E_UPPER = 69;
-    F_UPPER = 70;
-    G_UPPER = 71;
-    H_UPPER = 72;
-    I_UPPER = 73;
-    J_UPPER = 74;
-    K_UPPER = 75;
-    L_UPPER = 76;
-    M_UPPER = 77;
-    N_UPPER = 78;
-    O_UPPER = 79;
-    P_UPPER = 80;
-    Q_UPPER = 81;
-    R_UPPER = 82;
-    S_UPPER = 83;
-    T_UPPER = 84;
-    U_UPPER = 85;
-    V_UPPER = 86;
-    W_UPPER = 87;
-    X_UPPER = 88;
-    Y_UPPER = 89;
-    Z_UPPER = 90;
-    A_LOWER = 97;
-    B_LOWER = 98;
-    C_LOWER = 99;
-    D_LOWER = 100;
-    E_LOWER = 101;
-    F_LOWER = 102;
-    G_LOWER = 103;
-    H_LOWER = 104;
-    I_LOWER = 105;
-    J_LOWER = 106;
-    K_LOWER = 107;
-    L_LOWER = 108;
-    M_LOWER = 109;
-    N_LOWER = 110;
-    O_LOWER = 111;
-    P_LOWER = 112;
-    Q_LOWER = 113;
-    R_LOWER = 114;
-    S_LOWER = 115;
-    T_LOWER = 116;
-    U_LOWER = 117;
-    V_LOWER = 118;
-    W_LOWER = 119;
-    X_LOWER = 120;
-    Y_LOWER = 121;
-    Z_LOWER = 122;
-  }
-
-  oneof rhs {
-    AsciiValue ascii_value = 1;
-    Escape escape = 2;
-    // TODO(metzman): Add nonascii token once (if) I implement it.
-  }
-}
-
-message Nmchar {
-  enum AsciiValue {
-    MINUS = 45;
-    ZERO = 48;
-    ONE = 49;
-    TWO = 50;
-    THREE = 51;
-    FOUR = 52;
-    FIVE = 53;
-    SIX = 54;
-    SEVEN = 55;
-    EIGHT = 56;
-    NINE = 57;
-    A_UPPER = 65;
-    B_UPPER = 66;
-    C_UPPER = 67;
-    D_UPPER = 68;
-    E_UPPER = 69;
-    F_UPPER = 70;
-    G_UPPER = 71;
-    H_UPPER = 72;
-    I_UPPER = 73;
-    J_UPPER = 74;
-    K_UPPER = 75;
-    L_UPPER = 76;
-    M_UPPER = 77;
-    N_UPPER = 78;
-    O_UPPER = 79;
-    P_UPPER = 80;
-    Q_UPPER = 81;
-    R_UPPER = 82;
-    S_UPPER = 83;
-    T_UPPER = 84;
-    U_UPPER = 85;
-    V_UPPER = 86;
-    W_UPPER = 87;
-    X_UPPER = 88;
-    Y_UPPER = 89;
-    Z_UPPER = 90;
-    A_LOWER = 97;
-    B_LOWER = 98;
-    C_LOWER = 99;
-    D_LOWER = 100;
-    E_LOWER = 101;
-    F_LOWER = 102;
-    G_LOWER = 103;
-    H_LOWER = 104;
-    I_LOWER = 105;
-    J_LOWER = 106;
-    K_LOWER = 107;
-    L_LOWER = 108;
-    M_LOWER = 109;
-    N_LOWER = 110;
-    O_LOWER = 111;
-    P_LOWER = 112;
-    Q_LOWER = 113;
-    R_LOWER = 114;
-    S_LOWER = 115;
-    T_LOWER = 116;
-    U_LOWER = 117;
-    V_LOWER = 118;
-    W_LOWER = 119;
-    X_LOWER = 120;
-    Y_LOWER = 121;
-    Z_LOWER = 122;
-  }
-  oneof rhs {
-    AsciiValue ascii_value = 1;
-    Escape escape = 2;
-    // TODO(metzman): Add nonascii token once (if) I implement it.
-  }
-}
-
-// string1, string2 and string
-message String {
-  // TODO(metzman): determine if these hacks are more efficient than enforcing
-  // constraints on string types in the actual code.
-  required bool use_single_quotes = 1;
-  repeated StringCharOrQuote string_char_quotes = 2;
-}
-
-// Not in grammar.
-message StringCharOrQuote {
-  enum QuoteChar {
-    UNSET = 0;
-    IS_SET = 1;
-  }
-  oneof rhs {
-    StringChar string_char = 2;
-    QuoteChar quote_char = 1;
-  }
-}
-
-message StringChar {
-  enum Space {
-    UNSET = 0;
-    IS_SET = 1;
-  }
-  oneof rhs {
-    UrlChar url_char = 1;
-    Space space = 2;
-    // '\' nl
-    Nl nl = 3;
-  }
-}
-
-message Ident {
-  optional bool starting_minus = 1 [default = false];  // -
-  required Nmstart nmstart = 2;
-  repeated Nmchar nmchars = 3;
-}
-
-message Num {
-  required sint64 signed_int_value = 1;
-  optional float float_value = 2;
-}
-
-message UrlChar {
-  enum AsciiValue {
-    NUL = 0;
-    // #x9
-    HT = 9;
-
-    // #x21
-    EXCLAMATION_POINT = 33;
-
-    // #x23-#x26
-    HASH = 35;
-    DOLLAR = 36;
-    PERCENT = 37;
-    AMPERSAND = 38;
-
-    // #x26-#x27
-    APOSTROPHE = 39;
-    OPEN_PAREN = 40;
-    CLOSE_PAREN = 41;
-    STAR = 42;
-    PLUS = 43;
-    COMMA = 44;
-    MINUS = 45;
-    DOT = 46;
-    SLASH = 47;
-    ZERO = 48;
-    ONE = 49;
-    TWO = 50;
-    THREE = 51;
-    FOUR = 52;
-    FIVE = 53;
-    SIX = 54;
-    SEVEN = 55;
-    EIGHT = 56;
-    NINE = 57;
-    COLON = 58;
-    SEMI_COLON = 59;
-    LESS_THAN = 60;
-    EQUAL = 61;
-    GREATER_THAN = 62;
-    QUESTION = 63;
-    AT_SIGN = 64;
-    A_UPPER = 65;
-    B_UPPER = 66;
-    C_UPPER = 67;
-    D_UPPER = 68;
-    E_UPPER = 69;
-    F_UPPER = 70;
-    G_UPPER = 71;
-    H_UPPER = 72;
-    I_UPPER = 73;
-    J_UPPER = 74;
-    K_UPPER = 75;
-    L_UPPER = 76;
-    M_UPPER = 77;
-    N_UPPER = 78;
-    O_UPPER = 79;
-    P_UPPER = 80;
-    Q_UPPER = 81;
-    R_UPPER = 82;
-    S_UPPER = 83;
-    T_UPPER = 84;
-    U_UPPER = 85;
-    V_UPPER = 86;
-    W_UPPER = 87;
-    X_UPPER = 88;
-    Y_UPPER = 89;
-    Z_UPPER = 90;
-    OPEN_BRACKET = 91;
-    BACKSLASH = 92;
-    CLOSE_BRACKET = 93;
-    CARET = 94;
-    UNDERSCORE = 95;
-    BACKTICK = 96;
-    A_LOWER = 97;
-    B_LOWER = 98;
-    C_LOWER = 99;
-    D_LOWER = 100;
-    E_LOWER = 101;
-    F_LOWER = 102;
-    G_LOWER = 103;
-    H_LOWER = 104;
-    I_LOWER = 105;
-    J_LOWER = 106;
-    K_LOWER = 107;
-    L_LOWER = 108;
-    M_LOWER = 109;
-    N_LOWER = 110;
-    O_LOWER = 111;
-    P_LOWER = 112;
-    Q_LOWER = 113;
-    R_LOWER = 114;
-    S_LOWER = 115;
-    T_LOWER = 116;
-    U_LOWER = 117;
-    V_LOWER = 118;
-    W_LOWER = 119;
-    X_LOWER = 120;
-    Y_LOWER = 121;
-    Z_LOWER = 122;
-    OPEN_CURLY_BRACE = 123;
-    PIPE = 124;
-    CLOSE_CURLY_BRACE_ = 125;
-    TILDE = 126;
-  }
-  oneof rhs {
-    AsciiValue ascii_value = 1;
-    Escape escape = 2;
-    // TODO(metzman): Support nonascii tokens.
-  }
-}
-
-message W {
-  repeated UnrepeatedW unrepeated_w = 1;
-}
-
-// Not in grammar
-message UnrepeatedW {
-  enum AsciiValue {
-    // #x9 ('\t')
-    HT = 9;
-    // #xA ('\n')
-    LF = 10;
-    // #xC ('\f')
-    FF = 12;
-    // #xD ('\r')
-    CR = 13;
-    // #x20 (' ')
-    SPACE = 32;
-  }
-  required AsciiValue ascii_value = 1;
-}
-
-message Nl {
-  enum NewlineKind {
-    // #xA ('\n')
-    LF = 10;
-    // #xD #xA ('\r\n') Pseudo value, since we don't need SOH
-    CR_LF = 1;
-    // #xD ('\r')
-    CR = 13;
-    // #xC ('\f')
-    FF = 12;
-  }
-  required NewlineKind newline_kind = 1;
-}
-
-// {num}[px|cm|mm|in|pt|pc]
-message Length {
-  enum Unit {
-    PX = 1;
-    CM = 2;
-    MM = 3;
-    IN = 4;
-    PT = 5;
-    PC = 6;
-  }
-  required Num num = 1;
-  required Unit unit = 2;
-}
-
-// {num}[deg|rad|grad]
-message Angle {
-  enum Unit {
-    DEG = 1;
-    RAD = 2;
-    GRAD = 3;
-  }
-  required Num num = 1;
-  required Unit unit = 2;
-}
-
-// {num}[ms|s]
-message Time {
-  enum Unit {
-    MS = 1;
-    S = 2;
-  }
-  required Num num = 1;
-  required Unit unit = 2;
-}
-
-// {num}[Hz|kHz]
-message Freq {
-  enum Unit {
-    // Hack around build bug since some system header #defines HZ.
-    _HZ = 1;
-    KHZ = 2;
-  }
-  required Num num = 1;
-  required Unit unit = 2;
-}
-
-message Uri {
-  // "url(" w (string | url* ) w ")"
-  // TODO(metzman): Add url token once (if) I implement it.
-  // optional String value = 1;
-}
-
-// FUNCTION. Not named Function to avoid conflict.
-message FunctionToken {
-  required Ident ident = 1;
-}
-// end tokens
-
-// rules
-// TODO(metzman): Add rules for @keyframes (including
-// -webkit-keyframes), and @supports.
-message StyleSheet {
-  optional CharsetDeclaration charset_declaration = 1;
-  repeated Import imports = 2;
-  repeated Namespace namespaces = 3;
-  repeated NestedAtRule nested_at_rules = 4;
-}
-
-// Not in grammar.
-message CharsetDeclaration {
-  enum EncodingId {
-    UTF_8 = 1;
-    UTF_16 = 2;
-    UTF_32 = 3;
-  }
-  required EncodingId encoding_id = 1;
-}
-
-// Not in grammar.
-message NestedAtRule {
-  oneof rhs {
-    Ruleset ruleset = 1;
-    Media media = 2;
-    Page page = 3;
-    FontFace font_face = 4;
-    Viewport viewport = 5;
-    SupportsRule supports_rule = 6;
-  }
-}
-
-message SupportsRule {
-  required SupportsCondition supports_condition = 1;
-  repeated AtRuleOrRulesets at_rule_or_rulesets = 2;
-}
-
-message AtRuleOrRulesets {
-  required AtRuleOrRuleset first = 1;
-  repeated AtRuleOrRuleset laters = 2;
-}
-
-message AtRuleOrRuleset {
-  required Ruleset ruleset = 1;
-  optional NestedAtRule at_rule = 2;
-}
-
-message SupportsCondition {
-  // Using PropertyAndValue rather than declaration means that there
-  // will always be a declaration. This is syntactically correct but
-  // means we may miss some error paths.
-  // TODO(metzman): Figure out what to do about generating invalid output that
-  // causes more coverage. Maybe doing so infrequently is the correct move.
-  required PropertyAndValue property_and_value = 1;  // Default.
-  // TODO(metzman): Do functions also need to be supported?
-  required bool not_condition = 2;
-  oneof rhs {
-    BinarySupportsCondition and_supports_condition = 3;
-    BinarySupportsCondition or_supports_condition = 4;
-  }
-}
-
-message BinarySupportsCondition {
-  required SupportsCondition condition_1 = 1;
-  required SupportsCondition condition_2 = 2;
-}
-
-message Viewport {
-  repeated ViewportPropertyAndValue properties_and_values = 1;
-}
-
-message ViewportPropertyAndValue {
-  required ViewportProperty property = 1;
-  required ViewportValue value = 2;
-}
-
-message ViewportProperty {
-  enum PropertyId {
-    MIN_WIDTH = 1;
-    MAX_WIDTH = 2;
-    WIDTH = 3;
-    MIN_HEIGHT = 4;
-    MAX_HEIGHT = 5;
-    HEIGHT = 6;
-    ZOOM = 7;
-    MIN_ZOOM = 8;
-    USER_ZOOM = 9;
-    MAX_ZOOM = 10;
-    ORIENTATION = 11;
-  }
-  required PropertyId id = 1;
-}
-
-message ViewportValue {
-  enum ValueId {
-    LANDSCAPE = 1;
-    PORTRAIT = 2;
-    AUTO = 3;
-    ZOOM = 4;
-    FIXED = 5;
-    NONE = 6;
-  }
-  required ValueId value_id = 1;
-  oneof rhs {
-    Length length = 2;
-    Num num = 3;
-  }
-}
-
-message Import {
-  enum SrcId {
-    RELATIVE_STRING = 1;
-    FULL_URL = 2;
-  }
-  optional SrcId src_id = 1;
-  optional MediaQueryList media_query_list = 2;
-}
-
-// Media based on https://developer.mozilla.org/en-US/docs/Web/CSS/@media
-message MediaQueryList {
-  repeated MediaQuery media_queries = 1;
-}
-
-message MediaQuery {
-  required MediaCondition media_condition = 1;
-  optional MediaQueryPartTwo media_query_part_two = 2;
-}
-
-// Not in spec.
-message MediaQueryPartTwo {
-  enum NotOrOnly {
-    NOT = 1;
-    ONLY = 2;
-  }
-  optional NotOrOnly not_or_only = 1;
-  required MediaType media_type = 2;
-  optional MediaConditionWithoutOr media_condition_without_or = 3;
-}
-
-message MediaConditionWithoutOr {
-  required MediaNot media_not = 1;
-  oneof rhs {
-    MediaAnd media_and = 2;
-    MediaInParens media_in_parens = 3;
-  }
-}
-
-message MediaCondition {
-  required MediaAnd media_and = 1;
-  oneof rhs {
-    MediaNot media_not = 2;
-    MediaOr media_or = 3;
-    MediaInParens media_in_parens = 4;
-  }
-}
-
-message MediaType {
-  enum ValueId {
-    ALL = 1;
-    BRAILLE = 2;
-    EMBOSSED = 3;
-    HANDHELD = 4;
-    PRINT = 5;
-    PROJECTION = 6;
-    SCREEN = 7;
-    SPEECH = 8;
-    TTY = 9;
-    TV = 10;
-    INVALID_MEDIA_TYPE = 11;
-  }
-  required ValueId value_id = 1;
-}
-
-message MediaNot {
-  required MediaInParens media_in_parens = 1;
-}
-
-message MediaAnd {
-  required MediaInParens first_media_in_parens = 1;
-  required MediaInParens second_media_in_parens = 2;
-  repeated MediaInParens media_in_parens_list = 3;
-}
-
-message MediaOr {
-  required MediaInParens first_media_in_parens = 1;
-  required MediaInParens second_media_in_parens = 2;
-  repeated MediaInParens media_in_parens_list = 3;
-}
-
-message MediaInParens {
-  required MediaFeature media_feature = 1;
-  optional MediaCondition media_condition = 2;
-}
-
-message MediaFeature {
-  required MfPlain mf_plain = 1;
-  optional MfBool mf_bool = 2;
-  // TODO(metzman): Implement <mf-range>
-}
-
-// TODO(metzman): implement <general-enclosed>
-
-message MfPlain {
-  required MfName property = 1;
-  required MfValue value = 2;
-}
-
-message MfBool {
-  required MfName mf_name = 1;
-}
-
-message MfName {
-  enum ValueId {
-    ANY_HOVER = 1;
-    ANY_POINTER = 2;
-    COLOR = 3;
-    COLOR_INDEX = 4;
-    COLOR_GAMUT = 5;
-    GRID = 6;
-    MONOCHROME = 7;
-    HEIGHT = 8;
-    HOVER = 9;
-    WIDTH = 10;
-    ORIENTATION = 11;
-    ASPECT_RATIO = 12;
-    DEVICE_ASPECT_RATIO = 13;
-    _WEBKIT_DEVICE_PIXEL_RATIO = 14;
-    DEVICE_HEIGHT = 15;
-    DEVICE_WIDTH = 16;
-    DISPLAY_MODE = 17;
-    MAX_COLOR = 18;
-    MAX_COLOR_INDEX = 19;
-    MAX_ASPECT_RATIO = 20;
-    MAX_DEVICE_ASPECT_RATIO = 21;
-    _WEBKIT_MAX_DEVICE_PIXEL_RATIO = 22;
-    MAX_DEVICE_HEIGHT = 23;
-    MAX_DEVICE_WIDTH = 24;
-    MAX_HEIGHT = 25;
-    MAX_MONOCHROME = 26;
-    MAX_WIDTH = 27;
-    MAX_RESOLUTION = 28;
-    MIN_COLOR = 29;
-    MIN_COLOR_INDEX = 30;
-    MIN_ASPECT_RATIO = 31;
-    MIN_DEVICE_ASPECT_RATIO = 32;
-    _WEBKIT_MIN_DEVICE_PIXEL_RATIO = 33;
-    MIN_DEVICE_HEIGHT = 34;
-    MIN_DEVICE_WIDTH = 35;
-    MIN_HEIGHT = 36;
-    MIN_MONOCHROME = 37;
-    MIN_WIDTH = 38;
-    MIN_RESOLUTION = 39;
-    POINTER = 40;
-    RESOLUTION = 41;
-    _WEBKIT_TRANSFORM_3D = 42;
-    SCAN = 43;
-    SHAPE = 44;
-    IMMERSIVE = 45;
-    DYNAMIC_RANGE = 46;
-    VIDEO_DYNAMIC_RANGE = 47;
-    INVALID_NAME = 48;
-  }
-  required ValueId id = 1;
-}
-
-message MfValue {
-  required Num num = 1;
-  oneof rhs {
-    Length length = 2;  // Called <dimension> on developer.mozilla.org
-    Ident ident = 3;
-    // TODO(metzman) implement <mf-range>
-  }
-}
-
-message Namespace {
-  optional NamespacePrefix namespace_prefix = 1;
-  oneof rhs {
-    String string = 2;
-    Uri uri = 3;
-  }
-}
-
-// TODO(metzman): Determine if this is unnecessary.
-message NamespacePrefix {
-  required Ident ident = 1;
-}
-
-message Media {
-  required MediaQueryList media_query_list = 1;
-  repeated Ruleset rulesets = 2;
-}
-
-message Page {
-  optional Ident ident = 1;
-  optional PseudoPage pseudo_page = 2;
-  required DeclarationList declaration_list = 3;
-}
-
-// Not in grammar.
-message DeclarationList {
-  required Declaration first_declaration = 1;
-  repeated Declaration later_declarations = 2;
-}
-
-// TODO(metzman): Determine if this is unnecessary.
-message PseudoPage {
-  required Ident ident = 1;
-}
-
-message FontFace {
-  // required DeclarationList declaration_list = 1;
-  // https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face#Formal_syntax
-  // repeated FontFaceContents font_face_contents = 1;
-}
-
-message Operator {
-  enum AsciiValue {
-    COMMA = 44;
-    SLASH = 47;
-  }
-  optional AsciiValue ascii_value = 1;
-}
-
-message UnaryOperator {
-  enum AsciiValue {
-    PLUS = 43;
-    MINUS = 45;
-  }
-  required AsciiValue ascii_value = 1;
-}
-
-// TODO(metzman): Determine if this is unnecessary.
-message Property {
-  enum NameId {
-    ALL = 1;
-    _WEBKIT_ANIMATION_ITERATION_COUNT = 2;
-    FONT_FEATURE_SETTINGS = 3;
-    _WEBKIT_TEXT_EMPHASIS_POSITION = 4;
-    _WEBKIT_TEXT_EMPHASIS_STYLE = 5;
-    GRID_TEMPLATE_ROWS = 6;
-    TEXT_UNDERLINE_POSITION = 7;
-    _WEBKIT_FLEX_GROW = 8;
-    SCROLL_MARGIN_RIGHT = 9;
-    _WEBKIT_COLUMN_RULE = 10;
-    _WEBKIT_ORDER = 11;
-    GRID_ROW_GAP = 12;
-    ROW_GAP = 13;
-    BACKDROP_FILTER = 14;
-    FONT_VARIANT_EAST_ASIAN = 15;
-    BUFFERED_RENDERING = 16;
-    _WEBKIT_APPEARANCE = 17;
-    OUTLINE_WIDTH = 18;
-    ALIGNMENT_BASELINE = 19;
-    _WEBKIT_FLEX_FLOW = 20;
-    COLUMN_RULE = 21;
-    GRID_COLUMN_GAP = 22;
-    _WEBKIT_BORDER_AFTER = 23;
-    _WEBKIT_COLUMN_BREAK_INSIDE = 24;
-    _WEBKIT_SHAPE_OUTSIDE = 25;
-    _WEBKIT_PRINT_COLOR_ADJUST = 26;
-    LIST_STYLE_TYPE = 27;
-    PAGE_BREAK_BEFORE = 28;
-    FLOOD_COLOR = 29;
-    TEXT_ANCHOR = 30;
-    _WEBKIT_PADDING_START = 31;
-    _WEBKIT_USER_SELECT = 32;
-    _WEBKIT_COLUMN_RULE_COLOR = 33;
-    PADDING_LEFT = 34;
-    _WEBKIT_BACKFACE_VISIBILITY = 35;
-    _WEBKIT_MARGIN_BEFORE = 36;
-    BREAK_INSIDE = 37;
-    COLUMN_COUNT = 38;
-    _WEBKIT_LOGICAL_HEIGHT = 39;
-    PERSPECTIVE = 40;
-    MAX_BLOCK_SIZE = 41;
-    _WEBKIT_ANIMATION_PLAY_STATE = 42;
-    BORDER_IMAGE_REPEAT = 43;
-    _INTERNAL_FONT_SIZE_DELTA = 44;
-    SCROLL_PADDING_BOTTOM = 45;
-    BORDER_RIGHT_STYLE = 46;
-    BORDER_LEFT_STYLE = 47;
-    SCROLL_MARGIN_BLOCK = 48;
-    FLEX_FLOW = 49;
-    OUTLINE_COLOR = 50;
-    FLEX_GROW = 51;
-    MAX_WIDTH = 52;
-    GRID_COLUMN = 53;
-    IMAGE_ORIENTATION = 54;
-    ANIMATION_DURATION = 55;
-    _WEBKIT_COLUMNS = 56;
-    _WEBKIT_ANIMATION_DELAY = 57;
-    _EPUB_TEXT_EMPHASIS = 58;
-    FLEX_SHRINK = 59;
-    TEXT_RENDERING = 60;
-    ALIGN_ITEMS = 61;
-    BORDER_COLLAPSE = 62;
-    OFFSET = 63;
-    TEXT_COMBINE_UPRIGHT = 64;
-    _WEBKIT_MASK_POSITION_X = 65;
-    _WEBKIT_MASK_POSITION_Y = 66;
-    OUTLINE_STYLE = 67;
-    COLOR_INTERPOLATION_FILTERS = 68;
-    FONT_VARIANT = 69;
-    _WEBKIT_ANIMATION_FILL_MODE = 70;
-    BORDER_RIGHT = 71;
-    VISIBILITY = 72;
-    TRANSFORM_BOX = 73;
-    FONT_VARIANT_CAPS = 74;
-    _EPUB_TEXT_EMPHASIS_COLOR = 75;
-    _WEBKIT_BORDER_BEFORE_STYLE = 76;
-    RESIZE = 77;
-    _WEBKIT_RTL_ORDERING = 78;
-    _WEBKIT_BOX_ORDINAL_GROUP = 79;
-    PAINT_ORDER = 80;
-    STROKE_LINECAP = 81;
-    ANIMATION_DIRECTION = 82;
-    _WEBKIT_FONT_FEATURE_SETTINGS = 83;
-    BORDER_TOP_LEFT_RADIUS = 84;
-    _WEBKIT_COLUMN_WIDTH = 85;
-    _WEBKIT_BOX_ALIGN = 86;
-    _WEBKIT_PADDING_AFTER = 87;
-    COLUMN_WIDTH = 88;
-    LIST_STYLE = 89;
-    _WEBKIT_MASK_REPEAT_Y = 90;
-    STROKE = 91;
-    TEXT_DECORATION_LINE = 92;
-    _WEBKIT_BACKGROUND_SIZE = 93;
-    _WEBKIT_MASK_REPEAT_X = 94;
-    PADDING_BOTTOM = 95;
-    FONT_STYLE = 96;
-    _WEBKIT_TRANSITION_DELAY = 97;
-    BACKGROUND_REPEAT = 98;
-    FLEX_BASIS = 99;
-    BORDER_IMAGE_SLICE = 100;
-    _WEBKIT_TRANSFORM_ORIGIN = 101;
-    SCROLL_BOUNDARY_BEHAVIOR_X = 102;
-    SCROLL_BOUNDARY_BEHAVIOR_Y = 103;
-    VECTOR_EFFECT = 104;
-    _WEBKIT_ANIMATION_TIMING_FUNCTION = 105;
-    _WEBKIT_BORDER_AFTER_STYLE = 106;
-    _WEBKIT_PERSPECTIVE_ORIGIN_X = 107;
-    _WEBKIT_PERSPECTIVE_ORIGIN_Y = 108;
-    INLINE_SIZE = 109;
-    OUTLINE = 110;
-    FONT_DISPLAY = 111;
-    _WEBKIT_BORDER_BEFORE = 112;
-    BORDER_IMAGE_SOURCE = 113;
-    TRANSITION_DURATION = 114;
-    SCROLL_PADDING_TOP = 115;
-    ORDER = 116;
-    _WEBKIT_BOX_ORIENT = 117;
-    COUNTER_RESET = 118;
-    COLOR_RENDERING = 119;
-    FLEX_DIRECTION = 120;
-    _WEBKIT_TEXT_STROKE_WIDTH = 121;
-    FONT_VARIANT_NUMERIC = 122;
-    SCROLL_MARGIN_BLOCK_END = 123;
-    MIN_HEIGHT = 124;
-    SCROLL_PADDING_INLINE_START = 125;
-    _WEBKIT_MASK_BOX_IMAGE = 126;
-    LEFT = 127;
-    _WEBKIT_MASK = 128;
-    _WEBKIT_BORDER_AFTER_WIDTH = 129;
-    STROKE_WIDTH = 130;
-    _WEBKIT_BOX_DECORATION_BREAK = 131;
-    _WEBKIT_MASK_POSITION = 132;
-    BACKGROUND_ORIGIN = 133;
-    _WEBKIT_BORDER_START_COLOR = 134;
-    FONT_STRETCH = 135;
-    _WEBKIT_BACKGROUND_CLIP = 136;
-    SCROLL_MARGIN_TOP = 137;
-    _WEBKIT_BORDER_HORIZONTAL_SPACING = 138;
-    BORDER_RADIUS = 139;
-    FLEX = 140;
-    TEXT_INDENT = 141;
-    HYPHENS = 142;
-    COLUMN_RULE_WIDTH = 143;
-    _WEBKIT_MARGIN_AFTER = 144;
-    _EPUB_CAPTION_SIDE = 145;
-    BREAK_AFTER = 146;
-    TEXT_TRANSFORM = 147;
-    TOUCH_ACTION = 148;
-    FONT_SIZE = 149;
-    _WEBKIT_ANIMATION_NAME = 150;
-    SCROLL_PADDING_INLINE = 151;
-    OFFSET_PATH = 152;
-    SCROLL_MARGIN = 153;
-    PADDING_TOP = 154;
-    SCROLL_SNAP_ALIGN = 155;
-    _WEBKIT_TEXT_COMBINE = 156;
-    _WEBKIT_FLEX_SHRINK = 157;
-    RX = 158;
-    RY = 159;
-    CONTENT = 160;
-    PADDING_RIGHT = 161;
-    _WEBKIT_TRANSFORM = 162;
-    MARKER_MID = 163;
-    _WEBKIT_MIN_LOGICAL_WIDTH = 164;
-    CLIP_RULE = 165;
-    FONT_FAMILY = 166;
-    SCROLL_SNAP_TYPE = 167;
-    TEXT_DECORATION_SKIP_INK = 168;
-    TRANSITION = 169;
-    FILTER = 170;
-    BORDER_RIGHT_WIDTH = 171;
-    _WEBKIT_FLEX_DIRECTION = 172;
-    _WEBKIT_MASK_COMPOSITE = 173;
-    MIX_BLEND_MODE = 174;
-    COLOR_INTERPOLATION = 175;
-    BORDER_TOP_STYLE = 176;
-    FILL_OPACITY = 177;
-    MARKER_START = 178;
-    BORDER_BOTTOM_WIDTH = 179;
-    _WEBKIT_TEXT_EMPHASIS = 180;
-    GRID_AREA = 181;
-    SIZE = 182;
-    BACKGROUND_CLIP = 183;
-    _WEBKIT_TEXT_FILL_COLOR = 184;
-    TOP = 185;
-    _WEBKIT_BOX_REFLECT = 186;
-    BORDER_WIDTH = 187;
-    OFFSET_ANCHOR = 188;
-    MAX_INLINE_SIZE = 189;
-    _WEBKIT_COLUMN_RULE_STYLE = 190;
-    _WEBKIT_COLUMN_COUNT = 191;
-    ANIMATION_PLAY_STATE = 192;
-    PADDING = 193;
-    DOMINANT_BASELINE = 194;
-    BACKGROUND_ATTACHMENT = 195;
-    _WEBKIT_BOX_SIZING = 196;
-    _WEBKIT_BOX_FLEX = 197;
-    TEXT_ORIENTATION = 198;
-    BACKGROUND_POSITION = 199;
-    _WEBKIT_BORDER_START_WIDTH = 200;
-    _EPUB_TEXT_EMPHASIS_STYLE = 201;
-    ISOLATION = 202;
-    _EPUB_TEXT_ORIENTATION = 203;
-    _WEBKIT_BORDER_BOTTOM_RIGHT_RADIUS = 204;
-    R = 205;
-    BORDER_LEFT_WIDTH = 206;
-    GRID_COLUMN_END = 207;
-    BACKGROUND_BLEND_MODE = 208;
-    VERTICAL_ALIGN = 209;
-    CLIP = 210;
-    GRID_AUTO_ROWS = 211;
-    OFFSET_ROTATE = 212;
-    MARGIN_LEFT = 213;
-    ANIMATION_NAME = 214;
-    TEXT_DECORATION = 215;
-    BORDER = 216;
-    _WEBKIT_TRANSITION_TIMING_FUNCTION = 217;
-    MARGIN_BOTTOM = 218;
-    UNICODE_RANGE = 219;
-    ANIMATION = 220;
-    _WEBKIT_SHAPE_MARGIN = 221;
-    FONT_WEIGHT = 222;
-    SHAPE_MARGIN = 223;
-    MASK_TYPE = 224;
-    SCROLL_PADDING = 225;
-    MIN_INLINE_SIZE = 226;
-    OBJECT_POSITION = 227;
-    PAGE_BREAK_AFTER = 228;
-    _WEBKIT_MASK_CLIP = 229;
-    WHITE_SPACE = 230;
-    _WEBKIT_BORDER_AFTER_COLOR = 231;
-    _WEBKIT_MAX_LOGICAL_WIDTH = 232;
-    _WEBKIT_BORDER_BEFORE_COLOR = 233;
-    FONT_KERNING = 234;
-    _EPUB_WORD_BREAK = 235;
-    CLEAR = 236;
-    ANIMATION_TIMING_FUNCTION = 237;
-    _WEBKIT_BORDER_RADIUS = 238;
-    SCROLL_PADDING_RIGHT = 239;
-    _WEBKIT_TEXT_DECORATIONS_IN_EFFECT = 240;
-    _WEBKIT_ANIMATION_DIRECTION = 241;
-    JUSTIFY_SELF = 242;
-    TRANSITION_TIMING_FUNCTION = 243;
-    SCROLL_SNAP_STOP = 244;
-    COUNTER_INCREMENT = 245;
-    _WEBKIT_TRANSFORM_STYLE = 246;
-    GRID_AUTO_COLUMNS = 247;
-    _WEBKIT_ALIGN_CONTENT = 248;
-    FONT = 249;
-    FLEX_WRAP = 250;
-    GRID_ROW_START = 251;
-    LIST_STYLE_IMAGE = 252;
-    _WEBKIT_TAP_HIGHLIGHT_COLOR = 253;
-    _WEBKIT_TEXT_EMPHASIS_COLOR = 254;
-    BORDER_LEFT = 255;
-    _WEBKIT_BORDER_END_COLOR = 256;
-    COLUMNS = 257;
-    BOX_SHADOW = 258;
-    _WEBKIT_FLEX_WRAP = 259;
-    ALIGN_SELF = 260;
-    BORDER_BOTTOM = 261;
-    BORDER_SPACING = 262;
-    _WEBKIT_COLUMN_SPAN = 263;
-    GRID_ROW_END = 264;
-    _WEBKIT_BORDER_END = 265;
-    PERSPECTIVE_ORIGIN = 266;
-    PAGE_BREAK_INSIDE = 267;
-    ORPHANS = 268;
-    _WEBKIT_BORDER_START_STYLE = 269;
-    SCROLL_BEHAVIOR = 270;
-    COLUMN_SPAN = 271;
-    _WEBKIT_HYPHENATE_CHARACTER = 272;
-    COLUMN_FILL = 273;
-    TAB_SIZE = 274;
-    CONTAIN = 275;
-    X = 276;
-    GRID_ROW = 277;
-    BORDER_BOTTOM_RIGHT_RADIUS = 278;
-    LINE_HEIGHT = 279;
-    STROKE_LINEJOIN = 280;
-    TEXT_ALIGN_LAST = 281;
-    OFFSET_POSITION = 282;
-    WORD_SPACING = 283;
-    TRANSFORM_STYLE = 284;
-    _WEBKIT_APP_REGION = 285;
-    _WEBKIT_BORDER_END_STYLE = 286;
-    _WEBKIT_TRANSFORM_ORIGIN_Z = 287;
-    _WEBKIT_TRANSFORM_ORIGIN_X = 288;
-    _WEBKIT_TRANSFORM_ORIGIN_Y = 289;
-    BACKGROUND_REPEAT_X = 290;
-    BACKGROUND_REPEAT_Y = 291;
-    BORDER_BOTTOM_COLOR = 292;
-    _WEBKIT_RUBY_POSITION = 293;
-    _WEBKIT_LOGICAL_WIDTH = 294;
-    TEXT_JUSTIFY = 295;
-    SCROLL_MARGIN_INLINE_START = 296;
-    CAPTION_SIDE = 297;
-    MASK_SOURCE_TYPE = 298;
-    _WEBKIT_MASK_BOX_IMAGE_SLICE = 299;
-    _WEBKIT_BORDER_IMAGE = 300;
-    TEXT_SIZE_ADJUST = 301;
-    _WEBKIT_TEXT_SECURITY = 302;
-    _EPUB_WRITING_MODE = 303;
-    GRID_TEMPLATE = 304;
-    _WEBKIT_MASK_BOX_IMAGE_REPEAT = 305;
-    _WEBKIT_MASK_REPEAT = 306;
-    _WEBKIT_JUSTIFY_CONTENT = 307;
-    BASELINE_SHIFT = 308;
-    BORDER_IMAGE = 309;
-    TEXT_DECORATION_COLOR = 310;
-    COLOR = 311;
-    SHAPE_IMAGE_THRESHOLD = 312;
-    SHAPE_RENDERING = 313;
-    CY = 314;
-    CX = 315;
-    _WEBKIT_USER_MODIFY = 316;
-    OFFSET_DISTANCE = 317;
-    _WEBKIT_BORDER_BOTTOM_LEFT_RADIUS = 318;
-    SPEAK = 319;
-    BORDER_BOTTOM_LEFT_RADIUS = 320;
-    _WEBKIT_COLUMN_BREAK_AFTER = 321;
-    _WEBKIT_FONT_SMOOTHING = 322;
-    _WEBKIT_MAX_LOGICAL_HEIGHT = 323;
-    _WEBKIT_LINE_BREAK = 324;
-    FILL_RULE = 325;
-    _WEBKIT_MARGIN_START = 326;
-    MIN_WIDTH = 327;
-    _EPUB_TEXT_COMBINE = 328;
-    BREAK_BEFORE = 329;
-    CARET_COLOR = 330;
-    EMPTY_CELLS = 331;
-    DIRECTION = 332;
-    CLIP_PATH = 333;
-    JUSTIFY_CONTENT = 334;
-    SCROLL_PADDING_BLOCK_END = 335;
-    Z_INDEX = 336;
-    BACKGROUND_POSITION_Y = 337;
-    TEXT_DECORATION_STYLE = 338;
-    GRID_TEMPLATE_AREAS = 339;
-    _WEBKIT_MIN_LOGICAL_HEIGHT = 340;
-    FONT_SIZE_ADJUST = 341;
-    SCROLL_PADDING_BLOCK = 342;
-    OVERFLOW_ANCHOR = 343;
-    CURSOR = 344;
-    SCROLL_MARGIN_BLOCK_START = 345;
-    _WEBKIT_MASK_BOX_IMAGE_SOURCE = 346;
-    MARGIN = 347;
-    _WEBKIT_ANIMATION = 348;
-    LETTER_SPACING = 349;
-    ORIENTATION = 350;
-    WILL_CHANGE = 351;
-    _WEBKIT_HIGHLIGHT = 352;
-    TRANSFORM_ORIGIN = 353;
-    FONT_VARIANT_LIGATURES = 354;
-    _WEBKIT_ANIMATION_DURATION = 355;
-    _WEBKIT_MASK_ORIGIN = 356;
-    _WEBKIT_CLIP_PATH = 357;
-    WORD_BREAK = 358;
-    TABLE_LAYOUT = 359;
-    TEXT_OVERFLOW = 360;
-    _WEBKIT_LOCALE = 361;
-    _WEBKIT_FLEX = 362;
-    GRID_AUTO_FLOW = 363;
-    BORDER_TOP_RIGHT_RADIUS = 364;
-    BORDER_IMAGE_OUTSET = 365;
-    PLACE_ITEMS = 366;
-    BORDER_LEFT_COLOR = 367;
-    FONT_VARIATION_SETTINGS = 368;
-    BORDER_RIGHT_COLOR = 369;
-    MIN_ZOOM = 370;
-    SCROLL_MARGIN_INLINE = 371;
-    _WEBKIT_BORDER_BEFORE_WIDTH = 372;
-    BACKFACE_VISIBILITY = 373;
-    BACKGROUND_IMAGE = 374;
-    _WEBKIT_TRANSITION_PROPERTY = 375;
-    WRITING_MODE = 376;
-    STROKE_OPACITY = 377;
-    BOX_SIZING = 378;
-    MARGIN_TOP = 379;
-    COLUMN_RULE_COLOR = 380;
-    Y = 381;
-    POSITION = 382;
-    SCROLL_MARGIN_BOTTOM = 383;
-    LIST_STYLE_POSITION = 384;
-    _WEBKIT_BOX_PACK = 385;
-    SCROLL_PADDING_INLINE_END = 386;
-    QUOTES = 387;
-    BORDER_TOP = 388;
-    SCROLL_PADDING_LEFT = 389;
-    _WEBKIT_TRANSITION = 390;
-    _WEBKIT_COLUMN_BREAK_BEFORE = 391;
-    LIGHTING_COLOR = 392;
-    BACKGROUND_SIZE = 393;
-    _WEBKIT_PADDING_BEFORE = 394;
-    _WEBKIT_BORDER_TOP_LEFT_RADIUS = 395;
-    FLOOD_OPACITY = 396;
-    LINE_HEIGHT_STEP = 397;
-    _WEBKIT_MASK_SIZE = 398;
-    TEXT_ALIGN = 399;
-    _WEBKIT_FILTER = 400;
-    WORD_WRAP = 401;
-    MAX_ZOOM = 402;
-    GRID = 403;
-    BACKGROUND = 404;
-    HEIGHT = 405;
-    GRID_COLUMN_START = 406;
-    ANIMATION_FILL_MODE = 407;
-    ROTATE = 408;
-    MARKER_END = 409;
-    D = 410;
-    JUSTIFY_ITEMS = 411;
-    ZOOM = 412;
-    SCROLL_PADDING_BLOCK_START = 413;
-    PAGE = 414;
-    RIGHT = 415;
-    USER_SELECT = 416;
-    MARGIN_RIGHT = 417;
-    MARKER = 418;
-    LINE_BREAK = 419;
-    _WEBKIT_MARGIN_END = 420;
-    _WEBKIT_TRANSITION_DURATION = 421;
-    _WEBKIT_WRITING_MODE = 422;
-    BORDER_TOP_WIDTH = 423;
-    BOTTOM = 424;
-    PLACE_CONTENT = 425;
-    _WEBKIT_SHAPE_IMAGE_THRESHOLD = 426;
-    _WEBKIT_USER_DRAG = 427;
-    _WEBKIT_BORDER_VERTICAL_SPACING = 428;
-    _WEBKIT_COLUMN_GAP = 429;
-    _WEBKIT_OPACITY = 430;
-    BACKGROUND_COLOR = 431;
-    COLUMN_GAP = 432;
-    SHAPE_OUTSIDE = 433;
-    _WEBKIT_PADDING_END = 434;
-    _WEBKIT_BORDER_START = 435;
-    ANIMATION_DELAY = 436;
-    UNICODE_BIDI = 437;
-    TEXT_SHADOW = 438;
-    _WEBKIT_BOX_DIRECTION = 439;
-    IMAGE_RENDERING = 440;
-    SRC = 441;
-    GAP = 442;
-    GRID_GAP = 443;
-    POINTER_EVENTS = 444;
-    BORDER_IMAGE_WIDTH = 445;
-    MIN_BLOCK_SIZE = 446;
-    TRANSITION_PROPERTY = 447;
-    _WEBKIT_MASK_IMAGE = 448;
-    FLOAT = 449;
-    MAX_HEIGHT = 450;
-    OUTLINE_OFFSET = 451;
-    _WEBKIT_BOX_SHADOW = 452;
-    OVERFLOW_WRAP = 453;
-    BLOCK_SIZE = 454;
-    TRANSFORM = 455;
-    PLACE_SELF = 456;
-    WIDTH = 457;
-    STROKE_MITERLIMIT = 458;
-    STOP_OPACITY = 459;
-    BORDER_TOP_COLOR = 460;
-    TRANSLATE = 461;
-    OBJECT_FIT = 462;
-    _WEBKIT_MASK_BOX_IMAGE_WIDTH = 463;
-    _WEBKIT_BACKGROUND_ORIGIN = 464;
-    _WEBKIT_ALIGN_ITEMS = 465;
-    TRANSITION_DELAY = 466;
-    SCROLL_MARGIN_LEFT = 467;
-    BORDER_STYLE = 468;
-    ANIMATION_ITERATION_COUNT = 469;
-    // Should be named OVERFLOW but can't because a macro uses this. Needs two
-    // underscores for Windows.
-    __OVERFLOW = 470;
-    USER_ZOOM = 471;
-    _WEBKIT_BORDER_TOP_RIGHT_RADIUS = 472;
-    GRID_TEMPLATE_COLUMNS = 473;
-    _WEBKIT_ALIGN_SELF = 474;
-    _WEBKIT_PERSPECTIVE_ORIGIN = 475;
-    COLUMN_RULE_STYLE = 476;
-    DISPLAY = 477;
-    _WEBKIT_COLUMN_RULE_WIDTH = 478;
-    BORDER_COLOR = 479;
-    _WEBKIT_FLEX_BASIS = 480;
-    STROKE_DASHOFFSET = 481;
-    _WEBKIT_TEXT_SIZE_ADJUST = 482;
-    SCROLL_BOUNDARY_BEHAVIOR = 483;
-    _WEBKIT_TEXT_STROKE = 484;
-    WIDOWS = 485;
-    FILL = 486;
-    OVERFLOW_Y = 487;
-    OVERFLOW_X = 488;
-    OPACITY = 489;
-    _WEBKIT_PERSPECTIVE = 490;
-    _WEBKIT_TEXT_STROKE_COLOR = 491;
-    SCROLL_MARGIN_INLINE_END = 492;
-    SCALE = 493;
-    _WEBKIT_TEXT_ORIENTATION = 494;
-    _WEBKIT_MASK_BOX_IMAGE_OUTSET = 495;
-    ALIGN_CONTENT = 496;
-    _WEBKIT_BORDER_END_WIDTH = 497;
-    BORDER_BOTTOM_STYLE = 498;
-    MASK = 499;
-    BACKGROUND_POSITION_X = 500;
-    _EPUB_TEXT_TRANSFORM = 501;
-    STOP_COLOR = 502;
-    STROKE_DASHARRAY = 503;
-    _WEBKIT_LINE_CLAMP = 504;
-    MARGIN_BLOCK_START = 505;
-    MARGIN_BLOCK_END = 506;
-    MARGIN_INLINE_START = 507;
-    MARGIN_INLINE_END = 508;
-    PADDING_BLOCK_START = 509;
-    PADDING_BLOCK_END = 510;
-    PADDING_INLINE_START = 511;
-    PADDING_INLINE_END = 512;
-    BORDER_BLOCK_START_WIDTH = 513;
-    BORDER_BLOCK_START_STYLE = 514;
-    BORDER_BLOCK_START_COLOR = 515;
-    BORDER_BLOCK_END_WIDTH = 516;
-    BORDER_BLOCK_END_STYLE = 517;
-    BORDER_BLOCK_END_COLOR = 518;
-    BORDER_INLINE_START_WIDTH = 519;
-    BORDER_INLINE_START_STYLE = 520;
-    BORDER_INLINE_START_COLOR = 521;
-    BORDER_INLINE_END_WIDTH = 522;
-    BORDER_INLINE_END_STYLE = 523;
-    BORDER_INLINE_END_COLOR = 524;
-    BORDER_BLOCK_START = 525;
-    BORDER_BLOCK_END = 526;
-    BORDER_INLINE_START = 527;
-    BORDER_INLINE_END = 528;
-    MARGIN_BLOCK = 529;
-    MARGIN_INLINE = 530;
-    PADDING_BLOCK = 531;
-    PADDING_INLINE = 532;
-    BORDER_BLOCK_WIDTH = 533;
-    BORDER_BLOCK_STYLE = 534;
-    BORDER_BLOCK_COLOR = 535;
-    BORDER_INLINE_WIDTH = 536;
-    BORDER_INLINE_STYLE = 537;
-    BORDER_INLINE_COLOR = 538;
-    BORDER_BLOCK = 539;
-    BORDER_INLINE = 540;
-    INSET_BLOCK_START = 541;
-    INSET_BLOCK_END = 542;
-    INSET_BLOCK = 543;
-    INSET_INLINE_START = 544;
-    INSET_INLINE_END = 545;
-    INSET_INLINE = 546;
-    INSET = 547;
-    OVERFLOW_BLOCK = 548;
-    OVERFLOW_INLINE = 549;
-    FORCED_COLOR_ADJUST = 550;
-    OVERSCROLL_BEHAVIOR_INLINE = 551;
-    OVERSCROLL_BEHAVIOR_BLOCK = 552;
-    OVERSCROLL_BEHAVIOR_X = 553;
-    OVERSCROLL_BEHAVIOR_Y = 554;
-    ANIMATION_TIMELINE = 555;
-    COUNTER_SET = 556;
-    BORDER_START_START_RADIUS = 557;
-    BORDER_START_END_RADIUS = 558;
-    BORDER_END_START_RADIUS = 559;
-    BORDER_END_END_RADIUS = 560;
-    INVALID_PROPERTY = 561;
-  }
-  required NameId name_id = 1;
-}
-
-message Ruleset {
-  required SelectorList selector_list = 1;
-  required DeclarationList declaration_list = 2;
-}
-
-// Not in grammar.
-message SelectorList {
-  required Selector first_selector = 1;
-  repeated Selector later_selectors = 2;
-}
-
-message Attr {
-  enum Type {
-    NONE = 1;
-    EQUAL = 6;
-    TILDE = 126;
-    PIPE = 124;
-    DOLLAR = 36;
-    STAR = 42;
-  }
-  required Type type = 1;
-  optional bool attr_i = 2;
-  // TODO(metzman): Allow values to be set instead of hardcoding.
-}
-
-enum PseudoType {
-  CLASS = 1;
-  ELEMENT = 2;
-}
-
-enum Combinator {
-  NONE = 1;
-  DOLLAR = 36;
-  COMMA = 44;
-  GREATER_THAN = 62;
-  PLUS = 43;
-  TILDE = 126;
-}
-
-message Selector {
-  enum Type {
-    ELEMENT = 1;
-    CLASS = 2;
-    ID = 3;
-    // TODO(metzman): Support the different variations of universal selectors.
-    UNIVERSAL = 4;
-    ATTR = 5;
-  }
-  required Type type = 1;
-  required Attr attr = 2;
-  // TODO(metzman): Allow the selector value to be set by fuzzer.
-  required PseudoType pseudo_type = 3;
-  enum PseudoValueId {
-    _INTERNAL_AUTOFILL_PREVIEWED = 1;
-    _INTERNAL_AUTOFILL_SELECTED = 2;
-    _INTERNAL_IS_HTML = 3;
-    _INTERNAL_LIST_BOX = 4;
-    _INTERNAL_MEDIA_CONTROLS_OVERLAY_CAST_BUTTON = 5;
-    _INTERNAL_MULTI_SELECT_FOCUS = 6;
-    _INTERNAL_POPUP_OPEN = 7;
-    _INTERNAL_SHADOW_HOST_HAS_APPEARANCE = 8;
-    _INTERNAL_SPATIAL_NAVIGATION_FOCUS = 9;
-    _INTERNAL_VIDEO_PERSISTENT = 10;
-    _INTERNAL_VIDEO_PERSISTENT_ANCESTOR = 11;
-    _WEBKIT_ANY_LINK = 12;
-    _WEBKIT_AUTOFILL = 13;
-    _WEBKIT_DRAG = 14;
-    _WEBKIT_FULL_PAGE_MEDIA = 15;
-    _WEBKIT_FULL_SCREEN = 16;
-    _WEBKIT_FULL_SCREEN_ANCESTOR = 17;
-    _WEBKIT_RESIZER = 18;
-    _WEBKIT_SCROLLBAR = 19;
-    _WEBKIT_SCROLLBAR_BUTTON = 20;
-    _WEBKIT_SCROLLBAR_CORNER = 21;
-    _WEBKIT_SCROLLBAR_THUMB = 22;
-    _WEBKIT_SCROLLBAR_TRACK = 23;
-    _WEBKIT_SCROLLBAR_TRACK_PIECE = 24;
-    ACTIVE = 25;
-    AFTER = 26;
-    AUTOFILL = 27;
-    BACKDROP = 28;
-    BEFORE = 29;
-    CHECKED = 30;
-    CORNER_PRESENT = 31;
-    CUE = 32;
-    DECREMENT = 33;
-    DEFAULT = 34;
-    DEFINED = 35;
-    DISABLED = 36;
-    DOUBLE_BUTTON = 37;
-    EMPTY = 38;
-    ENABLED = 39;
-    END = 40;
-    FIRST = 41;
-    FIRST_CHILD = 42;
-    FIRST_LETTER = 43;
-    FIRST_LINE = 44;
-    FIRST_OF_TYPE = 45;
-    FOCUS = 46;
-    FOCUS_WITHIN = 47;
-    FULLSCREEN = 48;
-    FUTURE = 49;
-    HORIZONTAL = 50;
-    HOST = 51;
-    HOVER = 52;
-    IN_RANGE = 53;
-    INCREMENT = 54;
-    INDETERMINATE = 55;
-    INVALID = 56;
-    LAST_CHILD = 57;
-    LAST_OF_TYPE = 58;
-    LEFT = 59;
-    LINK = 60;
-    NO_BUTTON = 61;
-    ONLY_CHILD = 62;
-    ONLY_OF_TYPE = 63;
-    OPTIONAL = 64;
-    OUT_OF_RANGE = 65;
-    PAST = 66;
-    PLACEHOLDER = 67;
-    PLACEHOLDER_SHOWN = 68;
-    READ_ONLY = 69;
-    READ_WRITE = 70;
-    REQUIRED = 71;
-    RIGHT = 72;
-    ROOT = 73;
-    SCOPE = 74;
-    SELECTION = 75;
-    SINGLE_BUTTON = 76;
-    START = 77;
-    TARGET = 78;
-    VALID = 79;
-    VERTICAL = 80;
-    VISITED = 81;
-    WINDOW_INACTIVE = 82;
-    _WEBKIT_ANY = 83;
-    HOST_CONTEXT = 84;
-    LANG = 85;
-    NOT = 86;
-    NTH_CHILD = 87;
-    NTH_LAST_CHILD = 88;
-    NTH_LAST_OF_TYPE = 89;
-    NTH_OF_TYPE = 90;
-    SLOTTED = 91;
-    XR_OVERLAY = 92;
-    INVALID_PSEUDO_VALUE = 93;
-  }
-  optional PseudoValueId pseudo_value_id = 4;
-  required Combinator combinator = 5;
-}
-
-message Pseudo {
-  oneof rhs {
-    Ident ident_1 = 1;
-    FunctionToken function_token = 2;
-  }
-  required Ident ident_2 = 3;
-}
-
-message Declaration {
-  // property ':' S* expr prio? | /* empty */
-  optional PropertyAndValue property_and_value = 1;
-}
-
-message PropertyAndValue {
-  required Property property = 1;
-  required Expr expr = 2;
-  enum Prio {
-    UNSET = 0;
-    IS_SET = 1;
-  }
-  optional Prio prio = 3;
-  enum ValueId {
-    ALL = 1;
-    DYNAMIC = 2;
-    YELLOW = 3;
-    GRAYTEXT = 4;
-    COLOR_DODGE = 5;
-    DARKSEAGREEN = 6;
-    DISC = 7;
-    EXTRA_CONDENSED = 8;
-    HANGING = 9;
-    STEP_MIDDLE = 10;
-    MENULIST = 11;
-    ROW = 12;
-    PRE_WRAP = 13;
-    INLINE_BLOCK = 14;
-    STEP_START = 15;
-    ISOLATE_OVERRIDE = 16;
-    SWAP = 17;
-    RTL = 18;
-    CRIMSON = 19;
-    TB = 20;
-    COMMON_LIGATURES = 21;
-    _WEBKIT_MIN_CONTENT = 22;
-    BROWN = 23;
-    KHMER = 24;
-    INFINITE = 25;
-    TABLE_HEADER_GROUP = 26;
-    BEFORE_EDGE = 27;
-    READ_WRITE = 28;
-    RL = 29;
-    WAVY = 30;
-    PROPORTIONAL_WIDTH = 31;
-    NO_DROP = 32;
-    CYAN = 33;
-    DIFFERENCE = 34;
-    EXACT = 35;
-    SQUARE_BUTTON = 36;
-    SKYBLUE = 37;
-    _WEBKIT_ISOLATE_OVERRIDE = 38;
-    TABLE_ROW_GROUP = 39;
-    DARKGRAY = 40;
-    BUTTON = 41;
-    ETHIOPIC_HALEHAME_AM = 42;
-    LARGE = 43;
-    LIGHTPINK = 44;
-    CROSSHAIR = 45;
-    TEAL = 46;
-    FILL_BOX = 47;
-    SMALL = 48;
-    MEDIA_SLIDERTHUMB = 49;
-    ROUND = 50;
-    _INTERNAL_MEDIA_SUBTITLES_ICON = 51;
-    MEDIA_PLAY_BUTTON = 52;
-    SMALLER = 53;
-    JIS04 = 54;
-    LR_TB = 55;
-    LIGHTGOLDENRODYELLOW = 56;
-    LAVENDER = 57;
-    ULTRA_EXPANDED = 58;
-    DIMGREY = 59;
-    DIAGONAL_FRACTIONS = 60;
-    BLUE = 61;
-    XOR = 62;
-    SUB = 63;
-    TELUGU = 64;
-    CRISPEDGES = 65;
-    _WEBKIT_MINI_CONTROL = 66;
-    ZOOM_OUT = 67;
-    SEARCHFIELD = 68;
-    CELL = 69;
-    GUJARATI = 70;
-    ABOVE = 71;
-    NO_PUNCTUATION = 72;
-    NEW = 73;
-    FILLED = 74;
-    USE_SCRIPT = 75;
-    CONDENSED = 76;
-    LOOSE = 77;
-    SOURCE_OUT = 78;
-    OBJECTS = 79;
-    SLIDER_HORIZONTAL = 80;
-    ROW_RESIZE = 81;
-    BREAK_ALL = 82;
-    WAIT = 83;
-    MEDIA_EXIT_FULLSCREEN_BUTTON = 84;
-    KOREAN_HANGUL_FORMAL = 85;
-    FLORALWHITE = 86;
-    RESET_SIZE = 87;
-    ZOOM_IN = 88;
-    _WEBKIT_GRABBING = 89;
-    LARGER = 90;
-    MAX_CONTENT = 91;
-    SRGB = 92;
-    LITERAL_PUNCTUATION = 93;
-    WINDOWFRAME = 94;
-    SUBPIXEL_ANTIALIASED = 95;
-    BUTTONHIGHLIGHT = 96;
-    HUE = 97;
-    PIXELATED = 98;
-    STICKY = 99;
-    GREENYELLOW = 100;
-    LINEARRGB = 101;
-    LIGHTSEAGREEN = 102;
-    LOGICAL = 103;
-    _WEBKIT_RIGHT = 104;
-    SIENNA = 105;
-    FLOW_ROOT = 106;
-    OPTIMIZESPEED = 107;
-    KOREAN_HANJA_FORMAL = 108;
-    NOWRAP = 109;
-    X_SMALL = 110;
-    LANDSCAPE = 111;
-    LIME = 112;
-    X_LARGE = 113;
-    NS_RESIZE = 114;
-    APPWORKSPACE = 115;
-    PERU = 116;
-    ALL_PETITE_CAPS = 117;
-    BLACK = 118;
-    XX_SMALL = 119;
-    ALL_SCROLL = 120;
-    DARKSLATEGRAY = 121;
-    FLAT = 122;
-    GEORGIAN = 123;
-    UNDER = 124;
-    LEMONCHIFFON = 125;
-    CHOCOLATE = 126;
-    PRE_LINE = 127;
-    CONTEXT_MENU = 128;
-    DARKGREY = 129;
-    VIEW_BOX = 130;
-    OLIVE = 131;
-    _WEBKIT_PLAINTEXT = 132;
-    EXTRA_EXPANDED = 133;
-    ANTIQUEWHITE = 134;
-    NONE = 135;
-    MOCCASIN = 136;
-    LOCAL = 137;
-    STROKE = 138;
-    DARKSLATEBLUE = 139;
-    LIGHTSKYBLUE = 140;
-    CONTENT_BOX = 141;
-    THIN = 142;
-    DEEPPINK = 143;
-    SPELL_OUT = 144;
-    NON_SCALING_STROKE = 145;
-    SLIDER_VERTICAL = 146;
-    _WEBKIT_BOX = 147;
-    PLUM = 148;
-    _INTERNAL_MEDIA_OVERLAY_CAST_OFF_BUTTON = 149;
-    INACTIVECAPTIONTEXT = 150;
-    DODGERBLUE = 151;
-    THREEDSHADOW = 152;
-    PETITE_CAPS = 153;
-    PAUSED = 154;
-    _WEBKIT_LINK = 155;
-    MESSAGE_BOX = 156;
-    _INTERNAL_CENTER = 157;
-    TRIANGLE = 158;
-    MAGENTA = 159;
-    TAN = 160;
-    ABSOLUTE = 161;
-    PINK = 162;
-    HIRAGANA_IROHA = 163;
-    FARTHEST_SIDE = 164;
-    PALEVIOLETRED = 165;
-    CLOSE_QUOTE = 166;
-    THREEDLIGHTSHADOW = 167;
-    CAPTION = 168;
-    POWDERBLUE = 169;
-    TABLE_COLUMN = 170;
-    SOURCE_ATOP = 171;
-    HIRAGANA = 172;
-    UPPER_ARMENIAN = 173;
-    WINDOWTEXT = 174;
-    FULL_WIDTH = 175;
-    PROGRESS_BAR_VALUE = 176;
-    MIDNIGHTBLUE = 177;
-    INLINE_FLEX = 178;
-    ECONOMY = 179;
-    LAO = 180;
-    CLONE = 181;
-    AFTER = 182;
-    STATUS_BAR = 183;
-    LOWERCASE = 184;
-    MIXED = 185;
-    LINE_THROUGH = 186;
-    LIGHTSLATEGRAY = 187;
-    SMALL_CAPTION = 188;
-    INFOBACKGROUND = 189;
-    DISCARD = 190;
-    CAPTIONTEXT = 191;
-    END = 192;
-    _INTERNAL_INACTIVE_LIST_BOX_SELECTION_TEXT = 193;
-    CAPITALIZE = 194;
-    MEDIUMSEAGREEN = 195;
-    TOMATO = 196;
-    CADETBLUE = 197;
-    DECIMAL_LEADING_ZERO = 198;
-    SANS_SERIF = 199;
-    LINEN = 200;
-    GREEN = 201;
-    INACTIVEBORDER = 202;
-    INLINE = 203;
-    FALLBACK = 204;
-    PEACHPUFF = 205;
-    _WEBKIT_MAX_CONTENT = 206;
-    PLUS_LIGHTER = 207;
-    CHECKBOX = 208;
-    HELP = 209;
-    OBLIQUE = 210;
-    MOVE = 211;
-    METER = 212;
-    LEDGER = 213;
-    SLATEGREY = 214;
-    MEDIA_TIME_REMAINING_DISPLAY = 215;
-    URDU = 216;
-    POINTER = 217;
-    BEFORE = 218;
-    DARKSLATEGREY = 219;
-    _WEBKIT_CONTROL = 220;
-    _WEBKIT_INLINE_BOX = 221;
-    HARD_LIGHT = 222;
-    MITER = 223;
-    ORIYA = 224;
-    UPPER_LATIN = 225;
-    WINDOW = 226;
-    MEDIUMBLUE = 227;
-    LR = 228;
-    ORANGE = 229;
-    HIDDEN = 230;
-    _INTERNAL_ACTIVE_LIST_BOX_SELECTION = 231;
-    BOLDER = 232;
-    _WEBKIT_CENTER = 233;
-    SAFE = 234;
-    HIGHLIGHTTEXT = 235;
-    ACCUMULATE = 236;
-    FLEX_END = 237;
-    TRANSPARENT = 238;
-    _INTERNAL_MEDIA_REMOTING_CAST_ICON = 239;
-    GOLDENROD = 240;
-    HISTORICAL_LIGATURES = 241;
-    DARKVIOLET = 242;
-    ALWAYS = 243;
-    DECIMAL = 244;
-    BLOCK_AXIS = 245;
-    SCROLLBAR = 246;
-    EW_RESIZE = 247;
-    DARKMAGENTA = 248;
-    NOT_ALLOWED = 249;
-    EASE_IN = 250;
-    TABLE_COLUMN_GROUP = 251;
-    SQUARE = 252;
-    NO_CONTEXTUAL = 253;
-    _WEBKIT_FILL_AVAILABLE = 254;
-    FRAMES = 255;
-    PERSIAN = 256;
-    STATIC = 257;
-    NAVY = 258;
-    VISIBLEPAINTED = 259;
-    THICK = 260;
-    SIMP_CHINESE_FORMAL = 261;
-    GHOSTWHITE = 262;
-    SPACE = 263;
-    DARKKHAKI = 264;
-    KEEP_ALL = 265;
-    CONTENT = 266;
-    _INTERNAL_MEDIA_DOWNLOAD_BUTTON = 267;
-    UPPER_ROMAN = 268;
-    CORNSILK = 269;
-    RED = 270;
-    NO_CHANGE = 271;
-    LINEAR = 272;
-    _INTERNAL_MEDIA_CONTROL = 273;
-    SIDEWAYS = 274;
-    CONTAIN = 275;
-    KATAKANA_IROHA = 276;
-    STEELBLUE = 277;
-    DOUBLE_CIRCLE = 278;
-    ANTIALIASED = 279;
-    ALICEBLUE = 280;
-    LIGHTSLATEGREY = 281;
-    GEOMETRICPRECISION = 282;
-    GAINSBORO = 283;
-    INLINE_TABLE = 284;
-    LTR = 285;
-    BACKWARDS = 286;
-    S_RESIZE = 287;
-    LIGHTGREY = 288;
-    MEDIA_MUTE_BUTTON = 289;
-    LISTITEM = 290;
-    MISTYROSE = 291;
-    DARKSALMON = 292;
-    SIDEWAYS_RIGHT = 293;
-    JIS83 = 294;
-    MEDIUMSPRINGGREEN = 295;
-    CAPS_LOCK_INDICATOR = 296;
-    SLIDERTHUMB_HORIZONTAL = 297;
-    FORWARDS = 298;
-    UPPER_ALPHA = 299;
-    BLINK = 300;
-    FANTASY = 301;
-    SIMPLIFIED = 302;
-    ORANGERED = 303;
-    NAVAJOWHITE = 304;
-    OPEN = 305;
-    HORIZONTAL = 306;
-    SLATEGRAY = 307;
-    ACTIVECAPTION = 308;
-    KOREAN_HANJA_INFORMAL = 309;
-    STRICT = 310;
-    LIGHTCYAN = 311;
-    TOP = 312;
-    WHITE = 314;
-    TEXT_AFTER_EDGE = 315;
-    LIGHTGRAY = 316;
-    COLLAPSE = 317;
-    HOVER = 318;
-    _WEBKIT_OPTIMIZE_CONTRAST = 319;
-    PADDING = 320;
-    BUTT = 321;
-    OFF = 322;
-    THAI = 323;
-    COPY = 324;
-    HOTPINK = 325;
-    DOUBLE = 326;
-    LOWER_GREEK = 327;
-    GREY = 328;
-    MEDIA_VOLUME_SLIDER_CONTAINER = 329;
-    _WEBKIT_INLINE_FLEX = 330;
-    SPACE_EVENLY = 331;
-    ACTIVEBORDER = 332;
-    BROWSER = 333;
-    PRE = 334;
-    UNICASE = 335;
-    SIMP_CHINESE_INFORMAL = 336;
-    CLIP = 337;
-    CLOSEST_CORNER = 338;
-    PLAINTEXT = 339;
-    NO_REPEAT = 340;
-    TEXT_TOP = 341;
-    JIS78 = 342;
-    XX_LARGE = 343;
-    RL_TB = 344;
-    TABLE_ROW = 345;
-    MEDIUM = 346;
-    MONGOLIAN = 347;
-    KATAKANA = 348;
-    ELEMENT = 349;
-    BORDER = 350;
-    ROSYBROWN = 351;
-    PROGRESS_BAR = 352;
-    WHITESMOKE = 353;
-    LIGHTBLUE = 354;
-    _WEBKIT_LEFT = 355;
-    NO_COMMON_LIGATURES = 356;
-    LISTBOX = 357;
-    ISOLATE = 358;
-    SNOW = 359;
-    STEP_END = 360;
-    ETHIOPIC_HALEHAME_TI_ER = 361;
-    ETHIOPIC_HALEHAME_TI_ET = 362;
-    MULTIPLE = 363;
-    _INTERNAL_INACTIVE_LIST_BOX_SELECTION = 364;
-    NORMAL = 365;
-    BLUEVIOLET = 366;
-    SALMON = 367;
-    LOWER_ALPHA = 368;
-    OLDLACE = 369;
-    LETTER = 370;
-    BORDER_BOX = 371;
-    ALPHA = 372;
-    TIBETAN = 373;
-    ICON = 374;
-    FLEX_START = 375;
-    TEXTAREA = 376;
-    W_RESIZE = 377;
-    CLEAR = 378;
-    COVER = 379;
-    FARTHEST_CORNER = 380;
-    MENULIST_TEXTFIELD = 381;
-    TRADITIONAL = 382;
-    LEFT = 383;
-    DOT = 384;
-    LUMINANCE = 385;
-    GOLD = 386;
-    SHOW = 387;
-    TEXT = 388;
-    _WEBKIT_MATCH_PARENT = 389;
-    RADIO = 390;
-    CAMBODIAN = 391;
-    REPEAT_X = 392;
-    REPEAT_Y = 393;
-    FINE = 394;
-    TEXTFIELD = 395;
-    FROM_IMAGE = 396;
-    LINING_NUMS = 397;
-    MENU = 398;
-    PROPORTIONAL_NUMS = 399;
-    SOURCE_OVER = 400;
-    NE_RESIZE = 401;
-    PAPAYAWHIP = 402;
-    SOURCE_IN = 403;
-    SE_RESIZE = 404;
-    CIRCLE = 405;
-    DESTINATION_OUT = 406;
-    THREEDFACE = 407;
-    OVER = 408;
-    DISTRIBUTE = 409;
-    INACTIVECAPTION = 410;
-    LIGHTEN = 411;
-    _WEBKIT_FIT_CONTENT = 412;
-    LIGHTER = 413;
-    CONTEXTUAL = 414;
-    GRAY = 415;
-    DARKTURQUOISE = 416;
-    E_RESIZE = 417;
-    LUMINOSITY = 418;
-    LIST_ITEM = 419;
-    LIMEGREEN = 420;
-    FIXED = 421;
-    MIN_CONTENT = 422;
-    MEDIA_SLIDER = 423;
-    VISIBLESTROKE = 424;
-    CUBIC_BEZIER = 425;
-    CLOSEST_SIDE = 426;
-    RELATIVE = 427;
-    NO_OPEN_QUOTE = 428;
-    THISTLE = 429;
-    VIOLET = 430;
-    PORTRAIT = 431;
-    FULLSCREEN = 432;
-    HONEYDEW = 433;
-    ON_DEMAND = 434;
-    CORNFLOWERBLUE = 435;
-    DARKBLUE = 436;
-    OUTSIDE = 437;
-    PROGRESS = 438;
-    MEDIUMPURPLE = 439;
-    DARKCYAN = 440;
-    VERTICAL = 441;
-    MONOSPACE = 442;
-    BREAK_WORD = 443;
-    SCREEN = 444;
-    REBECCAPURPLE = 445;
-    DARKRED = 446;
-    VERTICAL_LR = 447;
-    OPTIMIZEQUALITY = 448;
-    ARMENIAN = 449;
-    NWSE_RESIZE = 450;
-    TEXT_BEFORE_EDGE = 451;
-    OPTIONAL = 452;
-    EXCLUSION = 453;
-    BOTH = 454;
-    MEDIUMTURQUOISE = 455;
-    LOWER_ROMAN = 456;
-    REVERSE = 457;
-    HANGUL_CONSONANT = 458;
-    SOFT_LIGHT = 459;
-    AQUA = 460;
-    BUTTON_BEVEL = 461;
-    GURMUKHI = 462;
-    LIGHTSTEELBLUE = 463;
-    SMALL_CAPS = 464;
-    N_RESIZE = 465;
-    TABLE_FOOTER_GROUP = 466;
-    DESTINATION_IN = 467;
-    OLIVEDRAB = 468;
-    READ_WRITE_PLAINTEXT_ONLY = 469;
-    PADDING_BOX = 470;
-    COL_RESIZE = 471;
-    _INTERNAL_MEDIA_TRACK_SELECTION_CHECKMARK = 472;
-    LOWER_LATIN = 473;
-    _WEBKIT_NOWRAP = 474;
-    TABLE = 475;
-    BUTTONSHADOW = 476;
-    PALEGREEN = 477;
-    JIS90 = 478;
-    FIT_CONTENT = 479;
-    STRETCH = 480;
-    SEASHELL = 481;
-    THREEDHIGHLIGHT = 482;
-    VISIBLEFILL = 483;
-    SPACE_AROUND = 484;
-    COARSE = 485;
-    AQUAMARINE = 486;
-    DIGITS = 487;
-    CURRENTCOLOR = 488;
-    PAINTED = 489;
-    TB_RL = 490;
-    BUTTONFACE = 491;
-    LAWNGREEN = 492;
-    BURLYWOOD = 493;
-    _WEBKIT_SMALL_CONTROL = 494;
-    SLATEBLUE = 495;
-    MINTCREAM = 496;
-    RUBY = 497;
-    SOLID = 498;
-    ULTRA_CONDENSED = 499;
-    EXPANDED = 500;
-    SADDLEBROWN = 501;
-    VERTICAL_RL = 502;
-    SESAME = 503;
-    _WEBKIT_BODY = 504;
-    DESTINATION_ATOP = 505;
-    MALAYALAM = 506;
-    WRAP_REVERSE = 507;
-    BALANCE = 508;
-    VERTICAL_RIGHT = 509;
-    NO_CLOSE_QUOTE = 510;
-    FLEX = 511;
-    PUSH_BUTTON = 512;
-    DARKGOLDENROD = 513;
-    SATURATION = 514;
-    MIDDLE = 515;
-    SANDYBROWN = 516;
-    HEBREW = 517;
-    MENUTEXT = 518;
-    INLINE_AXIS = 519;
-    BASELINE = 520;
-    _WEBKIT_GRAB = 521;
-    DARKORANGE = 522;
-    _WEBKIT_FLEX = 523;
-    NW_RESIZE = 524;
-    CONTENTS = 525;
-    AUTO = 526;
-    MARGIN_BOX = 527;
-    DOCUMENT = 528;
-    PALEGOLDENROD = 529;
-    ORDINAL = 530;
-    HAND = 531;
-    RUNNING = 532;
-    CJK_EARTHLY_BRANCH = 533;
-    TABLE_CAPTION = 534;
-    MEDIA_TOGGLE_CLOSED_CAPTIONS_BUTTON = 535;
-    AFTER_EDGE = 536;
-    SLIDERTHUMB_VERTICAL = 537;
-    CENTER = 538;
-    LIGHTYELLOW = 539;
-    LAVENDERBLUSH = 540;
-    _INTERNAL_MEDIA_CLOSED_CAPTIONS_ICON = 541;
-    INHERIT = 542;
-    MEDIA_CONTROLS_BACKGROUND = 543;
-    JUSTIFY = 544;
-    OPTIMIZELEGIBILITY = 545;
-    _WEBKIT_BASELINE_MIDDLE = 546;
-    INDIGO = 547;
-    MINIMAL_UI = 548;
-    FIREBRICK = 549;
-    INDIANRED = 550;
-    DARKOLIVEGREEN = 551;
-    SEMI_EXPANDED = 552;
-    UNDERLINE = 553;
-    MYANMAR = 554;
-    SPACE_BETWEEN = 555;
-    EASE = 556;
-    ALTERNATE = 557;
-    MEDIUMORCHID = 558;
-    SILVER = 559;
-    COLOR = 560;
-    CHARTREUSE = 561;
-    EASE_IN_OUT = 562;
-    SPRINGGREEN = 563;
-    LIGHTSALMON = 564;
-    TURQUOISE = 565;
-    HIDE = 566;
-    HORIZONTAL_TB = 567;
-    VERTICAL_TEXT = 568;
-    ALIAS = 569;
-    GRID = 570;
-    NO_DISCRETIONARY_LIGATURES = 571;
-    BACKGROUND = 572;
-    DEVANAGARI = 573;
-    TEXT_BOTTOM = 574;
-    DARKGREEN = 575;
-    VISIBLE = 576;
-    TABULAR_NUMS = 577;
-    MANUAL = 578;
-    ZOOM = 579;
-    CJK_HEAVENLY_STEM = 580;
-    STEPS = 581;
-    BOUNDING_BOX = 582;
-    ALPHABETIC = 583;
-    AFTER_WHITE_SPACE = 584;
-    ROW_REVERSE = 585;
-    MEDIA_CURRENT_TIME_DISPLAY = 586;
-    MATHEMATICAL = 587;
-    ETHIOPIC_HALEHAME = 588;
-    RIGHT = 589;
-    UPPERCASE = 590;
-    _WEBKIT_XXX_LARGE = 591;
-    B4 = 592;
-    B5 = 593;
-    YELLOWGREEN = 594;
-    MEDIA_CONTROLS_FULLSCREEN_BACKGROUND = 595;
-    LOWER_ARMENIAN = 596;
-    ORCHID = 597;
-    NONZERO = 598;
-    SLICE = 599;
-    DENSE = 600;
-    INTER_WORD = 601;
-    BOTTOM = 602;
-    PURPLE = 603;
-    AVOID = 604;
-    SEPARATE = 605;
-    HANGUL = 606;
-    LEGAL = 607;
-    ALTERNATE_REVERSE = 608;
-    PRESERVE_3D = 609;
-    READ_ONLY = 610;
-    ELLIPSIS = 611;
-    MEDIA_OVERLAY_PLAY_BUTTON = 612;
-    BISQUE = 613;
-    INFOTEXT = 614;
-    KHAKI = 615;
-    WHEAT = 616;
-    BOLD = 617;
-    NO_HISTORICAL_LIGATURES = 618;
-    BIDI_OVERRIDE = 619;
-    DEEPSKYBLUE = 620;
-    EASE_OUT = 621;
-    CJK_IDEOGRAPHIC = 622;
-    OLDSTYLE_NUMS = 623;
-    MEDIA_ENTER_FULLSCREEN_BUTTON = 624;
-    SUPER = 625;
-    CURSIVE = 626;
-    ON = 627;
-    CENTRAL = 628;
-    _INTERNAL_MEDIA_OVERFLOW_BUTTON = 629;
-    STANDALONE = 630;
-    COLUMN = 631;
-    CORAL = 632;
-    DESTINATION_OVER = 633;
-    DISCRETIONARY_LIGATURES = 634;
-    BEIGE = 635;
-    TABLE_CELL = 636;
-    AZURE = 637;
-    TRAD_CHINESE_INFORMAL = 638;
-    TITLING_CAPS = 639;
-    _WEBKIT_ZOOM_IN = 640;
-    BLOCK = 641;
-    OUTSET = 642;
-    MEDIUMVIOLETRED = 643;
-    ROYALBLUE = 644;
-    MENULIST_TEXT = 645;
-    SW_RESIZE = 646;
-    MULTIPLY = 647;
-    THREEDDARKSHADOW = 648;
-    WRAP = 649;
-    LIGHTCORAL = 650;
-    ELLIPSE = 651;
-    _INTERNAL_ACTIVE_LIST_BOX_SELECTION_TEXT = 652;
-    RIDGE = 653;
-    _WEBKIT_AUTO = 654;
-    _INTERNAL_QUIRK_INHERIT = 655;
-    INITIAL = 656;
-    FUCHSIA = 657;
-    MENULIST_BUTTON = 658;
-    BLANCHEDALMOND = 659;
-    CARET = 660;
-    START = 661;
-    _INTERNAL_MEDIA_CAST_OFF_BUTTON = 662;
-    ITALIC = 663;
-    IVORY = 664;
-    BUTTONTEXT = 665;
-    SEMI_CONDENSED = 666;
-    INLINE_GRID = 667;
-    _WEBKIT_ACTIVELINK = 668;
-    SERIF = 669;
-    FORESTGREEN = 670;
-    BENGALI = 671;
-    UPRIGHT = 672;
-    RESET = 673;
-    BEVEL = 674;
-    IDEOGRAPHIC = 675;
-    DARKEN = 676;
-    MEDIA_VOLUME_SLIDERTHUMB = 677;
-    DEFAULT = 678;
-    INSIDE = 679;
-    BELOW = 680;
-    HIGHLIGHT = 681;
-    EMBED = 682;
-    GROOVE = 683;
-    NESW_RESIZE = 684;
-    STACKED_FRACTIONS = 685;
-    UNSAFE = 686;
-    MAROON = 687;
-    KANNADA = 688;
-    SINGLE = 689;
-    AT = 690;
-    INK = 691;
-    ARABIC_INDIC = 692;
-    MEDIA_VOLUME_SLIDER = 693;
-    COLUMN_REVERSE = 694;
-    _WEBKIT_ZOOM_OUT = 695;
-    FILL = 696;
-    EVENODD = 697;
-    DOTTED = 698;
-    DIMGRAY = 699;
-    DASHED = 700;
-    SEAGREEN = 701;
-    TRAD_CHINESE_FORMAL = 702;
-    MEDIUMSLATEBLUE = 703;
-    PALETURQUOISE = 704;
-    INNER_SPIN_BUTTON = 705;
-    REPEAT = 706;
-    DARKORCHID = 707;
-    _WEBKIT_ISOLATE = 708;
-    SEARCHFIELD_CANCEL_BUTTON = 709;
-    ALL_SMALL_CAPS = 710;
-    A3 = 711;
-    A5 = 712;
-    A4 = 713;
-    OPEN_QUOTE = 714;
-    LIGHTGREEN = 715;
-    SLASHED_ZERO = 716;
-    COLOR_BURN = 717;
-    AUTO_FLOW = 718;
-    OVERLAY = 719;
-    VISUAL = 720;
-    SCALE_DOWN = 721;
-    OVERLINE = 722;
-    INSET = 723;
-    MEDIUMAQUAMARINE = 724;
-    SCROLL = 725;
-    LAYOUT = 726;
-    BREAK_SPACES = 727;
-    PAN_LEFT = 728;
-    PROXIMITY = 729;
-    INLINE_START = 730;
-    PAN_X = 731;
-    GRAB = 732;
-    GRABBING = 733;
-    INLINE_END = 734;
-    PAN_RIGHT = 735;
-    JUMP_END = 736;
-    MANIPULATION = 737;
-    PINCH_ZOOM = 738;
-    XXX_LARGE = 739;
-    PAN_DOWN = 740;
-    ANYWHERE = 741;
-    JUMP_NONE = 742;
-    DRAG = 743;
-    AVOID_PAGE = 744;
-    MANDATORY = 745;
-    PAINT = 746;
-    JUMP_BOTH = 747;
-    SIZE = 748;
-    STYLE = 749;
-    PAN_Y = 750;
-    RECTO = 751;
-    MARKERS = 752;
-    VERSO = 753;
-    PAGE = 754;
-    PAN_UP = 755;
-    AVOID_COLUMN = 756;
-    SMOOTH = 757;
-    JUMP_START = 758;
-    NO_DRAG = 759;
-    JIS_B5 = 760;
-    JIS_B4 = 761;
-    STANDARD = 762;
-    HIGH = 763;
-    SPELLING_ERROR = 764;
-    GRAMMAR_ERROR = 765;
-    INVALID_VALUE = 766;
-  };
-
-  optional ValueId value_id = 4;
-}
-
-message Expr {
-  required Term term = 1;
-  repeated OperatorTerm operator_terms = 2;
-}
-
-// Not in grammar.
-message OperatorTerm {
-  required Operator _operator = 1;
-  required Term term = 2;
-}
-
-message Term {
-  optional UnaryOperator unary_operator = 1;
-  oneof rhs {
-    //  [ NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* | ANGLE S* |
-    // TIME S* | FREQ S* | function ]
-    TermPart term_part = 2;
-    // | STRING
-    String string = 3;
-  }
-  // S* | IDENT S* | URI S* | UNICODERANGE S* | hexcolor
-  optional Ident ident = 4;
-  optional Uri uri = 5;
-  // TODO(metzman): Add UNICODERANGE token once (if) I implement it.
-  optional Hexcolor hexcolor = 6;
-}
-
-// Not in grammar.
-message TermPart {
-  // NUMBER
-  required Num number = 1;
-  // S* | PERCENTAGE
-  optional Num percentage = 2;  // num "%"
-  // S* | LENGTH
-  optional Length length = 3;
-  optional Num ems = 4;  // {num}em
-  optional Num exs = 5;  // {num}ex
-  optional Angle angle = 6;
-  optional Time time = 7;
-  optional Freq freq = 8;
-  optional Function function = 9;
-}
-
-message Function {
-  required FunctionToken function_token = 1;
-  required Expr expr = 2;
-}
-
-message Hexcolor {
-  required HexcolorThree first_three = 1;
-  optional HexcolorThree last_three = 2;
-}
-
-// Not in grammar.
-message HexcolorThree {
-  // 0-9A-Za-z
-  required H ascii_value_1 = 1;
-  required H ascii_value_2 = 2;
-  required H ascii_value_3 = 3;
-}
-
-message Input {
-  enum CSSParserMode {
-    kHTMLStandardMode = 0;
-    kHTMLQuirksMode = 1;
-    kSVGAttributeMode = 2;
-    kCSSViewportRuleMode = 3;
-    kCSSFontFaceRuleMode = 4;
-    kUASheetMode = 5;
-  }
-  enum SecureContextMode {
-    kInsecureContext = 0;
-    kSecureContext = 1;
-  }
-  required CSSParserMode css_parser_mode = 1;
-  required bool defer_property_parsing = 2;
-  required StyleSheet style_sheet = 3;
-  required bool is_live_profile = 4;
-  required SecureContextMode secure_context_mode = 5;
-}
diff --git a/third_party/blink/renderer/core/css/parser/css_proto_converter.cc b/third_party/blink/renderer/core/css/parser/css_proto_converter.cc
index 6c4ca462..556b3fb1 100644
--- a/third_party/blink/renderer/core/css/parser/css_proto_converter.cc
+++ b/third_party/blink/renderer/core/css/parser/css_proto_converter.cc
@@ -205,1340 +205,7 @@
     "UTF-8", "UTF-16", "UTF-32",
 };
 
-const std::string Converter::kValueLookupTable[] = {
-    "",  // This is just to fill the zeroth spot. It should not be used.
-    "all",
-    "dynamic",
-    "yellow",
-    "graytext",
-    "color-dodge",
-    "darkseagreen",
-    "disc",
-    "extra-condensed",
-    "hanging",
-    "step-middle",
-    "menulist",
-    "row",
-    "pre-wrap",
-    "inline-block",
-    "step-start",
-    "isolate-override",
-    "swap",
-    "rtl",
-    "crimson",
-    "tb",
-    "common-ligatures",
-    "-webkit-min-content",
-    "brown",
-    "khmer",
-    "infinite",
-    "table-header-group",
-    "before-edge",
-    "read-write",
-    "rl",
-    "wavy",
-    "proportional-width",
-    "no-drop",
-    "cyan",
-    "difference",
-    "exact",
-    "square-button",
-    "skyblue",
-    "-webkit-isolate-override",
-    "table-row-group",
-    "darkgray",
-    "button",
-    "ethiopic-halehame-am",
-    "large",
-    "lightpink",
-    "crosshair",
-    "teal",
-    "fill-box",
-    "small",
-    "media-sliderthumb",
-    "round",
-    "-internal-media-subtitles-icon",
-    "media-play-button",
-    "smaller",
-    "jis04",
-    "lr-tb",
-    "lightgoldenrodyellow",
-    "lavender",
-    "ultra-expanded",
-    "dimgrey",
-    "diagonal-fractions",
-    "blue",
-    "xor",
-    "sub",
-    "telugu",
-    "crispEdges",
-    "-webkit-mini-control",
-    "zoom-out",
-    "searchfield",
-    "cell",
-    "gujarati",
-    "above",
-    "no-punctuation",
-    "new",
-    "filled",
-    "use-script",
-    "condensed",
-    "loose",
-    "source-out",
-    "objects",
-    "slider-horizontal",
-    "row-resize",
-    "break-all",
-    "wait",
-    "media-exit-fullscreen-button",
-    "korean-hangul-formal",
-    "floralwhite",
-    "reset-size",
-    "zoom-in",
-    "-webkit-grabbing",
-    "larger",
-    "max-content",
-    "sRGB",
-    "literal-punctuation",
-    "windowframe",
-    "subpixel-antialiased",
-    "buttonhighlight",
-    "hue",
-    "pixelated",
-    "sticky",
-    "greenyellow",
-    "linearRGB",
-    "lightseagreen",
-    "logical",
-    "-webkit-right",
-    "sienna",
-    "flow-root",
-    "optimizeSpeed",
-    "korean-hanja-formal",
-    "nowrap",
-    "x-small",
-    "landscape",
-    "lime",
-    "x-large",
-    "ns-resize",
-    "appworkspace",
-    "peru",
-    "all-petite-caps",
-    "black",
-    "xx-small",
-    "all-scroll",
-    "darkslategray",
-    "flat",
-    "georgian",
-    "under",
-    "lemonchiffon",
-    "chocolate",
-    "pre-line",
-    "context-menu",
-    "darkgrey",
-    "view-box",
-    "olive",
-    "-webkit-plaintext",
-    "extra-expanded",
-    "antiquewhite",
-    "none",
-    "moccasin",
-    "local",
-    "stroke",
-    "darkslateblue",
-    "lightskyblue",
-    "content-box",
-    "thin",
-    "deeppink",
-    "spell-out",
-    "non-scaling-stroke",
-    "slider-vertical",
-    "-webkit-box",
-    "plum",
-    "-internal-media-overlay-cast-off-button",
-    "inactivecaptiontext",
-    "dodgerblue",
-    "threedshadow",
-    "petite-caps",
-    "paused",
-    "-webkit-link",
-    "message-box",
-    "-internal-center",
-    "triangle",
-    "magenta",
-    "tan",
-    "absolute",
-    "pink",
-    "hiragana-iroha",
-    "farthest-side",
-    "palevioletred",
-    "close-quote",
-    "threedlightshadow",
-    "caption",
-    "powderblue",
-    "table-column",
-    "source-atop",
-    "hiragana",
-    "upper-armenian",
-    "windowtext",
-    "full-width",
-    "progress-bar-value",
-    "midnightblue",
-    "inline-flex",
-    "economy",
-    "lao",
-    "clone",
-    "after",
-    "status-bar",
-    "lowercase",
-    "mixed",
-    "line-through",
-    "lightslategray",
-    "small-caption",
-    "infobackground",
-    "discard",
-    "captiontext",
-    "end",
-    "-internal-inactive-list-box-selection-text",
-    "capitalize",
-    "mediumseagreen",
-    "tomato",
-    "cadetblue",
-    "decimal-leading-zero",
-    "sans-serif",
-    "linen",
-    "green",
-    "inactiveborder",
-    "inline",
-    "fallback",
-    "peachpuff",
-    "-webkit-max-content",
-    "plus-lighter",
-    "checkbox",
-    "help",
-    "oblique",
-    "move",
-    "meter",
-    "ledger",
-    "slategrey",
-    "media-time-remaining-display",
-    "urdu",
-    "pointer",
-    "before",
-    "darkslategrey",
-    "-webkit-control",
-    "-webkit-inline-box",
-    "hard-light",
-    "miter",
-    "oriya",
-    "upper-latin",
-    "window",
-    "mediumblue",
-    "lr",
-    "orange",
-    "hidden",
-    "-internal-active-list-box-selection",
-    "bolder",
-    "-webkit-center",
-    "safe",
-    "highlighttext",
-    "accumulate",
-    "flex-end",
-    "transparent",
-    "-internal-media-remoting-cast-icon",
-    "goldenrod",
-    "historical-ligatures",
-    "darkviolet",
-    "always",
-    "decimal",
-    "block-axis",
-    "scrollbar",
-    "ew-resize",
-    "darkmagenta",
-    "not-allowed",
-    "ease-in",
-    "table-column-group",
-    "square",
-    "no-contextual",
-    "-webkit-fill-available",
-    "frames",
-    "persian",
-    "static",
-    "navy",
-    "visiblePainted",
-    "thick",
-    "simp-chinese-formal",
-    "ghostwhite",
-    "space",
-    "darkkhaki",
-    "keep-all",
-    "content",
-    "-internal-media-download-button",
-    "upper-roman",
-    "cornsilk",
-    "red",
-    "no-change",
-    "linear",
-    "-internal-media-control",
-    "sideways",
-    "contain",
-    "katakana-iroha",
-    "steelblue",
-    "double-circle",
-    "antialiased",
-    "aliceblue",
-    "lightslategrey",
-    "geometricPrecision",
-    "gainsboro",
-    "inline-table",
-    "ltr",
-    "backwards",
-    "s-resize",
-    "lightgrey",
-    "media-mute-button",
-    "listitem",
-    "mistyrose",
-    "darksalmon",
-    "sideways-right",
-    "jis83",
-    "mediumspringgreen",
-    "caps-lock-indicator",
-    "sliderthumb-horizontal",
-    "forwards",
-    "upper-alpha",
-    "blink",
-    "fantasy",
-    "simplified",
-    "orangered",
-    "navajowhite",
-    "open",
-    "horizontal",
-    "slategray",
-    "activecaption",
-    "korean-hanja-informal",
-    "strict",
-    "lightcyan",
-    "top",
-    "-webkit-pictograph",
-    "white",
-    "text-after-edge",
-    "lightgray",
-    "collapse",
-    "hover",
-    "-webkit-optimize-contrast",
-    "padding",
-    "butt",
-    "off",
-    "thai",
-    "copy",
-    "hotpink",
-    "double",
-    "lower-greek",
-    "grey",
-    "media-volume-slider-container",
-    "-webkit-inline-flex",
-    "space-evenly",
-    "activeborder",
-    "browser",
-    "pre",
-    "unicase",
-    "simp-chinese-informal",
-    "clip",
-    "closest-corner",
-    "plaintext",
-    "no-repeat",
-    "text-top",
-    "jis78",
-    "xx-large",
-    "rl-tb",
-    "table-row",
-    "medium",
-    "mongolian",
-    "katakana",
-    "element",
-    "border",
-    "rosybrown",
-    "progress-bar",
-    "whitesmoke",
-    "lightblue",
-    "-webkit-left",
-    "no-common-ligatures",
-    "listbox",
-    "isolate",
-    "snow",
-    "step-end",
-    "ethiopic-halehame-ti-er",
-    "ethiopic-halehame-ti-et",
-    "multiple",
-    "-internal-inactive-list-box-selection",
-    "normal",
-    "blueviolet",
-    "salmon",
-    "lower-alpha",
-    "oldlace",
-    "letter",
-    "border-box",
-    "alpha",
-    "tibetan",
-    "icon",
-    "flex-start",
-    "textarea",
-    "w-resize",
-    "clear",
-    "cover",
-    "farthest-corner",
-    "menulist-textfield",
-    "traditional",
-    "left",
-    "dot",
-    "luminance",
-    "gold",
-    "show",
-    "text",
-    "-webkit-match-parent",
-    "radio",
-    "cambodian",
-    "repeat-x",
-    "repeat-y",
-    "fine",
-    "textfield",
-    "from-image",
-    "lining-nums",
-    "menu",
-    "proportional-nums",
-    "source-over",
-    "ne-resize",
-    "papayawhip",
-    "source-in",
-    "se-resize",
-    "circle",
-    "destination-out",
-    "threedface",
-    "over",
-    "distribute",
-    "inactivecaption",
-    "lighten",
-    "-webkit-fit-content",
-    "lighter",
-    "contextual",
-    "gray",
-    "darkturquoise",
-    "e-resize",
-    "luminosity",
-    "list-item",
-    "limegreen",
-    "fixed",
-    "min-content",
-    "media-slider",
-    "visibleStroke",
-    "cubic-bezier",
-    "closest-side",
-    "relative",
-    "no-open-quote",
-    "thistle",
-    "violet",
-    "portrait",
-    "fullscreen",
-    "honeydew",
-    "on-demand",
-    "cornflowerblue",
-    "darkblue",
-    "outside",
-    "progress",
-    "mediumpurple",
-    "darkcyan",
-    "vertical",
-    "monospace",
-    "break-word",
-    "screen",
-    "rebeccapurple",
-    "darkred",
-    "vertical-lr",
-    "optimizeQuality",
-    "armenian",
-    "nwse-resize",
-    "text-before-edge",
-    "optional",
-    "exclusion",
-    "both",
-    "mediumturquoise",
-    "lower-roman",
-    "reverse",
-    "hangul-consonant",
-    "soft-light",
-    "aqua",
-    "button-bevel",
-    "gurmukhi",
-    "lightsteelblue",
-    "small-caps",
-    "n-resize",
-    "table-footer-group",
-    "destination-in",
-    "olivedrab",
-    "read-write-plaintext-only",
-    "padding-box",
-    "col-resize",
-    "-internal-media-track-selection-checkmark",
-    "lower-latin",
-    "-webkit-nowrap",
-    "table",
-    "buttonshadow",
-    "palegreen",
-    "jis90",
-    "fit-content",
-    "stretch",
-    "seashell",
-    "threedhighlight",
-    "visibleFill",
-    "space-around",
-    "coarse",
-    "aquamarine",
-    "digits",
-    "currentcolor",
-    "painted",
-    "tb-rl",
-    "buttonface",
-    "lawngreen",
-    "burlywood",
-    "-webkit-small-control",
-    "slateblue",
-    "mintcream",
-    "ruby",
-    "solid",
-    "ultra-condensed",
-    "expanded",
-    "saddlebrown",
-    "vertical-rl",
-    "sesame",
-    "-webkit-body",
-    "destination-atop",
-    "malayalam",
-    "wrap-reverse",
-    "balance",
-    "vertical-right",
-    "no-close-quote",
-    "flex",
-    "push-button",
-    "darkgoldenrod",
-    "saturation",
-    "middle",
-    "sandybrown",
-    "hebrew",
-    "menutext",
-    "inline-axis",
-    "baseline",
-    "-webkit-grab",
-    "darkorange",
-    "-webkit-flex",
-    "nw-resize",
-    "contents",
-    "auto",
-    "margin-box",
-    "document",
-    "palegoldenrod",
-    "ordinal",
-    "hand",
-    "running",
-    "cjk-earthly-branch",
-    "table-caption",
-    "media-toggle-closed-captions-button",
-    "after-edge",
-    "sliderthumb-vertical",
-    "center",
-    "lightyellow",
-    "lavenderblush",
-    "-internal-media-closed-captions-icon",
-    "inherit",
-    "media-controls-background",
-    "justify",
-    "optimizeLegibility",
-    "-webkit-baseline-middle",
-    "indigo",
-    "minimal-ui",
-    "firebrick",
-    "indianred",
-    "darkolivegreen",
-    "semi-expanded",
-    "underline",
-    "myanmar",
-    "space-between",
-    "ease",
-    "alternate",
-    "mediumorchid",
-    "silver",
-    "color",
-    "chartreuse",
-    "ease-in-out",
-    "springgreen",
-    "lightsalmon",
-    "turquoise",
-    "hide",
-    "horizontal-tb",
-    "vertical-text",
-    "alias",
-    "grid",
-    "no-discretionary-ligatures",
-    "background",
-    "devanagari",
-    "text-bottom",
-    "darkgreen",
-    "visible",
-    "tabular-nums",
-    "manual",
-    "zoom",
-    "cjk-heavenly-stem",
-    "steps",
-    "bounding-box",
-    "alphabetic",
-    "after-white-space",
-    "row-reverse",
-    "media-current-time-display",
-    "mathematical",
-    "ethiopic-halehame",
-    "right",
-    "uppercase",
-    "-webkit-xxx-large",
-    "b4",
-    "b5",
-    "yellowgreen",
-    "media-controls-fullscreen-background",
-    "lower-armenian",
-    "orchid",
-    "nonzero",
-    "slice",
-    "dense",
-    "inter-word",
-    "bottom",
-    "purple",
-    "avoid",
-    "separate",
-    "hangul",
-    "legal",
-    "alternate-reverse",
-    "preserve-3d",
-    "read-only",
-    "ellipsis",
-    "media-overlay-play-button",
-    "bisque",
-    "infotext",
-    "khaki",
-    "wheat",
-    "bold",
-    "no-historical-ligatures",
-    "bidi-override",
-    "deepskyblue",
-    "ease-out",
-    "cjk-ideographic",
-    "oldstyle-nums",
-    "media-enter-fullscreen-button",
-    "super",
-    "cursive",
-    "on",
-    "central",
-    "-internal-media-overflow-button",
-    "standalone",
-    "column",
-    "coral",
-    "destination-over",
-    "discretionary-ligatures",
-    "beige",
-    "table-cell",
-    "azure",
-    "trad-chinese-informal",
-    "titling-caps",
-    "-webkit-zoom-in",
-    "block",
-    "outset",
-    "mediumvioletred",
-    "royalblue",
-    "menulist-text",
-    "sw-resize",
-    "multiply",
-    "threeddarkshadow",
-    "wrap",
-    "lightcoral",
-    "ellipse",
-    "-internal-active-list-box-selection-text",
-    "ridge",
-    "-webkit-auto",
-    "-internal-quirk-inherit",
-    "initial",
-    "fuchsia",
-    "menulist-button",
-    "blanchedalmond",
-    "caret",
-    "start",
-    "-internal-media-cast-off-button",
-    "italic",
-    "ivory",
-    "buttontext",
-    "semi-condensed",
-    "inline-grid",
-    "-webkit-activelink",
-    "serif",
-    "forestgreen",
-    "bengali",
-    "upright",
-    "reset",
-    "bevel",
-    "ideographic",
-    "darken",
-    "media-volume-sliderthumb",
-    "default",
-    "inside",
-    "below",
-    "highlight",
-    "embed",
-    "groove",
-    "nesw-resize",
-    "stacked-fractions",
-    "unsafe",
-    "maroon",
-    "kannada",
-    "single",
-    "at",
-    "ink",
-    "arabic-indic",
-    "media-volume-slider",
-    "column-reverse",
-    "-webkit-zoom-out",
-    "fill",
-    "evenodd",
-    "dotted",
-    "dimgray",
-    "dashed",
-    "seagreen",
-    "trad-chinese-formal",
-    "mediumslateblue",
-    "paleturquoise",
-    "inner-spin-button",
-    "repeat",
-    "darkorchid",
-    "-webkit-isolate",
-    "searchfield-cancel-button",
-    "all-small-caps",
-    "a3",
-    "a5",
-    "a4",
-    "open-quote",
-    "lightgreen",
-    "slashed-zero",
-    "color-burn",
-    "auto-flow",
-    "overlay",
-    "visual",
-    "scale-down",
-    "overline",
-    "inset",
-    "mediumaquamarine",
-    "scroll",
-    "layout",
-    "break-spaces",
-    "pan-left",
-    "proximity",
-    "inline-start",
-    "pan-x",
-    "grab",
-    "grabbing",
-    "inline-end",
-    "pan-right",
-    "jump-end",
-    "manipulation",
-    "pinch-zoom",
-    "xxx-large",
-    "pan-down",
-    "anywhere",
-    "jump-none",
-    "drag",
-    "avoid-page",
-    "mandatory",
-    "paint",
-    "jump-both",
-    "size",
-    "style",
-    "pan-y",
-    "recto",
-    "markers",
-    "verso",
-    "page",
-    "pan-up",
-    "avoid-column",
-    "smooth",
-    "jump-start",
-    "no-drag",
-    "jis-b5",
-    "jis-b4",
-    "standard",
-    "high",
-    "spelling-error",
-    "grammar-error",
-    "INVALID_VALUE",
-};
-
-const std::string Converter::kPropertyLookupTable[] = {
-    "",  // This is just to fill the zeroth spot. It should not be used.
-    "all",
-    "-webkit-animation-iteration-count",
-    "font-feature-settings",
-    "-webkit-text-emphasis-position",
-    "-webkit-text-emphasis-style",
-    "grid-template-rows",
-    "text-underline-position",
-    "-webkit-flex-grow",
-    "scroll-margin-right",
-    "-webkit-column-rule",
-    "-webkit-order",
-    "grid-row-gap",
-    "row-gap",
-    "backdrop-filter",
-    "font-variant-east-asian",
-    "buffered-rendering",
-    "-webkit-appearance",
-    "outline-width",
-    "alignment-baseline",
-    "-webkit-flex-flow",
-    "column-rule",
-    "grid-column-gap",
-    "-webkit-border-after",
-    "-webkit-column-break-inside",
-    "-webkit-shape-outside",
-    "-webkit-print-color-adjust",
-    "list-style-type",
-    "page-break-before",
-    "flood-color",
-    "text-anchor",
-    "-webkit-padding-start",
-    "-webkit-user-select",
-    "-webkit-column-rule-color",
-    "padding-left",
-    "-webkit-backface-visibility",
-    "-webkit-margin-before",
-    "break-inside",
-    "column-count",
-    "-webkit-logical-height",
-    "perspective",
-    "max-block-size",
-    "-webkit-animation-play-state",
-    "border-image-repeat",
-    "-internal-font-size-delta",
-    "scroll-padding-bottom",
-    "border-right-style",
-    "border-left-style",
-    "scroll-margin-block",
-    "flex-flow",
-    "outline-color",
-    "flex-grow",
-    "max-width",
-    "grid-column",
-    "image-orientation",
-    "animation-duration",
-    "-webkit-columns",
-    "-webkit-animation-delay",
-    "-epub-text-emphasis",
-    "flex-shrink",
-    "text-rendering",
-    "align-items",
-    "border-collapse",
-    "offset",
-    "text-combine-upright",
-    "-webkit-mask-position-x",
-    "-webkit-mask-position-y",
-    "outline-style",
-    "color-interpolation-filters",
-    "font-variant",
-    "-webkit-animation-fill-mode",
-    "border-right",
-    "visibility",
-    "transform-box",
-    "font-variant-caps",
-    "-epub-text-emphasis-color",
-    "-webkit-border-before-style",
-    "resize",
-    "-webkit-rtl-ordering",
-    "-webkit-box-ordinal-group",
-    "paint-order",
-    "stroke-linecap",
-    "animation-direction",
-    "-webkit-font-feature-settings",
-    "border-top-left-radius",
-    "-webkit-column-width",
-    "-webkit-box-align",
-    "-webkit-padding-after",
-    "column-width",
-    "list-style",
-    "-webkit-mask-repeat-y",
-    "stroke",
-    "text-decoration-line",
-    "-webkit-background-size",
-    "-webkit-mask-repeat-x",
-    "padding-bottom",
-    "font-style",
-    "-webkit-transition-delay",
-    "background-repeat",
-    "flex-basis",
-    "border-image-slice",
-    "-webkit-transform-origin",
-    "scroll-boundary-behavior-x",
-    "scroll-boundary-behavior-y",
-    "vector-effect",
-    "-webkit-animation-timing-function",
-    "-webkit-border-after-style",
-    "-webkit-perspective-origin-x",
-    "-webkit-perspective-origin-y",
-    "inline-size",
-    "outline",
-    "font-display",
-    "-webkit-border-before",
-    "border-image-source",
-    "transition-duration",
-    "scroll-padding-top",
-    "order",
-    "-webkit-box-orient",
-    "counter-reset",
-    "color-rendering",
-    "flex-direction",
-    "-webkit-text-stroke-width",
-    "font-variant-numeric",
-    "scroll-margin-block-end",
-    "min-height",
-    "scroll-padding-inline-start",
-    "-webkit-mask-box-image",
-    "left",
-    "-webkit-mask",
-    "-webkit-border-after-width",
-    "stroke-width",
-    "-webkit-box-decoration-break",
-    "-webkit-mask-position",
-    "background-origin",
-    "-webkit-border-start-color",
-    "font-stretch",
-    "-webkit-background-clip",
-    "scroll-margin-top",
-    "-webkit-border-horizontal-spacing",
-    "border-radius",
-    "flex",
-    "text-indent",
-    "hyphens",
-    "column-rule-width",
-    "-webkit-margin-after",
-    "-epub-caption-side",
-    "break-after",
-    "text-transform",
-    "touch-action",
-    "font-size",
-    "-webkit-animation-name",
-    "scroll-padding-inline",
-    "offset-path",
-    "scroll-margin",
-    "padding-top",
-    "scroll-snap-align",
-    "-webkit-text-combine",
-    "-webkit-flex-shrink",
-    "rx",
-    "ry",
-    "content",
-    "padding-right",
-    "-webkit-transform",
-    "marker-mid",
-    "-webkit-min-logical-width",
-    "clip-rule",
-    "font-family",
-    "scroll-snap-type",
-    "text-decoration-skip-ink",
-    "transition",
-    "filter",
-    "border-right-width",
-    "-webkit-flex-direction",
-    "-webkit-mask-composite",
-    "mix-blend-mode",
-    "color-interpolation",
-    "border-top-style",
-    "fill-opacity",
-    "marker-start",
-    "border-bottom-width",
-    "-webkit-text-emphasis",
-    "grid-area",
-    "size",
-    "background-clip",
-    "-webkit-text-fill-color",
-    "top",
-    "-webkit-box-reflect",
-    "border-width",
-    "offset-anchor",
-    "max-inline-size",
-    "-webkit-column-rule-style",
-    "-webkit-column-count",
-    "animation-play-state",
-    "padding",
-    "dominant-baseline",
-    "background-attachment",
-    "-webkit-box-sizing",
-    "-webkit-box-flex",
-    "text-orientation",
-    "background-position",
-    "-webkit-border-start-width",
-    "-epub-text-emphasis-style",
-    "isolation",
-    "-epub-text-orientation",
-    "-webkit-border-bottom-right-radius",
-    "r",
-    "border-left-width",
-    "grid-column-end",
-    "background-blend-mode",
-    "vertical-align",
-    "clip",
-    "grid-auto-rows",
-    "offset-rotate",
-    "margin-left",
-    "animation-name",
-    "text-decoration",
-    "border",
-    "-webkit-transition-timing-function",
-    "margin-bottom",
-    "unicode-range",
-    "animation",
-    "-webkit-shape-margin",
-    "font-weight",
-    "shape-margin",
-    "mask-type",
-    "scroll-padding",
-    "min-inline-size",
-    "object-position",
-    "page-break-after",
-    "-webkit-mask-clip",
-    "white-space",
-    "-webkit-border-after-color",
-    "-webkit-max-logical-width",
-    "-webkit-border-before-color",
-    "font-kerning",
-    "-epub-word-break",
-    "clear",
-    "animation-timing-function",
-    "-webkit-border-radius",
-    "scroll-padding-right",
-    "-webkit-text-decorations-in-effect",
-    "-webkit-animation-direction",
-    "justify-self",
-    "transition-timing-function",
-    "scroll-snap-stop",
-    "counter-increment",
-    "-webkit-transform-style",
-    "grid-auto-columns",
-    "-webkit-align-content",
-    "font",
-    "flex-wrap",
-    "grid-row-start",
-    "list-style-image",
-    "-webkit-tap-highlight-color",
-    "-webkit-text-emphasis-color",
-    "border-left",
-    "-webkit-border-end-color",
-    "columns",
-    "box-shadow",
-    "-webkit-flex-wrap",
-    "align-self",
-    "border-bottom",
-    "border-spacing",
-    "-webkit-column-span",
-    "grid-row-end",
-    "-webkit-border-end",
-    "perspective-origin",
-    "page-break-inside",
-    "orphans",
-    "-webkit-border-start-style",
-    "scroll-behavior",
-    "column-span",
-    "-webkit-hyphenate-character",
-    "column-fill",
-    "tab-size",
-    "contain",
-    "x",
-    "grid-row",
-    "border-bottom-right-radius",
-    "line-height",
-    "stroke-linejoin",
-    "text-align-last",
-    "offset-position",
-    "word-spacing",
-    "transform-style",
-    "-webkit-app-region",
-    "-webkit-border-end-style",
-    "-webkit-transform-origin-z",
-    "-webkit-transform-origin-x",
-    "-webkit-transform-origin-y",
-    "background-repeat-x",
-    "background-repeat-y",
-    "border-bottom-color",
-    "-webkit-ruby-position",
-    "-webkit-logical-width",
-    "text-justify",
-    "scroll-margin-inline-start",
-    "caption-side",
-    "mask-source-type",
-    "-webkit-mask-box-image-slice",
-    "-webkit-border-image",
-    "text-size-adjust",
-    "-webkit-text-security",
-    "-epub-writing-mode",
-    "grid-template",
-    "-webkit-mask-box-image-repeat",
-    "-webkit-mask-repeat",
-    "-webkit-justify-content",
-    "baseline-shift",
-    "border-image",
-    "text-decoration-color",
-    "color",
-    "shape-image-threshold",
-    "shape-rendering",
-    "cy",
-    "cx",
-    "-webkit-user-modify",
-    "offset-distance",
-    "-webkit-border-bottom-left-radius",
-    "speak",
-    "border-bottom-left-radius",
-    "-webkit-column-break-after",
-    "-webkit-font-smoothing",
-    "-webkit-max-logical-height",
-    "-webkit-line-break",
-    "fill-rule",
-    "-webkit-margin-start",
-    "min-width",
-    "-epub-text-combine",
-    "break-before",
-    "caret-color",
-    "empty-cells",
-    "direction",
-    "clip-path",
-    "justify-content",
-    "scroll-padding-block-end",
-    "z-index",
-    "background-position-y",
-    "text-decoration-style",
-    "grid-template-areas",
-    "-webkit-min-logical-height",
-    "font-size-adjust",
-    "scroll-padding-block",
-    "overflow-anchor",
-    "cursor",
-    "scroll-margin-block-start",
-    "-webkit-mask-box-image-source",
-    "margin",
-    "-webkit-animation",
-    "letter-spacing",
-    "orientation",
-    "will-change",
-    "-webkit-highlight",
-    "transform-origin",
-    "font-variant-ligatures",
-    "-webkit-animation-duration",
-    "-webkit-mask-origin",
-    "-webkit-clip-path",
-    "word-break",
-    "table-layout",
-    "text-overflow",
-    "-webkit-locale",
-    "-webkit-flex",
-    "grid-auto-flow",
-    "border-top-right-radius",
-    "border-image-outset",
-    "place-items",
-    "border-left-color",
-    "font-variation-settings",
-    "border-right-color",
-    "min-zoom",
-    "scroll-margin-inline",
-    "-webkit-border-before-width",
-    "backface-visibility",
-    "background-image",
-    "-webkit-transition-property",
-    "writing-mode",
-    "stroke-opacity",
-    "box-sizing",
-    "margin-top",
-    "column-rule-color",
-    "y",
-    "position",
-    "scroll-margin-bottom",
-    "list-style-position",
-    "-webkit-box-pack",
-    "scroll-padding-inline-end",
-    "quotes",
-    "border-top",
-    "scroll-padding-left",
-    "-webkit-transition",
-    "-webkit-column-break-before",
-    "lighting-color",
-    "background-size",
-    "-webkit-padding-before",
-    "-webkit-border-top-left-radius",
-    "flood-opacity",
-    "line-height-step",
-    "-webkit-mask-size",
-    "text-align",
-    "-webkit-filter",
-    "word-wrap",
-    "max-zoom",
-    "grid",
-    "background",
-    "height",
-    "grid-column-start",
-    "animation-fill-mode",
-    "rotate",
-    "marker-end",
-    "d",
-    "justify-items",
-    "zoom",
-    "scroll-padding-block-start",
-    "page",
-    "right",
-    "user-select",
-    "margin-right",
-    "marker",
-    "line-break",
-    "-webkit-margin-end",
-    "-webkit-transition-duration",
-    "-webkit-writing-mode",
-    "border-top-width",
-    "bottom",
-    "place-content",
-    "-webkit-shape-image-threshold",
-    "-webkit-user-drag",
-    "-webkit-border-vertical-spacing",
-    "-webkit-column-gap",
-    "-webkit-opacity",
-    "background-color",
-    "column-gap",
-    "shape-outside",
-    "-webkit-padding-end",
-    "-webkit-border-start",
-    "animation-delay",
-    "unicode-bidi",
-    "text-shadow",
-    "-webkit-box-direction",
-    "image-rendering",
-    "src",
-    "gap",
-    "grid-gap",
-    "pointer-events",
-    "border-image-width",
-    "min-block-size",
-    "transition-property",
-    "-webkit-mask-image",
-    "float",
-    "max-height",
-    "outline-offset",
-    "-webkit-box-shadow",
-    "overflow-wrap",
-    "block-size",
-    "transform",
-    "place-self",
-    "width",
-    "stroke-miterlimit",
-    "stop-opacity",
-    "border-top-color",
-    "translate",
-    "object-fit",
-    "-webkit-mask-box-image-width",
-    "-webkit-background-origin",
-    "-webkit-align-items",
-    "transition-delay",
-    "scroll-margin-left",
-    "border-style",
-    "animation-iteration-count",
-    "overflow",
-    "user-zoom",
-    "-webkit-border-top-right-radius",
-    "grid-template-columns",
-    "-webkit-align-self",
-    "-webkit-perspective-origin",
-    "column-rule-style",
-    "display",
-    "-webkit-column-rule-width",
-    "border-color",
-    "-webkit-flex-basis",
-    "stroke-dashoffset",
-    "-webkit-text-size-adjust",
-    "scroll-boundary-behavior",
-    "-webkit-text-stroke",
-    "widows",
-    "fill",
-    "overflow-y",
-    "overflow-x",
-    "opacity",
-    "-webkit-perspective",
-    "-webkit-text-stroke-color",
-    "scroll-margin-inline-end",
-    "scale",
-    "-webkit-text-orientation",
-    "-webkit-mask-box-image-outset",
-    "align-content",
-    "-webkit-border-end-width",
-    "border-bottom-style",
-    "mask",
-    "background-position-x",
-    "-epub-text-transform",
-    "stop-color",
-    "stroke-dasharray",
-    "-webkit-line-clamp",
-    "margin-block-start",
-    "margin-block-end",
-    "margin-inline-start",
-    "margin-inline-end",
-    "padding-block-start",
-    "padding-block-end",
-    "padding-inline-start",
-    "padding-inline-end",
-    "border-block-start-width",
-    "border-block-start-style",
-    "border-block-start-color",
-    "border-block-end-width",
-    "border-block-end-style",
-    "border-block-end-color",
-    "border-inline-start-width",
-    "border-inline-start-style",
-    "border-inline-start-color",
-    "border-inline-end-width",
-    "border-inline-end-style",
-    "border-inline-end-color",
-    "border-block-start",
-    "border-block-end",
-    "border-inline-start",
-    "border-inline-end",
-    "margin-block",
-    "margin-inline",
-    "padding-block",
-    "padding-inline",
-    "border-block-width",
-    "border-block-style",
-    "border-block-color",
-    "border-inline-width",
-    "border-inline-style",
-    "border-inline-color",
-    "border-block",
-    "border-inline",
-    "inset-block-start",
-    "inset-block-end",
-    "inset-block",
-    "inset-inline-start",
-    "inset-inline-end",
-    "inset-inline",
-    "inset",
-    "overflow-block",
-    "overflow-inline",
-    "forced-color-adjust",
-    "overscroll-behavior-inline",
-    "overscroll-behavior-block",
-    "overscroll-behavior-x",
-    "overscroll-behavior-y",
-    "animation-timeline",
-    "counter-set",
-    "border-start-start-radius",
-    "border-start-end-radius",
-    "border-end-start-radius",
-    "border-end-end-radius",
-    "INVALID_PROPERTY",
-};
+#include "third_party/blink/renderer/core/css/parser/css_proto_converter_generated.h"
 
 Converter::Converter() = default;
 
diff --git a/third_party/blink/renderer/core/layout/depth_ordered_layout_object_list.h b/third_party/blink/renderer/core/layout/depth_ordered_layout_object_list.h
index fd96aef..e1e519c 100644
--- a/third_party/blink/renderer/core/layout/depth_ordered_layout_object_list.h
+++ b/third_party/blink/renderer/core/layout/depth_ordered_layout_object_list.h
@@ -5,6 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_DEPTH_ORDERED_LAYOUT_OBJECT_LIST_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_DEPTH_ORDERED_LAYOUT_OBJECT_LIST_H_
 
+#include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_set.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
@@ -61,7 +62,7 @@
   void Clear();
 
   int size() const;
-  bool IsEmpty() const;
+  CORE_EXPORT bool IsEmpty() const;
 
   const HeapHashSet<Member<LayoutObject>>& Unordered() const;
   const HeapVector<LayoutObjectWithDepth>& Ordered();
diff --git a/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.cc b/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.cc
index 7bb3909..718a1c6 100644
--- a/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.cc
+++ b/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.cc
@@ -416,7 +416,8 @@
 
   // Even if we are a layout root, our baseline may have shifted. In this
   // (rare) case, mark our containing-block for layout.
-  if (is_layout_root && previous_result) {
+  // The baseline of SVG <text> doesn't affect other boxes.
+  if (is_layout_root && previous_result && !Base::IsNGSVGText()) {
     if (To<NGPhysicalBoxFragment>(previous_result->PhysicalFragment())
             .Baseline() != physical_fragment.Baseline()) {
       if (auto* containing_block = Base::ContainingBlock()) {
diff --git a/third_party/blink/renderer/core/layout/ng/svg/layout_ng_svg_text_test.cc b/third_party/blink/renderer/core/layout/ng/svg/layout_ng_svg_text_test.cc
index 6edbae8..e73977f 100644
--- a/third_party/blink/renderer/core/layout/ng/svg/layout_ng_svg_text_test.cc
+++ b/third_party/blink/renderer/core/layout/ng/svg/layout_ng_svg_text_test.cc
@@ -3,12 +3,36 @@
 // found in the LICENSE file.
 
 #include "third_party/blink/renderer/core/layout/ng/ng_layout_test.h"
+#include "third_party/blink/renderer/core/loader/empty_clients.h"
 
 namespace blink {
 
+class LayoutCounts : public EmptyLocalFrameClient {
+ public:
+  uint32_t AllCallCount() const { return all_call_count_; }
+
+ private:
+  void DidObserveLayoutNg(uint32_t all_block_count,
+                          uint32_t ng_block_count,
+                          uint32_t all_call_count,
+                          uint32_t ng_call_count,
+                          uint32_t flexbox_ng_block_count,
+                          uint32_t grid_ng_block_count) override {
+    all_call_count_ += all_call_count;
+  }
+
+  uint32_t all_call_count_ = 0;
+};
+
 class LayoutNGSVGTextTest : public NGLayoutTest {
  public:
-  LayoutNGSVGTextTest() : svg_text_ng_(true) {}
+  LayoutNGSVGTextTest()
+      : NGLayoutTest(MakeGarbageCollected<LayoutCounts>()),
+        svg_text_ng_(true) {}
+
+  uint32_t AllLayoutCallCount() const {
+    return static_cast<LayoutCounts*>(GetFrame().Client())->AllCallCount();
+  }
 
  private:
   ScopedSVGTextNGForTest svg_text_ng_;
@@ -72,4 +96,27 @@
   EXPECT_FALSE(std::isinf(box.height()));
 }
 
+// crbug.com/1285666
+TEST_F(LayoutNGSVGTextTest, SubtreeLayout) {
+  SetBodyInnerHTML(R"HTML(
+<body>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 480 360">
+<text x="240" y="25" font-size="16" id="t">foo</text>
+<text x="240" y="50" font-size="16" id="t2">bar</text>
+</svg>
+</body>)HTML");
+  UpdateAllLifecyclePhasesForTest();
+  LocalFrameView* frame_view = GetFrame().View();
+  LayoutView& view = GetLayoutView();
+  ASSERT_FALSE(view.NeedsLayout());
+
+  GetElementById("t")->setAttribute("transform", "scale(0.5)");
+  EXPECT_TRUE(frame_view->IsSubtreeLayout());
+
+  uint32_t pre_layout_count = AllLayoutCallCount();
+  UpdateAllLifecyclePhasesForTest();
+  // Only the <text> and its parent <svg> should be laid out again.
+  EXPECT_EQ(2u, AllLayoutCallCount() - pre_layout_count);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder_test.cc b/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder_test.cc
index 2a7251e..31bbaf2 100644
--- a/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder_test.cc
+++ b/third_party/blink/renderer/core/paint/compositing/compositing_reason_finder_test.cc
@@ -51,17 +51,6 @@
 
 INSTANTIATE_PAINT_TEST_SUITE_P(CompositingReasonFinderTest);
 
-TEST_P(CompositingReasonFinderTest, CompositingReasonDependencies) {
-  EXPECT_FALSE(CompositingReason::kComboAllDirectNonStyleDeterminedReasons &
-               (~CompositingReason::kComboAllDirectReasons));
-  EXPECT_REASONS(
-      CompositingReason::kComboAllDirectReasons,
-      CompositingReason::kComboAllDirectStyleDeterminedReasons |
-          CompositingReason::kComboAllDirectNonStyleDeterminedReasons);
-  EXPECT_FALSE(CompositingReason::kComboAllDirectNonStyleDeterminedReasons &
-               CompositingReason::kComboAllStyleDeterminedReasons);
-}
-
 TEST_P(CompositingReasonFinderTest, PromoteTrivial3D) {
   SetBodyInnerHTML(R"HTML(
     <div id='target'
diff --git a/third_party/blink/renderer/core/streams/promise_handler.h b/third_party/blink/renderer/core/streams/promise_handler.h
index ce8d713..c6004ab 100644
--- a/third_party/blink/renderer/core/streams/promise_handler.h
+++ b/third_party/blink/renderer/core/streams/promise_handler.h
@@ -39,7 +39,7 @@
 // PromiseHandlers. It avoids having to call BindToV8Function()
 // explicitly. If |on_rejected| is null then behaves like single-argument
 // Then(). If |on_fulfilled| is null then it behaves like Catch().
-v8::Local<v8::Promise> StreamThenPromise(
+CORE_EXPORT v8::Local<v8::Promise> StreamThenPromise(
     v8::Local<v8::Context>,
     v8::Local<v8::Promise>,
     NewScriptFunction* on_fulfilled,
diff --git a/third_party/blink/renderer/modules/ad_auction/navigator_auction.cc b/third_party/blink/renderer/modules/ad_auction/navigator_auction.cc
index fb0c8106..9caa50f 100644
--- a/third_party/blink/renderer/modules/ad_auction/navigator_auction.cc
+++ b/third_party/blink/renderer/modules/ad_auction/navigator_auction.cc
@@ -928,35 +928,6 @@
   return out;
 }
 
-ScriptPromise NavigatorAuction::deprecatedURNToURL(
-    ScriptState* script_state,
-    const String& uuid_url_string,
-    ExceptionState& exception_state) {
-  if (!uuid_url_string.StartsWithIgnoringCase("urn:uuid:")) {
-    exception_state.ThrowTypeError(
-        String::Format("Passed URL must start with 'urn:uuid:'."));
-    return ScriptPromise();
-  }
-
-  auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
-  ScriptPromise promise = resolver->Promise();
-  KURL uuid_url(uuid_url_string);
-  ad_auction_service_->DeprecatedGetURLFromURN(
-      std::move(uuid_url),
-      WTF::Bind(&NavigatorAuction::GetURLFromURNComplete, WrapPersistent(this),
-                WrapPersistent(resolver)));
-  return promise;
-}
-
-ScriptPromise NavigatorAuction::deprecatedURNToURL(
-    ScriptState* script_state,
-    Navigator& navigator,
-    const String& uuid_url,
-    ExceptionState& exception_state) {
-  return From(ExecutionContext::From(script_state), navigator)
-      .deprecatedURNToURL(script_state, uuid_url, exception_state);
-}
-
 ScriptPromise NavigatorAuction::createAdRequest(
     ScriptState* script_state,
     const AdRequestConfig* config,
@@ -1092,17 +1063,4 @@
   }
 }
 
-void NavigatorAuction::GetURLFromURNComplete(
-    ScriptPromiseResolver* resolver,
-    const absl::optional<KURL>& decoded_url) {
-  if (!resolver->GetExecutionContext() ||
-      resolver->GetExecutionContext()->IsContextDestroyed())
-    return;
-  if (decoded_url) {
-    resolver->Resolve(*decoded_url);
-  } else {
-    resolver->Resolve(v8::Null(resolver->GetScriptState()->GetIsolate()));
-  }
-}
-
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/ad_auction/navigator_auction.h b/third_party/blink/renderer/modules/ad_auction/navigator_auction.h
index 9872e46..097a9d9 100644
--- a/third_party/blink/renderer/modules/ad_auction/navigator_auction.h
+++ b/third_party/blink/renderer/modules/ad_auction/navigator_auction.h
@@ -80,15 +80,6 @@
                                             uint16_t num_ad_components,
                                             ExceptionState& exception_state);
 
-  ScriptPromise deprecatedURNToURL(ScriptState* script_state,
-                                   const String& uuid_url_string,
-                                   ExceptionState& exception_state);
-
-  static ScriptPromise deprecatedURNToURL(ScriptState* script_state,
-                                          Navigator& navigator,
-                                          const String& uuid_url_string,
-                                          ExceptionState& exception_state);
-
   ScriptPromise createAdRequest(ScriptState*,
                                 const AdRequestConfig*,
                                 ExceptionState&);
@@ -120,9 +111,6 @@
                           const absl::optional<KURL>& creative_url);
   // Completion callback for Mojo call made by runAdAuction().
   void AuctionComplete(ScriptPromiseResolver*, const absl::optional<KURL>&);
-  // Completion callback for Mojo call made by deprecatedURNToURL().
-  void GetURLFromURNComplete(ScriptPromiseResolver*,
-                             const absl::optional<KURL>&);
 
   HeapMojoRemote<mojom::blink::AdAuctionService> ad_auction_service_;
 };
diff --git a/third_party/blink/renderer/modules/ad_auction/navigator_auction.idl b/third_party/blink/renderer/modules/ad_auction/navigator_auction.idl
index 0853b9f7..5e1749b5b 100644
--- a/third_party/blink/renderer/modules/ad_auction/navigator_auction.idl
+++ b/third_party/blink/renderer/modules/ad_auction/navigator_auction.idl
@@ -29,9 +29,6 @@
   [RuntimeEnabled=Fledge, CallWith=ScriptState, Measure, RaisesException]
   sequence<USVString> adAuctionComponents([Clamp] unsigned short numComponents);
 
-  [RuntimeEnable=AllowURLsinIframes, CallWith=ScriptState, Measure, RaisesException]
-  Promise<USVString> deprecatedURNToURL(USVString uuid_url);
-
   [RuntimeEnabled=Parakeet, CallWith=ScriptState, Measure, RaisesException]
   Promise<Ads> createAdRequest(AdRequestConfig config);
 
diff --git a/third_party/blink/renderer/modules/nfc/ndef_reader.idl b/third_party/blink/renderer/modules/nfc/ndef_reader.idl
index b7d7330c..4ca6843 100644
--- a/third_party/blink/renderer/modules/nfc/ndef_reader.idl
+++ b/third_party/blink/renderer/modules/nfc/ndef_reader.idl
@@ -24,6 +24,6 @@
         NDEFMessageSource message,
         optional NDEFWriteOptions options={});
 
-    [CallWith=ScriptState, RaisesException, RuntimeEnabled=WebNFCMakeReadOnly] Promise<void> makeReadOnly(
+    [CallWith=ScriptState, RaisesException, MeasureAs=WebNfcNdefMakeReadOnly, RuntimeEnabled=WebNFCMakeReadOnly] Promise<void> makeReadOnly(
         optional NDEFMakeReadOnlyOptions options={});
 };
diff --git a/third_party/blink/renderer/modules/webcodecs/audio_decoder.cc b/third_party/blink/renderer/modules/webcodecs/audio_decoder.cc
index 268ec1c1..17c10797 100644
--- a/third_party/blink/renderer/modules/webcodecs/audio_decoder.cc
+++ b/third_party/blink/renderer/modules/webcodecs/audio_decoder.cc
@@ -110,13 +110,13 @@
 }
 
 // static
-media::StatusOr<AudioDecoderTraits::OutputType*> AudioDecoderTraits::MakeOutput(
-    scoped_refptr<MediaOutputType> output,
-    ExecutionContext* context) {
+media::DecoderStatus::Or<AudioDecoderTraits::OutputType*>
+AudioDecoderTraits::MakeOutput(scoped_refptr<MediaOutputType> output,
+                               ExecutionContext* context) {
   if (!blink::audio_utilities::IsValidAudioBufferSampleRate(
           output->sample_rate())) {
-    return media::Status(
-        media::StatusCode::kInvalidArgument,
+    return media::DecoderStatus(
+        media::DecoderStatus::Codes::kInvalidArgument,
         String::Format("Invalid decoded audio output sample rate. Got %u, "
                        "which is outside [%f, %f]",
                        output->sample_rate(),
@@ -127,12 +127,13 @@
 
   if (static_cast<uint32_t>(output->channel_count()) >
       BaseAudioContext::MaxNumberOfChannels()) {
-    return media::Status(media::StatusCode::kInvalidArgument,
-                         String::Format("Invalid decoded audio output channel "
-                                        "count. Got %u, which exceeds %u",
-                                        output->channel_count(),
-                                        BaseAudioContext::MaxNumberOfChannels())
-                             .Ascii());
+    return media::DecoderStatus(
+        media::DecoderStatus::Codes::kInvalidArgument,
+        String::Format("Invalid decoded audio output channel "
+                       "count. Got %u, which exceeds %u",
+                       output->channel_count(),
+                       BaseAudioContext::MaxNumberOfChannels())
+            .Ascii());
   }
 
   return MakeGarbageCollected<AudioDecoderTraits::OutputType>(
@@ -243,11 +244,10 @@
                                      *out_console_message);
 }
 
-media::StatusOr<scoped_refptr<media::DecoderBuffer>>
+media::DecoderStatus::Or<scoped_refptr<media::DecoderBuffer>>
 AudioDecoder::MakeDecoderBuffer(const InputType& chunk, bool verify_key_frame) {
   if (verify_key_frame && !chunk.buffer()->is_key_frame())
-    return media::Status(media::StatusCode::kKeyFrameRequired);
-
+    return media::DecoderStatus::Codes::kKeyFrameRequired;
   return chunk.buffer();
 }
 
diff --git a/third_party/blink/renderer/modules/webcodecs/audio_decoder.h b/third_party/blink/renderer/modules/webcodecs/audio_decoder.h
index 074f48d..61b4cc8b 100644
--- a/third_party/blink/renderer/modules/webcodecs/audio_decoder.h
+++ b/third_party/blink/renderer/modules/webcodecs/audio_decoder.h
@@ -64,8 +64,9 @@
   static void UpdateDecoderLog(const MediaDecoderType& decoder,
                                const MediaConfigType& media_config,
                                media::MediaLog* media_log);
-  static media::StatusOr<OutputType*> MakeOutput(scoped_refptr<MediaOutputType>,
-                                                 ExecutionContext*);
+  static media::DecoderStatus::Or<OutputType*> MakeOutput(
+      scoped_refptr<MediaOutputType>,
+      ExecutionContext*);
   static const char* GetName();
 };
 
@@ -94,9 +95,8 @@
   CodecConfigEval MakeMediaConfig(const ConfigType& config,
                                   MediaConfigType* out_media_config,
                                   String* out_console_message) override;
-  media::StatusOr<scoped_refptr<media::DecoderBuffer>> MakeDecoderBuffer(
-      const InputType& chunk,
-      bool verify_key_frame) override;
+  media::DecoderStatus::Or<scoped_refptr<media::DecoderBuffer>>
+  MakeDecoderBuffer(const InputType& chunk, bool verify_key_frame) override;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webcodecs/audio_decoder_broker.cc b/third_party/blink/renderer/modules/webcodecs/audio_decoder_broker.cc
index 34d2a772..d47563a6 100644
--- a/third_party/blink/renderer/modules/webcodecs/audio_decoder_broker.cc
+++ b/third_party/blink/renderer/modules/webcodecs/audio_decoder_broker.cc
@@ -42,8 +42,8 @@
 };
 
 template <>
-struct CrossThreadCopier<media::Status>
-    : public CrossThreadCopierPassThrough<media::Status> {
+struct CrossThreadCopier<media::DecoderStatus>
+    : public CrossThreadCopierPassThrough<media::DecoderStatus> {
   STATIC_ONLY(CrossThreadCopier);
 };
 
@@ -64,10 +64,10 @@
 class MediaAudioTaskWrapper {
  public:
   using CrossThreadOnceInitCB =
-      WTF::CrossThreadOnceFunction<void(media::Status status,
+      WTF::CrossThreadOnceFunction<void(media::DecoderStatus status,
                                         absl::optional<DecoderDetails>)>;
   using CrossThreadOnceDecodeCB =
-      WTF::CrossThreadOnceFunction<void(media::Status)>;
+      WTF::CrossThreadOnceFunction<void(media::DecoderStatus)>;
   using CrossThreadOnceResetCB = WTF::CrossThreadOnceClosure;
 
   MediaAudioTaskWrapper(
@@ -134,7 +134,7 @@
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
     if (!decoder_) {
-      OnDecodeDone(cb_id, media::DecodeStatus::DECODE_ERROR);
+      OnDecodeDone(cb_id, media::DecoderStatus::Codes::kNotInitialized);
       return;
     }
 
@@ -195,13 +195,14 @@
 
     decoder_ = std::move(decoder);
 
-    media::Status status(media::StatusCode::kDecoderUnsupportedConfig);
-    absl::optional<DecoderDetails> decoder_details;
+    media::DecoderStatus status = media::DecoderStatus::Codes::kOk;
+    absl::optional<DecoderDetails> decoder_details = absl::nullopt;
     if (decoder_) {
-      status = media::OkStatus();
       decoder_details = DecoderDetails({decoder_->GetDecoderType(),
                                         decoder_->IsPlatformDecoder(),
                                         decoder_->NeedsBitstreamConversion()});
+    } else {
+      status = media::DecoderStatus::Codes::kUnsupportedConfig;
     }
 
     // Fire |init_cb|.
@@ -221,7 +222,7 @@
                                  weak_client_, std::move(buffer)));
   }
 
-  void OnDecodeDone(int cb_id, media::Status status) {
+  void OnDecodeDone(int cb_id, media::DecoderStatus status) {
     DVLOG(2) << __func__;
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
     PostCrossThreadTask(
@@ -326,7 +327,7 @@
   return last_callback_id_;
 }
 
-void AudioDecoderBroker::OnInitialize(media::Status status,
+void AudioDecoderBroker::OnInitialize(media::DecoderStatus status,
                                       absl::optional<DecoderDetails> details) {
   DVLOG(2) << __func__;
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -349,7 +350,7 @@
                                buffer, callback_id));
 }
 
-void AudioDecoderBroker::OnDecodeDone(int cb_id, media::Status status) {
+void AudioDecoderBroker::OnDecodeDone(int cb_id, media::DecoderStatus status) {
   DVLOG(2) << __func__;
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(pending_decode_cb_map_.Contains(cb_id));
diff --git a/third_party/blink/renderer/modules/webcodecs/audio_decoder_broker.h b/third_party/blink/renderer/modules/webcodecs/audio_decoder_broker.h
index fcdc3e20..cf9b2b49 100644
--- a/third_party/blink/renderer/modules/webcodecs/audio_decoder_broker.h
+++ b/third_party/blink/renderer/modules/webcodecs/audio_decoder_broker.h
@@ -15,7 +15,7 @@
 #include "base/sequence_checker.h"
 #include "media/base/audio_buffer.h"
 #include "media/base/audio_decoder.h"
-#include "media/base/decode_status.h"
+#include "media/base/decoder_status.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
@@ -45,10 +45,10 @@
     bool needs_bitstream_conversion;
   };
 
-  virtual void OnInitialize(media::Status status,
+  virtual void OnInitialize(media::DecoderStatus status,
                             absl::optional<DecoderDetails> details) = 0;
 
-  virtual void OnDecodeDone(int cb_id, media::Status status) = 0;
+  virtual void OnDecodeDone(int cb_id, media::DecoderStatus status) = 0;
 
   virtual void OnDecodeOutput(scoped_refptr<media::AudioBuffer> buffer) = 0;
 
@@ -96,9 +96,9 @@
   int CreateCallbackId();
 
   // MediaAudioTaskWrapper::CrossThreadAudioDecoderClient
-  void OnInitialize(media::Status status,
+  void OnInitialize(media::DecoderStatus status,
                     absl::optional<DecoderDetails> details) override;
-  void OnDecodeDone(int cb_id, media::Status status) override;
+  void OnDecodeDone(int cb_id, media::DecoderStatus status) override;
   void OnDecodeOutput(scoped_refptr<media::AudioBuffer> buffer) override;
   void OnReset(int cb_id) override;
 
diff --git a/third_party/blink/renderer/modules/webcodecs/audio_decoder_broker_test.cc b/third_party/blink/renderer/modules/webcodecs/audio_decoder_broker_test.cc
index 87101a3..dad591f 100644
--- a/third_party/blink/renderer/modules/webcodecs/audio_decoder_broker_test.cc
+++ b/third_party/blink/renderer/modules/webcodecs/audio_decoder_broker_test.cc
@@ -10,8 +10,8 @@
 #include "build/build_config.h"
 #include "media/base/audio_codecs.h"
 #include "media/base/channel_layout.h"
-#include "media/base/decode_status.h"
 #include "media/base/decoder_buffer.h"
+#include "media/base/decoder_status.h"
 #include "media/base/media_util.h"
 #include "media/base/mock_filters.h"
 #include "media/base/sample_format.h"
@@ -64,14 +64,14 @@
                   const OutputCB& output_cb,
                   const media::WaitingCB& waiting_cb) override {
     output_cb_ = output_cb;
-    std::move(init_cb).Run(media::OkStatus());
+    std::move(init_cb).Run(media::DecoderStatus::Codes::kOk);
   }
 
   void Decode(scoped_refptr<media::DecoderBuffer> buffer,
               DecodeCB done_cb) override {
     DCHECK(output_cb_);
 
-    std::move(done_cb).Run(media::DecodeStatus::OK);
+    std::move(done_cb).Run(media::DecoderStatus::Codes::kOk);
 
     if (!buffer->end_of_stream()) {
       output_cb_.Run(MakeAudioBuffer(kSampleFormat, kChannelLayout, kChannels,
@@ -164,12 +164,13 @@
   AudioDecoderBrokerTest() = default;
   ~AudioDecoderBrokerTest() override = default;
 
-  void OnInitWithClosure(base::RepeatingClosure done_cb, media::Status status) {
+  void OnInitWithClosure(base::RepeatingClosure done_cb,
+                         media::DecoderStatus status) {
     OnInit(status);
     done_cb.Run();
   }
   void OnDecodeDoneWithClosure(base::RepeatingClosure done_cb,
-                               media::Status status) {
+                               media::DecoderStatus status) {
     OnDecodeDone(std::move(status));
     done_cb.Run();
   }
@@ -179,8 +180,8 @@
     done_cb.Run();
   }
 
-  MOCK_METHOD1(OnInit, void(media::Status status));
-  MOCK_METHOD1(OnDecodeDone, void(media::Status));
+  MOCK_METHOD1(OnInit, void(media::DecoderStatus status));
+  MOCK_METHOD1(OnDecodeDone, void(media::DecoderStatus));
   MOCK_METHOD0(OnResetDone, void());
 
   void OnOutput(scoped_refptr<media::AudioBuffer> buffer) {
@@ -206,7 +207,8 @@
 
   void InitializeDecoder(media::AudioDecoderConfig config) {
     base::RunLoop run_loop;
-    EXPECT_CALL(*this, OnInit(media::SameStatusCode(media::OkStatus())));
+    EXPECT_CALL(*this, OnInit(media::SameStatusCode(media::DecoderStatus(
+                           media::DecoderStatus::Codes::kOk))));
     decoder_broker_->Initialize(
         config, nullptr /* cdm_context */,
         WTF::Bind(&AudioDecoderBrokerTest::OnInitWithClosure,
@@ -218,9 +220,9 @@
     testing::Mock::VerifyAndClearExpectations(this);
   }
 
-  void DecodeBuffer(
-      scoped_refptr<media::DecoderBuffer> buffer,
-      media::StatusCode expected_status = media::StatusCode::kOk) {
+  void DecodeBuffer(scoped_refptr<media::DecoderBuffer> buffer,
+                    media::DecoderStatus::Codes expected_status =
+                        media::DecoderStatus::Codes::kOk) {
     base::RunLoop run_loop;
     EXPECT_CALL(*this, OnDecodeDone(HasStatusCode(expected_status)));
     decoder_broker_->Decode(
@@ -263,9 +265,9 @@
   // No call to Initialize. Other APIs should fail gracefully.
 
   DecodeBuffer(media::ReadTestDataFile("vorbis-packet-0"),
-               media::DecodeStatus::DECODE_ERROR);
+               media::DecoderStatus::Codes::kNotInitialized);
   DecodeBuffer(media::DecoderBuffer::CreateEOSBuffer(),
-               media::DecodeStatus::DECODE_ERROR);
+               media::DecoderStatus::Codes::kNotInitialized);
   ASSERT_EQ(0U, output_buffers_.size());
 
   ResetDecoder();
diff --git a/third_party/blink/renderer/modules/webcodecs/decoder_selector_test.cc b/third_party/blink/renderer/modules/webcodecs/decoder_selector_test.cc
index ef65121..d1e96b6 100644
--- a/third_party/blink/renderer/modules/webcodecs/decoder_selector_test.cc
+++ b/third_party/blink/renderer/modules/webcodecs/decoder_selector_test.cc
@@ -69,8 +69,8 @@
                             const media::WaitingCB&) {
           EXPECT_TRUE(config.Matches(expected_config));
           std::move(init_cb).Run(capability == kSucceed
-                                     ? media::OkStatus()
-                                     : media::StatusCode::kCodeOnlyForTesting);
+                                     ? media::DecoderStatus::Codes::kOk
+                                     : media::DecoderStatus::Codes::kFailed);
         });
   }
 };
@@ -108,8 +108,8 @@
                             const media::WaitingCB&) {
           EXPECT_TRUE(config.Matches(expected_config));
           std::move(init_cb).Run(capability == kSucceed
-                                     ? media::OkStatus()
-                                     : media::StatusCode::kCodeOnlyForTesting);
+                                     ? media::DecoderStatus::Codes::kOk
+                                     : media::DecoderStatus::Codes::kFailed);
         });
   }
 };
diff --git a/third_party/blink/renderer/modules/webcodecs/decoder_template.cc b/third_party/blink/renderer/modules/webcodecs/decoder_template.cc
index 0e7c22d..bc5bc09 100644
--- a/third_party/blink/renderer/modules/webcodecs/decoder_template.cc
+++ b/third_party/blink/renderer/modules/webcodecs/decoder_template.cc
@@ -15,7 +15,7 @@
 #include "base/time/time.h"
 #include "base/trace_event/common/trace_event_common.h"
 #include "base/trace_event/trace_event.h"
-#include "media/base/decode_status.h"
+#include "media/base/decoder_status.h"
 #include "media/media_buildflags.h"
 #include "media/video/gpu_video_accelerator_factories.h"
 #include "third_party/blink/public/platform/platform.h"
@@ -83,7 +83,7 @@
   main_thread_task_runner_ =
       context->GetTaskRunner(TaskType::kInternalMediaRealTime);
 
-  logger_ = std::make_unique<CodecLogger<media::Status>>(
+  logger_ = std::make_unique<CodecLogger<media::DecoderStatus>>(
       context, main_thread_task_runner_);
 
   logger_->log()->SetProperty<media::MediaLogProperty::kFrameUrl>(
@@ -180,22 +180,20 @@
   Request* request = MakeGarbageCollected<Request>();
   request->type = Request::Type::kDecode;
   request->reset_generation = reset_generation_;
-  auto status_or_buffer =
-      MakeDecoderBuffer(*chunk, /*verify_key_frame=*/require_key_frame_);
 
+  auto status_or_buffer = MakeDecoderBuffer(*chunk, require_key_frame_);
   if (status_or_buffer.has_value()) {
     request->decoder_buffer = std::move(status_or_buffer).value();
     require_key_frame_ = false;
   } else {
     request->status = std::move(status_or_buffer).error();
-    if (request->status.code() == media::StatusCode::kKeyFrameRequired) {
+    if (request->status == media::DecoderStatus::Codes::kKeyFrameRequired) {
       exception_state.ThrowDOMException(
           DOMExceptionCode::kDataError,
           "A key frame is required after configure() or flush().");
       return;
     }
   }
-
   MarkCodecActive();
 
   requests_.push_back(request);
@@ -340,9 +338,9 @@
     decoder_ = Traits::CreateDecoder(*ExecutionContext::From(script_state_),
                                      gpu_factories_.value(), logger_->log());
     if (!decoder_) {
-      Shutdown(
-          logger_->MakeException("Internal error: Could not create decoder.",
-                                 media::StatusCode::kDecoderCreationFailed));
+      Shutdown(logger_->MakeException(
+          "Internal error: Could not create decoder.",
+          media::DecoderStatus::Codes::kFailedToCreateDecoder));
       return;
     }
 
@@ -375,9 +373,9 @@
   DCHECK_GT(num_pending_decodes_, 0);
 
   if (!decoder_) {
-    Shutdown(logger_->MakeException(
-        "Decoding error: no decoder found.",
-        media::StatusCode::kDecoderInitializeNeverCompleted));
+    Shutdown(
+        logger_->MakeException("Decoding error: no decoder found.",
+                               media::DecoderStatus::Codes::kNotInitialized));
     return false;
   }
 
@@ -391,7 +389,7 @@
   if (!request->decoder_buffer || request->decoder_buffer->data_size() == 0) {
     if (request->status.is_ok()) {
       Shutdown(logger_->MakeException("Null or empty decoder buffer.",
-                                      media::StatusCode::kDecoderFailedDecode));
+                                      media::DecoderStatus::Codes::kFailed));
     } else {
       Shutdown(logger_->MakeException("Decoder error.", request->status));
     }
@@ -565,7 +563,7 @@
 }
 
 template <typename Traits>
-void DecoderTemplate<Traits>::OnFlushDone(media::Status status) {
+void DecoderTemplate<Traits>::OnFlushDone(media::DecoderStatus status) {
   DVLOG(3) << __func__;
   if (IsClosed())
     return;
@@ -608,7 +606,7 @@
 }
 
 template <typename Traits>
-void DecoderTemplate<Traits>::OnInitializeDone(media::Status status) {
+void DecoderTemplate<Traits>::OnInitializeDone(media::DecoderStatus status) {
   DVLOG(3) << __func__;
   if (IsClosed())
     return;
@@ -622,7 +620,8 @@
     std::string error_message;
     if (is_flush) {
       error_message = "Error during initialize after flush.";
-    } else if (status.code() == media::StatusCode::kDecoderUnsupportedConfig) {
+    } else if (status.code() ==
+               media::DecoderStatus::Codes::kUnsupportedConfig) {
       error_message =
           "Unsupported configuration. Check isConfigSupported() prior to "
           "calling configure().";
@@ -654,7 +653,8 @@
 }
 
 template <typename Traits>
-void DecoderTemplate<Traits>::OnDecodeDone(uint32_t id, media::Status status) {
+void DecoderTemplate<Traits>::OnDecodeDone(uint32_t id,
+                                           media::DecoderStatus status) {
   DVLOG(3) << __func__;
   if (IsClosed())
     return;
@@ -666,8 +666,9 @@
     pending_decodes_.erase(it);
   }
 
-  if (!status.is_ok() && status.code() != media::StatusCode::kAborted) {
-    Shutdown(logger_->MakeException("Decoding error.", status));
+  if (!status.is_ok() &&
+      status.code() != media::DecoderStatus::Codes::kAborted) {
+    Shutdown(logger_->MakeException("Decoding error.", std::move(status)));
     return;
   }
 
diff --git a/third_party/blink/renderer/modules/webcodecs/decoder_template.h b/third_party/blink/renderer/modules/webcodecs/decoder_template.h
index f7350558e..4c310db 100644
--- a/third_party/blink/renderer/modules/webcodecs/decoder_template.h
+++ b/third_party/blink/renderer/modules/webcodecs/decoder_template.h
@@ -8,9 +8,8 @@
 #include <stdint.h>
 #include <memory>
 
-#include "media/base/decode_status.h"
+#include "media/base/decoder_status.h"
 #include "media/base/media_log.h"
-#include "media/base/status.h"
 #include "third_party/blink/renderer/bindings/core/v8/active_script_wrappable.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
@@ -108,7 +107,7 @@
   // When |verify_key_frame| is true, clients are expected to verify and set the
   // DecoderBuffer::is_key_frame() value. I.e., they must process the encoded
   // data to ensure the value is actually what the chunk says it is.
-  virtual media::StatusOr<scoped_refptr<media::DecoderBuffer>>
+  virtual media::DecoderStatus::Or<scoped_refptr<media::DecoderBuffer>>
   MakeDecoderBuffer(const InputType& chunk, bool verify_key_frame) = 0;
 
  private:
@@ -146,7 +145,7 @@
     Member<ScriptPromiseResolver> resolver;
 
     // For reporting an error at the time when a request is processed.
-    media::Status status;
+    media::DecoderStatus status;
 
     // The value of |reset_generation_| at the time of this request. Used to
     // abort pending requests following a reset().
@@ -173,9 +172,9 @@
   void Shutdown(DOMException* ex = nullptr);
 
   // Called by |decoder_|.
-  void OnInitializeDone(media::Status status);
-  void OnDecodeDone(uint32_t id, media::Status);
-  void OnFlushDone(media::Status);
+  void OnInitializeDone(media::DecoderStatus status);
+  void OnDecodeDone(uint32_t id, media::DecoderStatus);
+  void OnFlushDone(media::DecoderStatus);
   void OnResetDone();
   void OnOutput(uint32_t reset_generation, scoped_refptr<MediaOutputType>);
 
@@ -204,7 +203,7 @@
   // Could be a configure, flush, or reset. Decodes go in |pending_decodes_|.
   Member<Request> pending_request_;
 
-  std::unique_ptr<CodecLogger<media::Status>> logger_;
+  std::unique_ptr<CodecLogger<media::DecoderStatus>> logger_;
 
   // Empty - GPU factories haven't been retrieved yet.
   // nullptr - We tried to get GPU factories, but acceleration is unavailable.
diff --git a/third_party/blink/renderer/modules/webcodecs/reclaimable_codec.cc b/third_party/blink/renderer/modules/webcodecs/reclaimable_codec.cc
index a73e171c..cf73380 100644
--- a/third_party/blink/renderer/modules/webcodecs/reclaimable_codec.cc
+++ b/third_party/blink/renderer/modules/webcodecs/reclaimable_codec.cc
@@ -8,7 +8,9 @@
 #include "base/location.h"
 #include "base/time/default_tick_clock.h"
 #include "third_party/blink/renderer/core/dom/dom_exception.h"
+#include "third_party/blink/renderer/platform/heap/persistent.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread.h"
+#include "third_party/blink/renderer/platform/wtf/functional.h"
 
 namespace blink {
 
@@ -31,7 +33,9 @@
   if (base::FeatureList::IsEnabled(kOnlyReclaimBackgroundWebCodecs)) {
     // Do this last, it will immediately re-enter via OnLifecycleStateChanged().
     observer_handle_ = context->GetScheduler()->AddLifecycleObserver(
-        FrameOrWorkerScheduler::ObserverType::kWorkerScheduler, this);
+        FrameOrWorkerScheduler::ObserverType::kWorkerScheduler,
+        WTF::BindRepeating(&ReclaimableCodec::OnLifecycleStateChanged,
+                           WrapWeakPersistent(this)));
   } else {
     // Pretend we're always in the background to _always_ reclaim.
     is_backgrounded_ = true;
diff --git a/third_party/blink/renderer/modules/webcodecs/reclaimable_codec.h b/third_party/blink/renderer/modules/webcodecs/reclaimable_codec.h
index 4057372..4ec8682 100644
--- a/third_party/blink/renderer/modules/webcodecs/reclaimable_codec.h
+++ b/third_party/blink/renderer/modules/webcodecs/reclaimable_codec.h
@@ -25,9 +25,7 @@
 extern const MODULES_EXPORT base::Feature kReclaimInactiveWebCodecs;
 extern const MODULES_EXPORT base::Feature kOnlyReclaimBackgroundWebCodecs;
 
-class MODULES_EXPORT ReclaimableCodec
-    : public GarbageCollectedMixin,
-      public FrameOrWorkerScheduler::Observer {
+class MODULES_EXPORT ReclaimableCodec : public GarbageCollectedMixin {
  public:
   explicit ReclaimableCodec(ExecutionContext*);
 
@@ -56,7 +54,7 @@
 
   // Notified when throttling state is changed. May be called consecutively
   // with the same value.
-  void OnLifecycleStateChanged(scheduler::SchedulingLifecycleState) override;
+  void OnLifecycleStateChanged(scheduler::SchedulingLifecycleState);
 
  protected:
   // Pushes back the time at which |this| can be reclaimed due to inactivity.
diff --git a/third_party/blink/renderer/modules/webcodecs/video_decoder.cc b/third_party/blink/renderer/modules/webcodecs/video_decoder.cc
index 0540e604..85a6f5d 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_decoder.cc
+++ b/third_party/blink/renderer/modules/webcodecs/video_decoder.cc
@@ -295,9 +295,9 @@
 }
 
 // static
-media::StatusOr<VideoDecoderTraits::OutputType*> VideoDecoderTraits::MakeOutput(
-    scoped_refptr<MediaOutputType> output,
-    ExecutionContext* context) {
+media::DecoderStatus::Or<VideoDecoderTraits::OutputType*>
+VideoDecoderTraits::MakeOutput(scoped_refptr<MediaOutputType> output,
+                               ExecutionContext* context) {
   return MakeGarbageCollected<VideoDecoderTraits::OutputType>(std::move(output),
                                                               context);
 }
@@ -518,7 +518,7 @@
   return result;
 }
 
-media::StatusOr<scoped_refptr<media::DecoderBuffer>>
+media::DecoderStatus::Or<scoped_refptr<media::DecoderBuffer>>
 VideoDecoder::MakeDecoderBuffer(const InputType& chunk, bool verify_key_frame) {
   scoped_refptr<media::DecoderBuffer> decoder_buffer = chunk.buffer();
 #if BUILDFLAG(USE_PROPRIETARY_CODECS)
@@ -530,16 +530,18 @@
     uint32_t output_size = h264_converter_->CalculateNeededOutputBufferSize(
         src, static_cast<uint32_t>(src_size), h264_avcc_.get());
     if (!output_size) {
-      return media::Status(media::StatusCode::kH264ParsingError,
-                           "Unable to determine size of bitstream buffer.");
+      return media::DecoderStatus(
+          media::DecoderStatus::Codes::kMalformedBitstream,
+          "Unable to determine size of bitstream buffer.");
     }
 
     std::vector<uint8_t> buf(output_size);
     if (!h264_converter_->ConvertNalUnitStreamToByteStream(
             src, static_cast<uint32_t>(src_size), h264_avcc_.get(), buf.data(),
             &output_size)) {
-      return media::Status(media::StatusCode::kH264ParsingError,
-                           "Unable to convert NALU to byte stream.");
+      return media::DecoderStatus(
+          media::DecoderStatus::Codes::kMalformedBitstream,
+          "Unable to convert NALU to byte stream.");
     }
 
     decoder_buffer = media::DecoderBuffer::CopyFrom(buf.data(), output_size);
@@ -559,8 +561,11 @@
       ParseH264KeyFrame(*decoder_buffer, &is_key_frame);
     }
 
-    if (!is_key_frame)
-      return media::Status(media::StatusCode::kKeyFrameRequired);
+    if (!is_key_frame) {
+      return media::DecoderStatus(
+          media::DecoderStatus::Codes::kKeyFrameRequired,
+          "A key frame is required after configure() or flush().");
+    }
   }
 
   return decoder_buffer;
diff --git a/third_party/blink/renderer/modules/webcodecs/video_decoder.h b/third_party/blink/renderer/modules/webcodecs/video_decoder.h
index e31bfa3..ad0e1ee 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_decoder.h
+++ b/third_party/blink/renderer/modules/webcodecs/video_decoder.h
@@ -74,8 +74,9 @@
   static void UpdateDecoderLog(const MediaDecoderType& decoder,
                                const MediaConfigType& media_config,
                                media::MediaLog* media_log);
-  static media::StatusOr<OutputType*> MakeOutput(scoped_refptr<MediaOutputType>,
-                                                 ExecutionContext*);
+  static media::DecoderStatus::Or<OutputType*> MakeOutput(
+      scoped_refptr<MediaOutputType>,
+      ExecutionContext*);
   static const char* GetName();
 };
 
@@ -112,9 +113,8 @@
   CodecConfigEval MakeMediaConfig(const ConfigType& config,
                                   MediaConfigType* out_media_config,
                                   String* out_console_message) override;
-  media::StatusOr<scoped_refptr<media::DecoderBuffer>> MakeDecoderBuffer(
-      const InputType& input,
-      bool verify_key_frame) override;
+  media::DecoderStatus::Or<scoped_refptr<media::DecoderBuffer>>
+  MakeDecoderBuffer(const InputType& input, bool verify_key_frame) override;
 
   static ScriptPromise IsAcceleratedConfigSupported(ScriptState* script_state,
                                                     const VideoDecoderConfig*,
diff --git a/third_party/blink/renderer/modules/webcodecs/video_decoder_broker.cc b/third_party/blink/renderer/modules/webcodecs/video_decoder_broker.cc
index 9a2251e7..78a13abd 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_decoder_broker.cc
+++ b/third_party/blink/renderer/modules/webcodecs/video_decoder_broker.cc
@@ -11,8 +11,8 @@
 #include "base/memory/weak_ptr.h"
 #include "build/buildflag.h"
 #include "media/base/decoder_factory.h"
+#include "media/base/decoder_status.h"
 #include "media/base/media_util.h"
-#include "media/base/status_codes.h"
 #include "media/base/video_decoder_config.h"
 #include "media/mojo/buildflags.h"
 #include "media/mojo/clients/mojo_decoder_factory.h"
@@ -46,8 +46,8 @@
 };
 
 template <>
-struct CrossThreadCopier<media::Status>
-    : public CrossThreadCopierPassThrough<media::Status> {
+struct CrossThreadCopier<media::DecoderStatus>
+    : public CrossThreadCopierPassThrough<media::DecoderStatus> {
   STATIC_ONLY(CrossThreadCopier);
 };
 
@@ -68,10 +68,10 @@
 class MediaVideoTaskWrapper {
  public:
   using CrossThreadOnceInitCB =
-      WTF::CrossThreadOnceFunction<void(media::Status status,
+      WTF::CrossThreadOnceFunction<void(media::DecoderStatus status,
                                         absl::optional<DecoderDetails>)>;
   using CrossThreadOnceDecodeCB =
-      WTF::CrossThreadOnceFunction<void(const media::Status&)>;
+      WTF::CrossThreadOnceFunction<void(const media::DecoderStatus&)>;
   using CrossThreadOnceResetCB = WTF::CrossThreadOnceClosure;
 
   MediaVideoTaskWrapper(
@@ -148,7 +148,7 @@
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
     if (!decoder_) {
-      OnDecodeDone(cb_id, media::DecodeStatus::DECODE_ERROR);
+      OnDecodeDone(cb_id, media::DecoderStatus::Codes::kNotInitialized);
       return;
     }
 
@@ -254,14 +254,16 @@
 
     decoder_ = std::move(decoder);
 
-    media::Status status(media::StatusCode::kDecoderUnsupportedConfig);
-    absl::optional<DecoderDetails> decoder_details;
+    media::DecoderStatus status = media::DecoderStatus::Codes::kOk;
+    absl::optional<DecoderDetails> decoder_details = absl::nullopt;
+
     if (decoder_) {
-      status = media::OkStatus();
       decoder_details = DecoderDetails({decoder_->GetDecoderType(),
                                         decoder_->IsPlatformDecoder(),
                                         decoder_->NeedsBitstreamConversion(),
                                         decoder_->GetMaxDecodeRequests()});
+    } else {
+      status = media::DecoderStatus::Codes::kUnsupportedConfig;
     }
 
     // Fire |init_cb|.
@@ -282,7 +284,7 @@
                                  decoder_->CanReadWithoutStalling()));
   }
 
-  void OnDecodeDone(int cb_id, media::Status status) {
+  void OnDecodeDone(int cb_id, media::DecoderStatus status) {
     DVLOG(2) << __func__;
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
@@ -407,7 +409,7 @@
   return last_callback_id_;
 }
 
-void VideoDecoderBroker::OnInitialize(media::Status status,
+void VideoDecoderBroker::OnInitialize(media::DecoderStatus status,
                                       absl::optional<DecoderDetails> details) {
   DVLOG(2) << __func__;
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -431,7 +433,7 @@
                                buffer, callback_id));
 }
 
-void VideoDecoderBroker::OnDecodeDone(int cb_id, media::Status status) {
+void VideoDecoderBroker::OnDecodeDone(int cb_id, media::DecoderStatus status) {
   DVLOG(2) << __func__;
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(pending_decode_cb_map_.Contains(cb_id));
diff --git a/third_party/blink/renderer/modules/webcodecs/video_decoder_broker.h b/third_party/blink/renderer/modules/webcodecs/video_decoder_broker.h
index f53f3f2..0bf5d5c 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_decoder_broker.h
+++ b/third_party/blink/renderer/modules/webcodecs/video_decoder_broker.h
@@ -13,7 +13,7 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
-#include "media/base/decode_status.h"
+#include "media/base/decoder_status.h"
 #include "media/base/video_decoder.h"
 #include "media/base/video_frame.h"
 #include "media/video/gpu_video_accelerator_factories.h"
@@ -51,10 +51,10 @@
     int max_decode_requests;
   };
 
-  virtual void OnInitialize(media::Status status,
+  virtual void OnInitialize(media::DecoderStatus status,
                             absl::optional<DecoderDetails> details) = 0;
 
-  virtual void OnDecodeDone(int cb_id, media::Status status) = 0;
+  virtual void OnDecodeDone(int cb_id, media::DecoderStatus status) = 0;
 
   virtual void OnDecodeOutput(scoped_refptr<media::VideoFrame> frame,
                               bool can_read_without_stalling) = 0;
@@ -110,9 +110,9 @@
   int CreateCallbackId();
 
   // MediaVideoTaskWrapper::CrossThreadVideoDecoderClient
-  void OnInitialize(media::Status status,
+  void OnInitialize(media::DecoderStatus status,
                     absl::optional<DecoderDetails> details) override;
-  void OnDecodeDone(int cb_id, media::Status status) override;
+  void OnDecodeDone(int cb_id, media::DecoderStatus status) override;
   void OnDecodeOutput(scoped_refptr<media::VideoFrame> frame,
                       bool can_read_without_stalling) override;
   void OnReset(int cb_id) override;
diff --git a/third_party/blink/renderer/modules/webcodecs/video_decoder_broker_test.cc b/third_party/blink/renderer/modules/webcodecs/video_decoder_broker_test.cc
index 8a94e1a..5639d55 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_decoder_broker_test.cc
+++ b/third_party/blink/renderer/modules/webcodecs/video_decoder_broker_test.cc
@@ -10,8 +10,8 @@
 #include "base/threading/thread.h"
 #include "build/build_config.h"
 #include "gpu/command_buffer/common/mailbox_holder.h"
-#include "media/base/decode_status.h"
 #include "media/base/decoder_buffer.h"
+#include "media/base/decoder_status.h"
 #include "media/base/media_switches.h"
 #include "media/base/media_util.h"
 #include "media/base/test_data_util.h"
@@ -192,12 +192,13 @@
         base::RepeatingCallback<void(mojo::ScopedMessagePipeHandle)>());
   }
 
-  void OnInitWithClosure(base::RepeatingClosure done_cb, media::Status status) {
+  void OnInitWithClosure(base::RepeatingClosure done_cb,
+                         media::DecoderStatus status) {
     OnInit(status);
     done_cb.Run();
   }
   void OnDecodeDoneWithClosure(base::RepeatingClosure done_cb,
-                               media::Status status) {
+                               media::DecoderStatus status) {
     OnDecodeDone(std::move(status));
     done_cb.Run();
   }
@@ -207,8 +208,8 @@
     done_cb.Run();
   }
 
-  MOCK_METHOD1(OnInit, void(media::Status status));
-  MOCK_METHOD1(OnDecodeDone, void(media::Status));
+  MOCK_METHOD1(OnInit, void(media::DecoderStatus status));
+  MOCK_METHOD1(OnDecodeDone, void(media::DecoderStatus));
   MOCK_METHOD0(OnResetDone, void());
 
   void OnOutput(scoped_refptr<media::VideoFrame> frame) {
@@ -260,10 +261,12 @@
                          bool expect_success = true) {
     base::RunLoop run_loop;
     if (expect_success) {
-      EXPECT_CALL(*this, OnInit(media::SameStatusCode(media::OkStatus())));
+      EXPECT_CALL(*this, OnInit(media::SameStatusCode(media::DecoderStatus(
+                             media::DecoderStatus::Codes::kOk))));
     } else {
-      EXPECT_CALL(*this, OnInit(media::SameStatusCode(media::Status(
-                             media::StatusCode::kDecoderUnsupportedConfig))));
+      EXPECT_CALL(*this,
+                  OnInit(media::SameStatusCode(media::DecoderStatus(
+                      media::DecoderStatus::Codes::kUnsupportedConfig))));
     }
     decoder_broker_->Initialize(
         config, false /*low_delay*/, nullptr /* cdm_context */,
@@ -276,9 +279,9 @@
     testing::Mock::VerifyAndClearExpectations(this);
   }
 
-  void DecodeBuffer(
-      scoped_refptr<media::DecoderBuffer> buffer,
-      media::StatusCode expected_status = media::StatusCode::kOk) {
+  void DecodeBuffer(scoped_refptr<media::DecoderBuffer> buffer,
+                    media::DecoderStatus::Codes expected_status =
+                        media::DecoderStatus::Codes::kOk) {
     base::RunLoop run_loop;
     EXPECT_CALL(*this, OnDecodeDone(HasStatusCode(expected_status)));
     decoder_broker_->Decode(
@@ -334,9 +337,9 @@
   // No call to Initialize. Other APIs should fail gracefully.
 
   DecodeBuffer(media::ReadTestDataFile("vp8-I-frame-320x120"),
-               media::DecodeStatus::DECODE_ERROR);
+               media::DecoderStatus::Codes::kNotInitialized);
   DecodeBuffer(media::DecoderBuffer::CreateEOSBuffer(),
-               media::DecodeStatus::DECODE_ERROR);
+               media::DecoderStatus::Codes::kNotInitialized);
   ASSERT_EQ(0U, output_frames_.size());
 
   ResetDecoder();
diff --git a/third_party/blink/renderer/modules/webtransport/outgoing_stream.cc b/third_party/blink/renderer/modules/webtransport/outgoing_stream.cc
index d872f70..3f6416c 100644
--- a/third_party/blink/renderer/modules/webtransport/outgoing_stream.cc
+++ b/third_party/blink/renderer/modules/webtransport/outgoing_stream.cc
@@ -15,7 +15,9 @@
 #include "third_party/blink/renderer/bindings/core/v8/v8_typedefs.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_union_arraybuffer_arraybufferview.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_web_transport_error.h"
+#include "third_party/blink/renderer/core/dom/abort_signal.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
+#include "third_party/blink/renderer/core/streams/promise_handler.h"
 #include "third_party/blink/renderer/core/streams/underlying_sink_base.h"
 #include "third_party/blink/renderer/core/streams/writable_stream.h"
 #include "third_party/blink/renderer/core/streams/writable_stream_default_controller.h"
@@ -32,6 +34,26 @@
 
 namespace blink {
 
+namespace {
+
+class SendStreamAbortAlgorithm final : public AbortSignal::Algorithm {
+ public:
+  explicit SendStreamAbortAlgorithm(OutgoingStream* stream) : stream_(stream) {}
+  ~SendStreamAbortAlgorithm() override = default;
+
+  void Run() override { stream_->AbortAlgorithm(stream_); }
+
+  void Trace(Visitor* visitor) const override {
+    visitor->Trace(stream_);
+    Algorithm::Trace(visitor);
+  }
+
+ private:
+  Member<OutgoingStream> stream_;
+};
+
+}  // namespace
+
 class OutgoingStream::UnderlyingSink final : public UnderlyingSinkBase {
  public:
   explicit UnderlyingSink(OutgoingStream* outgoing_stream)
@@ -73,6 +95,8 @@
 
     outgoing_stream_->close_promise_resolver_ =
         MakeGarbageCollected<ScriptPromiseResolver>(script_state);
+    outgoing_stream_->pending_operation_ =
+        outgoing_stream_->close_promise_resolver_;
 
     // In some cases (when the stream is aborted by a network error for
     // example), there may not be a call to OnOutgoingStreamClose. In that case
@@ -173,12 +197,74 @@
   stream->InitWithCountQueueingStrategy(
       script_state_, MakeGarbageCollected<UnderlyingSink>(this), 1,
       /*optimizer=*/nullptr, exception_state);
+
+  controller_->signal()->AddAlgorithm(
+      MakeGarbageCollected<SendStreamAbortAlgorithm>(this));
+}
+
+void OutgoingStream::AbortAlgorithm(OutgoingStream* stream) {
+  // Step 6 of https://w3c.github.io/webtransport/#sendstream-create
+  // 1. If stream's [[PendingOperation]] is null, then abort these steps.
+  if (!stream->pending_operation_) {
+    return;
+  }
+
+  // 2. Let reason be stream’s [[controller]]'s [[signal]]'s abort reason.
+  ScriptValue reason = stream->controller_->signal()->reason(script_state_);
+
+  // 3. Let abortPromise be the result of aborting stream with reason.
+  // ASSERT_NO_EXCEPTION is used as OutgoingStream::UnderlyingSink::abort()
+  // does not throw an exception, and hence a proper ExceptionState does not
+  // have to be passed since it is not used.
+  auto* underlying_sink = MakeGarbageCollected<UnderlyingSink>(stream);
+  ScriptPromise abort_promise =
+      underlying_sink->abort(script_state_, reason, ASSERT_NO_EXCEPTION);
+
+  ScriptPromiseResolver* resolver =
+      MakeGarbageCollected<ScriptPromiseResolver>(script_state_);
+  class ResolveFunction final : public PromiseHandler {
+    // 4. Upon fulfillment of abortPromise,
+   public:
+    explicit ResolveFunction(ScriptValue reason,
+                             ScriptPromiseResolver* resolver)
+        : reason_(reason), resolver_(resolver) {}
+
+    void CallWithLocal(ScriptState*, v8::Local<v8::Value>) override {
+      //    reject promise with reason.
+      resolver_->Reject(reason_);
+    }
+
+    void Trace(Visitor* visitor) const override {
+      visitor->Trace(reason_);
+      visitor->Trace(resolver_);
+      PromiseHandler::Trace(visitor);
+    }
+
+   private:
+    ScriptValue reason_;
+    Member<ScriptPromiseResolver> resolver_;
+  };
+
+  StreamThenPromise(script_state_->GetContext(), abort_promise.V8Promise(),
+                    MakeGarbageCollected<NewScriptFunction>(
+                        script_state_, MakeGarbageCollected<ResolveFunction>(
+                                           reason, resolver)));
+
+  // 5. Let pendingOperation be stream’s [[PendingOperation]].
+  ScriptPromiseResolver* pending_operation = stream->pending_operation_;
+
+  // 6. Set stream’s [[PendingOperation]] to null.
+  stream->pending_operation_ = nullptr;
+
+  // 7. Resolve pendingOperation with promise.
+  pending_operation->Resolve(resolver->Promise());
 }
 
 void OutgoingStream::OnOutgoingStreamClosed() {
   DVLOG(1) << "OutgoingStream::OnOutgoingStreamClosed() this=" << this;
 
   DCHECK(close_promise_resolver_);
+  pending_operation_ = nullptr;
   close_promise_resolver_->Resolve();
   close_promise_resolver_ = nullptr;
 }
@@ -202,6 +288,7 @@
   visitor->Trace(controller_);
   visitor->Trace(write_promise_resolver_);
   visitor->Trace(close_promise_resolver_);
+  visitor->Trace(pending_operation_);
 }
 
 void OutgoingStream::OnHandleReady(MojoResult result,
@@ -292,6 +379,7 @@
   write_watcher_.ArmOrNotify();
   write_promise_resolver_ =
       MakeGarbageCollected<ScriptPromiseResolver>(script_state);
+  pending_operation_ = write_promise_resolver_;
   return write_promise_resolver_->Promise();
 }
 
@@ -311,6 +399,7 @@
 
     cached_data_.reset();
     offset_ = 0;
+    pending_operation_ = nullptr;
     write_promise_resolver_->Resolve();
     write_promise_resolver_ = nullptr;
     return;
diff --git a/third_party/blink/renderer/modules/webtransport/outgoing_stream.h b/third_party/blink/renderer/modules/webtransport/outgoing_stream.h
index 593432a..9fb27b6 100644
--- a/third_party/blink/renderer/modules/webtransport/outgoing_stream.h
+++ b/third_party/blink/renderer/modules/webtransport/outgoing_stream.h
@@ -65,6 +65,8 @@
 
   void InitWithExistingWritableStream(WritableStream*, ExceptionState&);
 
+  void AbortAlgorithm(OutgoingStream*);
+
   // Implementation of OutgoingStream IDL, used by client classes to implement
   // it. https://wicg.github.io/web-transport/#outgoing-stream
   WritableStream* Writable() const {
@@ -187,6 +189,8 @@
   // non-null.
   Member<ScriptPromiseResolver> close_promise_resolver_;
 
+  Member<ScriptPromiseResolver> pending_operation_;
+
   State state_ = State::kOpen;
 };
 
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
index af1e87d..6c902ea 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
@@ -1341,7 +1341,7 @@
   auto scroll_a = CreateScroll(ScrollPaintPropertyNode::Root(), ScrollState1(),
                                kNotScrollingOnMain, scroll_element_id_a);
   auto scroll_translation_a = CreateScrollTranslation(
-      t0(), 11, 13, *scroll_a, CompositingReason::kLayerForScrollingContents);
+      t0(), 11, 13, *scroll_a, CompositingReason::kOverflowScrolling);
 
   CompositorElementId scroll_element_id_b = ScrollElementId(3);
   auto scroll_b =
@@ -1948,7 +1948,7 @@
 
 TEST_P(PaintArtifactCompositorTest, NonCompositedSimpleMask) {
   auto masked = CreateOpacityEffect(
-      e0(), 1.0, CompositingReason::kIsolateCompositedDescendants);
+      e0(), 1.0, CompositingReason::kOpacityWithCompositedDescendants);
   EffectPaintPropertyNode::State masking_state;
   masking_state.local_transform_space = &t0();
   masking_state.output_clip = &c0();
@@ -1982,12 +1982,13 @@
 
 TEST_P(PaintArtifactCompositorTest, CompositedMaskOneChild) {
   auto masked = CreateOpacityEffect(
-      e0(), 1.0, CompositingReason::kIsolateCompositedDescendants);
+      e0(), 1.0, CompositingReason::kOpacityWithCompositedDescendants);
   EffectPaintPropertyNode::State masking_state;
   masking_state.local_transform_space = &t0();
   masking_state.output_clip = &c0();
   masking_state.blend_mode = SkBlendMode::kDstIn;
-  masking_state.direct_compositing_reasons = CompositingReason::kLayerForMask;
+  masking_state.direct_compositing_reasons =
+      CompositingReason::kMaskWithCompositedDescendants;
   auto masking =
       EffectPaintPropertyNode::Create(*masked, std::move(masking_state));
 
@@ -2015,7 +2016,7 @@
 
 TEST_P(PaintArtifactCompositorTest, CompositedMaskTwoChildren) {
   auto masked = CreateOpacityEffect(
-      e0(), 1.0, CompositingReason::kIsolateCompositedDescendants);
+      e0(), 1.0, CompositingReason::kOpacityWithCompositedDescendants);
   EffectPaintPropertyNode::State masking_state;
   masking_state.local_transform_space = &t0();
   masking_state.output_clip = &c0();
@@ -2024,7 +2025,7 @@
       EffectPaintPropertyNode::Create(*masked, std::move(masking_state));
 
   auto child_of_masked = CreateOpacityEffect(
-      *masking, 1.0, CompositingReason::kIsolateCompositedDescendants);
+      *masking, 1.0, CompositingReason::kOpacityWithCompositedDescendants);
 
   TestPaintArtifact artifact;
   artifact.Chunk(t0(), c0(), *masked)
@@ -2052,7 +2053,7 @@
 
 TEST_P(PaintArtifactCompositorTest, NonCompositedSimpleExoticBlendMode) {
   auto masked = CreateOpacityEffect(
-      e0(), 1.0, CompositingReason::kIsolateCompositedDescendants);
+      e0(), 1.0, CompositingReason::kOpacityWithCompositedDescendants);
   EffectPaintPropertyNode::State masking_state;
   masking_state.local_transform_space = &t0();
   masking_state.output_clip = &c0();
@@ -2079,7 +2080,7 @@
 
 TEST_P(PaintArtifactCompositorTest, ForcedCompositedExoticBlendMode) {
   auto masked = CreateOpacityEffect(
-      e0(), 1.0, CompositingReason::kIsolateCompositedDescendants);
+      e0(), 1.0, CompositingReason::kOpacityWithCompositedDescendants);
   EffectPaintPropertyNode::State masking_state;
   masking_state.local_transform_space = &t0();
   masking_state.output_clip = &c0();
@@ -2112,7 +2113,7 @@
 TEST_P(PaintArtifactCompositorTest,
        CompositedExoticBlendModeOnTwoOpacityAnimationLayers) {
   auto masked = CreateOpacityEffect(
-      e0(), 1.0, CompositingReason::kIsolateCompositedDescendants);
+      e0(), 1.0, CompositingReason::kOpacityWithCompositedDescendants);
   auto masked_child1 = CreateOpacityEffect(
       *masked, 1.0, CompositingReason::kActiveOpacityAnimation);
   auto masked_child2 = CreateOpacityEffect(
@@ -2150,7 +2151,7 @@
 TEST_P(PaintArtifactCompositorTest,
        CompositedExoticBlendModeOnTwo3DTransformLayers) {
   auto masked = CreateOpacityEffect(
-      e0(), 1.0, CompositingReason::kIsolateCompositedDescendants);
+      e0(), 1.0, CompositingReason::kOpacityWithCompositedDescendants);
   auto transform1 =
       CreateTransform(t0(), TransformationMatrix(), gfx::Point3F(),
                       CompositingReason::k3DTransform);
@@ -2189,7 +2190,7 @@
 
 TEST_P(PaintArtifactCompositorTest, DecompositeExoticBlendModeWithoutBackdrop) {
   auto parent_effect = CreateOpacityEffect(
-      e0(), 1.0, CompositingReason::kIsolateCompositedDescendants);
+      e0(), 1.0, CompositingReason::kOpacityWithCompositedDescendants);
   EffectPaintPropertyNode::State blend_state1;
   blend_state1.local_transform_space = &t0();
   blend_state1.blend_mode = SkBlendMode::kScreen;
@@ -2220,7 +2221,7 @@
 TEST_P(PaintArtifactCompositorTest,
        DecompositeExoticBlendModeWithNonDrawingLayer) {
   auto parent_effect = CreateOpacityEffect(
-      e0(), 1.0, CompositingReason::kIsolateCompositedDescendants);
+      e0(), 1.0, CompositingReason::kOpacityWithCompositedDescendants);
   EffectPaintPropertyNode::State blend_state1;
   blend_state1.local_transform_space = &t0();
   blend_state1.blend_mode = SkBlendMode::kScreen;
diff --git a/third_party/blink/renderer/platform/graphics/compositing_reasons.cc b/third_party/blink/renderer/platform/graphics/compositing_reasons.cc
index 31781d6f..09d093b 100644
--- a/third_party/blink/renderer/platform/graphics/compositing_reasons.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing_reasons.cc
@@ -27,7 +27,6 @@
      "promoted to a layer based on a performance heuristic."},
     {CompositingReason::kPlugin, "plugin", "Is an accelerated plugin"},
     {CompositingReason::kIFrame, "iFrame", "Is an accelerated iFrame"},
-    {CompositingReason::kSVGRoot, "SVGRoot", "Is an accelerated SVG root"},
     {CompositingReason::kBackfaceVisibilityHidden, "backfaceVisibilityHidden",
      "Has backface-visibility: hidden"},
     {CompositingReason::kActiveTransformAnimation, "activeTransformAnimation",
@@ -39,17 +38,11 @@
     {CompositingReason::kActiveBackdropFilterAnimation,
      "activeBackdropFilterAnimation",
      "Has an active accelerated backdrop filter animation or transition"},
-    {CompositingReason::kXrOverlay, "xrOverlay",
-     "Is DOM overlay for WebXR immersive-ar mode"},
     {CompositingReason::kFixedPosition, "fixedPosition", "Is fixed position"},
     {CompositingReason::kStickyPosition, "stickyPosition",
      "Is sticky position"},
     {CompositingReason::kOverflowScrolling, "overflowScrolling",
      "Is a scrollable overflow element"},
-    {CompositingReason::kOutOfFlowClipping, "outOfFlowClipping",
-     "Has clipping ancestor"},
-    {CompositingReason::kVideoOverlay, "videoOverlay",
-     "Is overlay controls for video"},
     {CompositingReason::kWillChangeTransform, "willChangeTransform",
      "Has a will-change: transform compositing hint"},
     {CompositingReason::kWillChangeOpacity, "willChangeOpacity",
@@ -66,12 +59,8 @@
      "Is a mask for backdrop filter"},
     {CompositingReason::kRootScroller, "rootScroller",
      "Is the document.rootScroller"},
-    {CompositingReason::kAssumedOverlap, "assumedOverlap",
-     "Might overlap other composited content"},
     {CompositingReason::kOverlap, "overlap",
      "Overlaps other composited content"},
-    {CompositingReason::kNegativeZIndexChildren, "negativeZIndexChildren",
-     "Parent with composited negative z-index content"},
     {CompositingReason::kOpacityWithCompositedDescendants,
      "opacityWithCompositedDescendants",
      "Has opacity that needs to be applied by compositor because of composited "
@@ -80,10 +69,6 @@
      "maskWithCompositedDescendants",
      "Has a mask that needs to be known by compositor because of composited "
      "descendants"},
-    {CompositingReason::kReflectionWithCompositedDescendants,
-     "reflectionWithCompositedDescendants",
-     "Has a reflection that needs to be known by compositor because of "
-     "composited descendants"},
     {CompositingReason::kFilterWithCompositedDescendants,
      "filterWithCompositedDescendants",
      "Has a filter effect that needs to be known by compositor because of "
@@ -100,31 +85,12 @@
      "preserve3DWith3DDescendants",
      "Has a preserves-3d property that needs to be known by compositor because "
      "of 3d descendants"},
-    {CompositingReason::kIsolateCompositedDescendants,
-     "isolateCompositedDescendants",
-     "Should isolate descendants to apply a blend effect"},
-    {CompositingReason::kFullscreenVideoWithCompositedDescendants,
-     "fullscreenVideoWithCompositedDescendants",
-     "Is a fullscreen video element with composited descendants"},
     {CompositingReason::kRoot, "root", "Is the root layer"},
     {CompositingReason::kLayerForHorizontalScrollbar,
      "layerForHorizontalScrollbar",
      "Secondary layer, the horizontal scrollbar layer"},
     {CompositingReason::kLayerForVerticalScrollbar, "layerForVerticalScrollbar",
      "Secondary layer, the vertical scrollbar layer"},
-    {CompositingReason::kLayerForScrollCorner, "layerForScrollCorner",
-     "Secondary layer, the scroll corner layer"},
-    {CompositingReason::kLayerForScrollingContents, "layerForScrollingContents",
-     "Secondary layer, to house contents that can be scrolled"},
-    {CompositingReason::kLayerForSquashingContents, "layerForSquashingContents",
-     "Secondary layer, home for a group of squashable content"},
-    {CompositingReason::kLayerForForeground, "layerForForeground",
-     "Secondary layer, to contain any normal flow and positive z-index "
-     "contents on top of a negative z-index layer"},
-    {CompositingReason::kLayerForMask, "layerForMask",
-     "Secondary layer, to contain the mask contents"},
-    {CompositingReason::kLayerForDecoration, "layerForDecoration",
-     "Layer painted on top of other layers as decoration"},
     {CompositingReason::kLayerForOther, "layerForOther",
      "Layer for link highlight, frame overlay, etc."},
     {CompositingReason::kBackfaceInvisibility3DAncestor,
diff --git a/third_party/blink/renderer/platform/graphics/compositing_reasons.h b/third_party/blink/renderer/platform/graphics/compositing_reasons.h
index 4cfec46..a14e3fde 100644
--- a/third_party/blink/renderer/platform/graphics/compositing_reasons.h
+++ b/third_party/blink/renderer/platform/graphics/compositing_reasons.h
@@ -24,8 +24,6 @@
   V(Plugin)                                                                   \
   V(IFrame)                                                                   \
   V(DocumentTransitionContentElement)                                         \
-  /* This is used for pre-CompositAfterPaint + CompositeSVG only. */          \
-  V(SVGRoot)                                                                  \
   V(BackfaceVisibilityHidden)                                                 \
   V(ActiveTransformAnimation)                                                 \
   V(ActiveOpacityAnimation)                                                   \
@@ -35,8 +33,6 @@
   V(FixedPosition)                                                            \
   V(StickyPosition)                                                           \
   V(OverflowScrolling)                                                        \
-  V(OutOfFlowClipping)                                                        \
-  V(VideoOverlay)                                                             \
   V(WillChangeTransform)                                                      \
   V(WillChangeOpacity)                                                        \
   V(WillChangeFilter)                                                         \
@@ -54,44 +50,30 @@
   V(BackdropFilter)                                                           \
   V(BackdropFilterMask)                                                       \
   V(RootScroller)                                                             \
-  V(XrOverlay)                                                                \
   V(Viewport)                                                                 \
                                                                               \
-  /* Overlap reasons that require knowing what's behind you in paint-order    \
-     before knowing the answer. */                                            \
-  V(AssumedOverlap)                                                           \
+  /* This is based on overlapping relationship among pending layers,          \
+     determined after paint. See PaintArtifactCompositor. */                  \
   V(Overlap)                                                                  \
-  V(NegativeZIndexChildren)                                                   \
                                                                               \
   /* Subtree reasons that require knowing what the status of your subtree is  \
      before knowing the answer. */                                            \
   V(OpacityWithCompositedDescendants)                                         \
   V(MaskWithCompositedDescendants)                                            \
-  V(ReflectionWithCompositedDescendants)                                      \
   V(FilterWithCompositedDescendants)                                          \
   V(BlendingWithCompositedDescendants)                                        \
   V(PerspectiveWith3DDescendants)                                             \
   V(Preserve3DWith3DDescendants)                                              \
-  V(IsolateCompositedDescendants)                                             \
-  V(FullscreenVideoWithCompositedDescendants)                                 \
                                                                               \
   /* The root layer is a special case. It may be forced to be a layer, but it \
   also needs to be a layer if anything else in the subtree is composited. */  \
   V(Root)                                                                     \
                                                                               \
-  /* CompositedLayerMapping internal hierarchy reasons. Some of them are also \
-  used in CompositeAfterPaint. */                                             \
   V(LayerForHorizontalScrollbar)                                              \
   V(LayerForVerticalScrollbar)                                                \
-  V(LayerForScrollCorner)                                                     \
-  V(LayerForScrollingContents)                                                \
-  V(LayerForSquashingContents)                                                \
-  V(LayerForForeground)                                                       \
-  V(LayerForMask)                                                             \
-  /* Composited layer painted on top of all other layers as decoration. */    \
-  V(LayerForDecoration)                                                       \
   /* Link highlight, frame overlay, etc. */                                   \
   V(LayerForOther)                                                            \
+                                                                              \
   /* DocumentTransition shared element.                                       \
   See third_party/blink/renderer/core/document_transition/README.md. */       \
   V(DocumentTransitionSharedElement)
@@ -129,47 +111,11 @@
     kComboActiveAnimation =
         kActiveTransformAnimation | kActiveOpacityAnimation |
         kActiveFilterAnimation | kActiveBackdropFilterAnimation,
-
-    kComboAllDirectStyleDeterminedReasons =
-        k3DTransform | kTrivial3DTransform | kBackfaceVisibilityHidden |
-        kComboActiveAnimation | kWillChangeTransform | kWillChangeOpacity |
-        kWillChangeFilter | kWillChangeOther | kBackdropFilter |
-        kWillChangeBackdropFilter,
-
     kComboScrollDependentPosition = kFixedPosition | kStickyPosition,
-
-    kComboAllDirectNonStyleDeterminedReasons =
-        kVideo | kCanvas | kPlugin | kIFrame | kSVGRoot | kOutOfFlowClipping |
-        kVideoOverlay | kXrOverlay | kRoot | kRootScroller |
-        kComboScrollDependentPosition | kAffectedByOuterViewportBoundsDelta |
-        kBackfaceInvisibility3DAncestor | kTransform3DSceneLeaf |
-        kDocumentTransitionSharedElement | kDocumentTransitionContentElement,
-
-    kComboAllDirectReasons = kComboAllDirectStyleDeterminedReasons |
-                             kComboAllDirectNonStyleDeterminedReasons,
-
-    kComboAllCompositedScrollingDeterminedReasons =
-        kComboScrollDependentPosition | kAffectedByOuterViewportBoundsDelta |
-        kOverflowScrolling,
-
-    kComboCompositedDescendants =
-        kIsolateCompositedDescendants | kOpacityWithCompositedDescendants |
-        kMaskWithCompositedDescendants | kFilterWithCompositedDescendants |
-        kBlendingWithCompositedDescendants |
-        kReflectionWithCompositedDescendants,
-
-    kCombo3DDescendants =
-        kPreserve3DWith3DDescendants | kPerspectiveWith3DDescendants,
-
-    kComboAllStyleDeterminedReasons = kComboAllDirectStyleDeterminedReasons |
-                                      kComboCompositedDescendants |
-                                      kCombo3DDescendants,
-
     kPreventingSubpixelAccumulationReasons = kWillChangeTransform,
-
     kDirectReasonsForPaintOffsetTranslationProperty =
         kComboScrollDependentPosition | kAffectedByOuterViewportBoundsDelta |
-        kVideo | kCanvas | kPlugin | kIFrame | kSVGRoot,
+        kVideo | kCanvas | kPlugin | kIFrame,
     kDirectReasonsForTransformProperty =
         k3DTransform | kTrivial3DTransform | kWillChangeTransform |
         kWillChangeOther | kPerspectiveWith3DDescendants |
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_load_scheduler.cc b/third_party/blink/renderer/platform/loader/fetch/resource_load_scheduler.cc
index 1261130..ea79b44 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_load_scheduler.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_load_scheduler.cc
@@ -16,6 +16,7 @@
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/mojom/devtools/console_message.mojom-blink.h"
 #include "third_party/blink/public/platform/platform.h"
+#include "third_party/blink/renderer/platform/heap/persistent.h"
 #include "third_party/blink/renderer/platform/instrumentation/histogram.h"
 #include "third_party/blink/renderer/platform/loader/fetch/console_logger.h"
 #include "third_party/blink/renderer/platform/loader/fetch/loading_behavior_observer.h"
@@ -23,6 +24,7 @@
 #include "third_party/blink/renderer/platform/network/network_state_notifier.h"
 #include "third_party/blink/renderer/platform/scheduler/public/aggregated_metric_reporter.h"
 #include "third_party/blink/renderer/platform/scheduler/public/frame_status.h"
+#include "third_party/blink/renderer/platform/wtf/functional.h"
 
 namespace blink {
 
@@ -104,7 +106,9 @@
                                kTightLimitForRendererSideResourceScheduler);
 
   scheduler_observer_handle_ = frame_or_worker_scheduler->AddLifecycleObserver(
-      FrameScheduler::ObserverType::kLoader, this);
+      FrameScheduler::ObserverType::kLoader,
+      WTF::BindRepeating(&ResourceLoadScheduler::OnLifecycleStateChanged,
+                         WrapWeakPersistent(this)));
 }
 
 ResourceLoadScheduler::~ResourceLoadScheduler() = default;
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_load_scheduler.h b/third_party/blink/renderer/platform/loader/fetch/resource_load_scheduler.h
index a68a78e..84b56d6 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_load_scheduler.h
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_load_scheduler.h
@@ -91,8 +91,7 @@
 //     and the milestones being experimented with are first paint and first
 //     contentful paint so far.
 class PLATFORM_EXPORT ResourceLoadScheduler final
-    : public GarbageCollected<ResourceLoadScheduler>,
-      public FrameOrWorkerScheduler::Observer {
+    : public GarbageCollected<ResourceLoadScheduler> {
  public:
   // An option to use in calling Request(). If kCanNotBeStoppedOrThrottled is
   // specified, the request should be granted and Run() should be called
@@ -180,7 +179,7 @@
                         LoadingBehaviorObserver* loading_behavior_observer);
   ResourceLoadScheduler(const ResourceLoadScheduler&) = delete;
   ResourceLoadScheduler& operator=(const ResourceLoadScheduler&) = delete;
-  ~ResourceLoadScheduler() override;
+  ~ResourceLoadScheduler();
 
   void Trace(Visitor*) const;
 
@@ -225,8 +224,8 @@
   }
   void SetOutstandingLimitForTesting(size_t tight_limit, size_t normal_limit);
 
-  // FrameOrWorkerScheduler::Observer overrides:
-  void OnLifecycleStateChanged(scheduler::SchedulingLifecycleState) override;
+  // FrameOrWorkerScheduler lifecycle observer callback.
+  void OnLifecycleStateChanged(scheduler::SchedulingLifecycleState);
 
   // The caller is the owner of the |clock|. The |clock| must outlive the
   // ResourceLoadScheduler.
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter.cc b/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter.cc
index 6a9f0c6..0bc3ee2 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter.cc
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter.cc
@@ -478,7 +478,7 @@
 
 // static
 void RTCVideoDecoderAdapter::OnInitializeDone(base::OnceCallback<void(bool)> cb,
-                                              media::Status status) {
+                                              media::DecoderStatus status) {
   std::move(cb).Run(status.is_ok());
 }
 
@@ -512,13 +512,15 @@
   }
 }
 
-void RTCVideoDecoderAdapter::OnDecodeDone(media::Status status) {
-  DVLOG(3) << __func__ << "(" << status.code() << ")";
+void RTCVideoDecoderAdapter::OnDecodeDone(media::DecoderStatus status) {
+  DVLOG(3) << __func__ << "(" << status.group() << ":"
+           << static_cast<int>(status.code()) << ")";
   DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
 
   outstanding_decode_requests_--;
 
-  if (!status.is_ok() && status.code() != media::StatusCode::kAborted) {
+  if (!status.is_ok() &&
+      status.code() != media::DecoderStatus::Codes::kAborted) {
     DVLOG(2) << "Entering permanent error state";
     base::UmaHistogramSparse("Media.RTCVideoDecoderError",
                              static_cast<int>(status.code()));
@@ -635,7 +637,7 @@
       media::DecoderBuffer::CreateEOSBuffer(),
       WTF::Bind(
           [](FlushDoneCB flush_success, FlushDoneCB flush_fail,
-             media::Status status) {
+             media::DecoderStatus status) {
             if (status.is_ok())
               std::move(flush_success).Run();
             else
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter.h b/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter.h
index 9d1c68a..1a4a561 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter.h
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter.h
@@ -14,7 +14,7 @@
 #include "base/sequence_checker.h"
 #include "base/synchronization/lock.h"
 #include "build/build_config.h"
-#include "media/base/decode_status.h"
+#include "media/base/decoder_status.h"
 #include "media/base/status.h"
 #include "media/base/supported_video_decoder_config.h"
 #include "media/base/video_codecs.h"
@@ -123,9 +123,9 @@
   void InitializeOnMediaThread(const media::VideoDecoderConfig& config,
                                InitCB init_cb);
   static void OnInitializeDone(base::OnceCallback<void(bool)> cb,
-                               media::Status status);
+                               media::DecoderStatus status);
   void DecodeOnMediaThread();
-  void OnDecodeDone(media::Status status);
+  void OnDecodeDone(media::DecoderStatus status);
   void OnOutput(scoped_refptr<media::VideoFrame> frame);
 
   bool ShouldReinitializeForSettingHDRColorSpace(
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter_test.cc b/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter_test.cc
index 4567a4e..667a7aa 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter_test.cc
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter_test.cc
@@ -20,7 +20,7 @@
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "gpu/command_buffer/common/mailbox.h"
-#include "media/base/decode_status.h"
+#include "media/base/decoder_status.h"
 #include "media/base/media_switches.h"
 #include "media/base/media_util.h"
 #include "media/base/video_decoder.h"
@@ -183,12 +183,11 @@
 
   bool CreateAndInitialize(bool init_cb_result = true) {
     EXPECT_CALL(*video_decoder_, Initialize_(_, _, _, _, _, _))
-        .WillOnce(DoAll(
-            SaveArg<0>(&vda_config_), SaveArg<4>(&output_cb_),
-            base::test::RunOnceCallback<3>(
-                init_cb_result
-                    ? media::OkStatus()
-                    : media::Status(media::StatusCode::kCodeOnlyForTesting))));
+        .WillOnce(
+            DoAll(SaveArg<0>(&vda_config_), SaveArg<4>(&output_cb_),
+                  base::test::RunOnceCallback<3>(
+                      init_cb_result ? media::DecoderStatus::Codes::kOk
+                                     : media::DecoderStatus::Codes::kFailed)));
     rtc_video_decoder_adapter_ =
         RTCVideoDecoderAdapter::Create(&gpu_factories_, sdp_format_);
     return !!rtc_video_decoder_adapter_;
@@ -314,7 +313,8 @@
   ASSERT_TRUE(BasicSetup());
 
   EXPECT_CALL(*video_decoder_, Decode_(_, _))
-      .WillOnce(base::test::RunOnceCallback<1>(media::DecodeStatus::OK));
+      .WillOnce(
+          base::test::RunOnceCallback<1>(media::DecoderStatus::Codes::kOk));
 
   ASSERT_EQ(Decode(0), WEBRTC_VIDEO_CODEC_OK);
 
@@ -328,7 +328,7 @@
 
   EXPECT_CALL(*video_decoder_, Decode_(_, _))
       .WillOnce(
-          base::test::RunOnceCallback<1>(media::DecodeStatus::DECODE_ERROR));
+          base::test::RunOnceCallback<1>(media::DecoderStatus::Codes::kFailed));
 
   ASSERT_EQ(Decode(0), WEBRTC_VIDEO_CODEC_OK);
   media_thread_.FlushForTesting();
@@ -383,13 +383,15 @@
   // Decode() is expected to be called for EOS flush as well.
   EXPECT_CALL(*video_decoder_, Decode_(_, _))
       .Times(3)
-      .WillRepeatedly(base::test::RunOnceCallback<1>(media::DecodeStatus::OK));
+      .WillRepeatedly(
+          base::test::RunOnceCallback<1>(media::DecoderStatus::Codes::kOk));
   EXPECT_CALL(decoded_cb_, Run(_)).Times(2);
 
   // First Decode() should cause a reinitialize as new color space is given.
   EXPECT_CALL(*video_decoder_, Initialize_(_, _, _, _, _, _))
-      .WillOnce(DoAll(SaveArg<0>(&vda_config_),
-                      base::test::RunOnceCallback<3>(media::OkStatus())));
+      .WillOnce(DoAll(
+          SaveArg<0>(&vda_config_),
+          base::test::RunOnceCallback<3>(media::DecoderStatus::Codes::kOk)));
   webrtc::EncodedImage first_input_image = GetEncodedImageWithColorSpace(0);
   ASSERT_EQ(rtc_video_decoder_adapter_->Decode(first_input_image, false, 0),
             WEBRTC_VIDEO_CODEC_OK);
@@ -417,12 +419,13 @@
 
   // Decode() is expected to be called for EOS flush as well.
   EXPECT_CALL(*video_decoder_, Decode_(_, _))
-      .WillOnce(base::test::RunOnceCallback<1>(media::DecodeStatus::OK));
+      .WillOnce(
+          base::test::RunOnceCallback<1>(media::DecoderStatus::Codes::kOk));
 
   // Set Initialize() to fail.
   EXPECT_CALL(*video_decoder_, Initialize_(_, _, _, _, _, _))
-      .WillOnce(base::test::RunOnceCallback<3>(
-          media::Status(media::StatusCode::kCodeOnlyForTesting)));
+      .WillOnce(
+          base::test::RunOnceCallback<3>(media::DecoderStatus::Codes::kFailed));
   ASSERT_EQ(rtc_video_decoder_adapter_->Decode(input_image, false, 0),
             WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE);
 }
@@ -438,7 +441,8 @@
 
   // Decode() is expected to be called for EOS flush, set to fail.
   EXPECT_CALL(*video_decoder_, Decode_(_, _))
-      .WillOnce(base::test::RunOnceCallback<1>(media::DecodeStatus::ABORTED));
+      .WillOnce(base::test::RunOnceCallback<1>(
+          media::DecoderStatus::Codes::kAborted));
   ASSERT_EQ(rtc_video_decoder_adapter_->Decode(input_image, false, 0),
             WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE);
 }
@@ -454,7 +458,8 @@
 
   // The first decode should increment the count.
   EXPECT_CALL(*video_decoder_, Decode_)
-      .WillOnce(base::test::RunOnceCallback<1>(media::DecodeStatus::OK));
+      .WillOnce(
+          base::test::RunOnceCallback<1>(media::DecoderStatus::Codes::kOk));
   EXPECT_EQ(Decode(0), WEBRTC_VIDEO_CODEC_OK);
   EXPECT_EQ(RTCVideoDecoderAdapter::GetCurrentDecoderCountForTesting(), 1);
 
@@ -525,7 +530,8 @@
 
   // The first decode should increment the count and succeed.
   EXPECT_CALL(*video_decoder_, Decode_(_, _))
-      .WillOnce(base::test::RunOnceCallback<1>(media::DecodeStatus::OK));
+      .WillOnce(
+          base::test::RunOnceCallback<1>(media::DecoderStatus::Codes::kOk));
   EXPECT_EQ(Decode(0), WEBRTC_VIDEO_CODEC_OK);
   EXPECT_EQ(RTCVideoDecoderAdapter::GetCurrentDecoderCountForTesting(),
             RTCVideoDecoderAdapter::kMaxDecoderInstances + 1);
@@ -541,7 +547,8 @@
   SetSpatialIndex(2);
   video_decoder_->SetDecoderType(media::VideoDecoderType::kD3D11);
   EXPECT_CALL(*video_decoder_, Decode_(_, _))
-      .WillOnce(base::test::RunOnceCallback<1>(media::DecodeStatus::OK));
+      .WillOnce(
+          base::test::RunOnceCallback<1>(media::DecoderStatus::Codes::kOk));
 
   ASSERT_EQ(Decode(0), WEBRTC_VIDEO_CODEC_OK);
 
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_stream_adapter.cc b/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_stream_adapter.cc
index c50a5f3..88335c6 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_stream_adapter.cc
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_stream_adapter.cc
@@ -825,9 +825,9 @@
   pending_read_ = false;
 
   switch (result.code()) {
-    case media::StatusCode::kOk:
+    case media::DecoderStatus::Codes::kOk:
       break;
-    case media::StatusCode::kAborted:
+    case media::DecoderStatus::Codes::kAborted:
       // We're doing a Reset(), so just ignore it and keep going.
       return;
     default:
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_stream_adapter.h b/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_stream_adapter.h
index 068735b..50200bf 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_stream_adapter.h
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_stream_adapter.h
@@ -14,7 +14,7 @@
 #include "base/metrics/single_sample_metrics.h"
 #include "base/sequence_checker.h"
 #include "base/synchronization/lock.h"
-#include "media/base/decode_status.h"
+#include "media/base/decoder_status.h"
 #include "media/base/media_switches.h"
 #include "media/base/overlay_info.h"
 #include "media/base/status.h"
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_stream_adapter_test.cc b/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_stream_adapter_test.cc
index a815b27..f12a73e 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_stream_adapter_test.cc
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_stream_adapter_test.cc
@@ -21,8 +21,8 @@
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "gpu/command_buffer/common/mailbox.h"
-#include "media/base/decode_status.h"
 #include "media/base/decoder_factory.h"
+#include "media/base/decoder_status.h"
 #include "media/base/media_util.h"
 #include "media/base/video_decoder.h"
 #include "media/base/video_decoder_config.h"
@@ -253,12 +253,11 @@
 
   void SetUpMockDecoder(MockVideoDecoder* decoder, bool init_cb_result) {
     EXPECT_CALL(*decoder, Initialize_(_, _, _, _, _, _))
-        .WillOnce(DoAll(
-            SaveArg<0>(&vda_config_), SaveArg<4>(&output_cb_),
-            base::test::RunOnceCallback<3>(
-                init_cb_result
-                    ? media::OkStatus()
-                    : media::Status(media::StatusCode::kCodeOnlyForTesting))));
+        .WillOnce(
+            DoAll(SaveArg<0>(&vda_config_), SaveArg<4>(&output_cb_),
+                  base::test::RunOnceCallback<3>(
+                      init_cb_result ? media::DecoderStatus::Codes::kOk
+                                     : media::DecoderStatus::Codes::kFailed)));
   }
 
   // Set up our decoder factory to provide a decoder that will succeed or fail
@@ -452,7 +451,8 @@
   auto* decoder = decoder_factory_->decoder();
   EXPECT_TRUE(BasicSetup());
   EXPECT_CALL(*decoder, Decode_(_, _))
-      .WillOnce(base::test::RunOnceCallback<1>(media::DecodeStatus::OK));
+      .WillOnce(
+          base::test::RunOnceCallback<1>(media::DecoderStatus::Codes::kOk));
   EXPECT_EQ(Decode(0), WEBRTC_VIDEO_CODEC_OK);
   task_environment_.RunUntilIdle();
   EXPECT_CALL(decoded_cb_, Run(_));
@@ -469,7 +469,8 @@
   // All Decodes succeed immediately.  The backup will come from the fact that
   // we won't run the media thread while sending decode requests in.
   EXPECT_CALL(*decoder, Decode_(_, _))
-      .WillRepeatedly(base::test::RunOnceCallback<1>(media::DecodeStatus::OK));
+      .WillRepeatedly(
+          base::test::RunOnceCallback<1>(media::DecoderStatus::Codes::kOk));
   // At some point, `adapter_` should trigger a reset.
   EXPECT_CALL(*decoder, Reset_(_)).WillOnce(base::test::RunOnceCallback<0>());
 
@@ -516,7 +517,8 @@
   // All Decodes succeed immediately.  The backup will come from the fact that
   // we won't run the media thread while sending decode requests in.
   EXPECT_CALL(*decoder, Decode_(_, _))
-      .WillRepeatedly(base::test::RunOnceCallback<1>(media::DecodeStatus::OK));
+      .WillRepeatedly(
+          base::test::RunOnceCallback<1>(media::DecoderStatus::Codes::kOk));
   // At some point, `adapter_` should trigger a reset, before it falls back.  It
   // should not do so more than once, since we won't complete the reset.
   EXPECT_CALL(*decoder, Reset_(_)).WillOnce(base::test::RunOnceCallback<0>());
@@ -553,7 +555,8 @@
   auto* decoder = decoder_factory_->decoder();
   EXPECT_TRUE(BasicSetup());
   EXPECT_CALL(*decoder, Decode_(_, _))
-      .WillOnce(base::test::RunOnceCallback<1>(media::DecodeStatus::OK));
+      .WillOnce(
+          base::test::RunOnceCallback<1>(media::DecoderStatus::Codes::kOk));
   EXPECT_EQ(Decode(0), WEBRTC_VIDEO_CODEC_OK);
   task_environment_.RunUntilIdle();
   // Should not be called.
@@ -602,7 +605,8 @@
   task_environment_.RunUntilIdle();
 
   EXPECT_CALL(*correct_decoder, Decode_(_, _))
-      .WillOnce(base::test::RunOnceCallback<1>(media::DecodeStatus::OK));
+      .WillOnce(
+          base::test::RunOnceCallback<1>(media::DecoderStatus::Codes::kOk));
   Decode(0);
   task_environment_.RunUntilIdle();
 }
@@ -615,7 +619,8 @@
   decoder->SetDecoderType(media::VideoDecoderType::kD3D11);
   EXPECT_TRUE(BasicSetup());
   EXPECT_CALL(*decoder, Decode_(_, _))
-      .WillOnce(base::test::RunOnceCallback<1>(media::DecodeStatus::OK));
+      .WillOnce(
+          base::test::RunOnceCallback<1>(media::DecoderStatus::Codes::kOk));
   EXPECT_EQ(Decode(0, false), WEBRTC_VIDEO_CODEC_OK);
   task_environment_.RunUntilIdle();
   EXPECT_CALL(decoded_cb_, Run(_));
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 8801e295..afa60b3a 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -539,7 +539,7 @@
     {
       // https://github.com/DevSDK/calc-infinity-and-NaN/blob/master/explainer.md
       name: "CSSCalcInfinityAndNaN",
-      status: "test",
+      status: "stable",
     },
     {
       // https://drafts.csswg.org/css-cascade-5/#layering
diff --git a/third_party/blink/renderer/platform/scheduler/common/frame_or_worker_scheduler.cc b/third_party/blink/renderer/platform/scheduler/common/frame_or_worker_scheduler.cc
index 66031c6..707f4dc6 100644
--- a/third_party/blink/renderer/platform/scheduler/common/frame_or_worker_scheduler.cc
+++ b/third_party/blink/renderer/platform/scheduler/common/frame_or_worker_scheduler.cc
@@ -4,16 +4,20 @@
 
 #include "third_party/blink/renderer/platform/scheduler/public/frame_or_worker_scheduler.h"
 
+#include <memory>
+#include <utility>
+
+#include "base/callback.h"
+
 namespace blink {
 
 FrameOrWorkerScheduler::LifecycleObserverHandle::LifecycleObserverHandle(
-    FrameOrWorkerScheduler* scheduler,
-    Observer* observer)
-    : scheduler_(scheduler->GetWeakPtr()), observer_(observer) {}
+    FrameOrWorkerScheduler* scheduler)
+    : scheduler_(scheduler->GetWeakPtr()) {}
 
 FrameOrWorkerScheduler::LifecycleObserverHandle::~LifecycleObserverHandle() {
   if (scheduler_)
-    scheduler_->RemoveLifecycleObserver(observer_);
+    scheduler_->RemoveLifecycleObserver(this);
 }
 
 FrameOrWorkerScheduler::SchedulingAffectingFeatureHandle::
@@ -65,25 +69,28 @@
 }
 
 std::unique_ptr<FrameOrWorkerScheduler::LifecycleObserverHandle>
-FrameOrWorkerScheduler::AddLifecycleObserver(ObserverType type,
-                                             Observer* observer) {
-  DCHECK(observer);
-  observer->OnLifecycleStateChanged(CalculateLifecycleState(type));
-  lifecycle_observers_.Set(observer, type);
-  return std::make_unique<LifecycleObserverHandle>(this, observer);
+FrameOrWorkerScheduler::AddLifecycleObserver(
+    ObserverType type,
+    OnLifecycleStateChangedCallback callback) {
+  callback.Run(CalculateLifecycleState(type));
+  auto handle = std::make_unique<LifecycleObserverHandle>(this);
+  lifecycle_observers_.Set(
+      handle.get(), std::make_unique<ObserverState>(type, std::move(callback)));
+  return handle;
 }
 
-void FrameOrWorkerScheduler::RemoveLifecycleObserver(Observer* observer) {
-  DCHECK(observer);
-  const auto found = lifecycle_observers_.find(observer);
+void FrameOrWorkerScheduler::RemoveLifecycleObserver(
+    LifecycleObserverHandle* handle) {
+  DCHECK(handle);
+  const auto found = lifecycle_observers_.find(handle);
   DCHECK(lifecycle_observers_.end() != found);
   lifecycle_observers_.erase(found);
 }
 
 void FrameOrWorkerScheduler::NotifyLifecycleObservers() {
   for (const auto& observer : lifecycle_observers_) {
-    observer.key->OnLifecycleStateChanged(
-        CalculateLifecycleState(observer.value));
+    observer.value->GetCallback().Run(
+        CalculateLifecycleState(observer.value->GetObserverType()));
   }
 }
 
@@ -91,4 +98,11 @@
   return weak_factory_.GetWeakPtr();
 }
 
+FrameOrWorkerScheduler::ObserverState::ObserverState(
+    FrameOrWorkerScheduler::ObserverType observer_type,
+    FrameOrWorkerScheduler::OnLifecycleStateChangedCallback callback)
+    : observer_type_(observer_type), callback_(callback) {}
+
+FrameOrWorkerScheduler::ObserverState::~ObserverState() = default;
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc b/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc
index 6505478..03a2b40 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc
@@ -550,7 +550,7 @@
 
 namespace {
 
-class MockLifecycleObserver final : public FrameScheduler::Observer {
+class MockLifecycleObserver {
  public:
   MockLifecycleObserver()
       : not_throttled_count_(0u),
@@ -570,7 +570,7 @@
     EXPECT_EQ(stopped_count_expectation, stopped_count_) << from.ToString();
   }
 
-  void OnLifecycleStateChanged(SchedulingLifecycleState state) override {
+  void OnLifecycleStateChanged(SchedulingLifecycleState state) {
     switch (state) {
       case SchedulingLifecycleState::kNotThrottled:
         not_throttled_count_++;
@@ -588,6 +588,11 @@
     }
   }
 
+  FrameOrWorkerScheduler::OnLifecycleStateChangedCallback GetCallback() {
+    return base::BindRepeating(&MockLifecycleObserver::OnLifecycleStateChanged,
+                               base::Unretained(this));
+  }
+
  private:
   size_t not_throttled_count_;
   size_t hidden_count_;
@@ -1102,7 +1107,7 @@
   EXPECT_EQ(2, counter);
 }
 
-// Tests if throttling observer interfaces work.
+// Tests if throttling observer callbacks work.
 TEST_F(FrameSchedulerImplTest, LifecycleObserver) {
   std::unique_ptr<MockLifecycleObserver> observer =
       std::make_unique<MockLifecycleObserver>();
@@ -1116,7 +1121,7 @@
                                throttled_count, stopped_count);
 
   auto observer_handle = frame_scheduler_->AddLifecycleObserver(
-      FrameScheduler::ObserverType::kLoader, observer.get());
+      FrameScheduler::ObserverType::kLoader, observer->GetCallback());
 
   // Initial state should be synchronously notified here.
   // We assume kNotThrottled is notified as an initial state, but it could
@@ -1215,10 +1220,11 @@
 
   // Adding the observers should recieve a non-throttled response
   auto loader_observer_handle = frame_scheduler_->AddLifecycleObserver(
-      FrameScheduler::ObserverType::kLoader, loader_observer.get());
+      FrameScheduler::ObserverType::kLoader, loader_observer->GetCallback());
 
   auto worker_observer_handle = frame_scheduler_->AddLifecycleObserver(
-      FrameScheduler::ObserverType::kWorkerScheduler, worker_observer.get());
+      FrameScheduler::ObserverType::kWorkerScheduler,
+      worker_observer->GetCallback());
 
   loader_observer->CheckObserverState(
       FROM_HERE, ++loader_not_throttled_count, loader_hidden_count,
@@ -1245,7 +1251,7 @@
     auto loader_observer_added_after_stopped_handle =
         frame_scheduler_->AddLifecycleObserver(
             FrameScheduler::ObserverType::kLoader,
-            loader_observer_added_after_stopped.get());
+            loader_observer_added_after_stopped->GetCallback());
     // This observer should see stopped when added.
     loader_observer_added_after_stopped->CheckObserverState(FROM_HERE, 0, 0, 0,
                                                             1u);
diff --git a/third_party/blink/renderer/platform/scheduler/public/frame_or_worker_scheduler.h b/third_party/blink/renderer/platform/scheduler/public/frame_or_worker_scheduler.h
index c04c69c..8232974e 100644
--- a/third_party/blink/renderer/platform/scheduler/public/frame_or_worker_scheduler.h
+++ b/third_party/blink/renderer/platform/scheduler/public/frame_or_worker_scheduler.h
@@ -5,6 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_PUBLIC_FRAME_OR_WORKER_SCHEDULER_H_
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_PUBLIC_FRAME_OR_WORKER_SCHEDULER_H_
 
+#include "base/callback.h"
 #include "base/memory/weak_ptr.h"
 #include "base/types/strong_alias.h"
 #include "third_party/blink/renderer/platform/platform_export.h"
@@ -26,30 +27,21 @@
   // Observer type that regulates conditions to invoke callbacks.
   enum class ObserverType { kLoader, kWorkerScheduler };
 
-  // Observer interface to receive scheduling policy change events.
-  class Observer {
-   public:
-    virtual ~Observer() = default;
-
-    // Notified when throttling state is changed. May be called consecutively
-    // with the same value.
-    virtual void OnLifecycleStateChanged(
-        scheduler::SchedulingLifecycleState) = 0;
-  };
+  // Callback type for receiving scheduling policy change events.
+  using OnLifecycleStateChangedCallback =
+      base::RepeatingCallback<void(scheduler::SchedulingLifecycleState)>;
 
   class PLATFORM_EXPORT LifecycleObserverHandle {
     USING_FAST_MALLOC(LifecycleObserverHandle);
 
    public:
-    LifecycleObserverHandle(FrameOrWorkerScheduler* scheduler,
-                            Observer* observer);
+    explicit LifecycleObserverHandle(FrameOrWorkerScheduler* scheduler);
     LifecycleObserverHandle(const LifecycleObserverHandle&) = delete;
     LifecycleObserverHandle& operator=(const LifecycleObserverHandle&) = delete;
     ~LifecycleObserverHandle();
 
    private:
     base::WeakPtr<FrameOrWorkerScheduler> scheduler_;
-    Observer* observer_;
   };
 
   // RAII handle which should be kept alive as long as the feature is active
@@ -120,14 +112,21 @@
   void RegisterStickyFeature(SchedulingPolicy::Feature feature,
                              SchedulingPolicy policy);
 
-  // Adds an Observer instance to be notified on scheduling policy changed.
-  // When an Observer is added, the initial state will be notified synchronously
-  // through the Observer interface.
-  // A RAII handle is returned and observer is unregistered when the handle is
-  // destroyed.
-  std::unique_ptr<LifecycleObserverHandle> AddLifecycleObserver(ObserverType,
-                                                                Observer*)
-      WARN_UNUSED_RESULT;
+  // Adds an observer callback to be notified on scheduling policy changed.
+  // When a callback is added, the initial state will be notified synchronously
+  // through the callback. The callback may be invoked consecutively with the
+  // same value. Returns a RAII handle that unregisters the callback when the
+  // handle is destroyed.
+  //
+  // New usage outside of platform/ should be rare. Prefer using
+  // ExecutionContextLifecycleStateObserver to observe paused and frozenness
+  // changes and PageVisibilityObserver to observe visibility changes. One
+  // exception is that this observer enables observing visibility changes of the
+  // associated page in workers, whereas PageVisibilityObserver does not
+  // (crbug.com/1286570).
+  std::unique_ptr<LifecycleObserverHandle> AddLifecycleObserver(
+      ObserverType,
+      OnLifecycleStateChangedCallback) WARN_UNUSED_RESULT;
 
   virtual std::unique_ptr<WebSchedulingTaskQueue> CreateWebSchedulingTaskQueue(
       WebSchedulingPriority) = 0;
@@ -157,10 +156,25 @@
   GetSchedulingAffectingFeatureWeakPtr() = 0;
 
  private:
-  void RemoveLifecycleObserver(Observer* observer);
+  class ObserverState {
+   public:
+    ObserverState(ObserverType, OnLifecycleStateChangedCallback);
+    ObserverState(const ObserverState&) = delete;
+    ObserverState& operator=(const ObserverState&) = delete;
+    ~ObserverState();
 
-  // Observers are not owned by the scheduler.
-  HashMap<Observer*, ObserverType> lifecycle_observers_;
+    ObserverType GetObserverType() const { return observer_type_; }
+    OnLifecycleStateChangedCallback& GetCallback() { return callback_; }
+
+   private:
+    ObserverType observer_type_;
+    OnLifecycleStateChangedCallback callback_;
+  };
+
+  void RemoveLifecycleObserver(LifecycleObserverHandle* handle);
+
+  HashMap<LifecycleObserverHandle*, std::unique_ptr<ObserverState>>
+      lifecycle_observers_;
   base::WeakPtrFactory<FrameOrWorkerScheduler> weak_factory_{this};
 };
 
diff --git a/third_party/blink/renderer/platform/scheduler/worker/worker_scheduler_proxy.cc b/third_party/blink/renderer/platform/scheduler/worker/worker_scheduler_proxy.cc
index 3ff3afd..9d73ff8 100644
--- a/third_party/blink/renderer/platform/scheduler/worker/worker_scheduler_proxy.cc
+++ b/third_party/blink/renderer/platform/scheduler/worker/worker_scheduler_proxy.cc
@@ -16,7 +16,9 @@
 WorkerSchedulerProxy::WorkerSchedulerProxy(FrameOrWorkerScheduler* scheduler) {
   DCHECK(scheduler);
   throttling_observer_handle_ = scheduler->AddLifecycleObserver(
-      FrameOrWorkerScheduler::ObserverType::kWorkerScheduler, this);
+      FrameOrWorkerScheduler::ObserverType::kWorkerScheduler,
+      base::BindRepeating(&WorkerSchedulerProxy::OnLifecycleStateChanged,
+                          base::Unretained(this)));
   if (FrameScheduler* frame_scheduler = scheduler->ToFrameScheduler()) {
     parent_frame_type_ = GetFrameOriginType(frame_scheduler);
     initial_frame_status_ = GetFrameStatus(frame_scheduler);
diff --git a/third_party/blink/renderer/platform/scheduler/worker/worker_scheduler_proxy.h b/third_party/blink/renderer/platform/scheduler/worker/worker_scheduler_proxy.h
index fcd56d50..747af85 100644
--- a/third_party/blink/renderer/platform/scheduler/worker/worker_scheduler_proxy.h
+++ b/third_party/blink/renderer/platform/scheduler/worker/worker_scheduler_proxy.h
@@ -29,19 +29,17 @@
 // on the parent thread. It's passed to WorkerScheduler during its
 // construction. Given that DedicatedWorkerThread object outlives worker thread,
 // this class outlives worker thread too.
-class PLATFORM_EXPORT WorkerSchedulerProxy
-    : public FrameOrWorkerScheduler::Observer {
+class PLATFORM_EXPORT WorkerSchedulerProxy {
  public:
   explicit WorkerSchedulerProxy(FrameOrWorkerScheduler* scheduler);
   WorkerSchedulerProxy(const WorkerSchedulerProxy&) = delete;
   WorkerSchedulerProxy& operator=(const WorkerSchedulerProxy&) = delete;
-  ~WorkerSchedulerProxy() override;
+  ~WorkerSchedulerProxy();
 
   void OnWorkerSchedulerCreated(
       base::WeakPtr<WorkerScheduler> worker_scheduler);
 
-  void OnLifecycleStateChanged(
-      SchedulingLifecycleState lifecycle_state) override;
+  void OnLifecycleStateChanged(SchedulingLifecycleState lifecycle_state);
 
   // Accessed only during init.
   SchedulingLifecycleState lifecycle_state() const {
diff --git a/third_party/blink/renderer/platform/wtf/OWNERS b/third_party/blink/renderer/platform/wtf/OWNERS
index 9707fda..306cb4e7 100644
--- a/third_party/blink/renderer/platform/wtf/OWNERS
+++ b/third_party/blink/renderer/platform/wtf/OWNERS
@@ -1,4 +1,3 @@
 haraken@chromium.org
 thakis@chromium.org
 tkent@chromium.org
-yutak@chromium.org
diff --git a/third_party/blink/web_tests/FlagExpectations/highdpi b/third_party/blink/web_tests/FlagExpectations/highdpi
index f96ef02..e84955b 100644
--- a/third_party/blink/web_tests/FlagExpectations/highdpi
+++ b/third_party/blink/web_tests/FlagExpectations/highdpi
@@ -1,57 +1,335 @@
 # results: [ Timeout Crash Pass Failure Slow Skip ]
 
-# Skip the following tests for high DPI test suite.
-crypto/* [ Skip ]
-css3/selectors3/* [ Skip ]
-custom-elements/* [ Skip ]
-custom-properties/* [ Skip ]
-external/wpt/* [ Skip ]
-gamepad/* [ Skip ]
-harness-tests/* [ Skip ]
-js/* [ Skip ]
-media/* [ Skip ]
-mhtml/* [ Skip ]
-netinfo/* [ Skip ]
-payments/* [ Skip ]
-plugins/* [ Skip ]
-scrollingcoordinator/* [ Skip ]
-storage/* [ Skip ]
-touchadjustment/* [ Skip ]
-transitions/* [ Skip ]
-traversal/* [ Skip ]
-virtual/controls-refresh-hc/* [ Skip ]
-virtual/dark-color-scheme/* [ Skip ]
-virtual/exotic-color-space/* [ Skip ]
-virtual/gpu/* [ Skip ]
-virtual/gpu-rasterization/* [ Skip ]
-virtual/prefer_compositing_to_lcd_text/* [ Skip ]
-virtual/system-color-compute/* [ Skip ]
-virtual/threaded/* [ Skip ]
-virtual/threaded-prefer-compositing/* [ Skip ]
-webaudio/* [ Skip ]
-webexposed/* [ Skip ]
-wpt_internal/* [ Skip ]
+# Skip all tests for high DPI test suite unless specified below.
+* [ Skip ]
 
-# TODO(crbug.com/1168836):Enable the following for highdpi.
-dark-mode/* [ Skip ]
-dom/* [ Skip ]
-fast/canvas/* [ Skip ]
-fast/clip/* [ Skip ]
-fast/css3-text/* [ Skip ]
-fast/dom/* [ Skip ]
-fast/dynamic/* [ Skip ]
-fast/frames/* [ Skip ]
-fast/invalid/* [ Skip ]
-fast/multicol/* [ Skip ]
-fast/scrolling/* [ Skip ]
-fast/spatial-navigation/* [ Skip ]
-fast/tokenizer/* [ Skip ]
-fast/webgl/* [ Skip ]
-fast/xsl/* [ Skip ]
-scrollbars/* [ Skip ]
-# TODO(crbug.com/1178099): Adding to skip list temporarily
-# to get around the size of results file due to large number of failures.
-tables/* [ Skip ]
+# Tests to be run on Highdpi. Trying to restrict this list to ~3000.
+# Only small number of tests are picked to run on HighDPI not to burden
+# the CQ/CI resources.
+animations/skew-notsequential-compositor.html [ Pass ]
+animations/state-at-end-event.html [ Pass ]
+animations/3d/matrix-transform-type-animation.html [ Pass ]
+animations/composition/background-position-composition.html [ Pass ]
+animations/composition/clip-composition.html [ Pass ]
+animations/composition/font-size-composition.html [ Pass ]
+animations/custom-properties/angle-type-interpolation.html [ Pass ]
+animations/custom-properties/animate-high-priority.html [ Pass ]
+animations/custom-properties/color-type-interpolation.html [ Pass ]
+animations/direction-and-fill/animation-direction-normal.html [ Pass ]
+animations/direction-and-fill/animation-direction-reverse-fill-mode.html [ Pass ]
+animations/interpolation/border-interpolation.html [ Pass ]
+animations/interpolation/clip-path-interpolation.html [ Pass ]
+animations/interpolation/color-interpolation.html [ Pass ]
+animations/responsive/responsive-neutral-keyframes.html [ Pass ]
+animations/responsive/stacked-visibility-animations-responsive.html [ Pass ]
+animations/responsive/zoom-responsive-transform-animation-001.html [ Pass ]
+animations/responsive/interpolation/fill-responsive.html [ Pass ]
+animations/responsive/interpolation/transform-responsive.html [ Pass ]
+animations/stability/font-builder-crash.html [ Pass ]
+animations/svg/animated-filter-svg-element.html [ Pass ]
+animations/svg-attribute-composition/svg-amplitude-composition.html [ Pass ]
+animations/svg-attribute-composition/svg-azimuth-composition.html [ Pass ]
+animations/svg-attribute-composition/svg-baseFrequency-composition.html [ Pass ]
+animations/svg-attribute-composition/svg-bias-composition.html [ Pass ]
+animations/svg-attribute-interpolation/svg-edgeMode-interpolation.html [ Pass ]
+animations/svg-attribute-interpolation/svg-elevation-interpolation.html [ Pass ]
+animations/svg-attribute-interpolation/svg-exponent-interpolation.html [ Pass ]
+animations/svg-attribute-interpolation/svg-viewBox-interpolation.html [ Pass ]
+animations/web-animations/KeyframeEffect-animation.html [ Pass ]
+compositing/3d-cube.html [ Pass ]
+compositing/checkerboard.html [ Pass ]
+compositing/absolute-inside-out-of-view-fixed.html [ Pass ]
+compositing/backface-visibility-transformed.html [ Pass ]
+compositing/compositing-visible-descendant.html [ Pass ]
+compositing/fixed-background-composited-html.html [ Pass ]
+compositing/perspective-origin-with-scrollbars.html [ Pass ]
+compositing/preserve-3d-toggle.html [ Pass ]
+compositing/render-surface-alpha-blending.html [ Pass ]
+compositing/scroll-with-ancestor-clip.html [ Pass ]
+compositing/scroll-with-inner-clip.html [ Pass ]
+compositing/scrollbar-painting.html [ Pass ]
+compositing/self-painting-layers.html [ Pass ]
+compositing/text-on-scaled-layer.html [ Pass ]
+compositing/text-on-scaled-surface.html [ Pass ]
+compositing/tiled-layers-hidpi.html [ Pass ]
+compositing/toggle-compositing.html [ Pass ]
+compositing/transform-complex-page.html [ Pass ]
+compositing/animation/animation-compositing.html [ Pass ]
+compositing/animation/animations-establish-stacking-context.html [ Pass ]
+compositing/backface-visibility/backface-visibility-3d.html [ Pass ]
+compositing/backface-visibility/backface-visibility-image.html [ Pass ]
+compositing/background-color/background-color-alpha.html [ Pass ]
+compositing/background-color/background-color-change-to-text.html [ Pass ]
+compositing/background-color/background-color-change-to-transparent.html [ Pass ]
+compositing/background-color/background-color-container.html [ Pass ]
+compositing/background-color/background-color-content-clip.html [ Pass ]
+compositing/background-color/background-color-text-change.html [ Pass ]
+compositing/background-color/background-color-text-clip.html [ Pass ]
+compositing/background-color/view-blending-base-background.html [ Pass ]
+compositing/backgrounds/fixed-backgrounds.html [ Pass ]
+compositing/backgrounds/local-background.html [ Pass ]
+compositing/contents-opaque/background-clip.html [ Pass ]
+compositing/contents-opaque/background-color.html [ Pass ]
+compositing/culling/filter-occlusion-alpha-large.html [ Pass ]
+compositing/culling/filter-occlusion-alpha.html [ Pass ]
+compositing/culling/filter-occlusion-blur-large.html [ Pass ]
+compositing/filters/sw-layer-overlaps-hw-shadow.html [ Pass ]
+compositing/filters/sw-nested-shadow-overlaps-hw-nested-shadow.html [ Pass ]
+compositing/force-compositing-mode/no-overflow-iframe-layer.html [ Pass ]
+compositing/force-compositing-mode/overflow-hidden-iframe-layer.html [ Pass ]
+compositing/force-compositing-mode/overflow-iframe-layer.html [ Pass ]
+compositing/geometry/clip-inside.html [ Pass ]
+compositing/geometry/clip-with-shadow.html [ Pass ]
+compositing/geometry/clip.html [ Pass ]
+compositing/geometry/clipped-video-controller.html [ Pass ]
+compositing/geometry/fixed-position.html [ Pass ]
+compositing/geometry/flipped-blocks-inline-mapping.html [ Pass ]
+compositing/geometry/flipped-writing-mode.html [ Pass ]
+compositing/geometry/foreground-layer.html [ Pass ]
+compositing/geometry/horizontal-scroll-composited.html [ Pass ]
+compositing/geometry/vertical-scroll-composited.html [ Pass ]
+compositing/geometry/video-fixed-scrolling.html [ Pass ]
+compositing/geometry/video-opacity-overlay.html [ Pass ]
+compositing/gestures/gesture-tapHighlight-1-iframe-composited-scrolled-clipped.html [ Pass ]
+compositing/gestures/gesture-tapHighlight-1-iframe-composited-scrolled-late-composite.html [ Pass ]
+compositing/gestures/gesture-tapHighlight-1-iframe-composited-scrolled-late-noncomposite.html [ Pass ]
+compositing/gestures/gesture-tapHighlight-1-iframe-composited-scrolled.html [ Pass ]
+compositing/gestures/gesture-tapHighlight-1-iframe-composited.html [ Pass ]
+compositing/gestures/gesture-tapHighlight-1-iframe-scrolled.html [ Pass ]
+compositing/gestures/gesture-tapHighlight-1-iframe.html [ Pass ]
+compositing/gestures/gesture-tapHighlight-1-overflow-div-composited-scroll-clip.html [ Pass ]
+compositing/gestures/gesture-tapHighlight-1-overflow-div-layout-change-2.html [ Pass ]
+compositing/gestures/gesture-tapHighlight-1-overflow-div-layout-change.html [ Pass ]
+compositing/gestures/gesture-tapHighlight-1-overflow-div-scrolled-late-composite.html [ Pass ]
+compositing/gestures/gesture-tapHighlight-1-overflow-div-scrolled.html [ Pass ]
+compositing/gestures/gesture-tapHighlight-1-overflow-div.html [ Pass ]
+compositing/gestures/gesture-tapHighlight-adjustment-clipping.html [ Pass ]
+compositing/gestures/gesture-tapHighlight-block-in-multicol.html [ Pass ]
+compositing/gestures/gesture-tapHighlight-composited-img.html [ Pass ]
+compositing/gestures/gesture-tapHighlight-display-contents.html [ Pass ]
+compositing/gestures/gesture-tapHighlight-form-input-text.html [ Pass ]
+compositing/gestures/gesture-tapHighlight-imagemap.html [ Pass ]
+compositing/gestures/gesture-tapHighlight-nested-cursor.html [ Pass ]
+compositing/gestures/gesture-tapHighlight-simple-div-boxshadow-fixed.html [ Pass ]
+compositing/gestures/gesture-tapHighlight-simple-div-boxshadow-static.html [ Pass ]
+compositing/gestures/gesture-tapHighlight-simple-div-boxshadow.html [ Pass ]
+compositing/gestures/gesture-tapHighlight-simple-navigate.html [ Pass ]
+compositing/gestures/gesture-tapHighlight-simple-scaled-document.html [ Pass ]
+compositing/gestures/gesture-tapHighlight-simple-scaledX.html [ Pass ]
+compositing/gestures/gesture-tapHighlight-skew-matrix.html [ Pass ]
+css1/box_properties/border.html [ Pass ]
+css1/box_properties/border_color_inline.html [ Pass ]
+css1/box_properties/border_style_inline.html [ Pass ]
+css1/box_properties/clear.html [ Pass ]
+css1/box_properties/float.html [ Pass ]
+css1/box_properties/float_on_text_elements.html [ Pass ]
+css1/box_properties/margin.html [ Pass ]
+css1/box_properties/padding_inline.html [ Pass ]
+css1/cascade/cascade_order.html [ Pass ]
+css1/classification/display.html [ Pass ]
+css1/classification/list_style_image.html [ Pass ]
+css1/color_and_background/background.html [ Pass ]
+css1/color_and_background/color.html [ Pass ]
+css1/font_properties/font.html [ Pass ]
+css1/formatting_model/horizontal_formatting.html [ Pass ]
+css1/formatting_model/vertical_formatting.html [ Pass ]
+css1/pseudo/multiple_pseudo_elements.html [ Pass ]
+css1/text_properties/text_align.html [ Pass ]
+css1/units/color_units.html [ Pass ]
+css2.1/t010403-shand-border-00-c.html [ Pass ]
+css2.1/t010403-shand-font-00-b.html [ Pass ]
+css2.1/t040103-escapes-00-b.html [ Pass ]
+css2.1/t040103-ident-00-c.html [ Pass ]
+css2.1/t0803-c5501-imrgn-t-00-b-ag.html [ Pass ]
+css2.1/t0803-c5501-mrgn-t-00-b-a.html [ Pass ]
+css2.1/t0804-c5506-padn-t-00-b-a.html [ Pass ]
+css2.1/t0804-c5507-ipadn-r-00-b-ag.html [ Pass ]
+css2.1/t0805-c5517-brdr-s-00-c.html [ Pass ]
+css2.1/t0805-c5517-ibrdr-s-00-a.html [ Pass ]
+css2.1/t0905-c5525-flthw-00-c-g.html [ Pass ]
+css2.1/t0905-c5525-fltinln-00-c-ag.html [ Pass ]
+css2.1/t1202-counters-00-b.html [ Pass ]
+css2.1/t140201-c533-bgimage-00-a.html [ Pass ]
+css2.1/t1504-c523-font-style-00-b.html [ Pass ]
+css2.1/t170602-bdr-conflct-w-00-d.html [ Pass ]
+css2.1/20110323/background-intrinsic-001.htm [ Pass ]
+css2.1/20110323/border-conflict-style-079.htm [ Pass ]
+css2.1/20110323/inline-table-001.htm [ Pass ]
+css2.1/20110323/margin-applies-to-001.htm [ Pass ]
+css2.1/20110323/outline-color-applies-to-014.htm [ Pass ]
+css2.1/20110323/table-caption-001.htm [ Pass ]
+css3/font-feature-settings-rendering.html [ Pass ]
+css3/background/background-color-gradient-alignment.html [ Pass ]
+css3/blending/background-blend-mode-crossfade-image-gradient.html [ Pass ]
+css3/blending/mix-blend-mode-composited-layers.html [ Pass ]
+css3/blending/svg-blend-color.html [ Pass ]
+css3/filters/add-filter-rendering.html [ Pass ]
+css3/filters/backdrop-filter-basic-blur.html [ Pass ]
+css3/filters/effect-blur.html [ Pass ]
+css3/filters/effect-combined.html [ Pass ]
+css3/masking/clip-path-circle.html [ Pass ]
+css3/masking/mask-repeat-round-border.html [ Pass ]
+editing/composition-underline-color.html [ Pass ]
+editing/caret/caret-color.html [ Pass ]
+editing/input/caret-at-the-edge-of-input.html [ Pass ]
+editing/inserting/insert-space-in-empty-doc.html [ Pass ]
+editing/pasteboard/drag-image-to-contenteditable-in-iframe.html [ Pass ]
+editing/selection/selection-background.html [ Pass ]
+fast/backgrounds/background-origin-root-element.html [ Pass ]
+fast/block/basic/001.html [ Pass ]
+fast/block/float/float-in-float-painting.html [ Pass ]
+fast/block/margin-collapse/001.html [ Pass ]
+fast/block/positioning/001.html [ Pass ]
+fast/body-propagation/background-color/001.html [ Pass ]
+fast/body-propagation/background-image/009.html [ Pass ]
+fast/body-propagation/overflow/001.html [ Pass ]
+fast/borders/bidi-002.html [ Pass ]
+fast/borders/border-image-outset.html [ Pass ]
+fast/box-shadow/box-shadow.html [ Pass ]
+fast/box-sizing/box-sizing.html [ Pass ]
+fast/css/001.html [ Pass ] 
+fast/css/color-correction.html [ Pass ]
+fast/css-generated-content/001.html [ Pass ]
+fast/events/pointer-events-2.html [ Pass ]
+fast/forms/001.html [ Pass ]
+fast/forms/form-element-geometry.html [ Pass ]
+fast/forms/accent-color/accent-color-contrast-dark.html [ Pass ]
+fast/forms/button/button-align.html [ Pass ]
+fast/forms/calendar-picker/calendar-picker-appearance.html [ Pass ]
+fast/forms/checkbox/checkbox-appearance-basic.html [ Pass ]
+fast/forms/color/input-appearance-color.html [ Pass ]
+fast/forms/color-scheme/button/button-appearance-basic.html [ Pass ]
+fast/forms/datalist/input-appearance-range-with-datalist.html [ Pass ]
+fast/forms/datetimelocal/datetimelocal-appearance-basic.html [ Pass ]
+fast/forms/fieldset/fieldset-align.html [ Pass ]
+fast/forms/file/file-appearance-basic.html [ Pass ]
+fast/forms/focus-rect/textarea-with-scrollbar.html [ Pass ]
+fast/forms/image/input-align-image.html [ Pass ]
+fast/forms/month/month-picker-appearance-step.html [ Pass ]
+fast/forms/number/number-appearance-rtl.html [ Pass ]
+fast/forms/password/password-alt-f8.html [ Pass ]
+fast/forms/radio/radio-appearance-basic.html [ Pass ]
+fast/forms/range/input-appearance-range.html [ Pass ]
+fast/forms/search/search-rtl.html [ Pass ]
+fast/forms/select/basic-selects.html [ Pass ]
+fast/forms/select-popup/popup-menu-appearance-coarse.html [ Pass ]
+fast/forms/suggestion-picker/month-suggestion-picker-appearance-rtl.html [ Pass ]
+fast/forms/text/input-baseline.html [ Pass ]
+fast/forms/textarea/textarea-align.html [ Pass ]
+fonts/default.html [ Pass ]
+fragmentation/outline-crossing-columns.html [ Pass ]
+html/details_summary/details-add-summary.html [ Pass ]
+html/document_metadata/head-has-text-1.html [ Pass ]
+html/grouping_content/listing.html [ Pass ]
+html/tabular_data/table_createcaption.html [ Pass ]
+html/text_level_semantics/font-weight-bold-for-b-and-strong.html [ Pass ]
+http/tests/csspaint/border-color.html [ Pass ]
+http/tests/images/jpeg-partial-load.html [ Pass ]
+http/tests/images/png-animated-partial-load.html [ Pass ]
+http/tests/images/png-partial-load-as-document.html [ Pass ]
+http/tests/inspector-protocol/page/page-captureScreenshot-clip.js [ Pass ]
+http/tests/media/video-buffered-range-contains-currentTime.html [ Pass ]
+http/tests/webfont/popup-menu-load-webfont-after-open.html [ Pass ]
+ietestcenter/css3/bordersbackgrounds/background-color-border-box.htm [ Pass ]
+ietestcenter/css3/text/textshadow-001.htm [ Pass ]
+images/color-profile-animate.html [ Pass ]
+images/cross-fade-blending.html [ Pass ]
+images/drag-image.html [ Pass ]
+images/jpeg-with-color-profile.html [ Pass ]
+images/png-with-color-profile.html [ Pass ]
+images/jxl/jxl-images.html [ Pass ]
+images/png-suite/test.html [ Pass ]
+images/yuv-decode-eligible/color-profile-border-radius.html [ Pass ]
+inspector-protocol/dom/dom-scrollIntoViewIfNeeded.js [ Pass ]
+inspector-protocol/dom-snapshot/dom-snapshot-captureSnapshot-details.js [ Pass ]
+inspector-protocol/emulation/device-emulation-desktop.js [ Pass ]
+inspector-protocol/input/dispatchMouseEvent.js [ Pass ]
+inspector-protocol/layers/paint-profiler.js [ Pass ]
+inspector-protocol/layout-fonts/languages-emoji-rare-glyphs.js [ Pass ]
+inspector-protocol/page/get-layout-metrics.js [ Pass ]
+paint/background/fieldset-legend-background-shadow-border-radius.html [ Pass ]
+paint/clipath/clip-path-with-background-and-box-behind.html [ Pass ]
+paint/filters/clip-under-filter.html [ Pass ]
+paint/float/float-under-inline-self-painting-change.html [ Pass ]
+paint/frames/frameset-with-stacking-contexts.html [ Pass ]
+paint/inline/outline-offset.html [ Pass ]
+paint/invalidation/canvas-resize.html [ Pass ]
+paint/invalidation/background/viewport-gradient-background-html-resize-overflow.html [ Pass ]
+paint/invalidation/box/border-radius-repaint-2.html [ Pass ]
+paint/invalidation/clip/background-clip-text.html [ Pass ]
+paint/invalidation/compositing/layer-repaint.html [ Pass ]
+paint/invalidation/css-grid-layout/grid-element-change-columns-repaint.html [ Pass ]
+paint/invalidation/filters/effect-reference-repaint-composite-1.html [ Pass ]
+paint/invalidation/flexbox/repaint-during-resize-no-flex.html [ Pass ]
+paint/invalidation/forms/textarea-caret.html [ Pass ]
+paint/invalidation/image/image-resize.html [ Pass ]
+paint/invalidation/multicol/column-rules-fixed-height.html [ Pass ]
+paint/invalidation/outline/focus-layers.html [ Pass ]
+paint/invalidation/overflow/composited-overflow-with-borderbox-background.html [ Pass ]
+paint/invalidation/position/absolute-position-changed.html [ Pass ]
+paint/invalidation/reflection/reflection-with-rotation.html [ Pass ]
+paint/invalidation/scroll/destroy-composited-scrollbar.html [ Pass ]
+paint/invalidation/selection/selection-within-composited-scroller.html [ Pass ]
+paint/invalidation/svg/animate-path-morphing.svg [ Pass ]
+paint/invalidation/table/composited-table-background.html [ Pass ]
+paint/invalidation/transform/caret-with-transformation.html [ Pass ]
+paint/markers/composition-marker-basic.html [ Pass ]
+paint/masks/fieldset-mask.html [ Pass ]
+paint/overflow/composited-rounded-clip-floating-element.html [ Pass ]
+paint/printing/print-text-shadow.html [ Pass ]
+paint/roundedrects/input-with-rounded-rect-and-shadow.html [ Pass ]
+paint/selection/text-selection-drag-in-frame-scrolled.html [ Pass ]
+paint/subpixel/transform-inside-clip.html [ Pass ]
+paint/tables/composited-collapsed-table-borders.html [ Pass ]
+paint/text/selection-no-clip-text.html [ Pass ]
+paint/theme/adjust-progress-bar-size.html [ Pass ]
+paint/transforms/percentage-transform-fractional-box-size.html [ Pass ]
+printing/fixed-positioned-headers-and-footers.html [ Pass ]
+printing/width-overflow.html [ Pass ]
+printing/css2.1/page-break-inside-000.html [ Pass ]
+svg/W3C-I18N/text-anchor-dirLTR-anchorMiddle.svg [ Pass ]
+svg/W3C-I18N/text-anchor-dirRTL-anchorMiddle.svg [ Pass ]
+svg/W3C-SVG-1.1/animate-elem-03-t.svg [ Pass ]
+svg/W3C-SVG-1.1-SE/filters-image-03-f.svg [ Pass ]
+svg/W3C-SVG-1.2-Tiny/struct-use-recursion-01-t.svg [ Pass ]
+svg/animations/animateMotion-accumulate-1a.svg [ Pass ]
+svg/as-background-image/animated-svg-as-background.html [ Pass ]
+svg/as-border-image/svg-as-border-image.html [ Pass ]
+svg/as-image/svg-as-relative-image.html [ Pass ]
+svg/as-object/svg-embedded-in-html-in-iframe.html [ Pass ]
+svg/batik/paints/patternRegions.svg [ Pass ]
+svg/batik/text/textEffect.svg [ Pass ]
+svg/canvas/canvas-pattern-svg.html [ Pass ]
+svg/carto.net/selectionlist.svg [ Pass ]
+svg/clip-path/clip-path-on-svg.svg [ Pass ]
+svg/css/background-image-svg.html [ Pass ]
+svg/custom/SVGRect-interface.svg [ Pass ]
+svg/dom/css-transforms.xhtml [ Pass ]
+svg/dynamic-updates/SVGFilterElement-dom-filterUnits-attr.html [ Pass ]
+svg/filters/feGaussianBlur.svg [ Pass ]
+svg/foreignObject/filter.html [ Pass ]
+svg/hittest/text-small-font-size-and-viewbox.html [ Pass ]
+svg/hixie/dynamic/002.xml [ Pass ]
+svg/hixie/error/001.xml [ Pass ]
+svg/hixie/links/001.xml [ Pass ]
+svg/hixie/mixed/003.xml [ Pass ]
+svg/hixie/rendering-model/001.xml [ Pass ]
+svg/hixie/viewbox/001.xml [ Pass ]
+svg/in-html/circle.html [ Pass ]
+svg/overflow/overflow-on-foreignObject.svg [ Pass ]
+svg/stroke/zero-length-arc-linecaps-rendering.svg [ Pass ]
+svg/text/bbox-with-glyph-overflow.html [ Pass ]
+svg/text/text-decorations-in-scaled-pattern.svg [ Pass ]
+svg/transforms/text-with-pattern-inside-transformed-html.xhtml [ Pass ]
+svg/wicd/test-scalable-background-image1.xhtml [ Pass ]
+svg/zoom/page/zoom-background-images.html [ Pass ]
+svg/zoom/text/zoom-svg-float-border-padding.xml [ Pass ]
+tables/layering/paint-test-layering-1.html [ Pass ]
+transforms/svg-vs-css.xhtml [ Pass ]
+transforms/transform-on-inline.html [ Pass ]
+transforms/2d/transform-borderbox.html [ Pass ]
+transforms/3d/general/perspective-units.html [ Pass ]
+transforms/3d/hit-testing/backface-hit-test.html [ Pass ]
 
 # Layout tests significatly differ from non scale baseline
 crbug.com/1105168 fast/sub-pixel/sub-pixel-accumulates-to-layers.html [ Failure ]
@@ -359,8 +637,8 @@
 crbug.com/1179570 virtual/layout_ng_printing/printing/block-width-relayout-shrink.html [ Failure ]
 crbug.com/1179570 virtual/layout_ng_printing/printing/multicol-2-pages.html [ Failure ]
 crbug.com/1179570 virtual/layout_ng_printing/printing/thead-repeats-with-translucent-text-and-borders.html [ Failure ]
-crbug.com/1179570 virtual/layout_ng_printing/printing/vertical-lr.html [ Failure ]
-crbug.com/1179570 virtual/layout_ng_printing/printing/vertical-rl.html [ Failure ]
+crbug.com/1286601 virtual/layout_ng_printing/printing/vertical-lr.html [ Failure Crash ]
+crbug.com/1286601 virtual/layout_ng_printing/printing/vertical-rl.html [ Failure Crash ]
 crbug.com/1179570 virtual/off-main-thread-css-paint/http/tests/csspaint/entire-canvas-visible-zoom.html [ Failure ]
 crbug.com/1179570 virtual/off-main-thread-css-paint/http/tests/csspaint/geometry-background-image-zoom.html [ Failure ]
 crbug.com/1179570 virtual/off-main-thread-css-paint/http/tests/csspaint/geometry-border-image-zoom.html [ Failure ]
@@ -382,6 +660,7 @@
 crbug.com/1179554 http/tests/images/document-policy-oversized-images-styles.php [ Failure ]
 
 # These appear to be flaky
+crbug.com/1191215 inspector-protocol/overlay/overlay-viewport.js [ Failure Pass ]
 crbug.com/1191215 fast/backgrounds/root-background-fixed-attachment-positioning.html [ Failure Pass ]
 crbug.com/1191215 fast/forms/date/date-appearance-basic.html [ Failure Pass ]
 crbug.com/1191215 fast/forms/number/number-appearance-datalist.html [ Failure Pass ]
@@ -443,7 +722,6 @@
 
 # Off by one errors cause by SwiftShader update
 crbug.com/1266821 compositing/3d-corners.html [ Failure ]
-crbug.com/1266821 compositing/3d-cube.html [ Failure ]
 crbug.com/1266821 compositing/composited-scaled-child-with-border-radius-parent-clip.html [ Failure ]
 crbug.com/1266821 compositing/direct-image-compositing.html [ Failure ]
 crbug.com/1266821 compositing/geometry/layer-due-to-layer-children-deep.html [ Failure ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 6934b100..e0343a0c 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -7656,3 +7656,6 @@
 # Sheriff 2022-01-11
 crbug.com/1286437 [ Mac ] media/picture-in-picture/v2/request-picture-in-picture-twice.html [ Failure Pass ]
 crbug.com/1286448 [ Mac ] virtual/without-coep-for-shared-worker/external/wpt/html/cross-origin-embedder-policy/anonymous-iframe/cache-storage.tentative.https.window.html [ Crash Pass ]
+
+# Sheriff 2022-01-12
+crbug.com/1286619 [ Mac ] external/wpt/storage-access-api/storageAccess.testdriver.sub.html [ Failure Pass ]
diff --git a/third_party/blink/web_tests/external/wpt/cors/access-control-expose-headers-parsing.window-expected.txt b/third_party/blink/web_tests/external/wpt/cors/access-control-expose-headers-parsing.window-expected.txt
index 0fe27f2f..3000d57 100644
--- a/third_party/blink/web_tests/external/wpt/cors/access-control-expose-headers-parsing.window-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/cors/access-control-expose-headers-parsing.window-expected.txt
@@ -8,7 +8,7 @@
 PASS Parsing: Access-Control-Expose-Headers%3A%20bb-8%0D%0AAccess-Control-Expose-Headers%3A%20no%20no
 PASS Parsing: Access-Control-Expose-Headers%3A%20no%0D%0AAccess-Control-Expose-Headers%3A%20bb-8
 FAIL Parsing: Access-Control-Expose-Headers%3A%0D%0AAccess-Control-Expose-Headers%3A%20bb-8 assert_equals: expected (string) "hey" but got (object) null
-FAIL Parsing: Access-Control-Expose-Headers%3A%20%2Cbb-8 assert_equals: expected (string) "hey" but got (object) null
+PASS Parsing: Access-Control-Expose-Headers%3A%20%2Cbb-8
 PASS Parsing: Access-Control-Expose-Headers%3A%20bb-8%0C
 PASS Parsing: Access-Control-Expose-Headers%3A%20bb-8%0B
 PASS Parsing: Access-Control-Expose-Headers%3A%20bb-8%0B%2Cbb-8
diff --git a/third_party/blink/web_tests/external/wpt/cors/resources/access-control-expose-headers.json b/third_party/blink/web_tests/external/wpt/cors/resources/access-control-expose-headers.json
index e8915f7..4ab38fb 100644
--- a/third_party/blink/web_tests/external/wpt/cors/resources/access-control-expose-headers.json
+++ b/third_party/blink/web_tests/external/wpt/cors/resources/access-control-expose-headers.json
@@ -33,7 +33,7 @@
   },
   {
     "input": "Access-Control-Expose-Headers: ,bb-8",
-    "exposed": true
+    "exposed": false
   },
   {
     "input": "Access-Control-Expose-Headers: bb-8\u000C",
diff --git a/third_party/blink/web_tests/external/wpt/mediacapture-insertable-streams/MediaStreamTrackGenerator-in-worker.https-expected.txt b/third_party/blink/web_tests/external/wpt/mediacapture-insertable-streams/MediaStreamTrackGenerator-in-worker.https-expected.txt
new file mode 100644
index 0000000..11ecc943
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/mediacapture-insertable-streams/MediaStreamTrackGenerator-in-worker.https-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL A worker is able to initialize a MediaStreamTrackGenerator without crashing promise_test: Unhandled rejection with value: "Failed with error ReferenceError: MediaStreamTrackGenerator is not defined"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/mediacapture-insertable-streams/MediaStreamTrackGenerator-in-worker.https.html b/third_party/blink/web_tests/external/wpt/mediacapture-insertable-streams/MediaStreamTrackGenerator-in-worker.https.html
new file mode 100644
index 0000000..ff3b945
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/mediacapture-insertable-streams/MediaStreamTrackGenerator-in-worker.https.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<title>Test creation of MediaStreamTrackGenerator in a worker</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script id="workerCode" type="javascript/worker">
+self.onmessage = (e) => {
+  try {
+    const mstg = new MediaStreamTrackGenerator({kind: 'video'});
+    self.postMessage({result: 'Success'});
+  } catch (e) {
+    self.postMessage({result: 'Failure', error: e});
+  }
+}
+</script>
+<script>
+'use strict';
+
+promise_test(async t => {
+  const workerBlob = new Blob([document.querySelector('#workerCode').textContent],
+                              {type: "text/javascript"});
+  const workerUrl = window.URL.createObjectURL(workerBlob);
+  const worker = new Worker(workerUrl);
+  window.URL.revokeObjectURL(workerUrl);
+  const result = new Promise((resolve, reject) => {
+    worker.onmessage = (e) => {
+      if (e.data.result === 'Failure') {
+        reject('Failed with error ' + e.data.error);
+      } else {
+        resolve();
+      }
+    };
+  });
+  worker.postMessage('Hello there');
+  return result;
+}, 'A worker is able to initialize a MediaStreamTrackGenerator without crashing');
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/webtransport/streams-close.https.any.js b/third_party/blink/web_tests/external/wpt/webtransport/streams-close.https.any.js
index be9a732..4871ee8 100644
--- a/third_party/blink/web_tests/external/wpt/webtransport/streams-close.https.any.js
+++ b/third_party/blink/web_tests/external/wpt/webtransport/streams-close.https.any.js
@@ -161,12 +161,15 @@
   const e = new WebTransportError({streamErrorCode: WT_CODE});
   // Write a chunk, close the stream, and then abort the stream immediately to
   // abort the closing operation.
+  // TODO: Check that the abort promise is correctly rejected/resolved based on
+  // the spec discussion at https://github.com/whatwg/streams/issues/1203.
   await writer.write(chunk);
   const close_promise = writer.close();
-  await writer.abort(e);
+  const abort_promise = writer.abort(e);
 
   await promise_rejects_exactly(t, e, close_promise, 'close_promise');
   await promise_rejects_exactly(t, e, writer.closed, '.closed');
+  await promise_rejects_exactly(t, e, abort_promise, 'abort_promise');
   writer.releaseLock();
 
   await wait(10);
diff --git a/third_party/blink/web_tests/flag-specific/highdpi/editing/input/caret-at-the-edge-of-input-expected.png b/third_party/blink/web_tests/flag-specific/highdpi/editing/input/caret-at-the-edge-of-input-expected.png
index 0c2cd15d..930d1fc0 100644
--- a/third_party/blink/web_tests/flag-specific/highdpi/editing/input/caret-at-the-edge-of-input-expected.png
+++ b/third_party/blink/web_tests/flag-specific/highdpi/editing/input/caret-at-the-edge-of-input-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/highdpi/fast/forms/datetimelocal/datetimelocal-appearance-basic-expected.png b/third_party/blink/web_tests/flag-specific/highdpi/fast/forms/datetimelocal/datetimelocal-appearance-basic-expected.png
index 89c7c9f..17be6af 100644
--- a/third_party/blink/web_tests/flag-specific/highdpi/fast/forms/datetimelocal/datetimelocal-appearance-basic-expected.png
+++ b/third_party/blink/web_tests/flag-specific/highdpi/fast/forms/datetimelocal/datetimelocal-appearance-basic-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/highdpi/fast/forms/number/number-appearance-rtl-expected.png b/third_party/blink/web_tests/flag-specific/highdpi/fast/forms/number/number-appearance-rtl-expected.png
index 38b84aed..b9cc23e 100644
--- a/third_party/blink/web_tests/flag-specific/highdpi/fast/forms/number/number-appearance-rtl-expected.png
+++ b/third_party/blink/web_tests/flag-specific/highdpi/fast/forms/number/number-appearance-rtl-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/highdpi/fast/forms/password/password-alt-f8-expected.png b/third_party/blink/web_tests/flag-specific/highdpi/fast/forms/password/password-alt-f8-expected.png
index b471bf4..9cce879 100644
--- a/third_party/blink/web_tests/flag-specific/highdpi/fast/forms/password/password-alt-f8-expected.png
+++ b/third_party/blink/web_tests/flag-specific/highdpi/fast/forms/password/password-alt-f8-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/highdpi/fast/forms/search/search-rtl-expected.png b/third_party/blink/web_tests/flag-specific/highdpi/fast/forms/search/search-rtl-expected.png
index d43ab380..7d03541 100644
--- a/third_party/blink/web_tests/flag-specific/highdpi/fast/forms/search/search-rtl-expected.png
+++ b/third_party/blink/web_tests/flag-specific/highdpi/fast/forms/search/search-rtl-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/highdpi/paint/invalidation/forms/textarea-caret-expected.png b/third_party/blink/web_tests/flag-specific/highdpi/paint/invalidation/forms/textarea-caret-expected.png
index 8bf5af6..d45c97a 100644
--- a/third_party/blink/web_tests/flag-specific/highdpi/paint/invalidation/forms/textarea-caret-expected.png
+++ b/third_party/blink/web_tests/flag-specific/highdpi/paint/invalidation/forms/textarea-caret-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/highdpi/paint/invalidation/forms/textarea-caret-expected.txt b/third_party/blink/web_tests/flag-specific/highdpi/paint/invalidation/forms/textarea-caret-expected.txt
index 70f899c..55ec39d 100644
--- a/third_party/blink/web_tests/flag-specific/highdpi/paint/invalidation/forms/textarea-caret-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/highdpi/paint/invalidation/forms/textarea-caret-expected.txt
@@ -45,6 +45,26 @@
     },
     {
       "name": "Scroll corner of LayoutNGTextControlMultiLine TEXTAREA id='editor'",
+      "position": [247, 29],
+      "bounds": [22, 22],
+      "contentsOpaque": true,
+      "invalidations": [
+        [0, 0, 22, 22]
+      ],
+      "transform": 1
+    },
+    {
+      "name": "Caret",
+      "position": [723, 5],
+      "bounds": [1, 22],
+      "contentsOpaque": true,
+      "invalidations": [
+        [0, 0, 1, 22]
+      ],
+      "transform": 2
+    },
+    {
+      "name": "LayoutNGTextControlMultiLine TEXTAREA id='editor'",
       "position": [-3, -3],
       "bounds": [277, 59],
       "contentsOpaqueForText": true,
diff --git a/third_party/blink/web_tests/flag-specific/highdpi/paint/invalidation/transform/caret-with-transformation-expected.png b/third_party/blink/web_tests/flag-specific/highdpi/paint/invalidation/transform/caret-with-transformation-expected.png
index a742bce..4d5d363 100644
--- a/third_party/blink/web_tests/flag-specific/highdpi/paint/invalidation/transform/caret-with-transformation-expected.png
+++ b/third_party/blink/web_tests/flag-specific/highdpi/paint/invalidation/transform/caret-with-transformation-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/highdpi/paint/invalidation/transform/caret-with-transformation-expected.txt b/third_party/blink/web_tests/flag-specific/highdpi/paint/invalidation/transform/caret-with-transformation-expected.txt
index b4f4d52..3f4affc 100644
--- a/third_party/blink/web_tests/flag-specific/highdpi/paint/invalidation/transform/caret-with-transformation-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/highdpi/paint/invalidation/transform/caret-with-transformation-expected.txt
@@ -4,11 +4,46 @@
       "name": "Scrolling background of LayoutView #document",
       "bounds": [1200, 900],
       "contentsOpaque": true,
-      "backgroundColor": "#FFFFFF",
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Caret",
+      "position": [533, 0],
+      "bounds": [2, 26],
+      "contentsOpaque": true,
       "invalidations": [
-        [64, 54, 16, 25],
-        [526, 321, 15, 24]
+        [0, 0, 2, 26]
+      ],
+      "transform": 2
+    },
+    {
+      "name": "LayoutNGBlockFlow DIV",
+      "position": [-4, -4],
+      "bounds": [1184, 35],
+      "contentsOpaqueForText": true,
+      "transform": 2
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [12, 12, 0, 1]
       ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [0.866025403784439, 0.5, 0, 0],
+        [-0.5, 0.866025403784439, 0, 0],
+        [0, 0, 1, 0],
+        [-20.0961894323342, 334.807621135332, 0, 1]
+      ],
+      "origin": [588, 13.5]
     }
   ]
 }
diff --git a/third_party/blink/web_tests/flag-specific/highdpi/tables/layering/paint-test-layering-1-expected.png b/third_party/blink/web_tests/flag-specific/highdpi/tables/layering/paint-test-layering-1-expected.png
index c1f407f..ca35f9f 100644
--- a/third_party/blink/web_tests/flag-specific/highdpi/tables/layering/paint-test-layering-1-expected.png
+++ b/third_party/blink/web_tests/flag-specific/highdpi/tables/layering/paint-test-layering-1-expected.png
Binary files differ
diff --git a/third_party/grpc/BUILD.gn b/third_party/grpc/BUILD.gn
index 764f96889..6947383 100644
--- a/third_party/grpc/BUILD.gn
+++ b/third_party/grpc/BUILD.gn
@@ -1161,7 +1161,7 @@
     "src/src/core/ext/upb-generated/envoy/config/route/v3/route_components.upb.c",
     "src/src/core/ext/upb-generated/envoy/config/route/v3/scoped_route.upb.c",
     "src/src/core/ext/upb-generated/envoy/config/trace/v3/http_tracer.upb.c",
-    "src/src/core/ext/upb-generated/envoy/extensions/filters/http/fault/v3/fault.upb.c",
+    "src/src/core/ext/upb-generated/envoy/extensions/filters/common/fault/v3/fault.upb.c",
     "src/src/core/ext/upb-generated/envoy/extensions/filters/http/router/v3/router.upb.c",
     "src/src/core/ext/upb-generated/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.upb.c",
     "src/src/core/ext/upb-generated/envoy/extensions/transport_sockets/tls/v3/cert.upb.c",
@@ -1185,7 +1185,9 @@
     "src/src/core/ext/upb-generated/envoy/type/matcher/v3/regex.upb.c",
     "src/src/core/ext/upb-generated/envoy/type/matcher/v3/string.upb.c",
     "src/src/core/ext/upb-generated/envoy/type/matcher/v3/struct.upb.c",
+    "src/src/core/ext/upb-generated/envoy/type/matcher/v3/value.upb.c",
     "src/src/core/ext/upb-generated/envoy/type/tracing/v3/custom_tag.upb.c",
+    "src/src/core/ext/upb-generated/envoy/type/v3/http.upb.c",
     "src/src/core/ext/upb-generated/envoy/type/v3/percent.upb.c",
     "src/src/core/ext/upb-generated/envoy/type/v3/range.upb.c",
     "src/src/core/ext/upb-generated/envoy/type/v3/semantic_version.upb.c",
@@ -1194,13 +1196,12 @@
     "src/src/core/ext/upb-generated/google/api/expr/v1alpha1/eval.upb.c",
     "src/src/core/ext/upb-generated/google/api/expr/v1alpha1/explain.upb.c",
     "src/src/core/ext/upb-generated/google/api/expr/v1alpha1/syntax.upb.c",
-    "src/src/core/ext/upb-generated/google/api/expr/v1alpha1/value.upb.c",
-    "src/src/core/ext/upb-generated/google/api/http.upb.c",
     "src/src/core/ext/upb-generated/google/protobuf/any.upb.c",
     "src/src/core/ext/upb-generated/google/protobuf/duration.upb.c",
     "src/src/core/ext/upb-generated/google/protobuf/empty.upb.c",
     "src/src/core/ext/upb-generated/google/protobuf/timestamp.upb.c",
     "src/src/core/ext/upb-generated/google/protobuf/wrappers.upb.c",
+    "src/src/core/ext/upb-generated/google/rpc/status.upb.c",
     "src/src/core/ext/upb-generated/src/proto/grpc/gcp/altscontext.upb.c",
     "src/src/core/ext/upb-generated/src/proto/grpc/gcp/handshaker.upb.c",
     "src/src/core/ext/upb-generated/src/proto/grpc/gcp/transport_security_common.upb.c",
@@ -1210,7 +1211,6 @@
     "src/src/core/ext/upb-generated/udpa/annotations/migrate.upb.c",
     "src/src/core/ext/upb-generated/udpa/annotations/security.upb.c",
     "src/src/core/ext/upb-generated/udpa/annotations/sensitive.upb.c",
-    "src/src/core/ext/upb-generated/udpa/annotations/status.upb.c",
     "src/src/core/ext/upb-generated/udpa/annotations/versioning.upb.c",
     "src/src/core/ext/upb-generated/validate/validate.upb.c",
     "src/src/core/ext/upb-generated/xds/core/v3/authority.upb.c",
@@ -1222,6 +1222,7 @@
     "src/src/core/ext/upb-generated/xds/type/v3/typed_struct.upb.c",
     "src/src/core/ext/upbdefs-generated/envoy/admin/v3/config_dump.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/envoy/annotations/deprecation.upbdefs.c",
+    "src/src/core/ext/upbdefs-generated/envoy/annotations/resource.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/envoy/config/accesslog/v3/accesslog.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/envoy/config/bootstrap/v3/bootstrap.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/envoy/config/cluster/v3/circuit_breaker.upbdefs.c",
@@ -1281,6 +1282,8 @@
     "src/src/core/ext/upbdefs-generated/envoy/type/matcher/v3/path.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/envoy/type/matcher/v3/regex.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/envoy/type/matcher/v3/string.upbdefs.c",
+    "src/src/core/ext/upbdefs-generated/envoy/type/matcher/v3/struct.upbdefs.c",
+    "src/src/core/ext/upbdefs-generated/envoy/type/matcher/v3/value.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/envoy/type/tracing/v3/custom_tag.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/envoy/type/v3/http.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/envoy/type/v3/percent.upbdefs.c",
@@ -1291,11 +1294,9 @@
     "src/src/core/ext/upbdefs-generated/google/api/expr/v1alpha1/eval.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/google/api/expr/v1alpha1/explain.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/google/api/expr/v1alpha1/syntax.upbdefs.c",
-    "src/src/core/ext/upbdefs-generated/google/api/expr/v1alpha1/value.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/google/protobuf/any.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/google/protobuf/duration.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/google/protobuf/empty.upbdefs.c",
-    "src/src/core/ext/upbdefs-generated/google/protobuf/struct.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/google/protobuf/timestamp.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/google/protobuf/wrappers.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/google/rpc/status.upbdefs.c",
@@ -1307,7 +1308,6 @@
     "src/src/core/ext/upbdefs-generated/xds/core/v3/authority.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/xds/core/v3/collection_entry.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/xds/core/v3/context_params.upbdefs.c",
-    "src/src/core/ext/upbdefs-generated/xds/core/v3/resource.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/xds/core/v3/resource_locator.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/xds/core/v3/resource_name.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/xds/type/v3/typed_struct.upbdefs.c",
@@ -1409,6 +1409,7 @@
     "src/src/core/lib/iomgr/event_engine/iomgr.cc",
     "src/src/core/lib/iomgr/event_engine/pollset.cc",
     "src/src/core/lib/iomgr/event_engine/resolved_address_internal.cc",
+    "src/src/core/lib/iomgr/event_engine/resolver.cc",
     "src/src/core/lib/iomgr/event_engine/tcp.cc",
     "src/src/core/lib/iomgr/event_engine/timer.cc",
     "src/src/core/lib/iomgr/exec_ctx.cc",
@@ -1477,11 +1478,13 @@
     "src/src/core/lib/iomgr/wakeup_fd_posix.cc",
     "src/src/core/lib/iomgr/work_serializer.cc",
     "src/src/core/lib/json/json_reader.cc",
+    "src/src/core/lib/json/json_util.cc",
     "src/src/core/lib/json/json_writer.cc",
+
+    # "src/src/core/lib/matchers/matchers.cc",
     "src/src/core/lib/profiling/basic_timers.cc",
     "src/src/core/lib/profiling/stap_timers.cc",
     "src/src/core/lib/promise/activity.cc",
-    "src/src/core/lib/resolver/resolver.cc",
     "src/src/core/lib/resolver/resolver_registry.cc",
     "src/src/core/lib/resolver/server_address.cc",
     "src/src/core/lib/resource_quota/api.cc",
@@ -1493,7 +1496,6 @@
     "src/src/core/lib/security/authorization/evaluate_args.cc",
 
     # "src/src/core/lib/security/authorization/grpc_authorization_engine.cc",
-    # "src/src/core/lib/security/authorization/matchers.cc",
     # "src/src/core/lib/security/authorization/rbac_policy.cc",
     "src/src/core/lib/security/authorization/sdk_server_authz_filter.cc",
     "src/src/core/lib/security/context/security_context.cc",
@@ -1530,6 +1532,8 @@
     "src/src/core/lib/security/credentials/tls/grpc_tls_credentials_options.cc",
     "src/src/core/lib/security/credentials/tls/tls_credentials.cc",
     "src/src/core/lib/security/credentials/tls/tls_utils.cc",
+
+    # "src/src/core/lib/security/credentials/xds/xds_credentials.cc",
     "src/src/core/lib/security/security_connector/alts/alts_security_connector.cc",
     "src/src/core/lib/security/security_connector/fake/fake_security_connector.cc",
     "src/src/core/lib/security/security_connector/insecure/insecure_security_connector.cc",
@@ -1546,7 +1550,6 @@
     "src/src/core/lib/security/transport/security_handshaker.cc",
     "src/src/core/lib/security/transport/server_auth_filter.cc",
     "src/src/core/lib/security/transport/tsi_error.cc",
-    "src/src/core/lib/security/util/json_util.cc",
     "src/src/core/lib/service_config/service_config.cc",
     "src/src/core/lib/slice/b64.cc",
     "src/src/core/lib/slice/percent_encoding.cc",
@@ -1625,8 +1628,6 @@
     "src/src/cpp/client/create_channel_posix.cc",
     "src/src/cpp/client/credentials_cc.cc",
     "src/src/cpp/client/secure_credentials.cc",
-
-    # "src/src/cpp/client/xds_credentials.cc",
     "src/src/cpp/codegen/codegen_init.cc",
     "src/src/cpp/common/alarm.cc",
     "src/src/cpp/common/auth_property_iterator.cc",
@@ -1696,36 +1697,36 @@
   sources = [
     "src/src/core/ext/transport/chttp2/client/insecure/channel_create.cc",
     "src/src/core/ext/upb-generated/envoy/extensions/clusters/aggregate/v3/cluster.upb.c",
-    "src/src/core/ext/upb-generated/envoy/extensions/filters/common/fault/v3/fault.upb.c",
+    "src/src/core/ext/upb-generated/envoy/extensions/filters/http/fault/v3/fault.upb.c",
     "src/src/core/ext/upb-generated/envoy/extensions/filters/http/rbac/v3/rbac.upb.c",
-    "src/src/core/ext/upb-generated/envoy/type/matcher/v3/value.upb.c",
     "src/src/core/ext/upb-generated/envoy/type/metadata/v3/metadata.upb.c",
-    "src/src/core/ext/upb-generated/envoy/type/v3/http.upb.c",
+    "src/src/core/ext/upb-generated/google/api/expr/v1alpha1/value.upb.c",
+    "src/src/core/ext/upb-generated/google/api/http.upb.c",
     "src/src/core/ext/upb-generated/google/protobuf/struct.upb.c",
-    "src/src/core/ext/upb-generated/google/rpc/status.upb.c",
+    "src/src/core/ext/upb-generated/udpa/annotations/status.upb.c",
     "src/src/core/ext/upb-generated/xds/core/v3/resource.upb.c",
-    "src/src/core/ext/upbdefs-generated/envoy/annotations/resource.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/envoy/extensions/clusters/aggregate/v3/cluster.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/envoy/extensions/filters/http/fault/v3/fault.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/envoy/extensions/filters/http/rbac/v3/rbac.upbdefs.c",
-    "src/src/core/ext/upbdefs-generated/envoy/type/matcher/v3/struct.upbdefs.c",
-    "src/src/core/ext/upbdefs-generated/envoy/type/matcher/v3/value.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/envoy/type/metadata/v3/metadata.upbdefs.c",
+    "src/src/core/ext/upbdefs-generated/google/api/expr/v1alpha1/value.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/google/api/http.upbdefs.c",
+    "src/src/core/ext/upbdefs-generated/google/protobuf/struct.upbdefs.c",
     "src/src/core/ext/upbdefs-generated/udpa/annotations/status.upbdefs.c",
+    "src/src/core/ext/upbdefs-generated/xds/core/v3/resource.upbdefs.c",
     "src/src/core/lib/iomgr/event_engine/endpoint.cc",
-    "src/src/core/lib/iomgr/event_engine/resolver.cc",
     "src/src/core/lib/iomgr/iomgr.cc",
     "src/src/core/lib/iomgr/pollset.cc",
     "src/src/core/lib/iomgr/timer.cc",
-    "src/src/core/lib/json/json_util.cc",
-
-    # "src/src/core/lib/matchers/matchers.cc",
+    "src/src/core/lib/resolver/resolver.cc",
     "src/src/core/lib/resource_quota/trace.cc",
 
-    # "src/src/core/lib/security/credentials/xds/xds_credentials.cc",
+    # "src/src/core/lib/security/authorization/matchers.cc",
+    "src/src/core/lib/security/util/json_util.cc",
     "src/src/core/lib/service_config/service_config_parser.cc",
     "src/src/cpp/client/insecure_credentials.cc",
+
+    # "src/src/cpp/client/xds_credentials.cc",
   ]
 
   deps = [
diff --git a/third_party/grpc/template/BUILD.chromium.gn.template b/third_party/grpc/template/BUILD.chromium.gn.template
index 29d6b7e4..924bd88 100644
--- a/third_party/grpc/template/BUILD.chromium.gn.template
+++ b/third_party/grpc/template/BUILD.chromium.gn.template
@@ -140,6 +140,8 @@
     # Deduplicate the sources files in the input so the same source file
     # don't appear in multiple sets
     sources = set(sources)
+    # Convert sources to sorted list so the sources are split into 3 targets deterministically
+    sources = sorted(sources)
     out_sources = []
     repeated1 = []
     repeated2 = []
diff --git a/tools/binary_size/README.md b/tools/binary_size/README.md
index 2fadc7f..9cf1d16 100644
--- a/tools/binary_size/README.md
+++ b/tools/binary_size/README.md
@@ -46,258 +46,8 @@
 
 ## SuperSize
 
-Collects, archives, and analyzes Chrome's binary size.
-Supports Android and Linux (although Linux
-[has issues](https://bugs.chromium.org/p/chromium/issues/detail?id=717550)).
-
-### Technical Details
-
-#### What's in a .size File?
-
-`.size` files are gzipped plain text files that contain:
-
-1. A list of section sizes, including:
-   * .so sections as reported by `readelf -S`
-   * .pak and .dex sections for apk files
-1. Metadata (apk size, GN args, filenames, timestamps, git revision, build id),
-1. A list of symbols, including name, address, size,
-  padding (caused by alignment), and associated source/object files.
-
-#### How are Symbols Collected?
-
-##### Native Symbols (.text, .rodata, .data, .data.rel.ro, .bss)
-
-1. Symbol list is extracted from linker `.map` file.
-   * Map files contain some unique pieces of information compared to `nm`
-      output, such as `** merge strings` entries, and some unnamed symbols
-      (which although unnamed, contain the `.o` path).
-   * Generated in `is_official_build=true` builds if `generate_linker_map` is
-     true. In official builds on Android generate_linker_map is true by default.
-1. `.o` files are mapped to `.cc` files by parsing `.ninja` files.
-   * This means that `.h` files are never listed as sources. No information
-     about inlined symbols is gathered.
-1. `** merge strings` symbols are further broken down into individual string
-   literal symbols. This is done by reading string literals from `.o` files, and
-   then searching for them within the `** merge strings` sections.
-   * For LLD with [ThinLTO](https://clang.llvm.org/docs/ThinLTO.html),
-   `llvm-bcanalyzer` is used to extract string literals.
-1. Symbol aliases:
-   * Aliases have the same address and size, but report their `.pss` as
-      `.size / .num_aliases`.
-   * Type 1: Different names. Caused by identical code folding.
-     * These are collected from debug information via `nm elf-file`.
-   * Type 2: Same names, different paths. Caused by inline functions defined in
-     `.h` files.
-     * These are collected by running `nm` on each `.o` file.
-       * For LLD with ThinLTO, `llvm-bcanalyzer` is used to process `.o` files,
-         which are actually LLVM Bitcode files.
-     * Normally represented using one alias per path, but are sometimes
-       collapsed into a single symbol with a path of `{shared}/$SYMBOL_COUNT`.
-       This collapsing is done only for symbols owned by a large number of
-       paths.
-   * Type 3: String literals that are de-duped at link-time.
-     * These are found as part of the string literal extraction process.
-
-##### Pak Symbols (.pak.nontranslated and .pak.translations)
-
-1. Grit creates a mapping between numeric id and textual id for grd files.
-   * A side effect of pak allowlist generation is a mapping of `.cc` to numeric
-     id.
-   * A complete per-apk mapping of numeric id to textual id is stored in the
-     `output_dir/size-info` dir.
-1. `supersize` uses these two mappings to find associated source files for the
-  pak entries found in all of the apk's `.pak` files.
-   * Pak entries with the same name are merged into a single symbol.
-     * This is the case of pak files for translations.
-   * The original grd file paths are stored in the full name of each symbol.
-
-##### Dex Symbols (.dex and .dex.method)
-
-1. Java compile targets create a mapping between java fully qualified names
-  (FQN) and source files.
-   * For `.java` files the FQN of the public class is mapped to the file.
-   * For `.srcjar` files the FQN of the public class is mapped to the `.srcjar`
-     file path.
-   * A complete per-apk class FQN to source mapping is stored in the
-     `output_dir/size-info` dir.
-1. The `apkanalyzer` sdk tool is used to find the size and FQN of entries in
-  the dex file.
-   * If a proguard `.mapping` file is available, that is used to get back the
-     original FQN.
-1. The output from `apkanalyzer` is used by `supersize` along with the mapping
-  file to find associated source files for the dex entries found in all of the
-  apk's `.dex` files.
-
-##### Other Symbols (.other)
-
-All files in an apk that are not broken down into sub-entries are tracked by a
-symbol within the `.other` section.
-
-##### Overhead and Star Symbols
-
-Overhead symbols track bytes that are generally unactionable. They are recorded
-as `size=0, padding=$size` (padding-only symbols) to de-emphasize them in diffs.
-
-Star symbols are those that track sections of the binary that are not padding,
-but which the tool is not able to break down further
-(e.g. "\*\* Merge Globals")
-
-* **\*\* symbol gap**: A gap between symbols that is larger than what could be
-  due to alignment.
-* **Overhead: ELF file**: `elf_file_size - sum(elf_sections)`.
-  * Captures bytes taken up by ELF headers and section alignment.
-* **Overhead: APK file**: `apk_file_size - sum(compressed_file_sizes)`
-  * Captures bytes taken up by `.zip` metadata and zipalign padding.
-* **Overhead: ${NAME}.pak**: `pak_file_size - sum(pak_entries)`
-* **Overhead: Pak compression artifacts**:
-  `compressed_size_of_paks - sum(pak_entries)`
-  * It would be possible to correctly attribute compressed size to pak symbols,
-    but doing so makes diffs very noisy (any change in compression ratio causes
-    every symbol to change by a small amount). Instead, SuperSize uses a
-    hard-coded compression ratio for compressed .pak symbols, and captures any
-    remainder in this overhead symbol.
-  * TODO([crbug/894320](https://crbug.com/894320)): Improve how compression is
-    tracked.
-
-#### What Other Processing Happens?
-
-1. Path normalization:
-   * Prefixes are removed: `out/Release/`, `gen/`, `obj/`
-   * Archive names made more pathy: `foo/bar.a(baz.o)` -> `foo/bar.a/baz.o`
-   * Shared symbols do not store the complete source paths. Instead, the
-     common ancestor is computed and stored as the path.
-      * Example: `base/{shared}/3` (the "3" means three different files contain
-        the symbol)
-
-1. Name normalization:
-   * `(anonymous::)` is removed from names (and stored as a symbol flag).
-   * `[clone]` suffix removed (and stored as a symbol flag).
-   * `vtable for FOO` -> `Foo [vtable]`
-   * Mangling done by linkers is undone (e.g. prefixing with "unlikely.")
-   * Names are processed into:
-     * `name`: Name without template and argument parameters
-     * `template_name`: Name without argument parameters.
-     * `full_name`: Name with all parameters.
-
-1. Special cases:
-   * LLVM function outlining creates many OUTLINED_FUNCTION_* symbols. These
-     renamed to '** outlined functions' or '** outlined functions * (count)',
-     and are deduped so an address can have at most one such symbol.
-
-1. Clustering:
-   * Compiler & linker optimizations can cause symbols to be broken into
-     multiple parts to become candidates for inlining ("partial inlining").
-   * These symbols are sometimes suffixed with "`[clone]`" (removed by
-     normalization).
-   * Clustering creates groups containing all pieces of a symbol (in the case
-     where multiple pieces remain after inlining).
-   * Clustering is done by default on `SizeInfo.symbols`. To view unclustered
-     symbols, use `SizeInfo.raw_symbols`.
-
-1. Diffing:
-   * Some heuristics for matching up before/after symbols.
-
-1. Simulated compression:
-   * Only some `.pak` files are compressed and others are kept uncompressed.
-   * To get a reasonable idea of actual impact to final apk size, we use a
-     constant compression factor for all the compressed `.pak` files.
-     * This prevents swings in compressed sizes for all symbols when new
-       entries are added or old entries are removed.
-     * The constant is chosen so that it minimizes overall discrepancy with
-       actual total compressed sizes.
-
-#### Is SuperSize a Generic Tool?
-
-No. Some examples of why it's Chrome-specific:
-
- * Assumes `.ninja` build rules are available.
- * Heuristic for locating `.so` given `.apk`.
- * Requires `size-info` dir in output directory to analyze `.pak` and `.dex`
-   files.
-
-### Usage: archive
-
-Collect size information and dump it into a `.size` file.
-
-*** note
-**Note:** Refer to
-[diagnose_bloat.py](https://cs.chromium.org/search/?q=file:diagnose_bloat.py+gn_args)
-for list of GN args to build a Release binary (or just use the tool with --single).
-***
-
-Example Usage:
-
-``` bash
-# Android:
-ninja -C out/Release -j 1000 apks/ChromePublic.apk
-tools/binary_size/supersize archive chrome.size --apk-file out/Release/apks/ChromePublic.apk -v
-
-# Linux:
-ninja -C out/Release -j 1000 chrome
-tools/binary_size/supersize archive chrome.size --elf-file out/Release/chrome -v
-```
-
-### Usage: start_server
-
-Locally view a `.size` file, by starting a web server that links to the file.
-
-Example Usage:
-
-``` bash
-# Starts a local server to view the data in ./report.size
-tools/binary_size/supersize start_server report.size
-
-# Set a custom address and port.
-tools/binary_size/supersize start_server report.size -a localhost -p 8080
-```
-
-### Usage: diff
-
-A convenience command equivalent to:
-`console before.size after.size --query='Print(Diff(size_info1, size_info2))'`
-
-Example Usage:
-
-``` bash
-tools/binary_size/supersize diff before.size after.size --all
-```
-
-### Usage: console
-
-Starts a Python interpreter where you can run custom queries, or run pre-made
-queries from `canned_queries.py`.
-
-Example Usage:
-
-```bash
-# Prints size infomation and exits (does not enter interactive mode).
-tools/binary_size/supersize console chrome.size --query='Print(size_info)'
-
-# Enters a Python REPL (it will print more guidance).
-tools/binary_size/supersize console chrome.size
-```
-
-Example session:
-
-``` python
->>> ShowExamples()  # Get some inspiration.
-...
->>> sorted = size_info.symbols.WhereInSection('t').Sorted()
->>> Print(sorted)  # Have a look at the largest symbols.
-...
->>> sym = sorted.WhereNameMatches('TrellisQuantizeBlock')[0]
->>> Disassemble(sym)  # Time to learn assembly.
-...
->>> help(canned_queries)
-...
->>> Print(canned_queries.TemplatesByName(depth=-1))
-...
->>> syms = size_info.symbols.WherePathMatches(r'skia').Sorted()
->>> Print(syms, verbose=True)  # Show full symbol names with parameter types.
-...
->>> # Dump all string literals from skia files to "strings.txt".
->>> Print((t[1] for t in ReadStringLiterals(syms)), to_file='strings.txt')
-```
+Collects, archives, and analyzes Chrome's binary size on Android.
+See [//tools/binary_size/libsupersize/README.md](/tools/binary_size/libsupersize/README.md).
 
 ## diagnose_bloat.py
 
diff --git a/tools/binary_size/libsupersize/README.md b/tools/binary_size/libsupersize/README.md
new file mode 100644
index 0000000..87c05b4
--- /dev/null
+++ b/tools/binary_size/libsupersize/README.md
@@ -0,0 +1,106 @@
+# SuperSize
+
+SuperSize is comprised of two parts:
+
+1. A command-line tool for creating and inspecting `.size` files,
+2. A web app for visualizing `.size` files.
+
+For more details, see [//tools/binary_size/libsupersize/docs].
+
+[//tools/binary_size/libsupersize/docs]: /tools/binary_size/libsupersize/docs
+
+[TOC]
+
+## Why SuperSize?
+
+Chrome on Android needs to be as lean as possible. Having a tool that can show
+why binary grows & shrinks helps keep it lean.
+
+The [android-binary-size trybot] uses SuperSize to show an APK Breakdown on
+every Chromium code review.
+
+SuperSize is also used when creating [milestone size reports] (Googlers only).
+
+[android-binary-size trybot]: /docs/speed/binary_size/android_binary_size_trybot.md
+[milestone size reports]: https://goto.google.com/chromemilestonesizes
+
+## Is SuperSize a Generic Tool?
+
+No. It works only for binaries built using Chrome's custom build system. E.g.:
+
+ * It assumes `.ninja` build rules are available.
+ * It uses heuristic for locating `.so` given `.apk`.
+ * It requires the `size-info` build directory to analyze `.pak` and `.dex`
+   files.
+
+## SuperSize Usage
+
+### supersize archive
+
+Collect size information into a `.size` file.
+
+*** note
+**Note:** Refer to
+[diagnose_bloat.py](https://cs.chromium.org/search/?q=file:diagnose_bloat.py+gn_args)
+for list of GN args to build a release binary (or just use the tool with --single).
+***
+
+Example Usage:
+
+``` bash
+# Android:
+autoninja -C out/Release apks/ChromePublic.apk
+tools/binary_size/supersize archive chrome.size -f out/Release/apks/ChromePublic.apk -v
+
+# Linux:
+autoninja -C out/Release chrome
+tools/binary_size/supersize archive chrome.size -f out/Release/chrome -v
+```
+
+### supersize console
+
+Starts a Python interpreter where you can run custom queries, or run pre-made
+queries from `canned_queries.py`.
+
+Example Usage:
+
+```bash
+# Prints size information and exits (does not enter interactive mode).
+tools/binary_size/supersize console chrome.size --query='Print(size_info)'
+
+# Enters a Python REPL (it will print more guidance).
+tools/binary_size/supersize console chrome.size
+```
+
+Example Session:
+
+``` python
+>>> ShowExamples()  # Get some inspiration.
+...
+>>> sorted = size_info.symbols.WhereInSection('t').Sorted()
+>>> Print(sorted)  # Have a look at the largest symbols.
+...
+>>> sym = sorted.WhereNameMatches('TrellisQuantizeBlock')[0]
+>>> Disassemble(sym)  # Time to learn assembly.
+...
+>>> help(canned_queries)
+...
+>>> Print(canned_queries.TemplatesByName(depth=-1))
+...
+>>> syms = size_info.symbols.WherePathMatches(r'skia').Sorted()
+>>> Print(syms, verbose=True)  # Show full symbol names with parameter types.
+...
+>>> # Dump all string literals from skia files to "strings.txt".
+>>> Print((t[1] for t in ReadStringLiterals(syms)), to_file='strings.txt')
+```
+
+### supersize diff
+
+A convenience command equivalent to:
+`console before.size after.size --query='Print(Diff(size_info1, size_info2))'`
+
+Example Usage:
+
+``` bash
+tools/binary_size/supersize diff before.size after.size --all
+```
diff --git a/tools/binary_size/libsupersize/docs/apk_symbols.md b/tools/binary_size/libsupersize/docs/apk_symbols.md
new file mode 100644
index 0000000..03ea7cd
--- /dev/null
+++ b/tools/binary_size/libsupersize/docs/apk_symbols.md
@@ -0,0 +1,41 @@
+# APK Symbols
+
+All files in an APK that are not broken down into sub-entries are tracked by a
+symbol within the `.other` section. This includes:
+
+ * `resources.arsc`
+ * `assets/*`
+ * `res/*`
+
+## Algorithm
+
+**During builds:**
+
+* An `$output_dir/size-info/Foo.res.info` file is created that records the
+  originating path for each android resource file.
+
+**During `supersize archive`:**
+
+For each zip entry:
+
+ * One symbol is created using the compressed size of the entry.
+ * Files within `res/` are given a source path using the mapping from
+   `$output_dir/Foo.apk.res.info`
+ * Other entries are given a source path of `$APK/$zip_path`.
+
+Each zip entry incurs additional size overhead from:
+
+ * Zip file local header,
+   * Size dependent on filename length and zipalign overhead.
+ * Zip file central directory entry,
+ * An entry within V1 signature files (`META-INF/` files).
+   * Applicable only for older `minSdkVersion` apks, and for system image apks.
+ * `res/` files have an associated entry within `resources.arsc`.
+
+Rather than include this overhead in each symbol, they are put altogether into a
+single `Overhead: APK file` symbol. Because:
+
+1. The symbol size is more understandable if it matches the `unzip -lv` output.
+2. It prevents zipalign overhead from causing many small size changes in diffs.
+3. When looking at size optimizations, it makes more sense to look at the total
+   overhead rather than the per-symbol overhead.
diff --git a/tools/binary_size/libsupersize/docs/dex_symbols.md b/tools/binary_size/libsupersize/docs/dex_symbols.md
new file mode 100644
index 0000000..1b8c78e
--- /dev/null
+++ b/tools/binary_size/libsupersize/docs/dex_symbols.md
@@ -0,0 +1,25 @@
+# Dex Symbols
+
+Dex symbols are those with a `section` of:
+ * `.dex.method` (Java methods)
+ * `.dex` (All other forms of dex size. E.g. Class descriptors)
+
+## Algorithm
+
+**During builds:**
+
+ * Java compile targets create a mapping between Java fully qualified names
+   (FQN) and source files.
+    * For `.java` files the FQN of the public class is mapped to the file.
+    * For `.srcjar` files the FQN of the public class is mapped to the `.srcjar`
+      file path.
+    * A complete per-apk class FQN to source mapping is stored in the
+      `$output_dir/size-info` dir.
+
+**During `supersize archive`:**
+
+1. `$ANDROID_SDK/cmdline-tools/apkanalyzer dex packages` is used to find the
+   size and FQN of entries in across all dex files.
+   * One symbol is created for each method and class entry in the output.
+2. Source paths are added to symbols using the mapping from
+   `$output_dir/Foo.apk.jar.info`
diff --git a/tools/binary_size/libsupersize/docs/file_format.md b/tools/binary_size/libsupersize/docs/file_format.md
new file mode 100644
index 0000000..ffbd2b3
--- /dev/null
+++ b/tools/binary_size/libsupersize/docs/file_format.md
@@ -0,0 +1,143 @@
+# File Format
+
+[TOC]
+
+## Overview
+
+There are three formats that SuperSize uses:
+
+1. Full `.size` files:
+   * Contains size information from running `supersize archive`.
+   * The fields `padding`, `short_name`, and `template_name` are omitted and
+     re-derived when loading.
+2. Sparse `.size` files:
+   * Can be created only via `supersize console`.
+   * Contains a subset of symbols from a full `.size` file.
+   * Encodes padding information for each symbol.
+3. `.sizediff` files:
+   * Created via `supersize console` or `supersize save_diff`.
+   * Contains two nested sparse `.size` files, where unchanged symbols are
+     removed from each.
+
+## Format Details: .size
+
+The file format is a `gzipped` encoding of symbols, where some symbol fields are
+delta-encoded to improve compressibility. Text was chosen over binary because it
+is easier to work with in Python and is easier than binary to debug. An effort
+was made to make the text highly compressible in order to keep the file sizes as
+small as possible.
+
+Each `.size` file contains:
+
+1. A list of section sizes.
+2. Metadata (GN args, git revision, timestamp, etc),
+3. A list of symbols.
+
+The following specifies the `.size` file format.
+
+### Header
+
+* Line 0: Vanity line - says what tool created the file. Ignored when loading.
+* Line 1: format version string. The current version is "Size File Format v1.1".
+* Line 2: number of bytes for the header fields.
+* Line 3+: the header fields, a stringified JSON object.
+
+The JSON for the header fields looks like:
+
+```json
+{
+  "build_config": {
+    "git_revision": "f49193cacf8ed34160a04ada4acf2ad6a1a030c8",
+    "gn_args": [ ... ],
+    "linker_name": "lld_v1",
+    "tool_prefix": "third_party/llvm-build/Release+Asserts/bin/llvm-"
+  },
+  "containers": [
+    {
+      "name": "TrichromeLibrary.apk",
+      "metadata": {
+        ... (see models.py METADATA_ for list of keys)
+      },
+      "section_sizes": {
+        ".rodata": 8344540,
+        ".shstrtab": 287,
+        ".text": 52389250
+        ...
+      }
+    }, ...
+  ],
+  "has_components": true,
+  "has_padding": false
+}
+```
+
+### Path List
+
+* Line 0: number of entries in the list.
+* Lines 1..N: tuple of (`object_path`, `source_path`) where the two parts
+    are tab-separated.
+
+### Component List
+
+* This section is only present if `has_components` is True in header fields.
+* Line 0: number of entries in the list.
+* Lines 1..N: Component names.
+
+### Symbol Counts
+
+* Line 0: Tab-separated list of `"<Container Name>section_names"`.
+* Line 1: Tab-separated list of symbol counts, in the same order as the previous
+  line.
+
+### Numeric Values
+
+In each section, the number of rows is the same as the number of items per line
+in Symbol Counts. The values on a row are space separated, in the order of the
+symbols in each group.
+
+The numeric values are:
+* Symbol addresses, delta-encoded
+* Symbol sizes (in bytes)
+* Symbol paddings (in bytes)
+  * This section is only present if `has_padding` is True in header fields.
+* Path indices, delta-encoded
+  * Indices reference paths from the Path List.
+* Component indices, delta-encoded
+  * Indices reference components in the Component List.
+  * This section is only present if `has_components` is True in header fields.
+
+### Symbols
+
+* Each line represents a single symbol.
+* Values are tab-separated.
+* Values include:
+  * `full_name`,
+  * `num_aliases` (omitted when identical to previous line),
+  * `flags` (omitted when 0).
+
+## Format Details .sizediff
+
+The `.sizediff` file stores two sparse `.size` files.
+
+### Header
+
+* Line 0: Vanity line - says what tool created the file. Ignored when loading.
+* Line 1: number of bytes for the header fields.
+* Line 2+: the header fields, a stringified JSON object.
+
+The JSON for the header fields looks like:
+
+```json
+{
+  "before_length": 1234,
+  "version": 1
+}
+```
+
+### Before
+
+A sparse `.size` file (gzipped).
+
+### After
+
+A sparse `.size` file (gzipped).
diff --git a/tools/binary_size/libsupersize/docs/native_symbols.md b/tools/binary_size/libsupersize/docs/native_symbols.md
new file mode 100644
index 0000000..1d16cbec
--- /dev/null
+++ b/tools/binary_size/libsupersize/docs/native_symbols.md
@@ -0,0 +1,122 @@
+# Native Symbols
+
+This doc describes how SuperSize breaks down native binaries into symbols.
+
+[TOC]
+
+## Overview
+
+Native symbols are those with a `section` of:
+
+ * `.text` (executable code)
+ * `.rodata` (read-only data)
+ * `.data` (writable data)
+ * `.data.rel.ro` (data that is read-only after ELF relocations are applied)
+ * `.bss` (symbols that are zero-initialized. These consume no space in the
+    binary, and so are generally ignored despite still being collected.
+
+Symbol aliases:
+ * Aliases have the same address and size, but report their `.pss` as
+    `.size / .num_aliases`.
+ * Type 1: Different names. Caused by identical code folding.
+ * Type 2: Same names, different paths. Caused by inline functions defined in
+   `.h` files.
+   * Represented as one alias per path, but are collapsed into a single symbol
+     with a path of `{common_prefix}/$SYMBOL_COUNT` when the number of aliases
+     is large.
+ * Type 3: String literals that are de-duped at link-time.
+   * These are found as part of the string literal extraction process.
+
+There are 3 modes that SuperSize can use to break an ELF down into symbols:
+
+ * `linker_map` - Uses linker map + build directory to create symbols.
+ * `dwarf` - Uses debug information to create symbols.
+ * `sections` - Creates one symbol for each ELF section.
+
+## Mode: linker_map
+
+This is the mode that produces the largest number of symbols, and thus is the
+preferred mode. Information provided only by this mode:
+
+ * Path information for symbols outside of .text
+   * DWARF information is complete for .text symbols (maybe because stack
+   symbolization is a primary use-case?), but incomplete or missing for symbols
+   in other sections.
+ * String literals (.rodata symbols that look like `"some string dat..."`).
+   * Linker map files contain `** merge strings` entries, which tell us where
+     to string tables exist within `.rodata`.
+ * `object_path`, which is useful for attributing STL usages to individual
+   source files.
+ * Path aliases - when an inline symbol is used by multiple source files, we
+   attribute the symbol's cost equally among the files.
+ * Linker-generated symbols. E.g. Switch tables.
+
+### Data Sources
+
+ * `build.ninja` is parsed to get:
+   * List of `.o` and `.a` files that were inputs to the linker.
+   * Mapping of `.cc` -> `.o` files.
+ * All `.o` (and `.a`) files are parsed:
+   * with `nm` to get symbol list.
+   * Non-ThinLTO: with `nm` to get list of string literals
+   * ThinLTO: with `llvm-bcanalyzer` to get list of string literals
+ * ELF file is parse with `nm` to get list of symbol names that were
+   identical-code-folded to the same address.
+ * Linker map (created via `-Wl,-Map=output.map`) parsed to get:
+   * Full list of symbols that comprise the binary,
+   * Location of string tables (`** merge strings` entries).
+   * Non-ThinLTO: `object_path` (`.o` file) associated with each symbol
+   * Note:
+     * With ThinLTO, `object_path` points to a hashed filename within the thinlto
+       cache (not useful).
+     * When multiple symbols are folded together due to Identical Code Folding,
+       the linker map file lists only one of them.
+ * ELF file string tables are parsed by looking for `\0` bytes and creating
+   string literal symbols for each string therein.
+
+### Algorithm
+
+1. Create initial symbol list from linker map.
+2. Assign object paths by seeing which `.o` files define each symbol (match up
+   the names).
+   * When multiple files define the same symbol, create symbol aliases.
+3. Create string literal symbols from string tables, and assign them paths based
+   on which `.o` files define the same string literal.
+4. Assign `source_path` using the `.o` -> `.cc` mapping from `build.ninja`.
+   * This means that `.h` files are never listed as sources. No information
+     about inlined symbols is gathered (by design).
+5. Create symbol aliases when `nm` reports multiple symbols mapping to the same
+   address.
+6. Normalize `source_path` by removing generated path prefix (and adding
+   `FLAG_GENERATED`) when applicable.
+7. Normalize symbol names.
+
+## Mode: dwarf
+
+Creates symbols using only an ELF with debug information enabled. Requires
+compiler flag `-gmlt` to enable full source paths (rather than just basename).
+
+### Algorithm
+
+1. Create initial symbol list with `nm --print-size`.
+2. Add name aliases using output from `nm` (this could have been done at the
+   same time as the previous step, but is done as a separate step in order to
+   share logic with `linker_map` mode.
+3. Uses `dwarfdump` to find all `DW_AT_compile_unit` and `DW_AT_ranges` entries
+   and create a map of address range -> source path.
+4. Assign source paths based to .text symbols based on symbol address.
+
+### Why not use Bloaty?
+
+[Bloaty](https://github.com/google/bloaty) is an excellent tool, and produces
+size information with similar fidelity to "dwarf" mode, as it uses the same
+data source. We did not use bloaty since "dwarfdump" was already readily
+available and gave similar results. It would be nice to also have a "bloaty"
+mode so that we could more direclty compare outputs.
+
+## Mode: sections
+
+This mode uses `readelf -s` to create one symbol for each ELF section. It is
+used for native files where no debug information or linker map file is
+available, and for native files whose ABI do not match the `--abi-filter`.
+
diff --git a/tools/binary_size/libsupersize/docs/pak_symbols.md b/tools/binary_size/libsupersize/docs/pak_symbols.md
new file mode 100644
index 0000000..6b7b976
--- /dev/null
+++ b/tools/binary_size/libsupersize/docs/pak_symbols.md
@@ -0,0 +1,33 @@
+## Pak Symbols
+
+Pak symbols are those with a `section` of:
+
+ * `.pak.nontranslated` (entries in `resources.pak`, `chrome_100_percent.pak`)
+ * `.pak.translations` (entries in `en_US.pak`, `fr.pak`, etc)
+ * There is one symbols for each GRIT ID. The size of the symbol is the sum of
+   the size of all pak entries with that ID across all `.pak` files.
+
+## Algorithm
+
+**During builds:**
+
+ * [Grit] records the originating `.grd` file, textual ID, and numeric ID for
+   each entry that it processes during a `grit()` step into a `.pak.info` file.
+ * These `.pak.info` files are merged during `repack` and APK / App Bundle steps
+   into a single `$output_dir/size-info/Foo.apk.pak.info`.
+
+[Grit]: /tools/grit/README.md
+
+**During `supersize archive`:**
+
+1. When native symbols are created using `linker_map` mode, a map of `pak_id` ->
+   `source_path` is created by looking for symbols named
+   `ui::AllowlistedResource<$PAK_ID>()`.
+2. Pak files are extracted and turned into dicts using grit.
+3. The size of each pak entry is summed with other entries of the same ID.
+4. Symbol aliases are created for pak entries with different IDs, but that
+   refer to the same data (the `.pak` file format supports this).
+5. Entry names ("textual IDs"), and the original `.grd` file paths are stored in
+   the `full_name` of each symbol by using the information in the
+   `$outdir/size-info/Foo.pak.info` file.
+6. Path information is added to the symbols using the map from Step 1.
diff --git a/tools/binary_size/libsupersize/file_format.py b/tools/binary_size/libsupersize/file_format.py
index 5da6fca..4e2184be 100644
--- a/tools/binary_size/libsupersize/file_format.py
+++ b/tools/binary_size/libsupersize/file_format.py
@@ -4,101 +4,7 @@
 
 """Deals with loading & saving .size and .sizediff files.
 
-The .size file is written in the following format. There are no section
-delimiters, instead the end of a section is usually determined by a row count on
-the first line of a section, followed by that number of rows. In other cases,
-the sections have a known size.
-
-Header
-------
-4 lines long.
-Line 0 of the file is a header comment.
-Line 1 is the serialization version of the file.
-Line 2 is the number of characters in the header fields string.
-Line 3 is the header fields string, a stringified JSON object.
-
-Path list
----------
-A list of paths. The first line is the size of the list,
-and the next N lines that follow are items in the list. Each item is a tuple
-of (object_path, source_path) where the two parts are tab separated.
-
-Component list
---------------
-A list of components. The first line is the size of the list,
-and the next N lines that follow are items in the list. Each item is a unique
-COMPONENT which is referenced later.
-This section is only present if 'has_components' is True in header fields.
-
-Symbol counts
--------------
-2 lines long.
-The first line is a tab separated list of section names.
-The second line is a tab separated list of symbol group lengths, in the same
-order as the previous line.
-
-Numeric values
---------------
-In each section, the number of rows is the same as the number of section names
-in Symbol counts. The values on a row are space separated, in the order of the
-symbols in each group.
-
-Addresses
-~~~~~~~~~~
-Symbol start addresses which are delta-encoded.
-
-Sizes
-~~~~~
-The number of bytes this symbol takes up.
-
-Padding
-~~~~~~~
-The number of padding bytes this symbol has.
-This section is only present if 'has_padding' is True in header fields.
-
-Path indices
-~~~~~~~~~~~~~
-Indices that reference paths in the prior Path list section. Delta-encoded.
-
-Component indices
-~~~~~~~~~~~~~~~~~~
-Indices that reference components in the prior Component list section.
-Delta-encoded.
-This section is only present if 'has_components' is True in header fields.
-
-Symbols
--------
-The final section contains details info on each symbol. Each line represents
-a single symbol. Values are tab separated and follow this format:
-symbol.full_name, symbol.num_aliases, symbol.flags
-|num_aliases| will be omitted if the aliases of the symbol are the same as the
-previous line. |flags| will be omitted if there are no flags.
-
-
-
-The .sizediff file stores a sparse representation of a difference between .size
-files. Each .sizediff file stores two sparse .size files, before and after,
-containing only symbols that differed between "before" and "after". They can
-be rendered via the Tiger viewer. .sizediff files use the following format:
-
-Header
-------
-3 lines long.
-Line 0 of the file is a header comment.
-Line 1 is the number of characters in the header fields string.
-Line 2 is the header fields string, a stringified JSON object. This currently
-contains two fields, 'before_length' (the length in bytes of the 'before'
-section) and 'version', which is always 1.
-
-Before
-------
-The next |header.before_length| bytes are a valid gzipped sparse .size file
-containing the "before" snapshot.
-
-After
------
-All remaining bytes are a valid gzipped sparse .size file containing the
-"after" snapshot.
+See docs/file_format.md for a specification of the file formats.
 """
 
 import contextlib
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index bbf57b4e..6538692 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -3113,6 +3113,7 @@
   <int value="23" label="LAUNCH_SOURCE_INTENT_URL"/>
   <int value="24" label="LAUNCH_SOURCE_RUN_ON_OS_LOGIN"/>
   <int value="25" label="LAUNCH_SOURCE_PROTOCOL_HANDLER"/>
+  <int value="26" label="LAUNCH_SOURCE_REPARENTING"/>
 </enum>
 
 <enum name="AppleScriptCommandEvents">
@@ -36697,7 +36698,7 @@
   <int value="4104" label="V8UDPSocket_Close_Method"/>
   <int value="4105" label="HTMLInputElementSimulatedClick"/>
   <int value="4106" label="RTCLocalSdpModificationIceUfragPwd"/>
-  <int value="4107" label="V8Navigator_DeprecatedURNToURL_Method"/>
+  <int value="4107" label="WebNfcNdefMakeReadOnly"/>
 </enum>
 
 <enum name="FeaturePolicyAllowlistType">
@@ -50847,6 +50848,7 @@
   <int value="-1951046208" label="SidePanelPrototype:disabled"/>
   <int value="-1950469963" label="SeparatePointingStickSettings:enabled"/>
   <int value="-1948540128" label="disable-webrtc-hw-encoding (deprecated)"/>
+  <int value="-1948261745" label="EnforceAshExtensionKeeplist:disabled"/>
   <int value="-1948236151" label="DrawPredictedInkPoint:disabled"/>
   <int value="-1946595906" label="enable-push-api-background-mode"/>
   <int value="-1946522787" label="VrCustomTabBrowsing:disabled"/>
@@ -54864,6 +54866,7 @@
   <int value="988981463" label="ImageCaptureAPI:enabled"/>
   <int value="989062160" label="ModuleScriptsImportMetaUrl:enabled"/>
   <int value="993486662" label="BorealisForceBetaClient:enabled"/>
+  <int value="993539774" label="EnforceAshExtensionKeeplist:enabled"/>
   <int value="994317727"
       label="AutofillSaveCreditCardUsesStrikeSystem:disabled"/>
   <int value="995112715" label="AutoDisableAccessibility:disabled"/>
diff --git a/tools/metrics/histograms/metadata/crostini/histograms.xml b/tools/metrics/histograms/metadata/crostini/histograms.xml
index 7cfdc8d64..6e19286 100644
--- a/tools/metrics/histograms/metadata/crostini/histograms.xml
+++ b/tools/metrics/histograms/metadata/crostini/histograms.xml
@@ -464,7 +464,7 @@
 </histogram>
 
 <histogram name="Crostini.Sshfs.Mount.Result.{Visibility}"
-    enum="CrostiniSshfsResult" expires_after="2022-02-14">
+    enum="CrostiniSshfsResult" expires_after="2022-08-14">
   <owner>davidmunro@google.com</owner>
   <owner>clumptini@google.com</owner>
   <summary>
@@ -483,7 +483,7 @@
 </histogram>
 
 <histogram name="Crostini.Sshfs.Mount.TimeTaken" units="ms"
-    expires_after="2022-02-14">
+    expires_after="2022-08-14">
   <owner>davidmunro@google.com</owner>
   <owner>clumptini@google.com</owner>
   <summary>
@@ -493,7 +493,7 @@
 </histogram>
 
 <histogram name="Crostini.Sshfs.Unmount.Result" enum="BooleanSuccess"
-    expires_after="2022-02-14">
+    expires_after="2022-08-14">
   <owner>davidmunro@google.com</owner>
   <owner>clumptini@google.com</owner>
   <summary>
@@ -503,7 +503,7 @@
 </histogram>
 
 <histogram name="Crostini.Sshfs.Unmount.TimeTaken" units="ms"
-    expires_after="2022-02-14">
+    expires_after="2022-08-14">
   <owner>davidmunro@google.com</owner>
   <owner>clumptini@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/media/histograms.xml b/tools/metrics/histograms/metadata/media/histograms.xml
index 65ce1df..0e290e112 100644
--- a/tools/metrics/histograms/metadata/media/histograms.xml
+++ b/tools/metrics/histograms/metadata/media/histograms.xml
@@ -92,6 +92,18 @@
   </summary>
 </histogram>
 
+<histogram name="Media.Android.BecomingNoisy" enum="Boolean"
+    expires_after="2022-07-01">
+  <owner>liberato@chromium.org</owner>
+  <owner>media-dev@chromium.org</owner>
+  <summary>
+    Android-specific metric. Occasionally, we cannot start a foreground service
+    when headphones are unplugged. This tracks how often that occurs, which is
+    one recorded boolean per &quot;user unplugs headphones while media is
+    playing&quot;. See https://crbug.com/1245017 for more details.
+  </summary>
+</histogram>
+
 <histogram name="Media.Android.GetColorSpaceError" enum="BooleanError"
     expires_after="2022-06-12">
   <owner>vasilyt@chromium.org</owner>
diff --git a/tools/traffic_annotation/scripts/annotation_tokenizer.py b/tools/traffic_annotation/scripts/annotation_tokenizer.py
index 06a5d1ae..c5b70ba 100644
--- a/tools/traffic_annotation/scripts/annotation_tokenizer.py
+++ b/tools/traffic_annotation/scripts/annotation_tokenizer.py
@@ -22,7 +22,7 @@
     # String literal. "string" or R"(string)". In Java, this will incorrectly
     # accept R-strings, which aren't part of the language's syntax. But since
     # that wouldn't compile anyways, we can just ignore this issue.
-    ('string_literal', re.compile(r'"((?:[^"]|\\.)*?)"|R"\((.*?)\)"',
+    ('string_literal', re.compile(r'"((?:\\.|[^"])*?)"|R"\((.*?)\)"',
                                   re.DOTALL)),
     # The '+' operator, for string concatenation. Java doesn't have multi-line
     # string literals, so this is the only way to keep long strings readable. It
@@ -97,7 +97,13 @@
     for (token_type, regex) in TOKEN_REGEXEN:
       re_match = regex.match(self.body, pos)
       if re_match:
+        raw_token = re_match.group(0)
         token_content = next(g for g in re_match.groups() if g is not None)
+        if token_type == 'string_literal' and not raw_token.startswith('R"'):
+          # Remove the extra backslash in backslash sequences, but only in
+          # non-R strings. R-strings don't need escaping.
+          backslash_regex = re.compile(r'\\(\\|")')
+          token_content = backslash_regex.sub(r'\1', token_content)
         token = Token(token_type, token_content, re_match.end())
         break
 
diff --git a/tools/traffic_annotation/scripts/annotation_tokenizer_test.py b/tools/traffic_annotation/scripts/annotation_tokenizer_test.py
index b9db5233..faefc5aa 100755
--- a/tools/traffic_annotation/scripts/annotation_tokenizer_test.py
+++ b/tools/traffic_annotation/scripts/annotation_tokenizer_test.py
@@ -96,6 +96,15 @@
     self.assertEqual('world', tokenizer.maybe_advance('symbol'))
     self.assertEqual(None, tokenizer.maybe_advance('right_paren'))
 
+  def testEscaping(self):
+    tokenizer = Tokenizer(
+        '''
+      "\\"abc \\\\\\" def \\\\\\""
+      "string ends here:\\\\" this is not part of the string"
+    ''', 'foo.txt', 33)
+    self.assertEqual('"abc \\" def \\"', tokenizer.advance('string_literal'))
+    self.assertEqual('string ends here:\\', tokenizer.advance('string_literal'))
+
 
 if __name__ == '__main__':
   unittest.main()
diff --git a/tools/traffic_annotation/scripts/test_data/ValidFile-stdout.txt b/tools/traffic_annotation/scripts/test_data/ValidFile-stdout.txt
index dbac3cb..332b238 100644
--- a/tools/traffic_annotation/scripts/test_data/ValidFile-stdout.txt
+++ b/tools/traffic_annotation/scripts/test_data/ValidFile-stdout.txt
@@ -8,7 +8,7 @@
 sender: 'sender1'
 description: 'desc1'
 trigger: 'trigger1'
-data: 'data1'
+data: 'data1 contains \'quotes '
 destination: GOOGLE_OWNED_SERVICE
 }
 policy {
@@ -24,7 +24,32 @@
 ==== ANNOTATION ENDS ====
 ==== NEW ANNOTATION ====
 ValidFile.java
-39
+32
+Definition
+id2
+
+semantics {
+sender: "sender1"
+description: "desc1"
+trigger: "trigger1 contains a backslash\
+"
+data: "data1 contains \"quotes "
+destination: GOOGLE_OWNED_SERVICE
+}
+policy {
+cookies_allowed: NO
+setting: "setting1"
+chrome_policy {
+SpellCheckServiceEnabled {
+SpellCheckServiceEnabled: false
+}
+}
+}
+comments: "comment1"
+==== ANNOTATION ENDS ====
+==== NEW ANNOTATION ====
+ValidFile.java
+61
 Definition
 test
 
@@ -32,7 +57,7 @@
 ==== ANNOTATION ENDS ====
 ==== NEW ANNOTATION ====
 ValidFile.java
-38
+60
 Definition
 missing
 
@@ -40,7 +65,7 @@
 ==== ANNOTATION ENDS ====
 ==== NEW ANNOTATION ====
 ValidFile.java
-37
+59
 Definition
 undefined
 
diff --git a/tools/traffic_annotation/scripts/test_data/ValidFile.java b/tools/traffic_annotation/scripts/test_data/ValidFile.java
index 3df6c946..0aaa8e6 100644
--- a/tools/traffic_annotation/scripts/test_data/ValidFile.java
+++ b/tools/traffic_annotation/scripts/test_data/ValidFile.java
@@ -14,7 +14,7 @@
                             + "sender: 'sender1'"
                             + "description: 'desc1'"
                             + "trigger: 'trigger1'"
-                            + "data: 'data1'"
+                            + "data: 'data1 contains \\'quotes '"
                             + "destination: GOOGLE_OWNED_SERVICE"
                             + "}"
                             + "policy {"
@@ -28,12 +28,34 @@
                             + "}"
                             + "comments: 'comment1'");
 
+    private static final NetworkTrafficAnnotationTag TRAFFIC_ANNOTATION_2 =
+            NetworkTrafficAnnotationTag.createComplete("id2",
+                    "semantics {"
+                            + "sender: \"sender1\""
+                            + "description: \"desc1\""
+                            + "trigger: \"trigger1 contains a backslash\\"
+                            + "\""
+                            + "data: \"data1 contains \\\"quotes \""
+                            + "destination: GOOGLE_OWNED_SERVICE"
+                            + "}"
+                            + "policy {"
+                            + "cookies_allowed: NO"
+                            + "setting: \"setting1\""
+                            + "chrome_policy {"
+                            + "SpellCheckServiceEnabled {"
+                            + "SpellCheckServiceEnabled: false"
+                            + "}"
+                            + "}"
+                            + "}"
+                            + "comments: \"comment1\"");
+
     private void doSomethingWith(NetworkTrafficAnnotationTag annotation) {
         // ...
     }
 
     public void fooBar() {
         doSomethingWith(TRAFFIC_ANNOTATION);
+        doSomethingWith(TRAFFIC_ANNOTATION_2);
         doSomethingWith(NetworkTrafficAnnotationTag.NO_TRAFFIC_ANNOTATION_YET);
         doSomethingWith(NetworkTrafficAnnotationTag.MISSING_TRAFFIC_ANNOTATION);
         doSomethingWith(NetworkTrafficAnnotationTag.TRAFFIC_ANNOTATION_FOR_TESTS);
diff --git a/ui/chromeos/styles/cros_colors.json5 b/ui/chromeos/styles/cros_colors.json5
index 98cd67f..14bcb97 100644
--- a/ui/chromeos/styles/cros_colors.json5
+++ b/ui/chromeos/styles/cros_colors.json5
@@ -121,6 +121,7 @@
     text_highlight_color: {
       light: "rgba($google_blue_600_rgb, 0.3)",
       dark: "rgba($google_blue_400_rgb, 0.3)",
+      generate_per_mode: true,
     },
 
     /*
@@ -188,6 +189,7 @@
       light: "$google_blue_50",
       dark: "rgba($google_blue_300_rgb, 0.3)",
       debug: "rgba($google_red_300_rgb, 0.3)",
+      generate_per_mode: true,
     },
     highlight_color_error: {
       light: "$google_red_50",
diff --git a/ui/events/fuchsia/pointer_events_handler.cc b/ui/events/fuchsia/pointer_events_handler.cc
index 2764d5d..1acbc32b1 100644
--- a/ui/events/fuchsia/pointer_events_handler.cc
+++ b/ui/events/fuchsia/pointer_events_handler.cc
@@ -209,7 +209,7 @@
   const auto& sample = event.pointer_sample();
 
   auto timestamp = base::TimeTicks::FromZxTime(event.timestamp());
-  PointerDetails pointer_details(EventPointerType::kTouch, sample.device_id());
+  PointerDetails pointer_details(EventPointerType::kMouse, sample.device_id());
   // View parameters can change mid-interaction; apply transform on the fly.
   auto logical =
       ViewportToViewCoordinates(sample.position_in_viewport(),
diff --git a/ui/events/fuchsia/pointer_events_handler_unittest.cc b/ui/events/fuchsia/pointer_events_handler_unittest.cc
index 7a30714..4ffaff0f 100644
--- a/ui/events/fuchsia/pointer_events_handler_unittest.cc
+++ b/ui/events/fuchsia/pointer_events_handler_unittest.cc
@@ -81,6 +81,8 @@
 
   ASSERT_EQ(events.size(), 1u);
   EXPECT_TRUE(events[0]->IsTouchEvent());
+  EXPECT_EQ(events[0]->AsTouchEvent()->pointer_details().pointer_type,
+            EventPointerType::kTouch);
 
   std::vector<fup::MouseEvent> mouse_events =
       MouseEventBuilder()
@@ -94,6 +96,8 @@
 
   ASSERT_EQ(events.size(), 2u);
   EXPECT_TRUE(events[1]->IsMouseEvent());
+  EXPECT_EQ(events[1]->AsMouseEvent()->pointer_details().pointer_type,
+            EventPointerType::kMouse);
 }
 
 TEST_F(PointerEventsHandlerTest, Data_FuchsiaTimeVersusChromeTime) {
diff --git a/ui/file_manager/file_manager/foreground/js/directory_tree_naming_controller.js b/ui/file_manager/file_manager/foreground/js/directory_tree_naming_controller.js
index 5fe5a92..9f078472 100644
--- a/ui/file_manager/file_manager/foreground/js/directory_tree_naming_controller.js
+++ b/ui/file_manager/file_manager/foreground/js/directory_tree_naming_controller.js
@@ -3,15 +3,14 @@
 // found in the LICENSE file.
 
 import {assert} from 'chrome://resources/js/assert.m.js';
-import {AlertDialog} from 'chrome://resources/js/cr/ui/dialogs.m.js';
-import {getParentEntry} from '../../common/js/api.js';
 
 import {util} from '../../common/js/util.js';
 import {VolumeInfo} from '../../externs/volume_info.js';
 
 import {DirectoryModel} from './directory_model.js';
-import {getRenameErrorMessage, renameEntry, validateExternalDriveName, validateFileName} from './file_rename.js';
+import {renameEntry, validateEntryName} from './file_rename.js';
 import {DirectoryItem, DirectoryTree} from './ui/directory_tree.js';
+import {FilesAlertDialog} from './ui/files_alert_dialog.js';
 
 /**
  * Naming controller for directory tree.
@@ -20,7 +19,7 @@
   /**
    * @param {!DirectoryModel} directoryModel
    * @param {!DirectoryTree} directoryTree
-   * @param {!AlertDialog} alertDialog
+   * @param {!FilesAlertDialog} alertDialog
    */
   constructor(directoryModel, directoryTree, alertDialog) {
     /** @private @const {!DirectoryModel} */
@@ -29,7 +28,7 @@
     /** @private @const {!DirectoryTree} */
     this.directoryTree_ = directoryTree;
 
-    /** @private @const {!AlertDialog} */
+    /** @private @const {!FilesAlertDialog} */
     this.alertDialog_ = alertDialog;
 
     /** @private {?DirectoryItem} */
@@ -141,21 +140,14 @@
     }
 
     try {
-      if (this.isRemovableRoot_) {
-        validateExternalDriveName(
-            newName,
-            assert(this.volumeInfo_ && this.volumeInfo_.diskFileSystemType));
-        this.performExternalDriveRename_(entry, newName);
-        return;
-      }
-      const parentEntry = await getParentEntry(entry);
-      await validateFileName(
-          parentEntry, newName,
-          this.directoryModel_.getFileFilter().isHiddenFilesVisible());
+      await validateEntryName(
+          entry, newName,
+          this.directoryModel_.getFileFilter().isHiddenFilesVisible(),
+          this.volumeInfo_, this.isRemovableRoot_);
       await this.performRename_(entry, newName);
     } catch (error) {
-      this.alertDialog_.show(
-          /** @type {string} */ (error.message), this.detach_.bind(this));
+      await this.alertDialog_.showAsync(/** @type {string} */ (error.message));
+      this.editing_ = true;
     }
   }
 
@@ -176,12 +168,20 @@
     // TODO(yawano): Rename might take time on some volumes. Optimistically show
     // new name in the UI before actual rename is completed.
     try {
-      const newEntry = await renameEntry(entry, newName);
+      const newEntry = await renameEntry(
+          entry, newName, this.volumeInfo_, this.isRemovableRoot_);
 
       // Put the new name in the .label element before detaching the
       // <input> to prevent showing the old name.
       this.getLabelElement_().textContent = newName;
 
+      // We currently don't have promises/callbacks for when removableRoots are
+      // successfully renamed, so we can't update their subdirectories or
+      // update the current directory to them at this point.
+      if (this.isRemovableRoot_) {
+        return;
+      }
+
       this.currentDirectoryItem_.entry = newEntry;
       this.currentDirectoryItem_.updateSubDirectories(/* recursive= */ true);
 
@@ -196,27 +196,10 @@
       this.directoryModel_.setIgnoringCurrentDirectoryDeletion(
           /* ignore= */ false);
 
-      this.alertDialog_.show(getRenameErrorMessage(
-          /** @type {DOMError} */ (error), entry, newName));
+      this.alertDialog_.show(/** @type {string} */ (error.message));
+    } finally {
+      this.detach_();
     }
-
-    this.detach_();
-  }
-
-  /**
-   * Performs external drive rename operation.
-   * @param {!DirectoryEntry} entry
-   * @param {string} newName Validated name.
-   * @private
-   */
-  performExternalDriveRename_(entry, newName) {
-    // Invoke external drive rename
-    chrome.fileManagerPrivate.renameVolume(this.volumeInfo_.volumeId, newName);
-
-    // Put the new name in the .label element before detaching the <input> to
-    // prevent showing the old name.
-    this.getLabelElement_().textContent = newName;
-    this.detach_();
   }
 
   /**
diff --git a/ui/file_manager/file_manager/foreground/js/file_rename.js b/ui/file_manager/file_manager/foreground/js/file_rename.js
index a75abfe..a69c8f5 100644
--- a/ui/file_manager/file_manager/foreground/js/file_rename.js
+++ b/ui/file_manager/file_manager/foreground/js/file_rename.js
@@ -7,70 +7,71 @@
  * by the files app frontend.
  */
 
+import {assert} from 'chrome://resources/js/assert.m.js';
+
 import {getEntry, getParentEntry, moveEntryTo, validatePathNameLength} from '../../common/js/api.js';
 import {str, strf, util} from '../../common/js/util.js';
 import {VolumeManagerCommon} from '../../common/js/volume_manager_types.js';
+import {VolumeInfo} from '../../externs/volume_info.js';
 
 /**
- * Renames the entry to newName.
- * @param {!Entry} entry The entry to be renamed.
- * @param {string} newName The new name.
- * @return {Promise<!Entry>} The renamed entry.
+ * Verifies name for file, folder, or removable root to be created or renamed.
+ * Names are restricted according to the target filesystem.
+ *
+ * @param {!Entry} entry The entry to be named.
+ * @param {string} name New file, folder, or removable root name.
+ * @param {boolean} areHiddenFilesVisible Whether to report hidden file
+ *     name errors or not.
+ * @param {?VolumeInfo} volumeInfo Volume information about the target entry.
+ * @param {boolean} isRemovableRoot Whether the target is a removable root.
+ * @return {!Promise} Fulfills on success, throws error message otherwise.
  */
-export async function renameEntry(entry, newName) {
-  // Before moving, we need to check if there is an existing entry at
-  // parent/newName, since moveTo will overwrite it.
-  // Note that this way has some timing issue. After existing check,
-  // a new entry may be created in the background. However, there is no way not
-  // to overwrite the existing file, unfortunately. The risk should be low,
-  // assuming the unsafe period is very short.
-
-  const parent = await getParentEntry(entry);
-
-  try {
-    await getEntry(parent, newName, entry.isFile, {create: false});
-  } catch (error) {
-    if (error.name == util.FileError.NOT_FOUND_ERR) {
-      return moveEntryTo(entry, parent, newName);
-    }
-
-    // Unexpected error found.
-    throw error;
+export async function validateEntryName(
+    entry, name, areHiddenFilesVisible, volumeInfo, isRemovableRoot) {
+  if (isRemovableRoot) {
+    const diskFileSystemType = volumeInfo && volumeInfo.diskFileSystemType;
+    validateExternalDriveName(name, assert(diskFileSystemType));
+  } else {
+    const parentEntry = await getParentEntry(entry);
+    await validateFileName(parentEntry, name, areHiddenFilesVisible);
   }
-
-  // The entry with the name already exists.
-  throw util.createDOMError(util.FileError.PATH_EXISTS_ERR);
 }
 
 /**
- * Converts DOMError response from renameEntry() to error message
- * @param {DOMError} error
- * @param {!Entry} entry
- * @param {string} newName
- * @return {string}
+ * Verifies the user entered name for external drive to be
+ * renamed to. Name restrictions must correspond to the target filesystem
+ * restrictions.
+ *
+ * It also verifies that name length is in the limits of the filesystem.
+ *
+ * This function throws if the new label is invalid, else it completes.
+ *
+ * @param {string} name New external drive name.
+ * @param {!VolumeManagerCommon.FileSystemType} fileSystem
  */
-export function getRenameErrorMessage(error, entry, newName) {
-  if (error &&
-      (error.name == util.FileError.PATH_EXISTS_ERR ||
-       error.name == util.FileError.TYPE_MISMATCH_ERR)) {
-    // Check the existing entry is file or not.
-    // 1) If the entry is a file:
-    //   a) If we get PATH_EXISTS_ERR, a file exists.
-    //   b) If we get TYPE_MISMATCH_ERR, a directory exists.
-    // 2) If the entry is a directory:
-    //   a) If we get PATH_EXISTS_ERR, a directory exists.
-    //   b) If we get TYPE_MISMATCH_ERR, a file exists.
-    return strf(
-        (entry.isFile && error.name == util.FileError.PATH_EXISTS_ERR) ||
-                (!entry.isFile &&
-                 error.name == util.FileError.TYPE_MISMATCH_ERR) ?
-            'FILE_ALREADY_EXISTS' :
-            'DIRECTORY_ALREADY_EXISTS',
-        newName);
+export function validateExternalDriveName(name, fileSystem) {
+  // Verify if entered name for external drive respects restrictions
+  // provided by the target filesystem.
+
+  const nameLength = name.length;
+  const lengthLimit = VolumeManagerCommon.FileSystemTypeVolumeNameLengthLimit;
+
+  // Verify length for the target file system type.
+  if (lengthLimit.hasOwnProperty(fileSystem) &&
+      nameLength > lengthLimit[fileSystem]) {
+    throw Error(
+        strf('ERROR_EXTERNAL_DRIVE_LONG_NAME', lengthLimit[fileSystem]));
   }
 
-  return strf(
-      'ERROR_RENAMING', entry.name, util.getFileErrorString(error.name));
+  // Checks if the name contains only alphanumeric characters or allowed
+  // special characters. This needs to stay in sync with
+  // cros-disks/filesystem_label.cc on the ChromeOS side.
+  const validCharRegex = /[a-zA-Z0-9 \!\#\$\%\&\(\)\-\@\^\_\`\{\}\~]/;
+  for (let i = 0; i < nameLength; i++) {
+    if (!validCharRegex.test(name[i])) {
+      throw Error(strf('ERROR_EXTERNAL_DRIVE_INVALID_CHARACTER', name[i]));
+    }
+  }
 }
 
 /**
@@ -78,16 +79,16 @@
  * renamed to. Name restrictions must correspond to File API restrictions
  * (see DOMFilePath::isValidPath). Curernt WebKit implementation is
  * out of date (spec is
- * http://dev.w3.org/2009/dap/file-system/file-dir-sys.html, 8.3) and going to
- * be fixed. Shows message box if the name is invalid.
+ * http://dev.w3.org/2009/dap/file-system/file-dir-sys.html, 8.3) and going
+ * to be fixed. Shows message box if the name is invalid.
  *
  * It also verifies if the name length is in the limit of the filesystem.
  *
  * @param {!DirectoryEntry} parentEntry The entry of the parent directory.
  * @param {string} name New file or folder name.
- * @param {boolean} areHiddenFilesVisible Whether to report the hidden file name
- *     error or not.
- * @return {Promise} Fulfills on success, throws error message otherwise.
+ * @param {boolean} areHiddenFilesVisible Whether to report the hidden file
+ *     name error or not.
+ * @return {!Promise} Fulfills on success, throws error message otherwise.
  */
 export async function validateFileName(
     parentEntry, name, areHiddenFilesVisible) {
@@ -115,38 +116,85 @@
 }
 
 /**
- * Verifies the user entered name for external drive to be
- * renamed to. Name restrictions must correspond to the target filesystem
- * restrictions.
- *
- * It also verifies that name length is in the limits of the filesystem.
- *
- * This function throws if the new label is invalid, else it completes.
- *
- * @param {string} name New external drive name.
- * @param {!VolumeManagerCommon.FileSystemType} fileSystem
+ * Renames file, folder, or removable root with newName.
+ * @param {!Entry} entry The entry to be renamed.
+ * @param {string} newName The new name.
+ * @param {?VolumeInfo} volumeInfo Volume information about the target entry.
+ * @param {boolean} isRemovableRoot Whether the target is a removable root.
+ * @return {!Promise<!Entry>} Resolves the renamed entry if successful, else
+ * throws error message.
  */
-export function validateExternalDriveName(name, fileSystem) {
-  // Verify if entered name for external drive respects restrictions provided by
-  // the target filesystem
-
-  const nameLength = name.length;
-  const lengthLimit = VolumeManagerCommon.FileSystemTypeVolumeNameLengthLimit;
-
-  // Verify length for the target file system type
-  if (lengthLimit.hasOwnProperty(fileSystem) &&
-      nameLength > lengthLimit[fileSystem]) {
-    throw Error(
-        strf('ERROR_EXTERNAL_DRIVE_LONG_NAME', lengthLimit[fileSystem]));
+export async function renameEntry(entry, newName, volumeInfo, isRemovableRoot) {
+  if (isRemovableRoot) {
+    chrome.fileManagerPrivate.renameVolume(volumeInfo.volumeId, newName);
+    return entry;
   }
+  return renameFile(entry, newName);
+}
 
-  // Checks if the name contains only alphanumeric characters or allowed
-  // special characters. This needs to stay in sync with
-  // cros-disks/filesystem_label.cc on the ChromeOS side.
-  const validCharRegex = /[a-zA-Z0-9 \!\#\$\%\&\(\)\-\@\^\_\`\{\}\~]/;
-  for (let i = 0; i < nameLength; i++) {
-    if (!validCharRegex.test(name[i])) {
-      throw Error(strf('ERROR_EXTERNAL_DRIVE_INVALID_CHARACTER', name[i]));
+/**
+ * Renames the entry to newName.
+ * @param {!Entry} entry The entry to be renamed.
+ * @param {string} newName The new name.
+ * @return {!Promise<!Entry>} Resolves the renamed entry if successful, else
+ * throws error message.
+ */
+export async function renameFile(entry, newName) {
+  try {
+    // Before moving, we need to check if there is an existing entry at
+    // parent/newName, since moveTo will overwrite it.
+    // Note that this way has a race condition. After existing check,
+    // a new entry may be created in the background. However, there is no way
+    // not to overwrite the existing file, unfortunately. The risk should be
+    // low, assuming the unsafe period is very short.
+
+    const parent = await getParentEntry(entry);
+
+    try {
+      await getEntry(parent, newName, entry.isFile, {create: false});
+    } catch (error) {
+      if (error.name == util.FileError.NOT_FOUND_ERR) {
+        return moveEntryTo(entry, parent, newName);
+      }
+
+      // Unexpected error found.
+      throw error;
     }
+
+    // The entry with the name already exists.
+    throw util.createDOMError(util.FileError.PATH_EXISTS_ERR);
+  } catch (error) {
+    throw getRenameErrorMessage(error, entry, newName);
   }
 }
+
+/**
+ * Converts DOMError response from renameEntry() to error message.
+ * @param {DOMError} error
+ * @param {!Entry} entry
+ * @param {string} newName
+ * @return {!Error<string>}
+ */
+function getRenameErrorMessage(error, entry, newName) {
+  if (error &&
+      (error.name == util.FileError.PATH_EXISTS_ERR ||
+       error.name == util.FileError.TYPE_MISMATCH_ERR)) {
+    // Check the existing entry is file or not.
+    // 1) If the entry is a file:
+    //   a) If we get PATH_EXISTS_ERR, a file exists.
+    //   b) If we get TYPE_MISMATCH_ERR, a directory exists.
+    // 2) If the entry is a directory:
+    //   a) If we get PATH_EXISTS_ERR, a directory exists.
+    //   b) If we get TYPE_MISMATCH_ERR, a file exists.
+    return Error(strf(
+        (entry.isFile && error.name == util.FileError.PATH_EXISTS_ERR) ||
+                (!entry.isFile &&
+                 error.name == util.FileError.TYPE_MISMATCH_ERR) ?
+            'FILE_ALREADY_EXISTS' :
+            'DIRECTORY_ALREADY_EXISTS',
+        newName));
+  }
+
+  return Error(
+      strf('ERROR_RENAMING', entry.name, util.getFileErrorString(error.name)));
+}
diff --git a/ui/file_manager/file_manager/foreground/js/naming_controller.js b/ui/file_manager/file_manager/foreground/js/naming_controller.js
index 1058d0d..7cc18973 100644
--- a/ui/file_manager/file_manager/foreground/js/naming_controller.js
+++ b/ui/file_manager/file_manager/foreground/js/naming_controller.js
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import {assert} from 'chrome://resources/js/assert.m.js';
-import {AlertDialog, ConfirmDialog} from 'chrome://resources/js/cr/ui/dialogs.m.js';
+import {ConfirmDialog} from 'chrome://resources/js/cr/ui/dialogs.m.js';
 import {getFile} from '../../common/js/api.js';
 
 import {strf, util} from '../../common/js/util.js';
@@ -11,8 +11,9 @@
 
 import {FileFilter} from './directory_contents.js';
 import {DirectoryModel} from './directory_model.js';
-import {getRenameErrorMessage, renameEntry, validateExternalDriveName, validateFileName} from './file_rename.js';
+import {renameEntry, validateEntryName, validateFileName} from './file_rename.js';
 import {FileSelectionHandler} from './file_selection.js';
+import {FilesAlertDialog} from './ui/files_alert_dialog.js';
 import {ListContainer} from './ui/list_container.js';
 
 /**
@@ -21,7 +22,7 @@
 export class NamingController {
   /**
    * @param {!ListContainer} listContainer
-   * @param {!AlertDialog} alertDialog
+   * @param {!FilesAlertDialog} alertDialog
    * @param {!ConfirmDialog} confirmDialog
    * @param {!DirectoryModel} directoryModel
    * @param {!FileFilter} fileFilter
@@ -33,7 +34,7 @@
     /** @private @const {!ListContainer} */
     this.listContainer_ = listContainer;
 
-    /** @private @const {!AlertDialog} */
+    /** @private @const {!FilesAlertDialog} */
     this.alertDialog_ = alertDialog;
 
     /** @private @const {!ConfirmDialog} */
@@ -80,13 +81,11 @@
     try {
       await validateFileName(
           parentEntry, name, this.fileFilter_.isHiddenFilesVisible());
+      return true;
     } catch (error) {
-      await new Promise(
-          (resolve) => this.alertDialog_.show(
-              /** @type {string} */ (error), resolve));
+      await this.alertDialog_.showAsync(/** @type {string} */ (error.message));
       return false;
     }
-    return true;
   }
 
   /**
@@ -282,40 +281,23 @@
 
     const volumeInfo = this.volumeInfo_;
     const isRemovableRoot = this.isRemovableRoot_;
-    let isValid = false;
-    input.validation_ = true;
 
-    if (isRemovableRoot) {
-      try {
-        const diskFileSystemType =
-            assert(volumeInfo && volumeInfo.diskFileSystemType);
-        validateExternalDriveName(newName, diskFileSystemType);
-        isValid = true;
-      } catch (error) {
-        isValid = false;
-        await new Promise(
-            (resolve) => this.alertDialog_.show(
-                /** @type {string} */ (error.message), resolve));
-      }
-    } else {
-      // TODO(mtomasz): this.getCurrentDirectoryEntry() might not return the
-      // actual parent if the directory content is a search result. Fix it to do
-      // proper validation.
-      isValid = await this.validateFileName(
-          /** @type {!DirectoryEntry} */ (
-              this.directoryModel_.getCurrentDirEntry()),
-          newName);
-    }
+    try {
+      input.validation_ = true;
+      await validateEntryName(
+          entry, newName, false, volumeInfo, isRemovableRoot);
+    } catch (error) {
+      await this.alertDialog_.showAsync(/** @type {string} */ (error.message));
 
-    input.validation_ = false;
-
-    if (!isValid) {
       // Cancel rename if it fails to restore focus from alert dialog.
       // Otherwise, just cancel the commitment and continue to rename.
       if (document.activeElement != input) {
         this.cancelRename_();
       }
+
       return;
+    } finally {
+      input.validation_ = false;
     }
 
     // Validation succeeded. Do renaming.
@@ -330,12 +312,11 @@
     nameNode.textContent = newName;
 
     try {
-      let newEntry;
-      if (isRemovableRoot) {
-        newEntry = entry;
-        chrome.fileManagerPrivate.renameVolume(volumeInfo.volumeId, newName);
-      } else {
-        newEntry = await renameEntry(entry, newName);
+      const newEntry =
+          await renameEntry(entry, newName, volumeInfo, isRemovableRoot);
+
+      // RemovableRoot doesn't have a callback to report renaming is done.
+      if (!isRemovableRoot) {
         await this.directoryModel_.onRenameEntry(entry, assert(newEntry));
       }
 
@@ -357,8 +338,7 @@
       this.listContainer_.endBatchUpdates();
 
       // Show error dialog.
-      const message = getRenameErrorMessage(error, entry, newName);
-      this.alertDialog_.show(message);
+      this.alertDialog_.show(error.message);
     }
   }
 
diff --git a/ui/file_manager/file_manager/foreground/js/ui/files_alert_dialog.js b/ui/file_manager/file_manager/foreground/js/ui/files_alert_dialog.js
index c303b299..b73d586 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/files_alert_dialog.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/files_alert_dialog.js
@@ -66,4 +66,13 @@
     this.frame.classList.toggle('no-title', !title);
     super.showHtml(title, message, ...args);
   }
+
+  /**
+   * Async version of show().
+   * @param {string} title
+   * @returns {!Promise<void>} Resolves when dismissed.
+   */
+  showAsync(title) {
+    return new Promise(resolve => this.show(title, resolve));
+  }
 }
diff --git a/ui/ozone/platform/wayland/BUILD.gn b/ui/ozone/platform/wayland/BUILD.gn
index 2661841..437960d 100644
--- a/ui/ozone/platform/wayland/BUILD.gn
+++ b/ui/ozone/platform/wayland/BUILD.gn
@@ -434,6 +434,8 @@
     "test/test_viewporter.h",
     "test/test_wayland_server_thread.cc",
     "test/test_wayland_server_thread.h",
+    "test/test_wp_pointer_gestures.cc",
+    "test/test_wp_pointer_gestures.h",
     "test/test_xdg_popup.cc",
     "test/test_xdg_popup.h",
     "test/test_zcr_text_input_extension.cc",
@@ -464,6 +466,7 @@
     "//third_party/wayland-protocols:gtk_primary_selection_protocol",
     "//third_party/wayland-protocols:linux_dmabuf_protocol",
     "//third_party/wayland-protocols:linux_explicit_synchronization_protocol",
+    "//third_party/wayland-protocols:pointer_gestures_protocol",
     "//third_party/wayland-protocols:presentation_time_protocol",
     "//third_party/wayland-protocols:primary_selection_protocol",
     "//third_party/wayland-protocols:text_input_extension_protocol",
@@ -494,6 +497,7 @@
     "host/wayland_window_manager_unittests.cc",
     "host/wayland_window_unittest.cc",
     "host/wayland_zaura_shell_unittest.cc",
+    "host/wayland_zwp_pointer_gestures_unittest.cc",
     "test/wayland_drag_drop_test.cc",
     "test/wayland_drag_drop_test.h",
     "test/wayland_test.cc",
diff --git a/ui/ozone/platform/wayland/host/wayland_connection.h b/ui/ozone/platform/wayland/host/wayland_connection.h
index e8c036e8..911275d 100644
--- a/ui/ozone/platform/wayland/host/wayland_connection.h
+++ b/ui/ozone/platform/wayland/host/wayland_connection.h
@@ -219,6 +219,10 @@
     return wayland_zwp_pointer_constraints_.get();
   }
 
+  WaylandZwpPointerGestures* wayland_zwp_pointer_gestures() const {
+    return wayland_zwp_pointer_gestures_.get();
+  }
+
   WaylandZwpRelativePointerManager* wayland_zwp_relative_pointer_manager()
       const {
     return wayland_zwp_relative_pointer_manager_.get();
diff --git a/ui/ozone/platform/wayland/host/wayland_event_source.cc b/ui/ozone/platform/wayland/host/wayland_event_source.cc
index 5c29233..103313f 100644
--- a/ui/ozone/platform/wayland/host/wayland_event_source.cc
+++ b/ui/ozone/platform/wayland/host/wayland_event_source.cc
@@ -425,11 +425,11 @@
                                       const gfx::Vector2dF& delta,
                                       base::TimeTicks timestamp,
                                       int device_id,
-                                      absl::optional<float> scale) {
+                                      absl::optional<float> scale_delta) {
   GestureEventDetails details(event_type);
   details.set_device_type(GestureDeviceType::DEVICE_TOUCHPAD);
-  if (scale)
-    details.set_scale(*scale);
+  if (scale_delta)
+    details.set_scale(*scale_delta);
 
   auto location = pointer_location_ + delta;
   GestureEvent event(location.x(), location.y(), 0 /* flags */, timestamp,
diff --git a/ui/ozone/platform/wayland/host/wayland_event_source.h b/ui/ozone/platform/wayland/host/wayland_event_source.h
index 6768bf2..ff314b1 100644
--- a/ui/ozone/platform/wayland/host/wayland_event_source.h
+++ b/ui/ozone/platform/wayland/host/wayland_event_source.h
@@ -128,7 +128,7 @@
                     const gfx::Vector2dF& delta,
                     base::TimeTicks timestamp,
                     int device_id,
-                    absl::optional<float> scale) override;
+                    absl::optional<float> scale_delta) override;
 
   // WaylandZwpRelativePointerManager::Delegate:
   void SetRelativePointerMotionEnabled(bool enabled) override;
diff --git a/ui/ozone/platform/wayland/host/wayland_zwp_pointer_gestures.cc b/ui/ozone/platform/wayland/host/wayland_zwp_pointer_gestures.cc
index 8a90ebe..fdc4dbf5 100644
--- a/ui/ozone/platform/wayland/host/wayland_zwp_pointer_gestures.cc
+++ b/ui/ozone/platform/wayland/host/wayland_zwp_pointer_gestures.cc
@@ -87,13 +87,15 @@
     uint32_t time,
     struct wl_surface* surface,
     uint32_t fingers) {
-  auto* thiz = static_cast<WaylandZwpPointerGestures*>(data);
+  auto* self = static_cast<WaylandZwpPointerGestures*>(data);
 
   base::TimeTicks timestamp = base::TimeTicks() + base::Milliseconds(time);
 
-  thiz->delegate_->OnPinchEvent(ET_GESTURE_PINCH_BEGIN,
+  self->current_scale_ = 1;
+
+  self->delegate_->OnPinchEvent(ET_GESTURE_PINCH_BEGIN,
                                 gfx::Vector2dF() /*delta*/, timestamp,
-                                thiz->obj_.id());
+                                self->obj_.id());
 }
 
 // static
@@ -105,14 +107,23 @@
     wl_fixed_t dy,
     wl_fixed_t scale,
     wl_fixed_t rotation) {
-  auto* thiz = static_cast<WaylandZwpPointerGestures*>(data);
+  auto* self = static_cast<WaylandZwpPointerGestures*>(data);
+
+  // During the pinch zoom session, libinput sends the current scale relative to
+  // the start of the session.  On the other hand, the compositor expects the
+  // change of the scale relative to the previous update in form of a multiplier
+  // applied to the current value.
+  // See https://crbug.com/1283652
+  const auto new_scale = wl_fixed_to_double(scale);
+  const auto scale_delta = new_scale / self->current_scale_;
+  self->current_scale_ = new_scale;
 
   base::TimeTicks timestamp = base::TimeTicks() + base::Milliseconds(time);
 
   gfx::Vector2dF delta = {static_cast<float>(wl_fixed_to_double(dx)),
                           static_cast<float>(wl_fixed_to_double(dy))};
-  thiz->delegate_->OnPinchEvent(ET_GESTURE_PINCH_UPDATE, delta, timestamp,
-                                thiz->obj_.id(), wl_fixed_to_double(scale));
+  self->delegate_->OnPinchEvent(ET_GESTURE_PINCH_UPDATE, delta, timestamp,
+                                self->obj_.id(), scale_delta);
 }
 
 void WaylandZwpPointerGestures::OnPinchEnd(
@@ -121,13 +132,13 @@
     uint32_t serial,
     uint32_t time,
     int32_t cancelled) {
-  auto* thiz = static_cast<WaylandZwpPointerGestures*>(data);
+  auto* self = static_cast<WaylandZwpPointerGestures*>(data);
 
   base::TimeTicks timestamp = base::TimeTicks() + base::Milliseconds(time);
 
-  thiz->delegate_->OnPinchEvent(ET_GESTURE_PINCH_END,
+  self->delegate_->OnPinchEvent(ET_GESTURE_PINCH_END,
                                 gfx::Vector2dF() /*delta*/, timestamp,
-                                thiz->obj_.id());
+                                self->obj_.id());
 }
 
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_zwp_pointer_gestures.h b/ui/ozone/platform/wayland/host/wayland_zwp_pointer_gestures.h
index 884deb1..de8e4e2 100644
--- a/ui/ozone/platform/wayland/host/wayland_zwp_pointer_gestures.h
+++ b/ui/ozone/platform/wayland/host/wayland_zwp_pointer_gestures.h
@@ -70,17 +70,25 @@
 
   wl::Object<zwp_pointer_gestures_v1> obj_;
   wl::Object<zwp_pointer_gesture_pinch_v1> pinch_;
+  double current_scale_ = 1;
   WaylandConnection* const connection_;
   Delegate* const delegate_;
 };
 
 class WaylandZwpPointerGestures::Delegate {
  public:
-  virtual void OnPinchEvent(EventType event_type,
-                            const gfx::Vector2dF& delta,
-                            base::TimeTicks timestamp,
-                            int device_id,
-                            absl::optional<float> scale = absl::nullopt) = 0;
+  // Handles the events coming during the pinch zoom session.
+  // |event_type| is one of ET_GESTURE_PINCH_### members.
+  // |delta| is empty on the BEGIN and END, and shows the movement of the centre
+  // of the gesture compared to the previous event.
+  // |scale_delta| is the change to the scale compared to the previous event, to
+  // be applied as multiplier (as the compositor expects it).
+  virtual void OnPinchEvent(
+      EventType event_type,
+      const gfx::Vector2dF& delta,
+      base::TimeTicks timestamp,
+      int device_id,
+      absl::optional<float> scale_delta = absl::nullopt) = 0;
 };
 
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_zwp_pointer_gestures_unittest.cc b/ui/ozone/platform/wayland/host/wayland_zwp_pointer_gestures_unittest.cc
new file mode 100644
index 0000000..c9a84951
--- /dev/null
+++ b/ui/ozone/platform/wayland/host/wayland_zwp_pointer_gestures_unittest.cc
@@ -0,0 +1,129 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <pointer-gestures-unstable-v1-server-protocol.h>
+#include <wayland-util.h>
+
+#include "ui/events/event.h"
+#include "ui/events/platform/platform_event_observer.h"
+#include "ui/ozone/platform/wayland/host/wayland_event_source.h"
+#include "ui/ozone/platform/wayland/host/wayland_zwp_pointer_gestures.h"
+#include "ui/ozone/platform/wayland/test/mock_surface.h"
+#include "ui/ozone/platform/wayland/test/wayland_test.h"
+
+namespace ui {
+
+namespace {
+
+// Observes events, filters the pinch zoom updates, and records the latest
+// update to the scale.
+class PinchEventScaleRecorder : public PlatformEventObserver {
+ public:
+  PinchEventScaleRecorder() {
+    PlatformEventSource::GetInstance()->AddPlatformEventObserver(this);
+  }
+
+  PinchEventScaleRecorder(const PinchEventScaleRecorder&) = delete;
+  PinchEventScaleRecorder& operator=(const PinchEventScaleRecorder&) = delete;
+
+  ~PinchEventScaleRecorder() override {
+    PlatformEventSource::GetInstance()->RemovePlatformEventObserver(this);
+  }
+
+  double latest_scale_update() const { return latest_scale_update_; }
+
+ protected:
+  // PlatformEventObserver:
+  void WillProcessEvent(const PlatformEvent& event) override {
+    if (!event->IsGestureEvent())
+      return;
+
+    const GestureEvent* const gesture = event->AsGestureEvent();
+    if (!gesture->IsPinchEvent() || gesture->type() != ET_GESTURE_PINCH_UPDATE)
+      return;
+
+    latest_scale_update_ = gesture->details().scale();
+  }
+
+  void DidProcessEvent(const PlatformEvent& event) override {}
+
+  double latest_scale_update_ = 1.0;
+};
+
+}  // namespace
+
+class WaylandPointerGesturesTest : public WaylandTest {
+ public:
+  WaylandPointerGesturesTest() = default;
+  WaylandPointerGesturesTest(const WaylandPointerGesturesTest&) = delete;
+  WaylandPointerGesturesTest& operator=(const WaylandPointerGesturesTest&) =
+      delete;
+  ~WaylandPointerGesturesTest() override = default;
+
+  void SetUp() override {
+    WaylandTest::SetUp();
+
+    // Pointer capability is required for gesture objects to be initialised.
+    wl_seat_send_capabilities(server_.seat()->resource(),
+                              WL_SEAT_CAPABILITY_POINTER);
+
+    Sync();
+
+    ASSERT_TRUE(connection_->wayland_zwp_pointer_gestures());
+  }
+};
+
+// Tests that scale in pinch zoom events is fixed to the progression expected by
+// the compositor.
+//
+// During the pinch zoom session, libinput sends the current scale relative to
+// the start of the session.  The compositor, however, expects every update to
+// have the relative change of the scale, compared to the previous update in
+// form of a multiplier applied to the current value.  The factor is fixed at
+// the low level immediately after values are received from the server via
+// WaylandZwpPointerGestures methods.
+//
+// See https://crbug.com/1283652
+TEST_P(WaylandPointerGesturesTest, PinchZoomScale) {
+  auto* const mock_surface = server_.GetObject<wl::MockSurface>(
+      window_->root_surface()->GetSurfaceId());
+
+  PinchEventScaleRecorder observer;
+
+  auto* pinch_resource = server_.wp_pointer_gestures().pinch()->resource();
+  zwp_pointer_gesture_pinch_v1_send_begin(pinch_resource,
+                                          /* serial */ 0,
+                                          /* time */ 0,
+                                          mock_surface->resource(),
+                                          /* fingers */ 2);
+  Sync();
+
+  constexpr double kScales[] = {1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.4,
+                                1.3, 1.2, 1.1, 1.0, 0.9, 0.8, 0.7,
+                                0.6, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0};
+  auto previous_scale = kScales[0];
+  for (auto scale : kScales) {
+    zwp_pointer_gesture_pinch_v1_send_update(
+        pinch_resource, /* time */ 0, /* dx */ 0, /* dy */ 0,
+        wl_fixed_from_double(scale), /* rotation */ 0);
+    Sync();
+    // The conversion from double to fixed and back is necessary because it
+    // happens during the roundtrip, and it creates significant error.
+    EXPECT_FLOAT_EQ(
+        observer.latest_scale_update(),
+        wl_fixed_to_double(wl_fixed_from_double(scale)) / previous_scale);
+    previous_scale = wl_fixed_to_double(wl_fixed_from_double(scale));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
+                         WaylandPointerGesturesTest,
+                         testing::Values(wl::ServerConfig{
+                             .shell_version = wl::ShellVersion::kStable}));
+INSTANTIATE_TEST_SUITE_P(XdgVersionV6Test,
+                         WaylandPointerGesturesTest,
+                         testing::Values(wl::ServerConfig{
+                             .shell_version = wl::ShellVersion::kV6}));
+
+}  // namespace ui
diff --git a/ui/ozone/platform/wayland/test/test_wayland_server_thread.cc b/ui/ozone/platform/wayland/test/test_wayland_server_thread.cc
index ae8b653..2450c2e 100644
--- a/ui/ozone/platform/wayland/test/test_wayland_server_thread.cc
+++ b/ui/ozone/platform/wayland/test/test_wayland_server_thread.cc
@@ -98,6 +98,8 @@
     return false;
   if (!surface_augmenter_.Initialize(display_.get()))
     return false;
+  if (!wp_pointer_gestures_.Initialize(display_.get()))
+    return false;
 
   client_ = wl_client_create(display_.get(), server_fd.release());
   if (!client_)
diff --git a/ui/ozone/platform/wayland/test/test_wayland_server_thread.h b/ui/ozone/platform/wayland/test/test_wayland_server_thread.h
index 7684293..33ed4b5 100644
--- a/ui/ozone/platform/wayland/test/test_wayland_server_thread.h
+++ b/ui/ozone/platform/wayland/test/test_wayland_server_thread.h
@@ -27,6 +27,7 @@
 #include "ui/ozone/platform/wayland/test/test_subcompositor.h"
 #include "ui/ozone/platform/wayland/test/test_surface_augmenter.h"
 #include "ui/ozone/platform/wayland/test/test_viewporter.h"
+#include "ui/ozone/platform/wayland/test/test_wp_pointer_gestures.h"
 #include "ui/ozone/platform/wayland/test/test_zcr_text_input_extension.h"
 #include "ui/ozone/platform/wayland/test/test_zwp_linux_explicit_synchronization.h"
 #include "ui/ozone/platform/wayland/test/test_zwp_text_input_manager.h"
@@ -121,6 +122,8 @@
     return primary_selection_device_manager_.get();
   }
 
+  TestWpPointerGestures& wp_pointer_gestures() { return wp_pointer_gestures_; }
+
   void set_output_delegate(OutputDelegate* delegate) {
     output_delegate_ = delegate;
   }
@@ -160,6 +163,7 @@
   TestZwpLinuxExplicitSynchronizationV1 zwp_linux_explicit_synchronization_v1_;
   MockZwpLinuxDmabufV1 zwp_linux_dmabuf_v1_;
   MockWpPresentation wp_presentation_;
+  TestWpPointerGestures wp_pointer_gestures_;
   std::unique_ptr<TestSelectionDeviceManager> primary_selection_device_manager_;
 
   std::vector<std::unique_ptr<GlobalObject>> globals_;
diff --git a/ui/ozone/platform/wayland/test/test_wp_pointer_gestures.cc b/ui/ozone/platform/wayland/test/test_wp_pointer_gestures.cc
new file mode 100644
index 0000000..ae606fb
--- /dev/null
+++ b/ui/ozone/platform/wayland/test/test_wp_pointer_gestures.cc
@@ -0,0 +1,65 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/ozone/platform/wayland/test/test_wp_pointer_gestures.h"
+
+#include <pointer-gestures-unstable-v1-server-protocol.h>
+#include <wayland-server-core.h>
+
+#include "base/logging.h"
+#include "base/notreached.h"
+
+namespace wl {
+
+namespace {
+
+const struct zwp_pointer_gesture_pinch_v1_interface kTestPinchImpl = {
+    DestroyResource};
+
+constexpr uint32_t kInterfaceVersion = 1;
+
+}  // namespace
+
+const struct zwp_pointer_gestures_v1_interface kInterfaceImpl = {
+    TestWpPointerGestures::GetSwipeGesture,
+    TestWpPointerGestures::GetPinchGesture,
+    DestroyResource,
+};
+
+TestWpPointerGestures::TestWpPointerGestures()
+    : GlobalObject(&zwp_pointer_gestures_v1_interface,
+                   &kInterfaceImpl,
+                   kInterfaceVersion) {}
+
+TestWpPointerGestures::~TestWpPointerGestures() = default;
+
+// static
+void TestWpPointerGestures::GetSwipeGesture(struct wl_client* client,
+                                            struct wl_resource* resource,
+                                            uint32_t id,
+                                            struct wl_resource* pointer) {
+  NOTIMPLEMENTED_LOG_ONCE();
+}
+
+// static
+void TestWpPointerGestures::GetPinchGesture(
+    struct wl_client* client,
+    struct wl_resource* pointer_gestures_resource,
+    uint32_t id,
+    struct wl_resource* pointer) {
+  wl_resource* pinch_gesture_resource =
+      CreateResourceWithImpl<TestPinchGesture>(
+          client, &zwp_pointer_gesture_pinch_v1_interface, 1, &kTestPinchImpl,
+          id);
+
+  GetUserDataAs<TestWpPointerGestures>(pointer_gestures_resource)->pinch_ =
+      GetUserDataAs<TestPinchGesture>(pinch_gesture_resource);
+}
+
+TestPinchGesture::TestPinchGesture(wl_resource* resource)
+    : ServerObject(resource) {}
+
+TestPinchGesture::~TestPinchGesture() = default;
+
+}  // namespace wl
diff --git a/ui/ozone/platform/wayland/test/test_wp_pointer_gestures.h b/ui/ozone/platform/wayland/test/test_wp_pointer_gestures.h
new file mode 100644
index 0000000..d275dee9
--- /dev/null
+++ b/ui/ozone/platform/wayland/test/test_wp_pointer_gestures.h
@@ -0,0 +1,50 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_OZONE_PLATFORM_WAYLAND_TEST_TEST_WP_POINTER_GESTURES_H_
+#define UI_OZONE_PLATFORM_WAYLAND_TEST_TEST_WP_POINTER_GESTURES_H_
+
+#include "ui/ozone/platform/wayland/test/global_object.h"
+#include "ui/ozone/platform/wayland/test/server_object.h"
+
+struct wl_client;
+struct wl_resource;
+
+namespace wl {
+
+class TestPinchGesture : public ServerObject {
+ public:
+  explicit TestPinchGesture(wl_resource* resource);
+  TestPinchGesture(const TestPinchGesture&) = delete;
+  TestPinchGesture& operator=(const TestPinchGesture&) = delete;
+  ~TestPinchGesture() override;
+};
+
+// Manage zwp_linux_dmabuf_v1 object.
+class TestWpPointerGestures : public GlobalObject {
+ public:
+  TestWpPointerGestures();
+  TestWpPointerGestures(const TestWpPointerGestures&) = delete;
+  TestWpPointerGestures& operator=(const TestWpPointerGestures&) = delete;
+  ~TestWpPointerGestures() override;
+
+  TestPinchGesture* pinch() const { return pinch_; }
+
+  static void GetSwipeGesture(struct wl_client* client,
+                              struct wl_resource* resource,
+                              uint32_t id,
+                              struct wl_resource* pointer);
+
+  static void GetPinchGesture(struct wl_client* client,
+                              struct wl_resource* pointer_gestures_resource,
+                              uint32_t id,
+                              struct wl_resource* pointer);
+
+ private:
+  TestPinchGesture* pinch_;
+};
+
+}  // namespace wl
+
+#endif  // UI_OZONE_PLATFORM_WAYLAND_TEST_TEST_WP_POINTER_GESTURES_H_
diff --git a/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_pairing_ui.js b/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_pairing_ui.js
index 52f8e51..5d78049 100644
--- a/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_pairing_ui.js
+++ b/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_pairing_ui.js
@@ -221,6 +221,9 @@
 
     /** @private {?ConfirmCodeCallback} */
     this.confirmCodeCallback_ = null;
+
+    /** @private {?function()} */
+    this.onBluetoothDiscoveryStartedCallbackForTest_ = null;
   }
 
   ready() {
@@ -275,6 +278,13 @@
   /** @override */
   onBluetoothDiscoveryStarted(handler) {
     this.devicePairingHandler_ = handler;
+
+    // Inform tests that onBluetoothDiscoveryStarted() has been called. This is
+    // to ensure tests don't progress until |devicePairingHandler_| has been
+    // set.
+    if (this.onBluetoothDiscoveryStartedCallbackForTest_) {
+      this.onBluetoothDiscoveryStartedCallbackForTest_();
+    }
   }
 
   /** @override */
@@ -283,6 +293,18 @@
     // selection page.
     this.bluetoothDiscoveryDelegateReceiver_.$.close();
     this.selectedPageId_ = BluetoothPairingSubpageId.DEVICE_SELECTION_PAGE;
+    this.devicePairingHandler_ = null;
+  }
+
+  /**
+   * Returns a promise that will be resolved the next time
+   * onBluetoothDiscoveryStarted() is called.
+   * @return {Promise}
+   */
+  waitForOnBluetoothDiscoveryStartedForTest() {
+    return new Promise((resolve) => {
+      this.onBluetoothDiscoveryStartedCallbackForTest_ = resolve;
+    });
   }
 
   /**
@@ -334,7 +356,8 @@
    * @private
    */
   pairDevice_(device) {
-    assert(this.devicePairingHandler_);
+    assert(
+        this.devicePairingHandler_, 'devicePairingHandler_ has not been set.');
 
     this.pairingDelegateReceiver_ =
         new chromeos.bluetoothConfig.mojom.DevicePairingDelegateReceiver(this);