/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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 "mozilla/image/ImageUtils.h" #include "DecodePool.h" #include "Decoder.h" #include "DecoderFactory.h" #include "IDecodingTask.h" #include "mozilla/AppShutdown.h" #include "mozilla/DebugOnly.h" #include "mozilla/gfx/2D.h" #include "mozilla/Logging.h" #include "nsNetUtil.h" #include "nsStreamUtils.h" namespace mozilla::image { static LazyLogModule sLog("ImageUtils"); AnonymousDecoder::AnonymousDecoder() = default; AnonymousDecoder::~AnonymousDecoder() = default; class AnonymousDecoderTask : public IDecodingTask { public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AnonymousDecoderTask, final) AnonymousDecoderTask(RefPtr&& aDecoder, ThreadSafeWeakPtr&& aOwner) : mDecoder(std::move(aDecoder)), mOwner(std::move(aOwner)) {} bool ShouldPreferSyncRun() const final { return false; } TaskPriority Priority() const final { return TaskPriority::eLow; } bool IsValid() const { return !AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownFinal) && !mOwner.IsDead(); } bool MaybeStart() { if (!IsValid()) { return false; } MOZ_LOG(sLog, LogLevel::Debug, ("[%p] AnonymousDecoderTask::Start -- queue", this)); DecodePool::Singleton()->AsyncRun(this); return true; } void Resume() final { if (!IsValid()) { return; } MOZ_LOG(sLog, LogLevel::Debug, ("[%p] AnonymousDecoderTask::Resume -- queue", this)); DecodePool::Singleton()->AsyncRun(this); } void Run() final { bool resume = true; while (!mOwner.IsDead() && resume) { LexerResult result = mDecoder->Decode(WrapNotNull(this)); if (result == LexerResult(Yield::NEED_MORE_DATA)) { MOZ_LOG(sLog, LogLevel::Debug, ("[%p] AnonymousDecoderTask::Run -- need more data", this)); MOZ_ASSERT(result == LexerResult(Yield::NEED_MORE_DATA)); OnNeedMoreData(); return; } // Check if we have a new frame to process. RefPtr frame = mDecoder->GetCurrentFrame(); if (frame) { RefPtr surface = frame->GetSourceSurface(); if (surface) { MOZ_LOG(sLog, LogLevel::Debug, ("[%p] AnonymousDecoderTask::Run -- new frame %p", this, frame.get())); resume = OnFrameAvailable(std::move(frame), std::move(surface)); } else { MOZ_ASSERT_UNREACHABLE("No surface from frame?"); } } if (result.is()) { MOZ_LOG(sLog, LogLevel::Debug, ("[%p] AnonymousDecoderTask::Run -- complete", this)); OnComplete(result == LexerResult(TerminalState::SUCCESS)); break; } MOZ_ASSERT(result == LexerResult(Yield::OUTPUT_AVAILABLE)); } } protected: virtual ~AnonymousDecoderTask() = default; virtual void OnNeedMoreData() {} // Returns true if the caller should continue decoding more frames if // possible. virtual bool OnFrameAvailable(RefPtr&& aFrame, RefPtr&& aSurface) { MOZ_ASSERT_UNREACHABLE("Unhandled frame!"); return true; } virtual void OnComplete(bool aSuccess) = 0; RefPtr mDecoder; ThreadSafeWeakPtr mOwner; }; class AnonymousMetadataDecoderTask final : public AnonymousDecoderTask { public: AnonymousMetadataDecoderTask(RefPtr&& aDecoder, ThreadSafeWeakPtr&& aOwner) : AnonymousDecoderTask(std::move(aDecoder), std::move(aOwner)) {} protected: void OnComplete(bool aSuccess) override { RefPtr owner(mOwner); if (!owner) { return; } if (!aSuccess) { owner->OnMetadata(nullptr); return; } const auto& mdIn = mDecoder->GetImageMetadata(); owner->OnMetadata(&mdIn); } }; class AnonymousFrameCountDecoderTask final : public AnonymousDecoderTask { public: AnonymousFrameCountDecoderTask(RefPtr&& aDecoder, ThreadSafeWeakPtr&& aOwner) : AnonymousDecoderTask(std::move(aDecoder), std::move(aOwner)) {} protected: void UpdateFrameCount(bool aComplete) { RefPtr owner(mOwner); if (!owner) { return; } const auto& mdIn = mDecoder->GetImageMetadata(); uint32_t frameCount = mdIn.HasFrameCount() ? mdIn.GetFrameCount() : 0; owner->OnFrameCount(frameCount, aComplete); } void OnNeedMoreData() override { UpdateFrameCount(/* aComplete */ false); } void OnComplete(bool aSuccess) override { UpdateFrameCount(/* aComplete */ true); } }; class AnonymousFramesDecoderTask final : public AnonymousDecoderTask { public: AnonymousFramesDecoderTask(RefPtr&& aDecoder, ThreadSafeWeakPtr&& aOwner) : AnonymousDecoderTask(std::move(aDecoder), std::move(aOwner)) {} void SetOutputSize(const OrientedIntSize& aSize) { if (mDecoder) { mDecoder->SetOutputSize(aSize); } } protected: bool OnFrameAvailable(RefPtr&& aFrame, RefPtr&& aSurface) override { RefPtr owner(mOwner); if (!owner) { return false; } return owner->OnFrameAvailable(std::move(aFrame), std::move(aSurface)); } void OnComplete(bool aSuccess) override { RefPtr owner(mOwner); if (!owner) { return; } owner->OnFramesComplete(); } }; class AnonymousDecoderImpl final : public AnonymousDecoder { public: explicit AnonymousDecoderImpl(const Maybe& aOutputSize) : mMutex("mozilla::image::AnonymousDecoderImpl::mMutex"), mOutputSize(aOutputSize) {} ~AnonymousDecoderImpl() override { Destroy(); } #ifdef MOZ_REFCOUNTED_LEAK_CHECKING const char* typeName() const override { return "mozilla::image::AnonymousDecoderImpl"; } size_t typeSize() const override { return sizeof(*this); } #endif bool Initialize(RefPtr&& aDecoder) override { MutexAutoLock lock(mMutex); if (NS_WARN_IF(!aDecoder)) { MOZ_LOG(sLog, LogLevel::Error, ("[%p] AnonymousDecoderImpl::Initialize -- bad decoder", this)); return false; } RefPtr metadataDecoder = DecoderFactory::CloneAnonymousMetadataDecoder(aDecoder); if (NS_WARN_IF(!metadataDecoder)) { MOZ_LOG(sLog, LogLevel::Error, ("[%p] AnonymousDecoderImpl::Initialize -- failed clone metadata " "decoder", this)); return false; } DecoderFlags flags = aDecoder->GetDecoderFlags() | DecoderFlags::COUNT_FRAMES; RefPtr frameCountDecoder = DecoderFactory::CloneAnonymousMetadataDecoder(aDecoder, Some(flags)); if (NS_WARN_IF(!frameCountDecoder)) { MOZ_LOG(sLog, LogLevel::Error, ("[%p] AnonymousDecoderImpl::Initialize -- failed clone frame " "count decoder", this)); return false; } mMetadataTask = new AnonymousMetadataDecoderTask( std::move(metadataDecoder), ThreadSafeWeakPtr(this)); mFrameCountTask = new AnonymousFrameCountDecoderTask( std::move(frameCountDecoder), ThreadSafeWeakPtr(this)); mFramesTask = new AnonymousFramesDecoderTask( std::move(aDecoder), ThreadSafeWeakPtr(this)); MOZ_LOG(sLog, LogLevel::Debug, ("[%p] AnonymousDecoderImpl::Initialize -- success", this)); return true; } void Destroy() override { MutexAutoLock lock(mMutex); DestroyLocked(NS_ERROR_ABORT); } void DestroyLocked(nsresult aResult) MOZ_REQUIRES(mMutex) { MOZ_LOG(sLog, LogLevel::Debug, ("[%p] AnonymousDecoderImpl::Destroy", this)); mFramesToDecode = 0; mMetadataTask = nullptr; mFrameCountTask = nullptr; mFramesTask = nullptr; mPendingFramesResult.mFrames.Clear(); mPendingFramesResult.mFinished = true; mMetadataPromise.RejectIfExists(aResult, __func__); mFrameCountPromise.RejectIfExists(aResult, __func__); mFramesPromise.RejectIfExists(aResult, __func__); } void OnMetadata(const ImageMetadata* aMetadata) override { MutexAutoLock lock(mMutex); // We must have already gotten destroyed before metadata decoding finished. if (!mMetadataTask) { return; } if (!aMetadata) { MOZ_LOG(sLog, LogLevel::Error, ("[%p] AnonymousDecoderImpl::OnMetadata -- failed", this)); DestroyLocked(NS_ERROR_FAILURE); return; } const auto size = aMetadata->GetSize(); mMetadataResult.mWidth = size.width; mMetadataResult.mHeight = size.height; mMetadataResult.mRepetitions = aMetadata->GetLoopCount(); mMetadataResult.mAnimated = aMetadata->HasAnimation(); MOZ_LOG(sLog, LogLevel::Debug, ("[%p] AnonymousDecoderImpl::OnMetadata -- %dx%d, repetitions %d, " "animated %d", this, size.width, size.height, mMetadataResult.mRepetitions, mMetadataResult.mAnimated)); if (mOutputSize && !mMetadataResult.mAnimated && mFramesTask) { if (mOutputSize->width <= size.width && mOutputSize->height <= size.height) { MOZ_LOG( sLog, LogLevel::Debug, ("[%p] AnonymousDecoderImpl::OnMetadata -- use output size %dx%d", this, mOutputSize->width, mOutputSize->height)); mFramesTask->SetOutputSize( OrientedIntSize::FromUnknownSize(*mOutputSize)); } else { MOZ_LOG(sLog, LogLevel::Debug, ("[%p] AnonymousDecoderImpl::OnMetadata -- cannot use output " "size %dx%d, exceeds metadata size", this, mOutputSize->width, mOutputSize->height)); } } if (!mMetadataResult.mAnimated) { mMetadataResult.mFrameCount = 1; mMetadataResult.mFrameCountComplete = true; mMetadataTask = nullptr; mFrameCountTask = nullptr; } else if (mFrameCountTask && !mFrameCountTaskRunning) { MOZ_LOG( sLog, LogLevel::Debug, ("[%p] AnonymousDecoderImpl::OnMetadata -- start frame count task", this)); mFrameCountTaskRunning = mFrameCountTask->MaybeStart(); return; } mMetadataPromise.Resolve(mMetadataResult, __func__); if (mFramesTask && mFramesToDecode > 0 && !mFramesTaskRunning) { MOZ_LOG(sLog, LogLevel::Debug, ("[%p] AnonymousDecoderImpl::OnMetadata -- start frames task, " "want %zu", this, mFramesToDecode)); mFramesTaskRunning = mFramesTask->MaybeStart(); } } void OnFrameCount(uint32_t aFrameCount, bool aComplete) override { MutexAutoLock lock(mMutex); // We must have already gotten destroyed before frame count decoding // finished. if (!mFrameCountTask) { return; } MOZ_LOG(sLog, LogLevel::Debug, ("[%p] AnonymousDecoderImpl::OnFrameCount -- frameCount %u, " "complete %d", this, aFrameCount, aComplete)); bool resolve = aComplete; if (mFrameCount < aFrameCount) { mFrameCount = aFrameCount; resolve = true; } // If metadata completing is waiting on an updated frame count, resolve it. mMetadataResult.mFrameCount = mFrameCount; mMetadataResult.mFrameCountComplete = aComplete; mMetadataPromise.ResolveIfExists(mMetadataResult, __func__); if (mMetadataTask) { mMetadataTask = nullptr; if (mFramesTask && mFramesToDecode > 0 && !mFramesTaskRunning) { MOZ_LOG( sLog, LogLevel::Debug, ("[%p] AnonymousDecoderImpl::OnFrameCount -- start frames task, " "want %zu", this, mFramesToDecode)); mFramesTaskRunning = mFramesTask->MaybeStart(); } } if (resolve) { mFrameCountPromise.ResolveIfExists( DecodeFrameCountResult{aFrameCount, aComplete}, __func__); } if (aComplete) { mFrameCountTask = nullptr; } } bool OnFrameAvailable(RefPtr&& aFrame, RefPtr&& aSurface) override { MutexAutoLock lock(mMutex); MOZ_DIAGNOSTIC_ASSERT(mFramesTaskRunning); // We must have already gotten destroyed before frame decoding finished. if (!mFramesTask) { mFramesTaskRunning = false; return false; } // Filter duplicate frames. if (mLastFrame == aFrame) { return true; } mPendingFramesResult.mFrames.AppendElement( DecodedFrame{std::move(aSurface), mMetadataResult.mAnimated ? aFrame->GetTimeout() : FrameTimeout::Forever()}); mLastFrame = std::move(aFrame); MOZ_LOG(sLog, LogLevel::Debug, ("[%p] AnonymousDecoderImpl::OnFrameAvailable -- want %zu, got %zu", this, mFramesToDecode, mPendingFramesResult.mFrames.Length())); // Check if we have satisfied the number of requested frames. if (mFramesToDecode > mPendingFramesResult.mFrames.Length()) { return true; } mFramesToDecode = 0; if (!mFramesPromise.IsEmpty()) { mFramesPromise.Resolve(std::move(mPendingFramesResult), __func__); } mFramesTaskRunning = false; return false; } void OnFramesComplete() override { MutexAutoLock lock(mMutex); // We must have already gotten destroyed before frame decoding finished. if (!mFramesTask) { return; } MOZ_LOG( sLog, LogLevel::Debug, ("[%p] AnonymousDecoderImpl::OnFramesComplete -- wanted %zu, got %zu", this, mFramesToDecode, mPendingFramesResult.mFrames.Length())); mFramesToDecode = 0; mPendingFramesResult.mFinished = true; if (!mFramesPromise.IsEmpty()) { mFramesPromise.Resolve(std::move(mPendingFramesResult), __func__); } mLastFrame = nullptr; mFramesTask = nullptr; } RefPtr DecodeMetadata() override { MutexAutoLock lock(mMutex); if (!mMetadataTask) { MOZ_LOG(sLog, LogLevel::Debug, ("[%p] AnonymousDecoderImpl::DecodeMetadata -- already complete", this)); if (mMetadataResult.mWidth > 0 && mMetadataResult.mHeight > 0) { return DecodeMetadataPromise::CreateAndResolve(mMetadataResult, __func__); } return DecodeMetadataPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); } if (!mMetadataTaskRunning) { MOZ_LOG(sLog, LogLevel::Debug, ("[%p] AnonymousDecoderImpl::DecodeMetadata -- queue", this)); mMetadataTaskRunning = mMetadataTask->MaybeStart(); } return mMetadataPromise.Ensure(__func__); } RefPtr DecodeFrameCount( uint32_t aKnownFrameCount) override { MutexAutoLock lock(mMutex); MOZ_ASSERT(mFrameCountPromise.IsEmpty()); // If we have finished, or we have an updated frame count, return right // away. This may drive the frame decoder for the application as the data // comes in from the network. if (!mFrameCountTask || aKnownFrameCount < mFrameCount) { MOZ_LOG(sLog, LogLevel::Debug, ("[%p] AnonymousDecoderImpl::DecodeFrameCount -- known %u, " "detected %u, complete %d", this, aKnownFrameCount, mFrameCount, !mFrameCountTask)); return DecodeFrameCountPromise::CreateAndResolve( DecodeFrameCountResult{mFrameCount, /* mFinished */ !mFrameCountTask}, __func__); } // mFrameCountTask is launching when metadata decoding is finished. MOZ_LOG(sLog, LogLevel::Debug, ("[%p] AnonymousDecoderImpl::DecodeFrameCount -- waiting, known " "%u, detected %u", this, aKnownFrameCount, mFrameCount)); return mFrameCountPromise.Ensure(__func__); } RefPtr DecodeFrames(size_t aCount) override { MutexAutoLock lock(mMutex); // If we cleared our task reference, then we know we finished decoding. if (!mFramesTask) { mPendingFramesResult.mFinished = true; return DecodeFramesPromise::CreateAndResolve( std::move(mPendingFramesResult), __func__); } // If we are not waiting on any frames, then we know we paused decoding. // If we still are metadata decoding, we need to wait. if (mFramesToDecode == 0 && !mMetadataTask && !mFramesTaskRunning) { MOZ_LOG(sLog, LogLevel::Debug, ("[%p] AnonymousDecoderImpl::DecodeFrames -- queue", this)); mFramesTaskRunning = mFramesTask->MaybeStart(); } mFramesToDecode = std::max(mFramesToDecode, aCount); return mFramesPromise.Ensure(__func__); } void CancelDecodeFrames() override { MutexAutoLock lock(mMutex); MOZ_LOG(sLog, LogLevel::Debug, ("[%p] AnonymousDecoderImpl::CancelDecodeFrames", this)); mFramesToDecode = 0; mFramesPromise.RejectIfExists(NS_ERROR_ABORT, __func__); } private: Mutex mMutex; MozPromiseHolder mMetadataPromise MOZ_GUARDED_BY(mMutex); MozPromiseHolder mFrameCountPromise MOZ_GUARDED_BY(mMutex); MozPromiseHolder mFramesPromise MOZ_GUARDED_BY(mMutex); RefPtr mFramesTask MOZ_GUARDED_BY(mMutex); RefPtr mMetadataTask MOZ_GUARDED_BY(mMutex); RefPtr mFrameCountTask MOZ_GUARDED_BY(mMutex); RefPtr mLastFrame MOZ_GUARDED_BY(mMutex); DecodeMetadataResult mMetadataResult MOZ_GUARDED_BY(mMutex); DecodeFramesResult mPendingFramesResult MOZ_GUARDED_BY(mMutex); Maybe mOutputSize MOZ_GUARDED_BY(mMutex); size_t mFramesToDecode MOZ_GUARDED_BY(mMutex) = 1; uint32_t mFrameCount MOZ_GUARDED_BY(mMutex) = 0; bool mMetadataTaskRunning MOZ_GUARDED_BY(mMutex) = false; bool mFrameCountTaskRunning MOZ_GUARDED_BY(mMutex) = false; bool mFramesTaskRunning MOZ_GUARDED_BY(mMutex) = false; }; /* static */ already_AddRefed ImageUtils::CreateDecoder( SourceBuffer* aSourceBuffer, DecoderType aType, const Maybe& aOutputSize, SurfaceFlags aSurfaceFlags) { if (NS_WARN_IF(!aSourceBuffer)) { return nullptr; } if (NS_WARN_IF(aType == DecoderType::UNKNOWN)) { return nullptr; } RefPtr decoder = DecoderFactory::CreateAnonymousDecoder( aType, WrapNotNull(aSourceBuffer), Nothing(), DecoderFlags::IMAGE_IS_TRANSIENT, aSurfaceFlags); if (NS_WARN_IF(!decoder)) { return nullptr; } auto anonymousDecoder = MakeRefPtr(aOutputSize); if (NS_WARN_IF(!anonymousDecoder->Initialize(std::move(decoder)))) { return nullptr; } return anonymousDecoder.forget(); } /* static */ DecoderType ImageUtils::GetDecoderType( const nsACString& aMimeType) { return DecoderFactory::GetDecoderType(aMimeType.Data()); } } // namespace mozilla::image