/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sw=2 sts=2 et cindent: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "ImageDecoderReadRequest.h" #include "MediaResult.h" #include "mozilla/CycleCollectedJSContext.h" #include "mozilla/dom/ImageDecoder.h" #include "mozilla/dom/ReadableStream.h" #include "mozilla/dom/ReadableStreamDefaultReader.h" #include "mozilla/image/SourceBuffer.h" #include "mozilla/Logging.h" extern mozilla::LazyLogModule gWebCodecsLog; namespace mozilla::dom { NS_IMPL_CYCLE_COLLECTION_INHERITED(ImageDecoderReadRequest, ReadRequest, mDecoder, mReader) NS_IMPL_ADDREF_INHERITED(ImageDecoderReadRequest, ReadRequest) NS_IMPL_RELEASE_INHERITED(ImageDecoderReadRequest, ReadRequest) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ImageDecoderReadRequest) NS_INTERFACE_MAP_END_INHERITING(ReadRequest) ImageDecoderReadRequest::ImageDecoderReadRequest( image::SourceBuffer* aSourceBuffer) : mSourceBuffer(std::move(aSourceBuffer)) { MOZ_LOG(gWebCodecsLog, LogLevel::Debug, ("ImageDecoderReadRequest %p ImageDecoderReadRequest", this)); } ImageDecoderReadRequest::~ImageDecoderReadRequest() { MOZ_LOG(gWebCodecsLog, LogLevel::Debug, ("ImageDecoderReadRequest %p ~ImageDecoderReadRequest", this)); } bool ImageDecoderReadRequest::Initialize(const GlobalObject& aGlobal, ImageDecoder* aDecoder, ReadableStream& aStream) { IgnoredErrorResult rv; mReader = aStream.GetReader(rv); if (NS_WARN_IF(rv.Failed())) { MOZ_LOG( gWebCodecsLog, LogLevel::Error, ("ImageDecoderReadRequest %p Initialize -- cannot get stream reader", this)); mSourceBuffer->Complete(NS_ERROR_FAILURE); Destroy(/* aCancel */ false); return false; } mDecoder = aDecoder; QueueRead(); return true; } void ImageDecoderReadRequest::Destroy(bool aCancel) { MOZ_LOG(gWebCodecsLog, LogLevel::Debug, ("ImageDecoderReadRequest %p Destroy", this)); if (aCancel) { // Ensure we stop reading from the ReadableStream. Cancel(); } if (mSourceBuffer) { if (!mSourceBuffer->IsComplete()) { mSourceBuffer->Complete(NS_ERROR_ABORT); } mSourceBuffer = nullptr; } mDecoder = nullptr; mReader = nullptr; } void ImageDecoderReadRequest::QueueRead() { class ReadRunnable final : public CancelableRunnable { public: explicit ReadRunnable(ImageDecoderReadRequest* aOwner) : CancelableRunnable( "mozilla::dom::ImageDecoderReadRequest::QueueRead"), mOwner(aOwner) {} NS_IMETHODIMP Run() override { mOwner->Read(); mOwner = nullptr; return NS_OK; } nsresult Cancel() override { mOwner->Complete( MediaResult(NS_ERROR_DOM_MEDIA_ABORT_ERR, "Read cancelled"_ns)); mOwner = nullptr; return NS_OK; } private: virtual ~ReadRunnable() { if (mOwner) { Cancel(); } } RefPtr mOwner; }; if (!mReader) { MOZ_LOG(gWebCodecsLog, LogLevel::Debug, ("ImageDecoderReadRequest %p QueueRead -- destroyed", this)); return; } MOZ_LOG(gWebCodecsLog, LogLevel::Debug, ("ImageDecoderReadRequest %p QueueRead -- queue", this)); auto task = MakeRefPtr(this); NS_DispatchToCurrentThread(task.forget()); } void ImageDecoderReadRequest::Read() { if (!mReader || !mDecoder) { MOZ_LOG(gWebCodecsLog, LogLevel::Debug, ("ImageDecoderReadRequest %p Read -- destroyed", this)); return; } AutoJSAPI jsapi; if (!jsapi.Init(mDecoder->GetParentObject())) { MOZ_LOG(gWebCodecsLog, LogLevel::Debug, ("ImageDecoderReadRequest %p Read -- no jsapi", this)); Complete(MediaResult(NS_ERROR_DOM_FILE_NOT_READABLE_ERR, "Reader cannot init jsapi"_ns)); return; } RefPtr self(this); RefPtr reader(mReader); MOZ_LOG(gWebCodecsLog, LogLevel::Debug, ("ImageDecoderReadRequest %p Read -- begin read chunk", this)); IgnoredErrorResult err; reader->ReadChunk(jsapi.cx(), *self, err); if (NS_WARN_IF(err.Failed())) { MOZ_LOG(gWebCodecsLog, LogLevel::Error, ("ImageDecoderReadRequest %p Read -- read chunk failed", this)); Complete(MediaResult(NS_ERROR_DOM_FILE_NOT_READABLE_ERR, "Reader cannot read chunk from stream"_ns)); } MOZ_LOG(gWebCodecsLog, LogLevel::Debug, ("ImageDecoderReadRequest %p Read -- end read chunk", this)); } void ImageDecoderReadRequest::Cancel() { RefPtr reader = std::move(mReader); if (!reader || !mDecoder) { return; } RefPtr self(this); AutoJSAPI jsapi; if (!jsapi.Init(mDecoder->GetParentObject())) { MOZ_LOG(gWebCodecsLog, LogLevel::Debug, ("ImageDecoderReadRequest %p Cancel -- no jsapi", this)); return; } ErrorResult rv; rv.ThrowAbortError("ImageDecoderReadRequest destroyed"); JS::Rooted errorValue(jsapi.cx()); if (ToJSValue(jsapi.cx(), std::move(rv), &errorValue)) { IgnoredErrorResult ignoredRv; if (RefPtr p = reader->Cancel(jsapi.cx(), errorValue, ignoredRv)) { MOZ_ALWAYS_TRUE(p->SetAnyPromiseIsHandled()); } } jsapi.ClearException(); } void ImageDecoderReadRequest::Complete(const MediaResult& aResult) { if (!mReader) { return; } MOZ_LOG(gWebCodecsLog, LogLevel::Debug, ("ImageDecoderReadRequest %p Read -- complete, success %d", this, NS_SUCCEEDED(aResult.Code()))); if (mSourceBuffer && !mSourceBuffer->IsComplete()) { mSourceBuffer->Complete(aResult.Code()); } if (mDecoder) { mDecoder->OnSourceBufferComplete(aResult); } Destroy(/* aCancel */ false); } void ImageDecoderReadRequest::ChunkSteps(JSContext* aCx, JS::Handle aChunk, ErrorResult& aRv) { // 10.2.5. Fetch Stream Data Loop (with reader) - chunk steps // 1. If [[closed]] is true, abort these steps. if (!mSourceBuffer) { return; } // 2. If chunk is not a Uint8Array object, queue a task to run the Close // ImageDecoder algorithm with a DataError DOMException and abort these steps. RootedSpiderMonkeyInterface chunk(aCx); if (!aChunk.isObject() || !chunk.Init(&aChunk.toObject())) { MOZ_LOG(gWebCodecsLog, LogLevel::Error, ("ImageDecoderReadRequest %p ChunkSteps -- bad chunk", this)); Complete(MediaResult(NS_ERROR_DOM_DATA_ERR, "Reader cannot read chunk from stream"_ns)); return; } chunk.ProcessFixedData([&](const Span& aData) { MOZ_LOG(gWebCodecsLog, LogLevel::Debug, ("ImageDecoderReadRequest %p ChunkSteps -- write %zu bytes", this, aData.Length())); // 3. Let bytes be the byte sequence represented by the Uint8Array object. // 4. Append bytes to the [[encoded data]] internal slot. nsresult rv = mSourceBuffer->Append( reinterpret_cast(aData.Elements()), aData.Length()); if (NS_WARN_IF(NS_FAILED(rv))) { MOZ_LOG( gWebCodecsLog, LogLevel::Debug, ("ImageDecoderReadRequest %p ChunkSteps -- failed to append", this)); Complete(MediaResult(NS_ERROR_DOM_UNKNOWN_ERR, "Reader cannot allocate storage for chunk"_ns)); } // 5. If [[tracks established]] is false, run the Establish Tracks // algorithm. // 6. Otherwise, run the Update Tracks algorithm. // // Note that these steps will be triggered by the decoder promise callbacks. }); // 7. Run the Fetch Stream Data Loop algorithm with reader. QueueRead(); } void ImageDecoderReadRequest::CloseSteps(JSContext* aCx, ErrorResult& aRv) { MOZ_LOG(gWebCodecsLog, LogLevel::Debug, ("ImageDecoderReadRequest %p CloseSteps", this)); // 10.2.5. Fetch Stream Data Loop (with reader) - close steps // 1. Assign true to [[complete]] // 2. Resolve [[completed promise]]. Complete(MediaResult(NS_OK)); } void ImageDecoderReadRequest::ErrorSteps(JSContext* aCx, JS::Handle aError, ErrorResult& aRv) { MOZ_LOG(gWebCodecsLog, LogLevel::Debug, ("ImageDecoderReadRequest %p ErrorSteps", this)); // 10.2.5. Fetch Stream Data Loop (with reader) - error steps // 1. Queue a task to run the Close ImageDecoder algorithm with a // NotReadableError DOMException Complete(MediaResult(NS_ERROR_DOM_FILE_NOT_READABLE_ERR, "Reader failed while waiting for chunk from stream"_ns)); } } // namespace mozilla::dom