tor-browser

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

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:
Mdom/media/webcodecs/ImageDecoder.cpp | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Mimage/SourceBuffer.cpp | 9+++++++++
Mimage/SourceBuffer.h | 19+++++++++++++++++--
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);