tor-browser

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

commit a946c644949e65faf10d6589b8b863e99356bd4f
parent eae85737b7952990c79155bfaf20e3090ab890e0
Author: pommicket <pommicket@gmail.com>
Date:   Sat,  8 Nov 2025 21:31:42 +0000

Bug 1994891 - Implement `transfer` for AudioDataInit r=aosmond

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

Diffstat:
Mdom/media/webcodecs/AudioData.cpp | 74+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mdom/media/webcodecs/AudioData.h | 23++++++++++++++++++-----
Mtesting/web-platform/meta/webcodecs/transfering.https.any.js.ini | 12++++--------
3 files changed, 93 insertions(+), 16 deletions(-)

diff --git a/dom/media/webcodecs/AudioData.cpp b/dom/media/webcodecs/AudioData.cpp @@ -22,6 +22,7 @@ #include "mozilla/dom/StructuredCloneTags.h" #include "nsFmtString.h" #include "nsStringFwd.h" +#include "nsTHashSet.h" extern mozilla::LazyLogModule gWebCodecsLog; @@ -214,15 +215,82 @@ already_AddRefed<AudioData> AudioData::Constructor(const GlobalObject& aGlobal, aRv.ThrowTypeError(rv.inspectErr()); return nullptr; } - auto resource = AudioDataResource::Construct(aInit.mData); + + nsTHashSet<const JSObject*> transferSet; + for (const auto& buffer : aInit.mTransfer) { + if (transferSet.Contains(buffer.Obj())) { + // 9.2.2.2. If init.transfer contains more than one reference to + // the same ArrayBuffer, then throw a DataCloneError DOMException. + LOGE("AudioData Constructor -- duplicate transferred ArrayBuffer"); + aRv.ThrowDataCloneError( + "Transfer contains duplicate ArrayBuffer objects"); + return nullptr; + } + transferSet.Insert(buffer.Obj()); + } + + for (const auto& buffer : aInit.mTransfer) { + if (JS::IsDetachedArrayBufferObject(buffer.Obj())) { + // 9.2.2.3.1. If [[Detached]] internal slot is true, then + // throw a DataCloneError DOMException. + LOGE("AudioData Constructor -- detached transferred ArrayBuffer"); + aRv.ThrowDataCloneError("Transfer contains detached ArrayBuffer objects"); + return nullptr; + } + } + + // 9.2.2.4.7. If init.transfer contains an ArrayBuffer referenced by init.data + // the User Agent MAY choose to: + // 9.2.2.4.7.1. Let resource be a new media resource referencing sample data + // in data. + size_t transferLen = 0; + size_t transferOffset = 0; + Maybe<JS::Rooted<JSObject*>> transferView; + Maybe<JS::Rooted<JSObject*>> transferBuffer; + const auto& data = aInit.mData; + if (data.IsArrayBuffer()) { + transferBuffer.emplace(aGlobal.Context(), data.GetAsArrayBuffer().Obj()); + if (transferSet.Contains(*transferBuffer)) { + transferLen = JS::GetArrayBufferByteLength(*transferBuffer); + } + } else if (data.IsArrayBufferView()) { + // store rooted ArrayBufferView object in outer variable to prolong + // its lifetime (for JS::Rooted's tracking). + transferView.emplace(aGlobal.Context(), data.GetAsArrayBufferView().Obj()); + bool isShared; + transferBuffer.emplace(aGlobal.Context(), + JS_GetArrayBufferViewBuffer( + aGlobal.Context(), *transferView, &isShared)); + if (transferSet.Contains(*transferBuffer)) { + transferOffset = JS_GetArrayBufferViewByteOffset(*transferView); + transferLen = JS_GetArrayBufferViewByteLength(*transferView); + } + } + UniquePtr<uint8_t[], JS::FreePolicy> transferData; + if (transferLen) { + void* bufferContents = + JS::StealArrayBufferContents(aGlobal.Context(), *transferBuffer); + transferData = UniquePtr<uint8_t[], JS::FreePolicy>( + static_cast<uint8_t*>(bufferContents)); + } + + auto resource = + transferData ? MakeAndAddRef<AudioDataResource>( + std::move(transferData), transferOffset, transferLen) + : AudioDataResource::Construct(data); if (resource.isErr()) { LOGD("AudioData::Constructor failure (OOM)"); aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return nullptr; } - return MakeAndAddRef<mozilla::dom::AudioData>(global, resource.unwrap(), - aInit); + // 9.2.2.3. For each transferable in init.transfer: + // 9.2.2.3.1. Perform DetachArrayBuffer on transferable + for (const auto& buffer : aInit.mTransfer) { + JS::Rooted<JSObject*> obj(aGlobal.Context(), buffer.Obj()); + JS::DetachArrayBuffer(aGlobal.Context(), obj); + } + return MakeAndAddRef<AudioData>(global, resource.unwrap(), aInit); } // https://w3c.github.io/webcodecs/#dom-audiodata-format diff --git a/dom/media/webcodecs/AudioData.h b/dom/media/webcodecs/AudioData.h @@ -132,13 +132,19 @@ class AudioData final : public nsISupports, public nsWrapperCache { class AudioDataResource final { NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AudioDataResource); explicit AudioDataResource(FallibleTArray<uint8_t>&& aData) - : mData(std::move(aData)) {} + : mCopiedData(std::move(aData)) {} - explicit AudioDataResource() : mData() {} + explicit AudioDataResource() : mCopiedData() {} + /// Create AudioDataResource, transferring ownership from js_malloc'd data. + AudioDataResource(UniquePtr<uint8_t[], JS::FreePolicy>&& aData, + size_t aOffset, size_t aLen) + : mAdoptedData(std::move(aData)), + mAdoptedDataOffset(aOffset), + mAdoptedDataLen(aLen) {} static AudioDataResource* Create(const Span<uint8_t>& aData) { AudioDataResource* resource = new AudioDataResource(); - if (!resource->mData.AppendElements(aData, mozilla::fallible_t())) { + if (!resource->mCopiedData.AppendElements(aData, mozilla::fallible_t())) { return nullptr; } return resource; @@ -147,13 +153,20 @@ class AudioDataResource final { static Result<already_AddRefed<AudioDataResource>, nsresult> Construct( const OwningAllowSharedBufferSource& aInit); - Span<uint8_t> Data() { return Span(mData.Elements(), mData.Length()); }; + Span<uint8_t> Data() { + return mAdoptedData + ? Span(mAdoptedData.get() + mAdoptedDataOffset, mAdoptedDataLen) + : Span(mCopiedData.Elements(), mCopiedData.Length()); + } private: ~AudioDataResource() = default; // It's always possible for the allocation to fail -- the size is // controled by script. - FallibleTArray<uint8_t> mData; + FallibleTArray<uint8_t> mCopiedData; + UniquePtr<uint8_t[], JS::FreePolicy> mAdoptedData; + size_t mAdoptedDataOffset; + size_t mAdoptedDataLen; }; struct AudioDataSerializedData { diff --git a/testing/web-platform/meta/webcodecs/transfering.https.any.js.ini b/testing/web-platform/meta/webcodecs/transfering.https.any.js.ini @@ -11,11 +11,9 @@ [Test transfering ArrayBuffer to EncodedVideoChunk] expected: FAIL - [Test transfering ArrayBuffer to AudioData] - expected: FAIL - [Encoding from AudioData with transferred buffer] - expected: FAIL + expected: + if os == "android": FAIL [Test transfering buffers to VideoFrame with uneven samples] expected: FAIL @@ -34,11 +32,9 @@ [Test transfering ArrayBuffer to EncodedVideoChunk] expected: FAIL - [Test transfering ArrayBuffer to AudioData] - expected: FAIL - [Encoding from AudioData with transferred buffer] - expected: FAIL + expected: + if os == "android": FAIL [Test transfering buffers to VideoFrame with uneven samples] expected: FAIL