Implement constructor in RTCEncodedVideoFrame

This is inline with the algorithm described in https://github.com/w3c/webrtc-encoded-transform/compare/main...palak8669:webrtc-encoded-transform:constructor_frame

This is a reland of https://chromium-review.googlesource.com/c/chromium/src/+/5295578 which got reverted due
to wrong include rules.

Bug: 14709
Change-Id: I5da20d10295908b2592c3c7202aa56fd206bf908
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5307134
Reviewed-by: Guido Urdaneta <guidou@chromium.org>
Commit-Queue: Palak Agarwal <agpalak@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1268408}
diff --git a/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom b/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
index aa6331b..53e115f5 100644
--- a/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
+++ b/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
@@ -4221,6 +4221,7 @@
   kStorageAccessAPI_requestStorageAccess_BeyondCookies_SharedWorker = 4857,
   kStorageAccessAPI_requestStorageAccess_BeyondCookies_SharedWorker_Use = 4858,
   kV8PointerEvent_GetCoalescedEvents_Method = 4859,
+  kV8RTCEncodedVideoFrame_Constructor = 4860,
   kCSSFunctions = 4861,
   kCSSPageRule = 4862,
   kV8RTCRtpReceiver_JitterBufferTarget_AttributeGetter = 4863,
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame.cc b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame.cc
index 396a86e..6864ce8 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame.cc
@@ -60,15 +60,13 @@
   if (!metadata->hasWidth() || !metadata->hasHeight() ||
       !metadata->hasSpatialIndex() || !metadata->hasTemporalIndex() ||
       !metadata->hasRtpTimestamp()) {
-    error_message = "Member(s) missing in RTCEncodedVideoFrameMetadata.";
+    error_message = "new metadata has member(s) missing.";
     return false;
   }
 
   // This might happen if the dependency descriptor is not set.
   if (!metadata->hasFrameId() && metadata->hasDependencies()) {
-    error_message =
-        "frameID missing, but has dependencies in "
-        "RTCEncodedVideoFrameMetadata.";
+    error_message = "new metadata has frameID missing, but has dependencies";
     return false;
   }
   if (!metadata->hasDependencies()) {
@@ -78,7 +76,7 @@
   // Ensure there are at most 8 deps. Enforced in WebRTC's
   // RtpGenericFrameDescriptor::AddFrameDependencyDiff().
   if (metadata->dependencies().size() > kMaxNumDependencies) {
-    error_message = "Too many dependencies.";
+    error_message = "new metadata has too many dependencies.";
     return false;
   }
   // Require deps to all be before frame_id, but within 2^14 of it. Enforced in
@@ -86,7 +84,7 @@
   for (const int64_t dep : metadata->dependencies()) {
     if ((dep >= metadata->frameId()) ||
         ((metadata->frameId() - dep) >= (1 << 14))) {
-      error_message = "Invalid frame dependency.";
+      error_message = "new metadata has invalid frame dependencies.";
       return false;
     }
   }
@@ -96,6 +94,38 @@
 
 }  // namespace
 
+RTCEncodedVideoFrame* RTCEncodedVideoFrame::Create(
+    RTCEncodedVideoFrame* original_frame,
+    ExceptionState& exception_state) {
+  return RTCEncodedVideoFrame::Create(original_frame, nullptr, exception_state);
+}
+
+RTCEncodedVideoFrame* RTCEncodedVideoFrame::Create(
+    RTCEncodedVideoFrame* original_frame,
+    RTCEncodedVideoFrameMetadata* new_metadata,
+    ExceptionState& exception_state) {
+  RTCEncodedVideoFrame* new_frame;
+  if (original_frame) {
+    new_frame = MakeGarbageCollected<RTCEncodedVideoFrame>(
+        original_frame->Delegate()->CloneWebRtcFrame());
+  } else {
+    exception_state.ThrowDOMException(
+        DOMExceptionCode::kInvalidAccessError,
+        "Cannot create a new VideoFrame from an empty VideoFrame");
+    return nullptr;
+  }
+  if (new_metadata) {
+    String error_message;
+    if (!new_frame->SetMetadata(new_metadata, error_message)) {
+      exception_state.ThrowDOMException(
+          DOMExceptionCode::kInvalidModificationError,
+          "Cannot create a new VideoFrame: " + error_message);
+      return nullptr;
+    }
+  }
+  return new_frame;
+}
+
 RTCEncodedVideoFrame::RTCEncodedVideoFrame(
     std::unique_ptr<webrtc::TransformableVideoFrameInterface> webrtc_frame)
     : delegate_(base::MakeRefCounted<RTCEncodedVideoFrameDelegate>(
@@ -115,7 +145,11 @@
 
 void RTCEncodedVideoFrame::setTimestamp(uint32_t timestamp,
                                         ExceptionState& exception_state) {
-  delegate_->SetRtpTimestamp(timestamp, exception_state);
+  String error_message;
+  if (!delegate_->SetRtpTimestamp(timestamp, error_message)) {
+    exception_state.ThrowDOMException(
+        DOMExceptionCode::kInvalidModificationError, error_message);
+  }
 }
 
 DOMArrayBuffer* RTCEncodedVideoFrame::data() const {
@@ -172,47 +206,38 @@
   return metadata;
 }
 
-void RTCEncodedVideoFrame::setMetadata(RTCEncodedVideoFrameMetadata* metadata,
-                                       ExceptionState& exception_state) {
+bool RTCEncodedVideoFrame::SetMetadata(
+    const RTCEncodedVideoFrameMetadata* metadata,
+    String& error_message) {
   const std::optional<webrtc::VideoFrameMetadata> original_webrtc_metadata =
       delegate_->GetMetadata();
   if (!original_webrtc_metadata) {
-    exception_state.ThrowDOMException(
-        DOMExceptionCode::kInvalidModificationError,
-        "Cannot set metadata on an empty frame.");
-    return;
+    error_message = "underlying webrtc frame is an empty frame.";
+    return false;
   }
 
-  String error_message;
   if (!ValidateMetadata(metadata, error_message)) {
-    exception_state.ThrowDOMException(
-        DOMExceptionCode::kInvalidModificationError, error_message);
-    return;
+    return false;
   }
 
   RTCEncodedVideoFrameMetadata* original_metadata = getMetadata();
   if (!original_metadata) {
-    exception_state.ThrowDOMException(
-        DOMExceptionCode::kInvalidModificationError,
-        "Internal error when calling setMetadata.");
-    return;
+    error_message = "internal error when calling getMetadata().";
+    return false;
   }
   if (!IsAllowedSetMetadataChange(original_metadata, metadata) &&
       !base::FeatureList::IsEnabled(
           kAllowRTCEncodedVideoFrameSetMetadataAllFields)) {
-    exception_state.ThrowDOMException(
-        DOMExceptionCode::kInvalidModificationError,
-        "Invalid modification of RTCEncodedVideoFrameMetadata.");
-    return;
+    error_message = "invalid modification of RTCEncodedVideoFrameMetadata.";
+    return false;
   }
 
   if ((metadata->hasPayloadType() != original_metadata->hasPayloadType()) ||
       (metadata->hasPayloadType() &&
        metadata->payloadType() != original_metadata->payloadType())) {
-    exception_state.ThrowDOMException(
-        DOMExceptionCode::kInvalidModificationError,
-        "Invalid modification of payloadType in RTCEncodedVideoFrameMetadata.");
-    return;
+    error_message =
+        "invalid modification of payloadType in RTCEncodedVideoFrameMetadata.";
+    return false;
   }
 
   // Initialize the new metadata from original_metadata to account for fields
@@ -238,8 +263,18 @@
     webrtc_metadata.SetCsrcs(csrcs);
   }
 
-  delegate_->SetMetadata(webrtc_metadata);
-  delegate_->SetRtpTimestamp(metadata->rtpTimestamp(), exception_state);
+  return delegate_->SetMetadata(webrtc_metadata, error_message) &&
+         delegate_->SetRtpTimestamp(metadata->rtpTimestamp(), error_message);
+}
+
+void RTCEncodedVideoFrame::setMetadata(RTCEncodedVideoFrameMetadata* metadata,
+                                       ExceptionState& exception_state) {
+  String error_message;
+  if (!SetMetadata(metadata, error_message)) {
+    exception_state.ThrowDOMException(
+        DOMExceptionCode::kInvalidModificationError,
+        "Cannot setMetadata: " + error_message);
+  }
 }
 
 void RTCEncodedVideoFrame::setData(DOMArrayBuffer* data) {
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame.h b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame.h
index 36b156b4..2db3b64 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame.h
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame.h
@@ -34,6 +34,12 @@
   DEFINE_WRAPPERTYPEINFO();
 
  public:
+  static RTCEncodedVideoFrame* Create(RTCEncodedVideoFrame* original_frame,
+                                      ExceptionState& exception_state);
+  static RTCEncodedVideoFrame* Create(
+      RTCEncodedVideoFrame* original_frame,
+      RTCEncodedVideoFrameMetadata* new_metadata,
+      ExceptionState& exception_state);
   explicit RTCEncodedVideoFrame(
       std::unique_ptr<webrtc::TransformableVideoFrameInterface> webrtc_frame);
   explicit RTCEncodedVideoFrame(
@@ -46,6 +52,8 @@
   void setTimestamp(uint32_t timestamp, ExceptionState& exception_state);
   DOMArrayBuffer* data() const;
   RTCEncodedVideoFrameMetadata* getMetadata() const;
+  bool SetMetadata(const RTCEncodedVideoFrameMetadata* metadata,
+                   String& error_message);
   void setMetadata(RTCEncodedVideoFrameMetadata* metadata,
                    ExceptionState& exception_state);
   void setData(DOMArrayBuffer*);
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame.idl b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame.idl
index 4d4b181..70ecf0df 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame.idl
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame.idl
@@ -14,6 +14,8 @@
 [
     Exposed=(Window, DedicatedWorker)
 ] interface RTCEncodedVideoFrame {
+    [RuntimeEnabled=RTCEncodedFrameSetMetadata, Measure, RaisesException]
+    constructor (RTCEncodedVideoFrame originalFrame, optional RTCEncodedVideoFrameMetadata newMetadata);
     readonly attribute RTCEncodedVideoFrameType type;
     readonly attribute unsigned long timestamp;  // RTP timestamp.
     attribute ArrayBuffer data;
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame_delegate.cc b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame_delegate.cc
index 4ef0c3d..7644d540 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame_delegate.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame_delegate.cc
@@ -33,16 +33,15 @@
   return webrtc_frame_ ? webrtc_frame_->GetTimestamp() : 0;
 }
 
-void RTCEncodedVideoFrameDelegate::SetRtpTimestamp(
-    uint32_t timestamp,
-    ExceptionState& exception_state) {
+bool RTCEncodedVideoFrameDelegate::SetRtpTimestamp(uint32_t timestamp,
+                                                   String& error_message) {
   base::AutoLock lock(lock_);
-  if (webrtc_frame_) {
-    webrtc_frame_->SetRTPTimestamp(timestamp);
-  } else {
-    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
-                                      "Video frame is empty.");
+  if (!webrtc_frame_) {
+    error_message = "underlying webrtc frame is empty.";
+    return false;
   }
+  webrtc_frame_->SetRTPTimestamp(timestamp);
+  return true;
 }
 
 std::optional<webrtc::Timestamp>
@@ -98,13 +97,16 @@
                        : std::nullopt;
 }
 
-void RTCEncodedVideoFrameDelegate::SetMetadata(
-    const webrtc::VideoFrameMetadata& metadata) {
+bool RTCEncodedVideoFrameDelegate::SetMetadata(
+    const webrtc::VideoFrameMetadata& metadata,
+    String& error_message) {
   base::AutoLock lock(lock_);
   if (!webrtc_frame_) {
-    return;
+    error_message = "underlying webrtc frame is empty.";
+    return false;
   }
   webrtc_frame_->SetMetadata(metadata);
+  return true;
 }
 
 std::unique_ptr<webrtc::TransformableVideoFrameInterface>
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame_delegate.h b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame_delegate.h
index d956207..d330a483 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame_delegate.h
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame_delegate.h
@@ -20,7 +20,6 @@
 namespace blink {
 
 class DOMArrayBuffer;
-class ExceptionState;
 
 // This class wraps a WebRTC video frame and allows making shallow
 // copies. Its purpose is to support making RTCEncodedVideoFrames
@@ -33,14 +32,15 @@
 
   String Type() const;
   uint32_t RtpTimestamp() const;
-  void SetRtpTimestamp(uint32_t timestamp, ExceptionState& exception_state);
+  bool SetRtpTimestamp(uint32_t timestamp, String& error_message);
   std::optional<webrtc::Timestamp> PresentationTimestamp() const;
   DOMArrayBuffer* CreateDataBuffer() const;
   void SetData(const DOMArrayBuffer* data);
   std::optional<uint8_t> PayloadType() const;
   std::optional<std::string> MimeType() const;
   std::optional<webrtc::VideoFrameMetadata> GetMetadata() const;
-  void SetMetadata(const webrtc::VideoFrameMetadata& metadata);
+  bool SetMetadata(const webrtc::VideoFrameMetadata& metadata,
+                   String& error_message);
   std::unique_ptr<webrtc::TransformableVideoFrameInterface> PassWebRtcFrame();
   std::unique_ptr<webrtc::TransformableVideoFrameInterface> CloneWebRtcFrame();
 
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame_test.cc b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame_test.cc
index 06701d4..3ad009b 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame_test.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame_test.cc
@@ -82,6 +82,7 @@
   webrtc_metadata.SetRTPVideoHeaderCodecSpecifics(webrtc_vp8_specifics);
 
   ON_CALL(*frame, Metadata()).WillByDefault(Return(webrtc_metadata));
+  ON_CALL(*frame, GetSsrc()).WillByDefault(Return(7));
 
   return webrtc_metadata;
 }
@@ -157,22 +158,32 @@
   encoded_frame.setMetadata(empty_metadata, exception_state);
   EXPECT_TRUE(exception_state.HadException());
   EXPECT_EQ(exception_state.Message(),
-            "Member(s) missing in RTCEncodedVideoFrameMetadata.");
+            "Cannot setMetadata: new metadata has member(s) missing.");
 }
 
-RTCEncodedVideoFrameMetadata* CreateMetadata() {
+RTCEncodedVideoFrameMetadata* CreateMetadata(bool change_all_fields = false) {
   RTCEncodedVideoFrameMetadata* new_metadata =
       RTCEncodedVideoFrameMetadata::Create();
   new_metadata->setFrameId(5);
   new_metadata->setDependencies({2, 3, 4});
-  new_metadata->setWidth(6);
-  new_metadata->setHeight(7);
-  new_metadata->setSpatialIndex(8);
-  new_metadata->setTemporalIndex(9);
-  new_metadata->setSynchronizationSource(10);
-  new_metadata->setContributingSources({11, 12, 13});
-  new_metadata->setPayloadType(14);
   new_metadata->setRtpTimestamp(1);
+  if (change_all_fields) {
+    new_metadata->setWidth(6);
+    new_metadata->setHeight(7);
+    new_metadata->setSpatialIndex(8);
+    new_metadata->setTemporalIndex(9);
+    new_metadata->setSynchronizationSource(10);
+    new_metadata->setContributingSources({11, 12, 13});
+    new_metadata->setPayloadType(14);
+  } else {
+    new_metadata->setWidth(800);
+    new_metadata->setHeight(600);
+    new_metadata->setSpatialIndex(3);
+    new_metadata->setTemporalIndex(4);
+    new_metadata->setSynchronizationSource(7);
+    new_metadata->setContributingSources({6});
+    new_metadata->setPayloadType(1);
+  }
   return new_metadata;
 }
 
@@ -186,19 +197,22 @@
   std::unique_ptr<MockTransformableVideoFrame> frame =
       std::make_unique<NiceMock<MockTransformableVideoFrame>>();
   MockVP8Metadata(frame.get());
+  EXPECT_CALL(*frame, GetPayloadType()).WillRepeatedly(Return(1));
 
   webrtc::VideoFrameMetadata actual_metadata;
   EXPECT_CALL(*frame, SetMetadata(_)).Times(0);
 
   RTCEncodedVideoFrame encoded_frame(std::move(frame));
 
-  RTCEncodedVideoFrameMetadata* new_metadata = CreateMetadata();
+  RTCEncodedVideoFrameMetadata* new_metadata =
+      CreateMetadata(/*change_all_fields=*/true);
 
   DummyExceptionStateForTesting exception_state;
   encoded_frame.setMetadata(new_metadata, exception_state);
   EXPECT_TRUE(exception_state.HadException());
   EXPECT_EQ(exception_state.Message(),
-            "Invalid modification of RTCEncodedVideoFrameMetadata.");
+            "Cannot setMetadata: invalid modification of "
+            "RTCEncodedVideoFrameMetadata.");
 }
 
 TEST_F(RTCEncodedVideoFrameTest, SetMetadataWithFeatureAllowsModifications) {
@@ -218,7 +232,8 @@
 
   RTCEncodedVideoFrame encoded_frame(std::move(frame));
 
-  RTCEncodedVideoFrameMetadata* new_metadata = CreateMetadata();
+  RTCEncodedVideoFrameMetadata* new_metadata =
+      CreateMetadata(/*change_all_fields=*/true);
 
   DummyExceptionStateForTesting exception_state;
   encoded_frame.setMetadata(new_metadata, exception_state);
@@ -261,7 +276,7 @@
 
   EXPECT_TRUE(exception_state.HadException());
   EXPECT_EQ(exception_state.Message(),
-            "Cannot set metadata on an empty frame.");
+            "Cannot setMetadata: underlying webrtc frame is an empty frame.");
 }
 
 TEST_F(RTCEncodedVideoFrameTest, SetMetadataRejectsInvalidDependencies) {
@@ -285,7 +300,9 @@
   DummyExceptionStateForTesting exception_state;
   encoded_frame.setMetadata(new_metadata, exception_state);
   EXPECT_TRUE(exception_state.HadException());
-  EXPECT_EQ(exception_state.Message(), "Invalid frame dependency.");
+  EXPECT_EQ(exception_state.Message(),
+            "Cannot setMetadata: new metadata has invalid frame "
+            "dependencies.");
 }
 
 TEST_F(RTCEncodedVideoFrameTest, SetMetadataRejectsTooEarlyDependencies) {
@@ -310,7 +327,9 @@
   DummyExceptionStateForTesting exception_state;
   encoded_frame.setMetadata(new_metadata, exception_state);
   EXPECT_TRUE(exception_state.HadException());
-  EXPECT_EQ(exception_state.Message(), "Invalid frame dependency.");
+  EXPECT_EQ(exception_state.Message(),
+            "Cannot setMetadata: new metadata has invalid frame "
+            "dependencies.");
 }
 
 TEST_F(RTCEncodedVideoFrameTest, SetMetadataRejectsTooManyDependencies) {
@@ -334,7 +353,8 @@
   DummyExceptionStateForTesting exception_state;
   encoded_frame.setMetadata(new_metadata, exception_state);
   EXPECT_TRUE(exception_state.HadException());
-  EXPECT_EQ(exception_state.Message(), "Too many dependencies.");
+  EXPECT_EQ(exception_state.Message(),
+            "Cannot setMetadata: new metadata has too many dependencies.");
 }
 
 TEST_F(RTCEncodedVideoFrameTest, SetMetadataModifiesRtpTimestamp) {
@@ -361,4 +381,331 @@
   EXPECT_FALSE(exception_state.HadException()) << exception_state.Message();
 }
 
+TEST_F(RTCEncodedVideoFrameTest, ConstructorPreservesVP9CodecSpecifics) {
+  V8TestingScope v8_scope;
+
+  std::unique_ptr<MockTransformableVideoFrame> frame =
+      std::make_unique<NiceMock<MockTransformableVideoFrame>>();
+  webrtc::VideoFrameMetadata webrtc_metadata = MockVP9Metadata(frame.get());
+
+  RTCEncodedVideoFrame encoded_frame(std::move(frame));
+  DummyExceptionStateForTesting exception_state;
+
+  RTCEncodedVideoFrame* new_frame =
+      RTCEncodedVideoFrame::Create(&encoded_frame, exception_state);
+  EXPECT_FALSE(exception_state.HadException()) << exception_state.Message();
+  EXPECT_EQ(new_frame->getMetadata()->frameId(), webrtc_metadata.GetFrameId());
+  EXPECT_EQ(new_frame->getMetadata()->width(), webrtc_metadata.GetWidth());
+  EXPECT_EQ(new_frame->getMetadata()->height(), webrtc_metadata.GetHeight());
+  EXPECT_EQ(new_frame->getMetadata()->spatialIndex(),
+            webrtc_metadata.GetSpatialIndex());
+  EXPECT_EQ(new_frame->getMetadata()->temporalIndex(),
+            webrtc_metadata.GetTemporalIndex());
+  EXPECT_EQ(new_frame->getMetadata()->synchronizationSource(),
+            webrtc_metadata.GetSsrc());
+  std::vector<uint32_t> actual_csrcs;
+  for (const auto& dependency :
+       new_frame->getMetadata()->contributingSources()) {
+    actual_csrcs.push_back(dependency);
+  }
+  EXPECT_EQ(actual_csrcs, webrtc_metadata.GetCsrcs());
+}
+
+TEST_F(RTCEncodedVideoFrameTest, ConstructorMissingFieldsFails) {
+  V8TestingScope v8_scope;
+  base::test::ScopedFeatureList feature_list_;
+
+  std::unique_ptr<MockTransformableVideoFrame> frame =
+      std::make_unique<NiceMock<MockTransformableVideoFrame>>();
+  MockVP8Metadata(frame.get());
+  RTCEncodedVideoFrame encoded_frame(std::move(frame));
+
+  RTCEncodedVideoFrameMetadata* empty_metadata =
+      RTCEncodedVideoFrameMetadata::Create();
+
+  DummyExceptionStateForTesting exception_state;
+  RTCEncodedVideoFrame* new_frame = RTCEncodedVideoFrame::Create(
+      &encoded_frame, empty_metadata, exception_state);
+  EXPECT_TRUE(exception_state.HadException());
+  EXPECT_EQ(exception_state.Message(),
+            "Cannot create a new VideoFrame: new metadata has member(s) "
+            "missing.");
+  EXPECT_EQ(new_frame, nullptr);
+}
+
+TEST_F(RTCEncodedVideoFrameTest, ConstructorWithoutFeatureFailsModifications) {
+  V8TestingScope v8_scope;
+  base::test::ScopedFeatureList feature_list_;
+  feature_list_.InitWithFeatures(
+      /*enabled_features=*/{},
+      /*disabled_features=*/{kAllowRTCEncodedVideoFrameSetMetadataAllFields});
+
+  std::unique_ptr<MockTransformableVideoFrame> frame =
+      std::make_unique<NiceMock<MockTransformableVideoFrame>>();
+  MockVP8Metadata(frame.get());
+
+  webrtc::VideoFrameMetadata actual_metadata;
+  EXPECT_CALL(*frame, SetMetadata(_)).Times(0);
+  EXPECT_CALL(*frame, GetPayloadType()).WillRepeatedly(Return(1));
+
+  RTCEncodedVideoFrame encoded_frame(std::move(frame));
+
+  RTCEncodedVideoFrameMetadata* new_metadata =
+      CreateMetadata(/*change_all_fields=*/true);
+
+  DummyExceptionStateForTesting exception_state;
+  RTCEncodedVideoFrame* new_frame = RTCEncodedVideoFrame::Create(
+      &encoded_frame, new_metadata, exception_state);
+  EXPECT_TRUE(exception_state.HadException());
+  EXPECT_EQ(exception_state.Message(),
+            "Cannot create a new VideoFrame: invalid modification of "
+            "RTCEncodedVideoFrameMetadata.");
+  EXPECT_EQ(new_frame, nullptr);
+}
+
+TEST_F(RTCEncodedVideoFrameTest, ConstructorWithFeatureAllowsModifications) {
+  V8TestingScope v8_scope;
+  base::test::ScopedFeatureList feature_list_;
+  feature_list_.InitWithFeatures(
+      /*enabled_features=*/{kAllowRTCEncodedVideoFrameSetMetadataAllFields},
+      /*disabled_features=*/{});
+
+  std::unique_ptr<MockTransformableVideoFrame> frame =
+      std::make_unique<NiceMock<MockTransformableVideoFrame>>();
+  MockVP8Metadata(frame.get());
+
+  webrtc::VideoFrameMetadata actual_metadata;
+  EXPECT_CALL(*frame, SetMetadata(_)).Times(0);
+  EXPECT_CALL(*frame, GetPayloadType()).WillRepeatedly(Return(14));
+
+  RTCEncodedVideoFrame encoded_frame(std::move(frame));
+
+  RTCEncodedVideoFrameMetadata* new_metadata =
+      CreateMetadata(/*change_all_fields=*/true);
+
+  DummyExceptionStateForTesting exception_state;
+  RTCEncodedVideoFrame* new_frame = RTCEncodedVideoFrame::Create(
+      &encoded_frame, new_metadata, exception_state);
+
+  EXPECT_FALSE(exception_state.HadException()) << exception_state.Message();
+
+  EXPECT_EQ(new_frame->getMetadata()->frameId(), new_metadata->frameId());
+  Vector<int64_t> actual_dependencies;
+  for (const auto& dependency : new_frame->getMetadata()->dependencies()) {
+    actual_dependencies.push_back(dependency);
+  }
+  EXPECT_EQ(actual_dependencies, new_metadata->dependencies());
+  EXPECT_EQ(new_frame->getMetadata()->width(), new_metadata->width());
+  EXPECT_EQ(new_frame->getMetadata()->height(), new_metadata->height());
+  EXPECT_EQ(new_frame->getMetadata()->spatialIndex(),
+            new_metadata->spatialIndex());
+  EXPECT_EQ(new_frame->getMetadata()->temporalIndex(),
+            new_metadata->temporalIndex());
+  EXPECT_EQ(new_frame->getMetadata()->synchronizationSource(),
+            new_metadata->synchronizationSource());
+  Vector<uint32_t> actual_csrcs;
+  for (const auto& dependency :
+       new_frame->getMetadata()->contributingSources()) {
+    actual_csrcs.push_back(dependency);
+  }
+  EXPECT_EQ(actual_csrcs, new_metadata->contributingSources());
+}
+
+TEST_F(RTCEncodedVideoFrameTest, ConstructorFromNull) {
+  V8TestingScope v8_scope;
+  DummyExceptionStateForTesting exception_state;
+  RTCEncodedVideoFrame* new_frame =
+      RTCEncodedVideoFrame::Create(nullptr, exception_state);
+
+  EXPECT_TRUE(exception_state.HadException());
+  EXPECT_EQ(exception_state.Message(),
+            "Cannot create a new VideoFrame from an empty VideoFrame");
+  EXPECT_EQ(new_frame, nullptr);
+}
+
+TEST_F(RTCEncodedVideoFrameTest, ConstructorOnEmptyFrameWorks) {
+  V8TestingScope v8_scope;
+
+  std::unique_ptr<MockTransformableVideoFrame> frame =
+      std::make_unique<NiceMock<MockTransformableVideoFrame>>();
+  MockVP8Metadata(frame.get());
+
+  RTCEncodedVideoFrame encoded_frame(std::move(frame));
+
+  // Move the WebRTC frame out, as if the frame had been written into
+  // an encoded insertable stream's WritableStream to be sent on.
+  encoded_frame.PassWebRtcFrame();
+
+  DummyExceptionStateForTesting exception_state;
+  RTCEncodedVideoFrame* new_frame =
+      RTCEncodedVideoFrame::Create(&encoded_frame, exception_state);
+
+  EXPECT_FALSE(exception_state.HadException());
+  EXPECT_NE(new_frame, nullptr);
+  EXPECT_EQ(new_frame->type(), "empty");
+}
+
+TEST_F(RTCEncodedVideoFrameTest, ConstructorWithMetadataOnEmptyFrameFails) {
+  V8TestingScope v8_scope;
+
+  std::unique_ptr<MockTransformableVideoFrame> frame =
+      std::make_unique<NiceMock<MockTransformableVideoFrame>>();
+  MockVP8Metadata(frame.get());
+
+  RTCEncodedVideoFrame encoded_frame(std::move(frame));
+  RTCEncodedVideoFrameMetadata* metadata = encoded_frame.getMetadata();
+
+  // Move the WebRTC frame out, as if the frame had been written into
+  // an encoded insertable stream's WritableStream to be sent on.
+  encoded_frame.PassWebRtcFrame();
+
+  DummyExceptionStateForTesting exception_state;
+  RTCEncodedVideoFrame* new_frame =
+      RTCEncodedVideoFrame::Create(&encoded_frame, metadata, exception_state);
+
+  EXPECT_TRUE(exception_state.HadException());
+  EXPECT_EQ(exception_state.Message(),
+            "Cannot create a new VideoFrame: underlying webrtc frame is "
+            "an empty frame.");
+  EXPECT_EQ(new_frame, nullptr);
+}
+
+TEST_F(RTCEncodedVideoFrameTest, ConstructorRejectsInvalidDependencies) {
+  V8TestingScope v8_scope;
+  base::test::ScopedFeatureList feature_list_;
+  feature_list_.InitWithFeatures(
+      /*enabled_features=*/{kAllowRTCEncodedVideoFrameSetMetadataAllFields},
+      /*disabled_features=*/{});
+
+  std::unique_ptr<MockTransformableVideoFrame> frame =
+      std::make_unique<NiceMock<MockTransformableVideoFrame>>();
+  MockVP8Metadata(frame.get());
+
+  EXPECT_CALL(*frame, SetMetadata(_)).Times(0);
+
+  RTCEncodedVideoFrame encoded_frame(std::move(frame));
+  RTCEncodedVideoFrameMetadata* new_metadata = CreateMetadata();
+  // Set an invalid dependency - all deps must be less than frame id.
+  new_metadata->setDependencies({new_metadata->frameId()});
+
+  DummyExceptionStateForTesting exception_state;
+  RTCEncodedVideoFrame* new_frame = RTCEncodedVideoFrame::Create(
+      &encoded_frame, new_metadata, exception_state);
+  EXPECT_TRUE(exception_state.HadException());
+  EXPECT_EQ(exception_state.Message(),
+            "Cannot create a new VideoFrame: new metadata has invalid "
+            "frame dependencies.");
+  EXPECT_EQ(new_frame, nullptr);
+}
+
+TEST_F(RTCEncodedVideoFrameTest, ConstructorCopiesMetadata) {
+  V8TestingScope v8_scope;
+
+  std::unique_ptr<MockTransformableVideoFrame> frame =
+      std::make_unique<NiceMock<MockTransformableVideoFrame>>();
+  MockVP8Metadata(frame.get());
+  EXPECT_CALL(*frame, GetTimestamp()).WillRepeatedly(Return(1));
+
+  RTCEncodedVideoFrame encoded_frame(std::move(frame));
+  DummyExceptionStateForTesting exception_state;
+  RTCEncodedVideoFrame* new_frame =
+      RTCEncodedVideoFrame::Create(&encoded_frame, exception_state);
+
+  EXPECT_FALSE(exception_state.HadException()) << exception_state.Message();
+
+  EXPECT_EQ(new_frame->getMetadata()->frameId(),
+            encoded_frame.getMetadata()->frameId());
+  EXPECT_EQ(new_frame->getMetadata()->dependencies(),
+            encoded_frame.getMetadata()->dependencies());
+  EXPECT_EQ(new_frame->getMetadata()->width(),
+            encoded_frame.getMetadata()->width());
+  EXPECT_EQ(new_frame->getMetadata()->height(),
+            encoded_frame.getMetadata()->height());
+  EXPECT_EQ(new_frame->getMetadata()->spatialIndex(),
+            encoded_frame.getMetadata()->spatialIndex());
+  EXPECT_EQ(new_frame->getMetadata()->temporalIndex(),
+            encoded_frame.getMetadata()->temporalIndex());
+  EXPECT_EQ(new_frame->getMetadata()->synchronizationSource(),
+            encoded_frame.getMetadata()->synchronizationSource());
+  EXPECT_EQ(new_frame->getMetadata()->contributingSources(),
+            encoded_frame.getMetadata()->contributingSources());
+  EXPECT_EQ(new_frame->getMetadata()->rtpTimestamp(),
+            encoded_frame.getMetadata()->rtpTimestamp());
+}
+
+TEST_F(RTCEncodedVideoFrameTest, ConstructorWithMetadataGetsNewMetadata) {
+  V8TestingScope v8_scope;
+
+  std::unique_ptr<MockTransformableVideoFrame> frame =
+      std::make_unique<NiceMock<MockTransformableVideoFrame>>();
+  MockVP8Metadata(frame.get());
+  EXPECT_CALL(*frame, GetPayloadType()).WillRepeatedly(Return(1));
+
+  RTCEncodedVideoFrame encoded_frame(std::move(frame));
+  RTCEncodedVideoFrameMetadata* new_metadata = CreateMetadata();
+
+  DummyExceptionStateForTesting exception_state;
+  RTCEncodedVideoFrame* new_frame = RTCEncodedVideoFrame::Create(
+      &encoded_frame, new_metadata, exception_state);
+
+  EXPECT_FALSE(exception_state.HadException()) << exception_state.Message();
+
+  // |new_frame|'s metadata is same as |new_metadata|.
+  EXPECT_EQ(new_frame->getMetadata()->frameId(), new_metadata->frameId());
+  Vector<int64_t> actual_dependencies;
+  for (const auto& dependency : new_frame->getMetadata()->dependencies()) {
+    actual_dependencies.push_back(dependency);
+  }
+  EXPECT_EQ(actual_dependencies, new_metadata->dependencies());
+  EXPECT_EQ(new_frame->getMetadata()->width(), new_metadata->width());
+  EXPECT_EQ(new_frame->getMetadata()->height(), new_metadata->height());
+  EXPECT_EQ(new_frame->getMetadata()->spatialIndex(),
+            new_metadata->spatialIndex());
+  EXPECT_EQ(new_frame->getMetadata()->temporalIndex(),
+            new_metadata->temporalIndex());
+  EXPECT_EQ(new_frame->getMetadata()->synchronizationSource(),
+            new_metadata->synchronizationSource());
+  Vector<uint32_t> actual_csrcs;
+  for (const auto& dependency :
+       new_frame->getMetadata()->contributingSources()) {
+    actual_csrcs.push_back(dependency);
+  }
+  EXPECT_EQ(actual_csrcs, new_metadata->contributingSources());
+
+  // |new_frame|'s metadata is different from original |encoded_frame|'s
+  // metadata.
+  EXPECT_NE(new_frame->getMetadata()->frameId(),
+            encoded_frame.getMetadata()->frameId());
+  EXPECT_NE(new_frame->getMetadata()->dependencies(),
+            encoded_frame.getMetadata()->dependencies());
+  EXPECT_NE(new_frame->getMetadata()->rtpTimestamp(),
+            encoded_frame.getMetadata()->rtpTimestamp());
+}
+
+TEST_F(RTCEncodedVideoFrameTest,
+       ConstructorWithMetadataDoesNotAllowChangingPayloadType) {
+  V8TestingScope v8_scope;
+
+  std::unique_ptr<MockTransformableVideoFrame> frame =
+      std::make_unique<NiceMock<MockTransformableVideoFrame>>();
+  MockVP8Metadata(frame.get());
+
+  webrtc::VideoFrameMetadata actual_metadata;
+  EXPECT_CALL(*frame, SetMetadata(_)).Times(0);
+  EXPECT_CALL(*frame, GetPayloadType()).WillRepeatedly(Return(14));
+
+  RTCEncodedVideoFrame encoded_frame(std::move(frame));
+
+  RTCEncodedVideoFrameMetadata* new_metadata = CreateMetadata();
+
+  DummyExceptionStateForTesting exception_state;
+  RTCEncodedVideoFrame* new_frame = RTCEncodedVideoFrame::Create(
+      &encoded_frame, new_metadata, exception_state);
+  EXPECT_TRUE(exception_state.HadException());
+  EXPECT_EQ(exception_state.Message(),
+            "Cannot create a new VideoFrame: invalid modification of "
+            "payloadType in RTCEncodedVideoFrameMetadata.");
+  EXPECT_EQ(new_frame, nullptr);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/web_tests/external/wpt/webrtc-encoded-transform/idlharness.https.window-expected.txt b/third_party/blink/web_tests/external/wpt/webrtc-encoded-transform/idlharness.https.window-expected.txt
index 4ffb4e7..0ea397e 100644
--- a/third_party/blink/web_tests/external/wpt/webrtc-encoded-transform/idlharness.https.window-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/webrtc-encoded-transform/idlharness.https.window-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 30 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 31 FAIL, 0 TIMEOUT, 0 NOTRUN.
 [FAIL] SFrameTransform interface: existence and properties of interface object
   assert_own_property: self does not have own property "SFrameTransform" expected property "SFrameTransform" missing
 [FAIL] SFrameTransform interface object length
@@ -34,6 +34,8 @@
   assert_own_property: self does not have own property "SFrameTransformErrorEvent" expected property "SFrameTransformErrorEvent" missing
 [FAIL] SFrameTransformErrorEvent interface: attribute frame
   assert_own_property: self does not have own property "SFrameTransformErrorEvent" expected property "SFrameTransformErrorEvent" missing
+[FAIL] RTCEncodedVideoFrame interface object length
+  assert_equals: wrong value for RTCEncodedVideoFrame.length expected 0 but got 1
 [FAIL] RTCRtpScriptTransform interface: existence and properties of interface object
   assert_own_property: self does not have own property "RTCRtpScriptTransform" expected property "RTCRtpScriptTransform" missing
 [FAIL] RTCRtpScriptTransform interface object length
diff --git a/third_party/blink/web_tests/external/wpt/webrtc-encoded-transform/set-metadata.https.html b/third_party/blink/web_tests/external/wpt/webrtc-encoded-transform/set-metadata.https.html
deleted file mode 100644
index c469959..0000000
--- a/third_party/blink/web_tests/external/wpt/webrtc-encoded-transform/set-metadata.https.html
+++ /dev/null
@@ -1,53 +0,0 @@
-<!DOCTYPE html>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src=/resources/testdriver.js></script>
-<script src=/resources/testdriver-vendor.js></script>
-<script src='../mediacapture-streams/permission-helper.js'></script>
-<script src="../webrtc/RTCPeerConnection-helper.js"></script>
-<script src="../service-workers/service-worker/resources/test-helpers.sub.js"></script>
-<script src='./helper.js'></script>
-<script>
-"use strict";
-
-promise_test(async t => {
-  const senderReader = await setupLoopbackWithCodecAndGetReader(t, 'VP8');
-  const result = await senderReader.read();
-  const metadata = result.value.getMetadata();
-  // TODO(https://crbug.com/webrtc/14709): When RTCEncodedVideoFrame has a
-  // constructor, create a new frame from scratch instead of cloning it to
-  // ensure that none of the metadata was carried over via structuredClone().
-  // This would allow us to be confident that setMetadata() is doing all the
-  // work.
-  //
-  // At that point, we can refactor the structuredClone() implementation to be
-  // the same as constructor() + set data + setMetadata() to ensure that
-  // structuredClone() cannot do things that are not already exposed in
-  // JavaScript (no secret steps!).
-  const clone = structuredClone(result.value);
-  clone.setMetadata(metadata);
-  const cloneMetadata = clone.getMetadata();
-  // Encoding related metadata.
-  assert_equals(cloneMetadata.frameId, metadata.frameId, 'frameId');
-  assert_array_equals(cloneMetadata.dependencies, metadata.dependencies,
-                      'dependencies');
-  assert_equals(cloneMetadata.width, metadata.width, 'width');
-  assert_equals(cloneMetadata.height, metadata.height, 'height');
-  assert_equals(cloneMetadata.spatialIndex, metadata.spatialIndex,
-                'spatialIndex');
-  assert_equals(cloneMetadata.temporalIndex, metadata.temporalIndex,
-                'temporalIndex');
-  // RTP related metadata.
-  // TODO(https://crbug.com/webrtc/14709): This information also needs to be
-  // settable but isn't - the assertions only pass because structuredClone()
-  // copies them for us. It would be great if different layers didn't consider
-  // different subset of the struct as "the metadata". Can we consolidate into a
-  // single webrtc struct for all metadata?
-  assert_equals(cloneMetadata.synchronizationSource,
-                metadata.synchronizationSource, 'synchronizationSource');
-  assert_array_equals(cloneMetadata.contributingSources,
-                      metadata.contributingSources, 'contributingSources');
-  assert_equals(cloneMetadata.payloadType, metadata.payloadType, 'payloadType');
-}, "[VP8] setMetadata() carries over codec-specific properties");
-
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/webrtc-encoded-transform/tentative/RTCEncodedVideoFrame-metadata.https.html b/third_party/blink/web_tests/external/wpt/webrtc-encoded-transform/tentative/RTCEncodedVideoFrame-metadata.https.html
new file mode 100644
index 0000000..a2c684c1
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webrtc-encoded-transform/tentative/RTCEncodedVideoFrame-metadata.https.html
@@ -0,0 +1,140 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=/resources/testdriver.js></script>
+<script src=/resources/testdriver-vendor.js></script>
+<script src='../../mediacapture-streams/permission-helper.js'></script>
+<script src="../../webrtc/RTCPeerConnection-helper.js"></script>
+<script src="../../service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script src='./../helper.js'></script>
+<script>
+"use strict";
+
+promise_test(async t => {
+  const senderReader = await setupLoopbackWithCodecAndGetReader(t, 'VP8');
+  const result = await senderReader.read();
+  const metadata = result.value.getMetadata();
+  // TODO(https://crbug.com/webrtc/14709): When RTCEncodedVideoFrame has a
+  // constructor, create a new frame from scratch instead of cloning it to
+  // ensure that none of the metadata was carried over via structuredClone().
+  // This would allow us to be confident that setMetadata() is doing all the
+  // work.
+  //
+  // At that point, we can refactor the structuredClone() implementation to be
+  // the same as constructor() + set data + setMetadata() to ensure that
+  // structuredClone() cannot do things that are not already exposed in
+  // JavaScript (no secret steps!).
+  const clone = structuredClone(result.value);
+  clone.setMetadata(metadata);
+  const cloneMetadata = clone.getMetadata();
+  // Encoding-related metadata.
+  assert_equals(cloneMetadata.frameId, metadata.frameId, 'frameId');
+  assert_array_equals(cloneMetadata.dependencies, metadata.dependencies,
+                      'dependencies');
+  assert_equals(cloneMetadata.width, metadata.width, 'width');
+  assert_equals(cloneMetadata.height, metadata.height, 'height');
+  assert_equals(cloneMetadata.spatialIndex, metadata.spatialIndex,
+                'spatialIndex');
+  assert_equals(cloneMetadata.temporalIndex, metadata.temporalIndex,
+                'temporalIndex');
+  // RTP-related metadata.
+  // TODO(https://crbug.com/webrtc/14709): This information also needs to be
+  // settable but isn't - the assertions only pass because structuredClone()
+  // copies them for us. It would be great if different layers didn't consider
+  // different subset of the struct as "the metadata". Can we consolidate into a
+  // single webrtc struct for all metadata?
+  assert_equals(cloneMetadata.synchronizationSource,
+                metadata.synchronizationSource, 'synchronizationSource');
+  assert_array_equals(cloneMetadata.contributingSources,
+                      metadata.contributingSources, 'contributingSources');
+  assert_equals(cloneMetadata.payloadType, metadata.payloadType, 'payloadType');
+}, "[VP8] setMetadata() carries over codec-specific properties");
+
+promise_test(async t => {
+  const senderReader = await setupLoopbackWithCodecAndGetReader(t, 'VP8');
+  const result = await senderReader.read();
+  const metadata = result.value.getMetadata();
+  const newFrame = new RTCEncodedVideoFrame(result.value);
+  const newMetadata = newFrame.getMetadata();
+
+  // Encoding-related metadata.
+  assert_equals(newMetadata.frameId, metadata.frameId, 'frameId');
+  assert_array_equals(newMetadata.dependencies, metadata.dependencies,
+                      'dependencies');
+  assert_equals(newMetadata.width, metadata.width, 'width');
+  assert_equals(newMetadata.height, metadata.height, 'height');
+  assert_equals(newMetadata.spatialIndex, metadata.spatialIndex,
+                'spatialIndex');
+  assert_equals(newMetadata.temporalIndex, metadata.temporalIndex,
+                'temporalIndex');
+
+  // RTP-related metadata.
+  assert_equals(newMetadata.synchronizationSource,
+                metadata.synchronizationSource, 'synchronizationSource');
+  assert_array_equals(newMetadata.contributingSources,
+                      metadata.contributingSources, 'contributingSources');
+  assert_equals(newMetadata.payloadType, metadata.payloadType, 'payloadType');
+  assert_equals(newMetadata.rtpTimestamp, metadata.rtpTimestamp, 'rtpTimestamp');
+}, "[VP8] constructor carries over codec-specific properties");
+
+promise_test(async t => {
+  const senderReader = await setupLoopbackWithCodecAndGetReader(t, 'VP8');
+  const result = await senderReader.read();
+  const metadata = result.value.getMetadata();
+  metadata.rtpTimestamp = 100;
+  const newFrame = new RTCEncodedVideoFrame(result.value, metadata);
+  const newMetadata = newFrame.getMetadata();
+
+  // Encoding-related metadata.
+  assert_equals(newMetadata.frameId, metadata.frameId, 'frameId');
+  assert_array_equals(newMetadata.dependencies, metadata.dependencies,
+                      'dependencies');
+  assert_equals(newMetadata.width, metadata.width, 'width');
+  assert_equals(newMetadata.height, metadata.height, 'height');
+  assert_equals(newMetadata.spatialIndex, metadata.spatialIndex,
+                'spatialIndex');
+  assert_equals(newMetadata.temporalIndex, metadata.temporalIndex,
+                'temporalIndex');
+
+  // RTP-related metadata.
+  assert_equals(newMetadata.synchronizationSource,
+                metadata.synchronizationSource, 'synchronizationSource');
+  assert_array_equals(newMetadata.contributingSources,
+                      metadata.contributingSources, 'contributingSources');
+  assert_equals(newMetadata.payloadType, metadata.payloadType, 'payloadType');
+  assert_equals(newMetadata.rtpTimestamp, metadata.rtpTimestamp, 'rtpTimestamp');
+  assert_not_equals(newMetadata.rtpTimestamp, result.value.getMetadata().rtpTimestamp, 'rtpTimestamp');
+}, "[VP8] constructor with metadata carries over codec-specific properties");
+
+promise_test(async t => {
+  const senderReader = await setupLoopbackWithCodecAndGetReader(t, 'VP8');
+  const result = await senderReader.read();
+  const metadata = result.value.getMetadata();
+  // Change the metadata by setting a new RTPTimestamp.
+  metadata.rtpTimestamp = 100;
+  const newFrame = new RTCEncodedVideoFrame(result.value);
+  const newMetadata = newFrame.getMetadata();
+
+  // Encoding-related metadata.
+  assert_equals(newMetadata.frameId, metadata.frameId, 'frameId');
+  assert_array_equals(newMetadata.dependencies, metadata.dependencies,
+                      'dependencies');
+  assert_equals(newMetadata.width, metadata.width, 'width');
+  assert_equals(newMetadata.height, metadata.height, 'height');
+  assert_equals(newMetadata.spatialIndex, metadata.spatialIndex,
+                'spatialIndex');
+  assert_equals(newMetadata.temporalIndex, metadata.temporalIndex,
+                'temporalIndex');
+
+  // RTP-related metadata.
+  assert_equals(newMetadata.synchronizationSource,
+                metadata.synchronizationSource, 'synchronizationSource');
+  assert_array_equals(newMetadata.contributingSources,
+                      metadata.contributingSources, 'contributingSources');
+  assert_equals(newMetadata.payloadType, metadata.payloadType, 'payloadType');
+
+  // RTPTimestamp don't match because the constructor did not use the metadata for construction.
+  assert_not_equals(newMetadata.rtpTimestamp, metadata.rtpTimestamp, 'rtpTimestamp');
+  assert_equals(newMetadata.rtpTimestamp, result.value.getMetadata().rtpTimestamp, 'rtpTimestamp');
+}, "[VP8] constructor without metadata does not carry over modified metadata ");
+</script>
diff --git a/third_party/blink/web_tests/fast/peerconnection/RTCEncodedVideoFrame-set-metadata-expected.txt b/third_party/blink/web_tests/fast/peerconnection/RTCEncodedVideoFrame-set-metadata-expected.txt
index bce5755..5778c9a 100644
--- a/third_party/blink/web_tests/fast/peerconnection/RTCEncodedVideoFrame-set-metadata-expected.txt
+++ b/third_party/blink/web_tests/fast/peerconnection/RTCEncodedVideoFrame-set-metadata-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
 [FAIL] setMetadata() allows changes when AllowRTCEncodedVideoFrameSetMetadataAllFields is enabled
-  promise_test: Unhandled rejection with value: object "InvalidModificationError: Failed to execute 'setMetadata' on 'RTCEncodedVideoFrame': Invalid modification of RTCEncodedVideoFrameMetadata."
+  promise_test: Unhandled rejection with value: object "InvalidModificationError: Failed to execute 'setMetadata' on 'RTCEncodedVideoFrame': Cannot setMetadata: invalid modification of RTCEncodedVideoFrameMetadata."
 Harness: the test ran to completion.
 
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 63f2684..56cf38b9 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -11304,6 +11304,7 @@
   <int value="4858"
       label="StorageAccessAPI_requestStorageAccess_BeyondCookies_SharedWorker_Use"/>
   <int value="4859" label="V8PointerEvent_GetCoalescedEvents_Method"/>
+  <int value="4860" label="V8RTCEncodedVideoFrame_Constructor"/>
   <int value="4861" label="CSSFunctions"/>
   <int value="4862" label="CSSPageRule"/>
   <int value="4863"