tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 17b8e797b622c8e5c2f05ab8c30dcce401ff5981
parent 5c0daa9ed9ff148beb20b22345c7c105173c4008
Author: Jan-Ivar Bruaroey <jib@mozilla.com>
Date:   Wed,  8 Oct 2025 00:14:01 +0000

Bug 1868223 - Implement custom RTCEncodedVideoFrame::Read/WriteStructuredClone for same process. r=bwc,webidl,smaug

Differential Revision: https://phabricator.services.mozilla.com/D267543

Diffstat:
Mdom/base/StructuredCloneHolder.cpp | 25+++++++++++++++++++++++++
Mdom/base/StructuredCloneHolder.h | 27++++++++++++++++++---------
Mdom/base/StructuredCloneTags.h | 4++++
Mdom/media/webrtc/jsapi/RTCEncodedAudioFrame.cpp | 21+++++++++++++++++++--
Mdom/media/webrtc/jsapi/RTCEncodedAudioFrame.h | 22++++++++++++++++++++--
Mdom/media/webrtc/jsapi/RTCEncodedFrameBase.cpp | 51+++++++++++++++++++++++++++++++--------------------
Mdom/media/webrtc/jsapi/RTCEncodedFrameBase.h | 45++++++++++++++++++++++++++++++++++++---------
Mdom/media/webrtc/jsapi/RTCEncodedVideoFrame.cpp | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mdom/media/webrtc/jsapi/RTCEncodedVideoFrame.h | 47+++++++++++++++++++++++++++++++++++++++--------
Mdom/media/webrtc/jsapi/RTCRtpScriptTransformer.cpp | 5++++-
Mdom/webidl/RTCEncodedAudioFrame.webidl | 2++
Mdom/webidl/RTCEncodedVideoFrame.webidl | 3+++
12 files changed, 295 insertions(+), 61 deletions(-)

diff --git a/dom/base/StructuredCloneHolder.cpp b/dom/base/StructuredCloneHolder.cpp @@ -22,6 +22,7 @@ #include "mozilla/RefPtr.h" #include "mozilla/ScopeExit.h" #include "mozilla/StaticPrefs_dom.h" +#include "mozilla/StaticPrefs_media.h" #include "mozilla/dom/AudioData.h" #include "mozilla/dom/BindingDeclarations.h" #include "mozilla/dom/BindingUtils.h" @@ -52,6 +53,8 @@ #include "mozilla/dom/MessagePortBinding.h" #include "mozilla/dom/OffscreenCanvas.h" #include "mozilla/dom/OffscreenCanvasBinding.h" +#include "mozilla/dom/RTCEncodedVideoFrame.h" +#include "mozilla/dom/RTCEncodedVideoFrameBinding.h" #include "mozilla/dom/ReadableStream.h" #include "mozilla/dom/ReadableStreamBinding.h" #include "mozilla/dom/ScriptSettings.h" @@ -1155,6 +1158,17 @@ JSObject* StructuredCloneHolder::CustomReadHandler( } } + if (StaticPrefs::media_peerconnection_enabled() && + aTag == SCTAG_DOM_RTCENCODEDVIDEOFRAME && + CloneScope() == StructuredCloneScope::SameProcess && + aCloneDataPolicy.areIntraClusterClonableSharedObjectsAllowed()) { + JS::Rooted<JSObject*> global(aCx, mGlobal->GetGlobalJSObject()); + if (RTCEncodedVideoFrame_Binding::ConstructorEnabled(aCx, global)) { + return RTCEncodedVideoFrame::ReadStructuredClone( + aCx, mGlobal, aReader, RtcEncodedVideoFrames()[aIndex]); + } + } + return ReadFullySerializableObjects(aCx, aReader, aTag, false); } @@ -1297,6 +1311,17 @@ bool StructuredCloneHolder::CustomWriteHandler( } } + // See if this is an RTCEncodedVideoFrame object. + if (StaticPrefs::media_peerconnection_enabled()) { + RTCEncodedVideoFrame* rtcFrame = nullptr; + if (NS_SUCCEEDED(UNWRAP_OBJECT(RTCEncodedVideoFrame, &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 @@ -172,6 +172,7 @@ class MessagePort; class MessagePortIdentifier; struct VideoFrameSerializedData; struct AudioDataSerializedData; +struct RTCEncodedVideoFrameData; class StructuredCloneHolder : public StructuredCloneHolderBase { public: @@ -214,15 +215,16 @@ class StructuredCloneHolder : public StructuredCloneHolderBase { // Create a statement for each of the side DOM-ish data members. // mTransferredPorts is not included because it is part of the // deserialized state. -#define CLONED_DATA_MEMBERS \ - STMT(mBlobImplArray); \ - STMT(mWasmModuleArray); \ - STMT(mInputStreamArray); \ - STMT(mClonedSurfaces); \ - STMT(mVideoFrames); \ - STMT(mAudioData); \ - STMT(mEncodedVideoChunks); \ - STMT(mEncodedAudioChunks); \ +#define CLONED_DATA_MEMBERS \ + STMT(mBlobImplArray); \ + STMT(mWasmModuleArray); \ + STMT(mInputStreamArray); \ + STMT(mClonedSurfaces); \ + STMT(mVideoFrames); \ + STMT(mAudioData); \ + STMT(mEncodedVideoChunks); \ + STMT(mEncodedAudioChunks); \ + STMT(mRtcEncodedVideoFrames); \ STMT(mPortIdentifiers); // Call this method to know if this object is keeping some DOM object alive. @@ -302,6 +304,10 @@ class StructuredCloneHolder : public StructuredCloneHolderBase { return mEncodedAudioChunks; } + nsTArray<RTCEncodedVideoFrameData>& RtcEncodedVideoFrames() { + return mRtcEncodedVideoFrames; + } + // Implementations of the virtual methods to allow cloning of objects which // JS engine itself doesn't clone. @@ -416,6 +422,9 @@ class StructuredCloneHolder : public StructuredCloneHolderBase { // Used for cloning EncodedAudioChunk in the structured cloning algorithm. nsTArray<EncodedAudioChunkData> mEncodedAudioChunks; + // Used for cloning RTCEncodedVideoFrame in the structured cloning algorithm. + nsTArray<RTCEncodedVideoFrameData> mRtcEncodedVideoFrames; + // This raw pointer is only set within ::Read() and is unset by the end. nsIGlobalObject* MOZ_NON_OWNING_REF mGlobal; diff --git a/dom/base/StructuredCloneTags.h b/dom/base/StructuredCloneTags.h @@ -163,6 +163,10 @@ enum StructuredCloneTags : uint32_t { SCTAG_DOM_RTCDATACHANNEL, + SCTAG_DOM_RTCENCODEDVIDEOFRAME, + + SCTAG_DOM_RTCENCODEDAUDIOFRAME, + // IMPORTANT: If you plan to add an new IDB tag, it _must_ be add before the // "less stable" tags! }; diff --git a/dom/media/webrtc/jsapi/RTCEncodedAudioFrame.cpp b/dom/media/webrtc/jsapi/RTCEncodedAudioFrame.cpp @@ -42,12 +42,13 @@ RTCEncodedAudioFrame::RTCEncodedAudioFrame( nsIGlobalObject* aGlobal, std::unique_ptr<webrtc::TransformableFrameInterface> aFrame, uint64_t aCounter, RTCRtpScriptTransformer* aOwner) - : RTCEncodedFrameBase(aGlobal, std::move(aFrame), aCounter), + : RTCEncodedAudioFrameData{{std::move(aFrame), aCounter, /*timestamp*/ 0}}, + RTCEncodedFrameBase(aGlobal, static_cast<RTCEncodedFrameState&>(*this)), mOwner(aOwner) { mMetadata.mSynchronizationSource.Construct(mFrame->GetSsrc()); mMetadata.mPayloadType.Construct(mFrame->GetPayloadType()); const auto& audioFrame( - static_cast<webrtc::TransformableAudioFrameInterface&>(*mFrame)); + static_cast<webrtc::TransformableAudioFrameInterface&>(*mState.mFrame)); mMetadata.mContributingSources.Construct(); for (const auto csrc : audioFrame.GetContributingSources()) { Unused << mMetadata.mContributingSources.Value().AppendElement(csrc, @@ -85,4 +86,20 @@ void RTCEncodedAudioFrame::GetMetadata( bool RTCEncodedAudioFrame::CheckOwner(RTCRtpScriptTransformer* aOwner) const { return aOwner == mOwner; } + +// 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; +} + +bool RTCEncodedAudioFrame::WriteStructuredClone( + JSContext* aCx, JSStructuredCloneWriter* aWriter) const { + return false; +} + } // namespace mozilla::dom diff --git a/dom/media/webrtc/jsapi/RTCEncodedAudioFrame.h b/dom/media/webrtc/jsapi/RTCEncodedAudioFrame.h @@ -14,11 +14,16 @@ namespace mozilla::dom { +struct RTCEncodedAudioFrameData : RTCEncodedFrameState { + RTCEncodedAudioFrameMetadata mMetadata; +}; + // Wraps a libwebrtc frame, allowing the frame buffer to be modified, and // providing read-only access to various metadata. After the libwebrtc frame is // extracted (with RTCEncodedFrameBase::TakeFrame), the frame buffer is // detached, but the metadata remains accessible. -class RTCEncodedAudioFrame final : public RTCEncodedFrameBase { +class RTCEncodedAudioFrame final : public RTCEncodedAudioFrameData, + public RTCEncodedFrameBase { public: explicit RTCEncodedAudioFrame( nsIGlobalObject* aGlobal, @@ -42,10 +47,23 @@ class RTCEncodedAudioFrame final : public RTCEncodedFrameBase { 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; + private: virtual ~RTCEncodedAudioFrame(); + + // forbid copy/move to keep mState member in base valid + RTCEncodedAudioFrame(const RTCEncodedAudioFrame&) = delete; + RTCEncodedAudioFrame& operator=(const RTCEncodedAudioFrame&) = delete; + RTCEncodedAudioFrame(RTCEncodedAudioFrame&&) = delete; + RTCEncodedAudioFrame& operator=(RTCEncodedAudioFrame&&) = delete; + RefPtr<RTCRtpScriptTransformer> mOwner; - RTCEncodedAudioFrameMetadata mMetadata; }; } // namespace mozilla::dom diff --git a/dom/media/webrtc/jsapi/RTCEncodedFrameBase.cpp b/dom/media/webrtc/jsapi/RTCEncodedFrameBase.cpp @@ -6,6 +6,7 @@ #include "jsapi/RTCEncodedFrameBase.h" +#include "api/frame_transformer_interface.h" #include "js/ArrayBuffer.h" #include "js/GCAPI.h" #include "mozilla/dom/ScriptSettings.h" @@ -21,14 +22,10 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCEncodedFrameBase) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END -RTCEncodedFrameBase::RTCEncodedFrameBase( - nsIGlobalObject* aGlobal, - std::unique_ptr<webrtc::TransformableFrameInterface> aFrame, - uint64_t aCounter) - : mGlobal(aGlobal), - mFrame(std::move(aFrame)), - mCounter(aCounter), - mTimestamp(mFrame->GetTimestamp()) { +RTCEncodedFrameBase::RTCEncodedFrameBase(nsIGlobalObject* aGlobal, + RTCEncodedFrameState& aState) + : mGlobal(aGlobal), mState(aState), mData(nullptr) { + mState.mTimestamp = mState.mFrame->GetTimestamp(); AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.Init(mGlobal))) { return; @@ -36,38 +33,52 @@ RTCEncodedFrameBase::RTCEncodedFrameBase( // Avoid a copy mData = JS::NewArrayBufferWithUserOwnedContents( - jsapi.cx(), mFrame->GetData().size(), (void*)(mFrame->GetData().data())); + jsapi.cx(), mState.mFrame->GetData().size(), + (void*)(mState.mFrame->GetData().data())); } RTCEncodedFrameBase::~RTCEncodedFrameBase() = default; -unsigned long RTCEncodedFrameBase::Timestamp() const { return mTimestamp; } +unsigned long RTCEncodedFrameBase::Timestamp() const { + return mState.mTimestamp; +} void RTCEncodedFrameBase::SetData(const ArrayBuffer& aData) { mData.set(aData.Obj()); - if (mFrame) { + if (mState.mFrame) { aData.ProcessData([&](const Span<uint8_t>& aData, JS::AutoCheckCannotGC&&) { - mFrame->SetData( + mState.mFrame->SetData( webrtc::ArrayView<const uint8_t>(aData.Elements(), aData.Length())); }); } } -void RTCEncodedFrameBase::GetData(JSContext* aCx, JS::Rooted<JSObject*>* aObj) { +void RTCEncodedFrameBase::GetData(JSContext* aCx, + JS::Rooted<JSObject*>* aObj) const { aObj->set(mData); } -uint64_t RTCEncodedFrameBase::GetCounter() const { return mCounter; } +uint64_t RTCEncodedFrameBase::GetCounter() const { return mState.mCounter; } std::unique_ptr<webrtc::TransformableFrameInterface> RTCEncodedFrameBase::TakeFrame() { - AutoJSAPI jsapi; - if (!jsapi.Init(mGlobal)) { - MOZ_CRASH("Could not init JSAPI!"); + if (mState.mFrame) { + AutoJSAPI jsapi; + if (!jsapi.Init(mGlobal)) { + MOZ_CRASH("Could not init JSAPI!"); + } + // If the JS buffer was transferred (or otherwise detached), neuter native. + JS::Rooted<JSObject*> rootedData(jsapi.cx(), mData); + if (rootedData && JS::IsDetachedArrayBufferObject(rootedData)) { + mState.mFrame.reset(); + return nullptr; + } + // Still attached: detach now since we're consuming the frame. + JS::DetachArrayBuffer(jsapi.cx(), rootedData); } - JS::Rooted<JSObject*> rootedData(jsapi.cx(), mData); - JS::DetachArrayBuffer(jsapi.cx(), rootedData); - return std::move(mFrame); + return std::move(mState.mFrame); } +RTCEncodedFrameState::~RTCEncodedFrameState() = default; + } // namespace mozilla::dom diff --git a/dom/media/webrtc/jsapi/RTCEncodedFrameBase.h b/dom/media/webrtc/jsapi/RTCEncodedFrameBase.h @@ -9,23 +9,40 @@ #include <memory> -#include "api/frame_transformer_interface.h" #include "js/TypeDecls.h" #include "mozilla/Assertions.h" #include "mozilla/dom/TypedArray.h" // ArrayBuffer class nsIGlobalObject; +namespace webrtc { +class TransformableFrameInterface; +} + namespace mozilla::dom { +struct RTCEncodedFrameState { + std::unique_ptr<webrtc::TransformableFrameInterface> mFrame; + uint64_t mCounter = 0; + unsigned long mTimestamp = 0; + + // work around only having forward-declared TransformableFrameInterface + ~RTCEncodedFrameState(); + + // avoid "move got disabled by a user-declared destructor” trap + RTCEncodedFrameState() = default; + RTCEncodedFrameState(RTCEncodedFrameState&&) noexcept = default; + RTCEncodedFrameState& operator=(RTCEncodedFrameState&&) noexcept = default; + RTCEncodedFrameState(const RTCEncodedFrameState&) = delete; + RTCEncodedFrameState& operator=(const RTCEncodedFrameState&) = delete; +}; + class RTCRtpScriptTransformer; class RTCEncodedFrameBase : public nsISupports, public nsWrapperCache { public: - explicit RTCEncodedFrameBase( - nsIGlobalObject* aGlobal, - std::unique_ptr<webrtc::TransformableFrameInterface> aFrame, - uint64_t aCounter); + explicit RTCEncodedFrameBase(nsIGlobalObject* aGlobal, + RTCEncodedFrameState& aState); // nsISupports NS_DECL_CYCLE_COLLECTING_ISUPPORTS @@ -36,7 +53,7 @@ class RTCEncodedFrameBase : public nsISupports, public nsWrapperCache { void SetData(const ArrayBuffer& aData); - void GetData(JSContext* aCx, JS::Rooted<JSObject*>* aObj); + void GetData(JSContext* aCx, JS::Rooted<JSObject*>* aObj) const; uint64_t GetCounter() const; @@ -48,10 +65,20 @@ class RTCEncodedFrameBase : public nsISupports, public nsWrapperCache { protected: virtual ~RTCEncodedFrameBase(); + + // forbid copy/move to protect mState + RTCEncodedFrameBase(const RTCEncodedFrameBase&) = delete; + RTCEncodedFrameBase& operator=(const RTCEncodedFrameBase&) = delete; + RTCEncodedFrameBase(RTCEncodedFrameBase&&) = delete; + RTCEncodedFrameBase& operator=(RTCEncodedFrameBase&&) = delete; + RefPtr<nsIGlobalObject> mGlobal; - std::unique_ptr<webrtc::TransformableFrameInterface> mFrame; - const uint64_t mCounter = 0; - const unsigned long mTimestamp = 0; + + // Keep serializable state separate in this base and its subclasses + // in a manner that avoids diamond inheritance. Subclasses must pass + // in *this, to ensure it's constructed before and destroyed after + // this base; copy and move are deleted. + RTCEncodedFrameState& mState; JS::Heap<JSObject*> mData; }; diff --git a/dom/media/webrtc/jsapi/RTCEncodedVideoFrame.cpp b/dom/media/webrtc/jsapi/RTCEncodedVideoFrame.cpp @@ -12,6 +12,7 @@ #include <string> #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/RTCEncodedVideoFrameBinding.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,11 +32,22 @@ namespace mozilla::dom { -NS_IMPL_CYCLE_COLLECTION_INHERITED(RTCEncodedVideoFrame, RTCEncodedFrameBase, - mOwner) +NS_IMPL_CYCLE_COLLECTION_CLASS(RTCEncodedVideoFrame) +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(RTCEncodedVideoFrame, + 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(RTCEncodedVideoFrame, + RTCEncodedFrameBase) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(RTCEncodedVideoFrame, + RTCEncodedFrameBase) + NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_TRACE_END NS_IMPL_ADDREF_INHERITED(RTCEncodedVideoFrame, RTCEncodedFrameBase) NS_IMPL_RELEASE_INHERITED(RTCEncodedVideoFrame, RTCEncodedFrameBase) - NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCEncodedVideoFrame) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_END_INHERITING(RTCEncodedFrameBase) @@ -42,8 +56,30 @@ RTCEncodedVideoFrame::RTCEncodedVideoFrame( nsIGlobalObject* aGlobal, std::unique_ptr<webrtc::TransformableFrameInterface> aFrame, uint64_t aCounter, RTCRtpScriptTransformer* aOwner) - : RTCEncodedFrameBase(aGlobal, std::move(aFrame), aCounter), + : RTCEncodedVideoFrameData{{std::move(aFrame), aCounter, /*timestamp*/ 0}}, + RTCEncodedFrameBase(aGlobal, static_cast<RTCEncodedFrameState&>(*this)), mOwner(aOwner) { + InitMetadata(); + // Base class needs this, but can't do it itself because of an assertion in + // the cycle-collector. + mozilla::HoldJSObjects(this); +} + +RTCEncodedVideoFrame::RTCEncodedVideoFrame(nsIGlobalObject* aGlobal, + RTCEncodedVideoFrameData&& aData) + : RTCEncodedVideoFrameData{{std::move(aData.mFrame), aData.mCounter, + aData.mTimestamp}, + aData.mType, + std::move(aData.mMetadata), + aData.mRid}, + 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); +} + +void RTCEncodedVideoFrame::InitMetadata() { const auto& videoFrame( static_cast<webrtc::TransformableVideoFrameInterface&>(*mFrame)); mType = videoFrame.IsKeyFrame() ? RTCEncodedVideoFrameType::Key @@ -77,15 +113,14 @@ RTCEncodedVideoFrame::RTCEncodedVideoFrame( // The metadata timestamp is different, and not presently present in the // libwebrtc types if (!videoFrame.GetRid().empty()) { - mRid = Some(videoFrame.GetRid()); + mRid = Some(videoFrame.GetRid().c_str()); } - - // Base class needs this, but can't do it itself because of an assertion in - // the cycle-collector. - mozilla::HoldJSObjects(this); } RTCEncodedVideoFrame::~RTCEncodedVideoFrame() { + // 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); @@ -96,6 +131,17 @@ JSObject* RTCEncodedVideoFrame::WrapObject(JSContext* aCx, return RTCEncodedVideoFrame_Binding::Wrap(aCx, this, aGivenProto); } +RTCEncodedVideoFrameData RTCEncodedVideoFrameData::Clone() const { + return RTCEncodedVideoFrameData{ + {webrtc::CloneVideoFrame( + static_cast<webrtc::TransformableVideoFrameInterface*>( + mFrame.get())), + mCounter, mTimestamp}, + mType, + RTCEncodedVideoFrameMetadata(mMetadata), + mRid}; +} + nsIGlobalObject* RTCEncodedVideoFrame::GetParentObject() const { return mGlobal; } @@ -111,6 +157,44 @@ bool RTCEncodedVideoFrame::CheckOwner(RTCRtpScriptTransformer* aOwner) const { return aOwner == mOwner; } -Maybe<std::string> RTCEncodedVideoFrame::Rid() const { return mRid; } +Maybe<nsCString> RTCEncodedVideoFrame::Rid() const { return mRid; } + +// https://www.w3.org/TR/webrtc-encoded-transform/#RTCEncodedVideoFrame-serialization +/* static */ +JSObject* RTCEncodedVideoFrame::ReadStructuredClone( + JSContext* aCx, nsIGlobalObject* aGlobal, JSStructuredCloneReader* aReader, + RTCEncodedVideoFrameData& 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<RTCEncodedVideoFrame> + // 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<RTCEncodedVideoFrame>(aGlobal, std::move(aData)); + if (!GetOrCreateDOMReflector(aCx, frame, &value) || !value.isObject()) { + return nullptr; + } + } + return value.toObjectOrNull(); +} + +bool RTCEncodedVideoFrame::WriteStructuredClone( + 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->RtcEncodedVideoFrames().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->RtcEncodedVideoFrames().AppendElement(Clone()); + return !NS_WARN_IF( + !JS_WriteUint32Pair(aWriter, SCTAG_DOM_RTCENCODEDVIDEOFRAME, index)); +} } // namespace mozilla::dom diff --git a/dom/media/webrtc/jsapi/RTCEncodedVideoFrame.h b/dom/media/webrtc/jsapi/RTCEncodedVideoFrame.h @@ -7,29 +7,43 @@ #ifndef MOZILLA_DOM_MEDIA_WEBRTC_JSAPI_RTCENCODEDVIDEOFRAME_H_ #define MOZILLA_DOM_MEDIA_WEBRTC_JSAPI_RTCENCODEDVIDEOFRAME_H_ -#include "jsapi/RTCEncodedFrameBase.h" #include "mozilla/RefPtr.h" +#include "mozilla/dom/RTCEncodedFrameBase.h" #include "mozilla/dom/RTCEncodedVideoFrameBinding.h" #include "nsIGlobalObject.h" namespace mozilla::dom { + class RTCRtpScriptTransformer; +class StructuredCloneHolder; + +struct RTCEncodedVideoFrameData : RTCEncodedFrameState { + RTCEncodedVideoFrameType mType; + RTCEncodedVideoFrameMetadata mMetadata; + Maybe<nsCString> mRid; + + [[nodiscard]] RTCEncodedVideoFrameData Clone() const; +}; // Wraps a libwebrtc frame, allowing the frame buffer to be modified, and // providing read-only access to various metadata. After the libwebrtc frame is // extracted (with RTCEncodedFrameBase::TakeFrame), the frame buffer is // detached, but the metadata remains accessible. -class RTCEncodedVideoFrame final : public RTCEncodedFrameBase { +class RTCEncodedVideoFrame final : public RTCEncodedVideoFrameData, + public RTCEncodedFrameBase { public: explicit RTCEncodedVideoFrame( nsIGlobalObject* aGlobal, std::unique_ptr<webrtc::TransformableFrameInterface> aFrame, uint64_t aCounter, RTCRtpScriptTransformer* aOwner); + explicit RTCEncodedVideoFrame(nsIGlobalObject* aGlobal, + RTCEncodedVideoFrameData&& aData); + // nsISupports NS_DECL_ISUPPORTS_INHERITED - NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(RTCEncodedVideoFrame, - RTCEncodedFrameBase) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(RTCEncodedVideoFrame, + RTCEncodedFrameBase) // webidl (timestamp and data accessors live in base class) JSObject* WrapObject(JSContext* aCx, @@ -39,6 +53,8 @@ class RTCEncodedVideoFrame final : public RTCEncodedFrameBase { RTCEncodedVideoFrameType Type() const; + void InitMetadata(); + void GetMetadata(RTCEncodedVideoFrameMetadata& aMetadata); bool CheckOwner(RTCRtpScriptTransformer* aOwner) const override; @@ -47,14 +63,29 @@ class RTCEncodedVideoFrame final : public RTCEncodedFrameBase { // Not in webidl right now. Might change. // https://github.com/w3c/webrtc-encoded-transform/issues/147 - Maybe<std::string> Rid() const; + Maybe<nsCString> Rid() const; + + static JSObject* ReadStructuredClone(JSContext* aCx, nsIGlobalObject* aGlobal, + JSStructuredCloneReader* aReader, + RTCEncodedVideoFrameData& aData); + bool WriteStructuredClone(JSStructuredCloneWriter* aWriter, + StructuredCloneHolder* aHolder) const; private: virtual ~RTCEncodedVideoFrame(); + + // forbid copy/move to keep mState member in base valid + RTCEncodedVideoFrame(const RTCEncodedVideoFrame&) = delete; + RTCEncodedVideoFrame& operator=(const RTCEncodedVideoFrame&) = delete; + RTCEncodedVideoFrame(RTCEncodedVideoFrame&&) = delete; + RTCEncodedVideoFrame& operator=(RTCEncodedVideoFrame&&) = delete; + + // RTCEncodedVideoFrame can run on either main thread or worker thread. + void AssertIsOnOwningThread() const { + NS_ASSERT_OWNINGTHREAD(RTCEncodedVideoFrame); + } + RefPtr<RTCRtpScriptTransformer> mOwner; - RTCEncodedVideoFrameType mType; - RTCEncodedVideoFrameMetadata mMetadata; - Maybe<std::string> mRid; }; } // namespace mozilla::dom diff --git a/dom/media/webrtc/jsapi/RTCRtpScriptTransformer.cpp b/dom/media/webrtc/jsapi/RTCRtpScriptTransformer.cpp @@ -428,7 +428,10 @@ already_AddRefed<Promise> RTCRtpScriptTransformer::OnTransformedFrame( if (aFrame->GetCounter() > mLastReceivedFrameCounter && aFrame->CheckOwner(this) && mProxy) { mLastReceivedFrameCounter = aFrame->GetCounter(); - mProxy->OnTransformedFrame(aFrame->TakeFrame()); + // also skip if frame has been detached (transferred away) + if (auto frame = aFrame->TakeFrame()) { + mProxy->OnTransformedFrame(std::move(frame)); + } } return Promise::CreateResolvedWithUndefined(GetParentObject(), aError); diff --git a/dom/webidl/RTCEncodedAudioFrame.webidl b/dom/webidl/RTCEncodedAudioFrame.webidl @@ -14,6 +14,8 @@ dictionary RTCEncodedAudioFrameMetadata { short sequenceNumber; }; +// [Serializable] is implemented without adding attribute here, +// because we don't implement "full serialization" to disk. [Pref="media.peerconnection.enabled", Pref="media.peerconnection.scripttransform.enabled", Exposed=(Window,DedicatedWorker)] diff --git a/dom/webidl/RTCEncodedVideoFrame.webidl b/dom/webidl/RTCEncodedVideoFrame.webidl @@ -30,6 +30,9 @@ dictionary RTCEncodedVideoFrameMetadata { // New interfaces to define encoded video and audio frames. Will eventually // re-use or extend the equivalent defined in WebCodecs. +// +// [Serializable] is implemented without adding attribute here, +// because we don't implement "full serialization" to disk. [Pref="media.peerconnection.enabled", Pref="media.peerconnection.scripttransform.enabled", Exposed=(Window,DedicatedWorker)]