commit 4ab0445514bf0b80dc15297fd128e7ace4f4e582
parent 643d732886fe0de4e2a3eee3c5ed9bd0d47c77cf
Author: pommicket <pommicket@gmail.com>
Date: Sun, 26 Oct 2025 16:53:10 +0000
Bug 1994898 - Implement data transfer for ImageDecoder. r=aosmond
Allows constructing an ImageDecoder object without copying
encoded image data.
Differential Revision: https://phabricator.services.mozilla.com/D269301
Diffstat:
3 files changed, 100 insertions(+), 23 deletions(-)
diff --git a/dom/media/webcodecs/ImageDecoder.cpp b/dom/media/webcodecs/ImageDecoder.cpp
@@ -600,28 +600,43 @@ void ImageDecoder::Initialize(const GlobalObject& aGlobal,
mSourceBuffer = MakeRefPtr<image::SourceBuffer>();
+ bool transferOwnership = false;
const auto fnSourceBufferFromSpan = [&](const Span<uint8_t>& aData) {
- nsresult rv = mSourceBuffer->ExpectLength(aData.Length());
- if (NS_WARN_IF(NS_FAILED(rv))) {
- MOZ_LOG(
- gWebCodecsLog, LogLevel::Error,
- ("ImageDecoder %p Initialize -- failed to pre-allocate source buffer",
- this));
- aRv.ThrowRangeError("Could not allocate for encoded source buffer");
- return;
- }
+ if (transferOwnership) {
+ // 10.2.2.18.2.1 Let [[encoded data]] reference bytes in data
+ // representing an encoded image.
+ nsresult rv =
+ mSourceBuffer->AdoptData(reinterpret_cast<char*>(aData.Elements()),
+ aData.Length(), js_realloc, js_free);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gWebCodecsLog, LogLevel::Error,
+ ("ImageDecoder %p Initialize -- failed to adopt source buffer",
+ this));
+ aRv.ThrowRangeError("Could not allocate for encoded source buffer");
+ return;
+ }
+ } else {
+ nsresult rv = mSourceBuffer->ExpectLength(aData.Length());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gWebCodecsLog, LogLevel::Error,
+ ("ImageDecoder %p Initialize -- failed to pre-allocate source "
+ "buffer",
+ this));
+ aRv.ThrowRangeError("Could not allocate for encoded source buffer");
+ return;
+ }
- // 10.2.2.18.3.2. Assign a copy of init.data to [[encoded data]].
- rv = mSourceBuffer->Append(reinterpret_cast<const char*>(aData.Elements()),
- aData.Length());
- if (NS_WARN_IF(NS_FAILED(rv))) {
- MOZ_LOG(gWebCodecsLog, LogLevel::Error,
- ("ImageDecoder %p Initialize -- failed to append source buffer",
- this));
- aRv.ThrowRangeError("Could not allocate for encoded source buffer");
- return;
+ // 10.2.2.18.3.2. Assign a copy of init.data to [[encoded data]].
+ rv = mSourceBuffer->Append(
+ reinterpret_cast<const char*>(aData.Elements()), aData.Length());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gWebCodecsLog, LogLevel::Error,
+ ("ImageDecoder %p Initialize -- failed to append source buffer",
+ this));
+ aRv.ThrowRangeError("Could not allocate for encoded source buffer");
+ return;
+ }
}
-
mSourceBuffer->Complete(NS_OK);
// 10.2.2.18.4. Assign true to [[complete]].
@@ -650,14 +665,52 @@ void ImageDecoder::Initialize(const GlobalObject& aGlobal,
} else if (aInit.mData.IsArrayBufferView()) {
// 10.2.2.18.3.1. Assert that init.data is of type BufferSource.
const auto& view = aInit.mData.GetAsArrayBufferView();
- view.ProcessFixedData(fnSourceBufferFromSpan);
+ bool isShared;
+ JS::Rooted<JSObject*> viewObj(aGlobal.Context(), view.Obj());
+ JSObject* arrayBuffer =
+ JS_GetArrayBufferViewBuffer(aGlobal.Context(), viewObj, &isShared);
+ bool inTransferList = false;
+ for (const auto& transferBuffer : aInit.mTransfer) {
+ if (arrayBuffer == transferBuffer.Obj()) {
+ inTransferList = true;
+ break;
+ }
+ }
+ size_t length;
+ if (inTransferList) {
+ length = JS_GetArrayBufferViewByteLength(view.Obj());
+ // Only transfer ownership if the view's byte offset is 0
+ // (Otherewise we would have problems with freeing a pointer
+ // to halfway through the ArrayBuffer data)
+ transferOwnership = JS_GetArrayBufferViewByteOffset(view.Obj()) == 0;
+ }
+ if (transferOwnership) {
+ JS::Rooted<JSObject*> bufferObj(aGlobal.Context(), arrayBuffer);
+ void* data = JS::StealArrayBufferContents(aGlobal.Context(), bufferObj);
+ fnSourceBufferFromSpan(Span(static_cast<uint8_t*>(data), length));
+ } else {
+ view.ProcessFixedData(fnSourceBufferFromSpan);
+ }
if (aRv.Failed()) {
return;
}
} else if (aInit.mData.IsArrayBuffer()) {
// 10.2.2.18.3.1. Assert that init.data is of type BufferSource.
const auto& buffer = aInit.mData.GetAsArrayBuffer();
- buffer.ProcessFixedData(fnSourceBufferFromSpan);
+ for (const auto& transferBuffer : aInit.mTransfer) {
+ if (buffer.Obj() == transferBuffer.Obj()) {
+ transferOwnership = true;
+ break;
+ }
+ }
+ if (transferOwnership) {
+ JS::Rooted<JSObject*> bufferObj(aGlobal.Context(), buffer.Obj());
+ size_t length = JS::GetArrayBufferByteLength(bufferObj);
+ void* data = JS::StealArrayBufferContents(aGlobal.Context(), bufferObj);
+ fnSourceBufferFromSpan(Span(static_cast<uint8_t*>(data), length));
+ } else {
+ buffer.ProcessFixedData(fnSourceBufferFromSpan);
+ }
if (aRv.Failed()) {
return;
}
diff --git a/image/SourceBuffer.cpp b/image/SourceBuffer.cpp
@@ -441,6 +441,15 @@ nsresult SourceBuffer::Append(const char* aData, size_t aLength) {
return NS_OK;
}
+nsresult SourceBuffer::AdoptData(char* aData, size_t aLength,
+ void* (*aRealloc)(void*, size_t),
+ void (*aFree)(void*)) {
+ MOZ_ASSERT(aData, "Should have a buffer");
+ MOZ_ASSERT(aLength > 0, "Writing a zero-sized chunk");
+ MutexAutoLock lock(mMutex);
+ return AppendChunk(Some(Chunk(aData, aLength, aRealloc, aFree)));
+}
+
static nsresult AppendToSourceBuffer(nsIInputStream*, void* aClosure,
const char* aFromRawSegment, uint32_t,
uint32_t aCount, uint32_t* aWriteCount) {
diff --git a/image/SourceBuffer.h b/image/SourceBuffer.h
@@ -324,6 +324,10 @@ class SourceBuffer final {
/// Append the data available on the provided nsIInputStream to the buffer.
nsresult AppendFromInputStream(nsIInputStream* aInputStream, uint32_t aCount);
+ /// Take ownership of provided data and append it without copying.
+ nsresult AdoptData(char* aData, size_t aLength,
+ void* (*aRealloc)(void*, size_t), void (*aFree)(void*));
+
/**
* Mark the buffer complete, with a status that will be available to
* consumers. Further calls to Append() are forbidden after Complete().
@@ -382,7 +386,16 @@ class SourceBuffer final {
mData = static_cast<char*>(malloc(mCapacity));
}
- ~Chunk() { free(mData); }
+ /// Create an "adopted" chunk taking ownership of the provided data.
+ Chunk(char* aData, size_t aLength, void* (*aRealloc)(void*, size_t),
+ void (*aFree)(void*))
+ : mCapacity(aLength),
+ mLength(aLength),
+ mData(aData),
+ mRealloc(aRealloc),
+ mFree(aFree) {}
+
+ ~Chunk() { mFree(mData); }
Chunk(Chunk&& aOther)
: mCapacity(aOther.mCapacity),
@@ -418,7 +431,7 @@ class SourceBuffer final {
bool SetCapacity(size_t aCapacity) {
MOZ_ASSERT(mData, "Allocation failed but nobody checked for it");
- char* data = static_cast<char*>(realloc(mData, aCapacity));
+ char* data = static_cast<char*>(mRealloc(mData, aCapacity));
if (!data) {
return false;
}
@@ -435,6 +448,8 @@ class SourceBuffer final {
size_t mCapacity;
size_t mLength;
char* mData;
+ void* (*mRealloc)(void*, size_t) = realloc;
+ void (*mFree)(void*) = free;
};
nsresult AppendChunk(Maybe<Chunk>&& aChunk) MOZ_REQUIRES(mMutex);