/* -*- 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 "mozilla/dom/MediaMetadata.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/MediaSessionBinding.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/ToJSValue.h" #include "mozilla/image/FetchDecodedImage.h" #include "nsNetUtil.h" extern mozilla::LazyLogModule gMediaControlLog; // avoid redefined macro in unified build #undef LOG #define LOG(msg, ...) \ MOZ_LOG(gMediaControlLog, LogLevel::Debug, \ ("MediaMetadata, " msg, ##__VA_ARGS__)) namespace mozilla::dom { MediaImage MediaImageData::ToMediaImage() const { MediaImage image; image.mSizes = mSizes; image.mSrc = mSrc; image.mType = mType; return image; } // Only needed for refcounted objects. NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaMetadata, mParent) NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaMetadata) NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaMetadata) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaMetadata) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END MediaMetadata::MediaMetadata(nsIGlobalObject* aParent, const nsString& aTitle, const nsString& aArtist, const nsString& aAlbum) : MediaMetadataBase(aTitle, aArtist, aAlbum), mParent(aParent) { MOZ_ASSERT(mParent); } nsIGlobalObject* MediaMetadata::GetParentObject() const { return mParent; } JSObject* MediaMetadata::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return MediaMetadata_Binding::Wrap(aCx, this, aGivenProto); } already_AddRefed MediaMetadata::Constructor( const GlobalObject& aGlobal, const MediaMetadataInit& aInit, ErrorResult& aRv) { nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); if (!global) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } RefPtr mediaMetadata = new MediaMetadata(global, aInit.mTitle, aInit.mArtist, aInit.mAlbum); mediaMetadata->SetArtworkInternal(aInit.mArtwork, aRv); return aRv.Failed() ? nullptr : mediaMetadata.forget(); } void MediaMetadata::GetTitle(nsString& aRetVal) const { aRetVal = mTitle; } void MediaMetadata::SetTitle(const nsAString& aTitle) { if (mTitle == aTitle) { return; } mTitle = aTitle; mMetadataChangeEvent.Notify(); } void MediaMetadata::GetArtist(nsString& aRetVal) const { aRetVal = mArtist; } void MediaMetadata::SetArtist(const nsAString& aArtist) { if (mArtist == aArtist) { return; } mArtist = aArtist; mMetadataChangeEvent.Notify(); } void MediaMetadata::GetAlbum(nsString& aRetVal) const { aRetVal = mAlbum; } void MediaMetadata::SetAlbum(const nsAString& aAlbum) { if (mAlbum == aAlbum) { return; } mAlbum = aAlbum; mMetadataChangeEvent.Notify(); } void MediaMetadata::GetArtwork(JSContext* aCx, nsTArray& aRetVal, ErrorResult& aRv) const { // Convert the MediaImages to JS Objects if (!aRetVal.SetCapacity(mArtwork.Length(), fallible)) { aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return; } for (size_t i = 0; i < mArtwork.Length(); ++i) { JS::Rooted value(aCx); if (!ToJSValue(aCx, mArtwork[i].ToMediaImage(), &value)) { aRv.NoteJSContextException(aCx); return; } JS::Rooted object(aCx, &value.toObject()); if (!JS_FreezeObject(aCx, object)) { aRv.NoteJSContextException(aCx); return; } aRetVal.AppendElement(object); } } void MediaMetadata::SetArtwork(JSContext* aCx, const Sequence& aArtwork, ErrorResult& aRv) { // Convert the JS Objects to MediaImages Sequence artwork; if (!artwork.SetCapacity(aArtwork.Length(), fallible)) { aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return; } for (JSObject* object : aArtwork) { JS::Rooted value(aCx, JS::ObjectValue(*object)); MediaImage* image = artwork.AppendElement(fallible); MOZ_ASSERT(image, "The capacity is preallocated"); if (!image->Init(aCx, value)) { aRv.NoteJSContextException(aCx); return; } } SetArtworkInternal(artwork, aRv); mMetadataChangeEvent.Notify(); } RefPtr MediaMetadata::FetchArtwork( const MediaMetadataBase& aMetadata, nsIPrincipal* aPrincipal, const size_t aIndex) { if (!aPrincipal || aIndex >= aMetadata.mArtwork.Length()) { // No image loaded successfully or no principal, but still resolve without // any image data. LOG("FetchArtwork loaded no image, aPrincipal=%p.", aPrincipal); return MediaMetadataBasePromise::CreateAndResolve(aMetadata, __func__); } nsCOMPtr uri; if (NS_WARN_IF(NS_FAILED( NS_NewURI(getter_AddRefs(uri), aMetadata.mArtwork[aIndex].mSrc)))) { return FetchArtwork(aMetadata, aPrincipal, aIndex + 1); } return image::FetchDecodedImage(uri, gfx::IntSize{}, aPrincipal) ->Then( GetCurrentSerialEventTarget(), __func__, [metadata = aMetadata, principal = RefPtr{aPrincipal}, aIndex](already_AddRefed aImage) { nsCOMPtr image(std::move(aImage)); // The promise should only be resolved for decoded images, so using // FLAG_SYNC_DECODE is just a precaution. if (RefPtr surface = image->GetFrame(imgIContainer::FRAME_FIRST, imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY)) { if (RefPtr dataSurface = surface->GetDataSurface()) { MediaMetadataBase data(metadata); data.mArtwork[aIndex].mDataSurface = dataSurface; LOG("FetchArtwork successfully loaded and decoded an image."); return MediaMetadataBasePromise::CreateAndResolve(data, __func__); } } return FetchArtwork(metadata, principal, aIndex + 1); }, [metadata = aMetadata, principal = RefPtr{aPrincipal}, aIndex](nsresult aStatus) { return FetchArtwork(metadata, principal, aIndex + 1); }); } RefPtr MediaMetadata::LoadMetadataArtwork() { return FetchArtwork(*this, mParent->PrincipalOrNull(), 0); } static nsIURI* GetEntryBaseURL() { nsCOMPtr doc = GetEntryDocument(); return doc ? doc->GetDocBaseURI() : nullptr; } // `aURL` is an inout parameter. static nsresult ResolveURL(nsString& aURL, nsIURI* aBaseURI) { nsCOMPtr uri; nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, /* UTF-8 for charset */ nullptr, aBaseURI); if (NS_FAILED(rv)) { return rv; } nsAutoCString spec; rv = uri->GetSpec(spec); if (NS_FAILED(rv)) { return rv; } CopyUTF8toUTF16(spec, aURL); return NS_OK; } void MediaMetadata::SetArtworkInternal(const Sequence& aArtwork, ErrorResult& aRv) { nsCOMPtr baseURI = GetEntryBaseURL(); nsTArray artwork; for (const MediaImage& image : aArtwork) { MediaImageData imageData(image); nsresult rv = ResolveURL(imageData.mSrc, baseURI); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.ThrowTypeError(NS_ConvertUTF16toUTF8(image.mSrc)); return; } artwork.AppendElement(std::move(imageData)); } mArtwork = std::move(artwork); } } // namespace mozilla::dom