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:
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