commit e37d4c6dc5f50ccc92cfce0a8bb8622780a85703
parent 17b8e797b622c8e5c2f05ab8c30dcce401ff5981
Author: Jan-Ivar Bruaroey <jib@mozilla.com>
Date: Wed, 8 Oct 2025 00:14:02 +0000
Bug 1868223 - Implement custom RTCEncodedAudioFrame::Read/WriteStructuredClone for same process. r=bwc
Differential Revision: https://phabricator.services.mozilla.com/D267544
Diffstat:
4 files changed, 125 insertions(+), 20 deletions(-)
diff --git a/dom/base/StructuredCloneHolder.cpp b/dom/base/StructuredCloneHolder.cpp
@@ -53,6 +53,8 @@
#include "mozilla/dom/MessagePortBinding.h"
#include "mozilla/dom/OffscreenCanvas.h"
#include "mozilla/dom/OffscreenCanvasBinding.h"
+#include "mozilla/dom/RTCEncodedAudioFrame.h"
+#include "mozilla/dom/RTCEncodedAudioFrameBinding.h"
#include "mozilla/dom/RTCEncodedVideoFrame.h"
#include "mozilla/dom/RTCEncodedVideoFrameBinding.h"
#include "mozilla/dom/ReadableStream.h"
@@ -1169,6 +1171,17 @@ JSObject* StructuredCloneHolder::CustomReadHandler(
}
}
+ if (StaticPrefs::media_peerconnection_enabled() &&
+ aTag == SCTAG_DOM_RTCENCODEDAUDIOFRAME &&
+ CloneScope() == StructuredCloneScope::SameProcess &&
+ aCloneDataPolicy.areIntraClusterClonableSharedObjectsAllowed()) {
+ JS::Rooted<JSObject*> global(aCx, mGlobal->GetGlobalJSObject());
+ if (RTCEncodedAudioFrame_Binding::ConstructorEnabled(aCx, global)) {
+ return RTCEncodedAudioFrame::ReadStructuredClone(
+ aCx, mGlobal, aReader, RtcEncodedAudioFrames()[aIndex]);
+ }
+ }
+
return ReadFullySerializableObjects(aCx, aReader, aTag, false);
}
@@ -1322,6 +1335,17 @@ bool StructuredCloneHolder::CustomWriteHandler(
}
}
+ // See if this is an RTCEncodedAudioFrame object.
+ if (StaticPrefs::media_peerconnection_enabled()) {
+ RTCEncodedAudioFrame* rtcFrame = nullptr;
+ if (NS_SUCCEEDED(UNWRAP_OBJECT(RTCEncodedAudioFrame, &obj, rtcFrame))) {
+ SameProcessScopeRequired(aSameProcessScopeRequired);
+ return CloneScope() == StructuredCloneScope::SameProcess
+ ? rtcFrame->WriteStructuredClone(aWriter, this)
+ : false;
+ }
+ }
+
{
// We only care about streams, so ReflectorToISupportsStatic is fine.
nsCOMPtr<nsISupports> base = xpc::ReflectorToISupportsStatic(aObj);
diff --git a/dom/base/StructuredCloneHolder.h b/dom/base/StructuredCloneHolder.h
@@ -173,6 +173,7 @@ class MessagePortIdentifier;
struct VideoFrameSerializedData;
struct AudioDataSerializedData;
struct RTCEncodedVideoFrameData;
+struct RTCEncodedAudioFrameData;
class StructuredCloneHolder : public StructuredCloneHolderBase {
public:
@@ -225,6 +226,7 @@ class StructuredCloneHolder : public StructuredCloneHolderBase {
STMT(mEncodedVideoChunks); \
STMT(mEncodedAudioChunks); \
STMT(mRtcEncodedVideoFrames); \
+ STMT(mRtcEncodedAudioFrames); \
STMT(mPortIdentifiers);
// Call this method to know if this object is keeping some DOM object alive.
@@ -308,6 +310,10 @@ class StructuredCloneHolder : public StructuredCloneHolderBase {
return mRtcEncodedVideoFrames;
}
+ nsTArray<RTCEncodedAudioFrameData>& RtcEncodedAudioFrames() {
+ return mRtcEncodedAudioFrames;
+ }
+
// Implementations of the virtual methods to allow cloning of objects which
// JS engine itself doesn't clone.
@@ -425,6 +431,9 @@ class StructuredCloneHolder : public StructuredCloneHolderBase {
// Used for cloning RTCEncodedVideoFrame in the structured cloning algorithm.
nsTArray<RTCEncodedVideoFrameData> mRtcEncodedVideoFrames;
+ // Used for cloning RTCEncodedAudioFrame in the structured cloning algorithm.
+ nsTArray<RTCEncodedAudioFrameData> mRtcEncodedAudioFrames;
+
// This raw pointer is only set within ::Read() and is unset by the end.
nsIGlobalObject* MOZ_NON_OWNING_REF mGlobal;
diff --git a/dom/media/webrtc/jsapi/RTCEncodedAudioFrame.cpp b/dom/media/webrtc/jsapi/RTCEncodedAudioFrame.cpp
@@ -11,6 +11,7 @@
#include <memory>
#include <utility>
+#include "api/frame_transformer_factory.h"
#include "api/frame_transformer_interface.h"
#include "js/RootingAPI.h"
#include "jsapi/RTCEncodedFrameBase.h"
@@ -20,6 +21,8 @@
#include "mozilla/Unused.h"
#include "mozilla/dom/RTCEncodedAudioFrameBinding.h"
#include "mozilla/dom/RTCRtpScriptTransformer.h"
+#include "mozilla/dom/StructuredCloneHolder.h"
+#include "mozilla/dom/StructuredCloneTags.h"
#include "mozilla/fallible.h"
#include "nsContentUtils.h"
#include "nsCycleCollectionParticipant.h"
@@ -29,8 +32,20 @@
namespace mozilla::dom {
-NS_IMPL_CYCLE_COLLECTION_INHERITED(RTCEncodedAudioFrame, RTCEncodedFrameBase,
- mOwner)
+NS_IMPL_CYCLE_COLLECTION_CLASS(RTCEncodedAudioFrame)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(RTCEncodedAudioFrame,
+ RTCEncodedFrameBase)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(RTCEncodedAudioFrame,
+ RTCEncodedFrameBase)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(RTCEncodedAudioFrame,
+ RTCEncodedFrameBase)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_IMPL_ADDREF_INHERITED(RTCEncodedAudioFrame, RTCEncodedFrameBase)
NS_IMPL_RELEASE_INHERITED(RTCEncodedAudioFrame, RTCEncodedFrameBase)
@@ -48,7 +63,7 @@ RTCEncodedAudioFrame::RTCEncodedAudioFrame(
mMetadata.mSynchronizationSource.Construct(mFrame->GetSsrc());
mMetadata.mPayloadType.Construct(mFrame->GetPayloadType());
const auto& audioFrame(
- static_cast<webrtc::TransformableAudioFrameInterface&>(*mState.mFrame));
+ static_cast<webrtc::TransformableAudioFrameInterface&>(*mFrame));
mMetadata.mContributingSources.Construct();
for (const auto csrc : audioFrame.GetContributingSources()) {
Unused << mMetadata.mContributingSources.Value().AppendElement(csrc,
@@ -63,7 +78,22 @@ RTCEncodedAudioFrame::RTCEncodedAudioFrame(
mozilla::HoldJSObjects(this);
}
+RTCEncodedAudioFrame::RTCEncodedAudioFrame(nsIGlobalObject* aGlobal,
+ RTCEncodedAudioFrameData&& aData)
+ : RTCEncodedAudioFrameData{{std::move(aData.mFrame), aData.mCounter,
+ aData.mTimestamp},
+ std::move(aData.mMetadata)},
+ RTCEncodedFrameBase(aGlobal, static_cast<RTCEncodedFrameState&>(*this)),
+ mOwner(nullptr) {
+ // Base class needs this, but can't do it itself because of an assertion in
+ // the cycle-collector.
+ mozilla::HoldJSObjects(this);
+}
+
RTCEncodedAudioFrame::~RTCEncodedAudioFrame() {
+ // Clear JS::Heap<> members before unregistering as a script holder,
+ // so their destructors don't barrier against a finalized JS object.
+ mData = nullptr; // from RTCEncodedFrameBase (protected)
// Base class needs this, but can't do it itself because of an assertion in
// the cycle-collector.
mozilla::DropJSObjects(this);
@@ -74,6 +104,14 @@ JSObject* RTCEncodedAudioFrame::WrapObject(JSContext* aCx,
return RTCEncodedAudioFrame_Binding::Wrap(aCx, this, aGivenProto);
}
+RTCEncodedAudioFrameData RTCEncodedAudioFrameData::Clone() const {
+ return RTCEncodedAudioFrameData{
+ {webrtc::CloneAudioFrame(
+ static_cast<webrtc::TransformableAudioFrameInterface*>(
+ mFrame.get()))},
+ RTCEncodedAudioFrameMetadata(mMetadata)};
+}
+
nsIGlobalObject* RTCEncodedAudioFrame::GetParentObject() const {
return mGlobal;
}
@@ -89,17 +127,40 @@ bool RTCEncodedAudioFrame::CheckOwner(RTCRtpScriptTransformer* aOwner) const {
// https://www.w3.org/TR/webrtc-encoded-transform/#RTCEncodedAudioFrame-serialization
/* static */
-already_AddRefed<RTCEncodedAudioFrame>
-RTCEncodedAudioFrame::ReadStructuredClone(JSContext* aCx,
- nsIGlobalObject* aGlobal,
- JSStructuredCloneReader* aReader) {
- // TBD implementation to follow in followup patch
- return nullptr;
+JSObject* RTCEncodedAudioFrame::ReadStructuredClone(
+ JSContext* aCx, nsIGlobalObject* aGlobal, JSStructuredCloneReader* aReader,
+ RTCEncodedAudioFrameData& aData) {
+ JS::Rooted<JS::Value> value(aCx, JS::NullValue());
+ // To avoid a rooting hazard error from returning a raw JSObject* before
+ // running the RefPtr destructor, RefPtr needs to be destructed before
+ // returning the raw JSObject*, which is why the RefPtr<RTCEncodedAudioFrame>
+ // is created in the scope below. Otherwise, the static analysis infers the
+ // RefPtr cannot be safely destructed while the unrooted return JSObject* is
+ // on the stack.
+ {
+ auto frame = MakeRefPtr<RTCEncodedAudioFrame>(aGlobal, std::move(aData));
+ if (!GetOrCreateDOMReflector(aCx, frame, &value) || !value.isObject()) {
+ return nullptr;
+ }
+ }
+ return value.toObjectOrNull();
}
bool RTCEncodedAudioFrame::WriteStructuredClone(
- JSContext* aCx, JSStructuredCloneWriter* aWriter) const {
- return false;
+ JSStructuredCloneWriter* aWriter, StructuredCloneHolder* aHolder) const {
+ AssertIsOnOwningThread();
+
+ // Indexing the chunk and send the index to the receiver.
+ const uint32_t index =
+ static_cast<uint32_t>(aHolder->RtcEncodedAudioFrames().Length());
+ // The serialization is limited to the same process scope so it's ok to
+ // hand over a (copy of a) webrtc internal object here.
+ //
+ // TODO: optimize later once encoded source API materializes
+ // .AppendElement(aHolder->IsTransferred(mData) ? Take() : Clone())
+ aHolder->RtcEncodedAudioFrames().AppendElement(Clone());
+ return !NS_WARN_IF(
+ !JS_WriteUint32Pair(aWriter, SCTAG_DOM_RTCENCODEDAUDIOFRAME, index));
}
} // namespace mozilla::dom
diff --git a/dom/media/webrtc/jsapi/RTCEncodedAudioFrame.h b/dom/media/webrtc/jsapi/RTCEncodedAudioFrame.h
@@ -7,15 +7,19 @@
#ifndef MOZILLA_DOM_MEDIA_WEBRTC_JSAPI_RTCENCODEDAUDIOFRAME_H_
#define MOZILLA_DOM_MEDIA_WEBRTC_JSAPI_RTCENCODEDAUDIOFRAME_H_
-#include "jsapi/RTCEncodedFrameBase.h"
#include "mozilla/RefPtr.h"
#include "mozilla/dom/RTCEncodedAudioFrameBinding.h"
+#include "mozilla/dom/RTCEncodedFrameBase.h"
#include "nsIGlobalObject.h"
namespace mozilla::dom {
+class StructuredCloneHolder;
+
struct RTCEncodedAudioFrameData : RTCEncodedFrameState {
RTCEncodedAudioFrameMetadata mMetadata;
+
+ [[nodiscard]] RTCEncodedAudioFrameData Clone() const;
};
// Wraps a libwebrtc frame, allowing the frame buffer to be modified, and
@@ -30,10 +34,13 @@ class RTCEncodedAudioFrame final : public RTCEncodedAudioFrameData,
std::unique_ptr<webrtc::TransformableFrameInterface> aFrame,
uint64_t aCounter, RTCRtpScriptTransformer* aOwner);
+ explicit RTCEncodedAudioFrame(nsIGlobalObject* aGlobal,
+ RTCEncodedAudioFrameData&& aData);
+
// nsISupports
NS_DECL_ISUPPORTS_INHERITED
- NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(RTCEncodedAudioFrame,
- RTCEncodedFrameBase)
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(RTCEncodedAudioFrame,
+ RTCEncodedFrameBase)
// webidl (timestamp and data accessors live in base class)
JSObject* WrapObject(JSContext* aCx,
@@ -47,12 +54,11 @@ class RTCEncodedAudioFrame final : public RTCEncodedAudioFrameData,
bool IsVideo() const override { return false; }
- // [Serializable] implementations: {Read, Write}StructuredClone
- static already_AddRefed<RTCEncodedAudioFrame> ReadStructuredClone(
- JSContext* aCx, nsIGlobalObject* aGlobal,
- JSStructuredCloneReader* aReader);
- bool WriteStructuredClone(JSContext* aCx,
- JSStructuredCloneWriter* aWriter) const;
+ static JSObject* ReadStructuredClone(JSContext* aCx, nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader,
+ RTCEncodedAudioFrameData& aData);
+ bool WriteStructuredClone(JSStructuredCloneWriter* aWriter,
+ StructuredCloneHolder* aHolder) const;
private:
virtual ~RTCEncodedAudioFrame();
@@ -63,6 +69,11 @@ class RTCEncodedAudioFrame final : public RTCEncodedAudioFrameData,
RTCEncodedAudioFrame(RTCEncodedAudioFrame&&) = delete;
RTCEncodedAudioFrame& operator=(RTCEncodedAudioFrame&&) = delete;
+ // RTCEncodedAudioFrame can run on either main thread or worker thread.
+ void AssertIsOnOwningThread() const {
+ NS_ASSERT_OWNINGTHREAD(RTCEncodedAudioFrame);
+ }
+
RefPtr<RTCRtpScriptTransformer> mOwner;
};