tor-browser

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

commit 4df889591754afec179b7cb419fb803d67bb6a06
parent 5268af90d48886646439fb7f46fe2374e4ac2561
Author: Cristina Horotan <chorotan@mozilla.com>
Date:   Mon, 10 Nov 2025 14:03:33 +0200

Revert "Bug 1944033: apply code formatting via Lando" for causing build bustages at MediaSession.h

This reverts commit 3394e884c5a49f2fb34e52e4503b0fefbca050c4.

Revert "Bug 1944033 - Remove FetchImageHelper. r=media-playback-reviewers,aosmond"

This reverts commit 59332a010eb378b6656819bc798eb7477d70f2a6.

Revert "Bug 1944033 - Update macOS/cocoa to use the image from the child. r=media-playback-reviewers,aosmond"

This reverts commit 5d7b2b78ccbdd03698a47be1f9cff4eb335f00a2.

Revert "Bug 1944033 - Update Windows to use the image from the child. r=media-playback-reviewers,win-reviewers,gstoll,aosmond"

This reverts commit 44b88dad2e5fa531f43c11169c2c170e9073dfda.

Revert "Bug 1944033 - Update Linux/MPRIS to use the image from the child. r=media-playback-reviewers,aosmond"

This reverts commit 570166de598ed3f4b5f10bfdaefac07a6fb98218.

Revert "Bug 1944033 - Load MediaSession artwork in the child. r=media-playback-reviewers,aosmond"

This reverts commit 6dc346b92490dcac3b401a34ad7a346febc75193.

Revert "Bug 1944033 - Add MediaImageData. r=media-playback-reviewers,aosmond"

This reverts commit 01d56417ef9c72252ffe694010c2d7cf96adb2b9.

Diffstat:
Adom/media/mediacontrol/FetchImageHelper.cpp | 164+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adom/media/mediacontrol/FetchImageHelper.h | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdom/media/mediacontrol/MediaControlUtils.h | 12+++---------
Mdom/media/mediacontrol/moz.build | 2++
Mdom/media/mediasession/MediaMetadata.cpp | 71++++++-----------------------------------------------------------------
Mdom/media/mediasession/MediaMetadata.h | 34+++++-----------------------------
Mdom/media/mediasession/MediaSession.cpp | 30++++++------------------------
Mdom/media/mediasession/MediaSession.h | 3---
Mdom/media/mediasession/MediaSessionIPCUtils.h | 19++-----------------
Mwidget/cocoa/MediaHardwareKeysEventSourceMacMediaCenter.h | 9+++++++++
Mwidget/cocoa/MediaHardwareKeysEventSourceMacMediaCenter.mm | 124++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mwidget/gtk/MPRISServiceHandler.cpp | 122++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Mwidget/gtk/MPRISServiceHandler.h | 10++++++++++
Mwidget/windows/WindowsSMTCProvider.cpp | 119++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Mwidget/windows/WindowsSMTCProvider.h | 10+++++++++-
15 files changed, 561 insertions(+), 250 deletions(-)

diff --git a/dom/media/mediacontrol/FetchImageHelper.cpp b/dom/media/mediacontrol/FetchImageHelper.cpp @@ -0,0 +1,164 @@ +/* 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 "FetchImageHelper.h" + +#include "mozilla/Logging.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/gfx/2D.h" +#include "nsIChannel.h" +#include "nsNetUtil.h" + +mozilla::LazyLogModule gFetchImageLog("FetchImageHelper"); + +#undef LOG +#define LOG(msg, ...) \ + MOZ_LOG(gFetchImageLog, LogLevel::Debug, \ + ("FetchImageHelper=%p, " msg, this, ##__VA_ARGS__)) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +FetchImageHelper::FetchImageHelper(const MediaImage& aImage) + : mSrc(aImage.mSrc) {} + +FetchImageHelper::~FetchImageHelper() { AbortFetchingImage(); } + +RefPtr<ImagePromise> FetchImageHelper::FetchImage() { + MOZ_ASSERT(NS_IsMainThread()); + if (IsFetchingImage()) { + return mPromise.Ensure(__func__); + } + + LOG("Start fetching image from %s", NS_ConvertUTF16toUTF8(mSrc).get()); + nsCOMPtr<nsIURI> uri; + if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), mSrc))) { + LOG("Failed to create URI"); + return ImagePromise::CreateAndReject(false, __func__); + } + + MOZ_ASSERT(!mListener); + mListener = new ImageFetchListener(); + if (NS_FAILED(mListener->FetchDecodedImageFromURI(uri, this))) { + LOG("Failed to decode image from async channel"); + return ImagePromise::CreateAndReject(false, __func__); + } + return mPromise.Ensure(__func__); +} + +void FetchImageHelper::AbortFetchingImage() { + MOZ_ASSERT(NS_IsMainThread()); + LOG("AbortFetchingImage"); + mPromise.RejectIfExists(false, __func__); + ClearListenerIfNeeded(); +} + +void FetchImageHelper::ClearListenerIfNeeded() { + if (mListener) { + mListener->Clear(); + mListener = nullptr; + } +} + +bool FetchImageHelper::IsFetchingImage() const { + MOZ_ASSERT(NS_IsMainThread()); + return !mPromise.IsEmpty() && mListener; +} + +void FetchImageHelper::HandleFetchSuccess(imgIContainer* aImage) { + MOZ_ASSERT(aImage); + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(IsFetchingImage()); + LOG("Finished fetching image"); + mPromise.Resolve(aImage, __func__); + ClearListenerIfNeeded(); +} + +void FetchImageHelper::HandleFetchFail() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(IsFetchingImage()); + LOG("Reject the promise because of fetching failed"); + mPromise.RejectIfExists(false, __func__); + ClearListenerIfNeeded(); +} + +/** + * Implementation for FetchImageHelper::ImageFetchListener + */ +NS_IMPL_ISUPPORTS(FetchImageHelper::ImageFetchListener, imgIContainerCallback) + +FetchImageHelper::ImageFetchListener::~ImageFetchListener() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mHelper, "Cancel() should be called before desturction!"); +} + +nsresult FetchImageHelper::ImageFetchListener::FetchDecodedImageFromURI( + nsIURI* aURI, FetchImageHelper* aHelper) { + MOZ_ASSERT(!mHelper && !mChannel, + "Should call Clear() berfore running another fetching process!"); + RefPtr<nsIPrincipal> nullPrincipal = + NullPrincipal::CreateWithoutOriginAttributes(); + nsCOMPtr<nsIChannel> channel; + nsresult rv = + NS_NewChannel(getter_AddRefs(channel), aURI, nullPrincipal, + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + nsIContentPolicy::TYPE_INTERNAL_IMAGE, nullptr, nullptr, + nullptr, nullptr, nsIRequest::LOAD_ANONYMOUS); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<imgITools> imgTools = do_GetService("@mozilla.org/image/tools;1"); + if (!imgTools) { + return NS_ERROR_FAILURE; + } + + rv = imgTools->DecodeImageFromChannelAsync(aURI, channel, this, nullptr); + if (NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } + MOZ_ASSERT(aHelper); + mHelper = aHelper; + mChannel = channel; + return NS_OK; +} + +void FetchImageHelper::ImageFetchListener::Clear() { + MOZ_ASSERT(NS_IsMainThread()); + if (mChannel) { + mChannel->CancelWithReason( + NS_BINDING_ABORTED, "FetchImageHelper::ImageFetchListener::Clear"_ns); + mChannel = nullptr; + } + mHelper = nullptr; +} + +bool FetchImageHelper::ImageFetchListener::IsFetchingImage() const { + MOZ_ASSERT(NS_IsMainThread()); + return mHelper ? mHelper->IsFetchingImage() : false; +} + +NS_IMETHODIMP FetchImageHelper::ImageFetchListener::OnImageReady( + imgIContainer* aImage, nsresult aStatus) { + MOZ_ASSERT(NS_IsMainThread()); + if (!IsFetchingImage()) { + return NS_OK; + } + // We have received image, so we don't need the channel anymore. + mChannel = nullptr; + + MOZ_ASSERT(mHelper); + if (NS_FAILED(aStatus) || !aImage) { + mHelper->HandleFetchFail(); + Clear(); + return aStatus; + } + + mHelper->HandleFetchSuccess(aImage); + + return NS_OK; +} + +} // namespace mozilla::dom diff --git a/dom/media/mediacontrol/FetchImageHelper.h b/dom/media/mediacontrol/FetchImageHelper.h @@ -0,0 +1,82 @@ +/* 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/. */ + +#ifndef DOM_MEDIA_MEDIACONTROL_FETCHIMAGEHELPER_H_ +#define DOM_MEDIA_MEDIACONTROL_FETCHIMAGEHELPER_H_ + +#include "imgIContainer.h" +#include "imgITools.h" +#include "mozilla/MozPromise.h" +#include "mozilla/dom/MediaSessionBinding.h" + +namespace mozilla::dom { +/** + * FetchImageHelper is used to fetch image data from MediaImage, and the fetched + * image data would be used to show on the virtual control inferface. The URL of + * MediaImage is defined by websites by using MediaSession API [1]. + * + * By using `FetchImage()`, it would return a promise that would resolve with a + * `imgIContainer`, then we can get the image data from the container. + * + * [1] https://w3c.github.io/mediasession/#dictdef-mediaimage + */ +using ImagePromise = MozPromise<nsCOMPtr<imgIContainer>, bool, + /* IsExclusive = */ true>; +class FetchImageHelper final { + public: + explicit FetchImageHelper(const MediaImage& aImage); + ~FetchImageHelper(); + + // Return a promise which would be resolved with the decoded image surface + // when we finish fetching and decoding image data, and it would be rejected + // when we fail to fecth the image. + RefPtr<ImagePromise> FetchImage(); + + // Stop fetching and decoding image and reject the image promise. If we have + // not started yet fetching image, then nothing would happen. + void AbortFetchingImage(); + + // Return true if we're fecthing image. + bool IsFetchingImage() const; + + private: + /** + * ImageFetchListener is used to listen the notification of finishing fetching + * image data (via `OnImageReady()`) and finishing decoding image data (via + * `Notify()`). + */ + class ImageFetchListener final : public imgIContainerCallback { + public: + NS_DECL_ISUPPORTS + ImageFetchListener() = default; + + // Start an async channel to load the image, and return error if the channel + // opens failed. It would use `aHelper::HandleFetchSuccess/Fail()` to notify + // the result asynchronously. + nsresult FetchDecodedImageFromURI(nsIURI* aURI, FetchImageHelper* aHelper); + void Clear(); + bool IsFetchingImage() const; + + // Method of imgIContainerCallback + NS_IMETHOD OnImageReady(imgIContainer* aImage, nsresult aStatus) override; + + private: + ~ImageFetchListener(); + + FetchImageHelper* MOZ_NON_OWNING_REF mHelper = nullptr; + nsCOMPtr<nsIChannel> mChannel; + }; + + void ClearListenerIfNeeded(); + void HandleFetchSuccess(imgIContainer* aImage); + void HandleFetchFail(); + + nsString mSrc; + MozPromiseHolder<ImagePromise> mPromise; + RefPtr<ImageFetchListener> mListener; +}; + +} // namespace mozilla::dom + +#endif // DOM_MEDIA_MEDIACONTROL_FETCHIMAGEHELPER_H_ diff --git a/dom/media/mediacontrol/MediaControlUtils.h b/dom/media/mediacontrol/MediaControlUtils.h @@ -7,9 +7,7 @@ #ifndef DOM_MEDIA_MEDIACONTROL_MEDIACONTROLUTILS_H_ #define DOM_MEDIA_MEDIACONTROL_MEDIACONTROLUTILS_H_ -#include "ImageOps.h" #include "MediaController.h" -#include "gfxDrawable.h" #include "imgIEncoder.h" #include "imgITools.h" #include "mozilla/Logging.h" @@ -96,23 +94,19 @@ inline bool IsImageIn(const nsTArray<MediaImage>& aArtwork, // The image buffer would be allocated in aStream whose size is aSize and the // buffer head is aBuffer -inline nsresult GetEncodedImageBuffer(gfx::DataSourceSurface* aSurface, +inline nsresult GetEncodedImageBuffer(imgIContainer* aImage, const nsACString& aMimeType, nsIInputStream** aStream, uint32_t* aSize, char** aBuffer) { - MOZ_ASSERT(aSurface); + MOZ_ASSERT(aImage); nsCOMPtr<imgITools> imgTools = do_GetService("@mozilla.org/image/tools;1"); if (!imgTools) { return NS_ERROR_FAILURE; } - RefPtr<gfxDrawable> drawable = - new gfxSurfaceDrawable(aSurface, aSurface->GetSize()); - nsCOMPtr<imgIContainer> image = image::ImageOps::CreateFromDrawable(drawable); - nsCOMPtr<nsIInputStream> inputStream; - nsresult rv = imgTools->EncodeImage(image, aMimeType, u""_ns, + nsresult rv = imgTools->EncodeImage(aImage, aMimeType, u""_ns, getter_AddRefs(inputStream)); if (NS_FAILED(rv)) { return rv; diff --git a/dom/media/mediacontrol/moz.build b/dom/media/mediacontrol/moz.build @@ -7,6 +7,7 @@ EXPORTS.mozilla.dom += [ "AudioFocusManager.h", "ContentMediaController.h", "ContentPlaybackController.h", + "FetchImageHelper.h", "MediaControlKeyManager.h", "MediaControlKeySource.h", "MediaController.h", @@ -24,6 +25,7 @@ UNIFIED_SOURCES += [ "AudioFocusManager.cpp", "ContentMediaController.cpp", "ContentPlaybackController.cpp", + "FetchImageHelper.cpp", "MediaControlKeyManager.cpp", "MediaControlKeySource.cpp", "MediaController.cpp", diff --git a/dom/media/mediasession/MediaMetadata.cpp b/dom/media/mediasession/MediaMetadata.cpp @@ -10,19 +10,10 @@ #include "mozilla/dom/MediaSessionBinding.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/ToJSValue.h" -#include "mozilla/image/FetchDecodedImage.h" #include "nsNetUtil.h" 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) @@ -82,7 +73,7 @@ void MediaMetadata::GetArtwork(JSContext* aCx, nsTArray<JSObject*>& aRetVal, for (size_t i = 0; i < mArtwork.Length(); ++i) { JS::Rooted<JS::Value> value(aCx); - if (!ToJSValue(aCx, mArtwork[i].ToMediaImage(), &value)) { + if (!ToJSValue(aCx, mArtwork[i], &value)) { aRv.NoteJSContextException(aCx); return; } @@ -121,54 +112,6 @@ void MediaMetadata::SetArtwork(JSContext* aCx, SetArtworkInternal(artwork, aRv); }; -RefPtr<MediaMetadataBasePromise> 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. - return MediaMetadataBasePromise::CreateAndResolve(aMetadata, __func__); - } - - nsCOMPtr<nsIURI> 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<imgIContainer> aImage) { - nsCOMPtr<imgIContainer> image(std::move(aImage)); - // The promise should only be resolved for decoded images, so using - // FLAG_SYNC_DECODE is just a precaution. - if (RefPtr<mozilla::gfx::SourceSurface> surface = - image->GetFrame(imgIContainer::FRAME_FIRST, - imgIContainer::FLAG_SYNC_DECODE | - imgIContainer::FLAG_ASYNC_NOTIFY)) { - if (RefPtr<mozilla::gfx::DataSourceSurface> dataSurface = - surface->GetDataSurface()) { - MediaMetadataBase data(metadata); - data.mArtwork[aIndex].mDataSurface = dataSurface; - 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<MediaMetadataBasePromise> MediaMetadata::LoadMetadataArtwork() { - // TODO: Track Promise? - return FetchArtwork(*this, mParent->PrincipalOrNull(), 0); -} - static nsIURI* GetEntryBaseURL() { nsCOMPtr<Document> doc = GetEntryDocument(); return doc ? doc->GetDocBaseURI() : nullptr; @@ -195,19 +138,17 @@ static nsresult ResolveURL(nsString& aURL, nsIURI* aBaseURI) { void MediaMetadata::SetArtworkInternal(const Sequence<MediaImage>& aArtwork, ErrorResult& aRv) { - nsCOMPtr<nsIURI> baseURI = GetEntryBaseURL(); + nsTArray<MediaImage> artwork; + artwork.Assign(aArtwork); - nsTArray<MediaImageData> artwork; - for (const MediaImage& image : aArtwork) { - MediaImageData imageData(image); - nsresult rv = ResolveURL(imageData.mSrc, baseURI); + nsCOMPtr<nsIURI> baseURI = GetEntryBaseURL(); + for (MediaImage& image : artwork) { + nsresult rv = ResolveURL(image.mSrc, baseURI); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.ThrowTypeError<MSG_INVALID_URL>(NS_ConvertUTF16toUTF8(image.mSrc)); return; } - artwork.AppendElement(std::move(imageData)); } - mArtwork = std::move(artwork); } diff --git a/dom/media/mediasession/MediaMetadata.h b/dom/media/mediasession/MediaMetadata.h @@ -10,7 +10,6 @@ #include "js/TypeDecls.h" #include "mozilla/dom/BindingDeclarations.h" #include "mozilla/dom/MediaSessionBinding.h" -#include "mozilla/gfx/2D.h" #include "nsCycleCollectionParticipant.h" #include "nsWrapperCache.h" @@ -21,22 +20,6 @@ class ErrorResult; namespace dom { -class MediaImageData { - public: - MediaImageData() = default; - explicit MediaImageData(const MediaImage& aImage) - : mSizes(aImage.mSizes), mSrc(aImage.mSrc), mType(aImage.mType) {} - - MediaImage ToMediaImage() const; - - nsString mSizes; - nsString mSrc; - nsString mType; - // Maybe null, only the first valid artwork is fetched by - // MediaMetadata::FetchArtwork. - RefPtr<mozilla::gfx::DataSourceSurface> mDataSurface; -}; - class MediaMetadataBase { public: MediaMetadataBase() = default; @@ -50,12 +33,9 @@ class MediaMetadataBase { nsString mArtist; nsString mAlbum; nsCString mUrl; - CopyableTArray<MediaImageData> mArtwork; + CopyableTArray<MediaImage> mArtwork; }; -using MediaMetadataBasePromise = - mozilla::MozPromise<MediaMetadataBase, nsresult, true>; - class MediaMetadata final : public nsISupports, public nsWrapperCache, private MediaMetadataBase { @@ -92,10 +72,10 @@ class MediaMetadata final : public nsISupports, void SetArtwork(JSContext* aCx, const Sequence<JSObject*>& aArtwork, ErrorResult& aRv); - // This function will always resolve successfully, even when no artwork was - // loaded. - // At most, it returns one decoded image of the artwork. - RefPtr<MediaMetadataBasePromise> LoadMetadataArtwork(); + // This would expose MediaMetadataBase's members as public, so use this method + // carefully. Now we only use this when we want to update the metadata to the + // media session controller in the chrome process. + MediaMetadataBase* AsMetadataBase() { return this; } private: MediaMetadata(nsIGlobalObject* aParent, const nsString& aTitle, @@ -108,10 +88,6 @@ class MediaMetadata final : public nsISupports, void SetArtworkInternal(const Sequence<MediaImage>& aArtwork, ErrorResult& aRv); - static RefPtr<MediaMetadataBasePromise> FetchArtwork( - const MediaMetadataBase& aMetadata, nsIPrincipal* aPrincipal, - const size_t aIndex); - nsCOMPtr<nsIGlobalObject> mParent; }; diff --git a/dom/media/mediasession/MediaSession.cpp b/dom/media/mediasession/MediaSession.cpp @@ -87,7 +87,6 @@ void MediaSession::Shutdown() { if (mParent) { SetMediaSessionDocStatus(SessionDocStatus::eInactive); } - mMetadataRequest.DisconnectIfExists(); } void MediaSession::NotifyOwnerDocumentActivityChanged() { @@ -316,30 +315,13 @@ void MediaSession::NotifyMetadataUpdated() { RefPtr<BrowsingContext> currentBC = GetParentObject()->GetBrowsingContext(); MOZ_ASSERT(currentBC, "Update session metadata after context destroyed!"); - if (!mMediaMetadata) { - if (RefPtr<IMediaInfoUpdater> updater = ContentMediaAgent::Get(currentBC)) { - updater->UpdateMetadata(currentBC->Id(), Nothing()); - } - return; + Maybe<MediaMetadataBase> metadata; + if (GetMetadata()) { + metadata.emplace(*(GetMetadata()->AsMetadataBase())); + } + if (RefPtr<IMediaInfoUpdater> updater = ContentMediaAgent::Get(currentBC)) { + updater->UpdateMetadata(currentBC->Id(), metadata); } - - mMetadataRequest.DisconnectIfExists(); - - mMediaMetadata->LoadMetadataArtwork() - ->Then( - GetCurrentSerialEventTarget(), __func__, - [self = RefPtr{this}, currentBC](MediaMetadataBase&& aMetadata) { - if (RefPtr<IMediaInfoUpdater> updater = - ContentMediaAgent::Get(currentBC)) { - updater->UpdateMetadata(currentBC->Id(), Some(aMetadata)); - } - - self->mMetadataRequest.Complete(); - }, - [](nsresult rv) { - MOZ_ASSERT_UNREACHABLE("LoadMetadataArtwork should always resolve"); - }) - ->Track(mMetadataRequest); } void MediaSession::NotifyEnableSupportedAction(MediaSessionAction aAction) { diff --git a/dom/media/mediasession/MediaSession.h b/dom/media/mediasession/MediaSession.h @@ -131,9 +131,6 @@ class MediaSession final : public nsIDocumentActivity, public nsWrapperCache { Maybe<PositionState> mPositionState; RefPtr<Document> mDoc; SessionDocStatus mSessionDocState = SessionDocStatus::eInactive; - - MozPromiseRequestHolder<mozilla::dom::MediaMetadataBasePromise> - mMetadataRequest; }; } // namespace dom diff --git a/dom/media/mediasession/MediaSessionIPCUtils.h b/dom/media/mediasession/MediaSessionIPCUtils.h @@ -11,7 +11,6 @@ #include "mozilla/dom/BindingIPCUtils.h" #include "mozilla/dom/MediaSession.h" #include "mozilla/dom/MediaSessionBinding.h" -#include "nsContentUtils.h" namespace mozilla { namespace dom { @@ -24,19 +23,13 @@ typedef Maybe<MediaMetadataBase> MaybeMediaMetadataBase; namespace IPC { template <> -struct ParamTraits<mozilla::dom::MediaImageData> { - typedef mozilla::dom::MediaImageData paramType; +struct ParamTraits<mozilla::dom::MediaImage> { + typedef mozilla::dom::MediaImage paramType; static void Write(MessageWriter* aWriter, const paramType& aParam) { WriteParam(aWriter, aParam.mSizes); WriteParam(aWriter, aParam.mSrc); WriteParam(aWriter, aParam.mType); - - mozilla::Maybe<mozilla::dom::IPCImage> image; - if (aParam.mDataSurface) { - image = nsContentUtils::SurfaceToIPCImage(*aParam.mDataSurface); - } - WriteParam(aWriter, std::move(image)); } static bool Read(MessageReader* aReader, paramType* aResult) { @@ -45,14 +38,6 @@ struct ParamTraits<mozilla::dom::MediaImageData> { !ReadParam(aReader, &(aResult->mType))) { return false; } - - mozilla::Maybe<mozilla::dom::IPCImage> image; - if (!ReadParam(aReader, &image)) { - return false; - } - if (image) { - aResult->mDataSurface = nsContentUtils::IPCImageToSurface(*image); - } return true; } }; diff --git a/widget/cocoa/MediaHardwareKeysEventSourceMacMediaCenter.h b/widget/cocoa/MediaHardwareKeysEventSourceMacMediaCenter.h @@ -5,6 +5,7 @@ #ifndef WIDGET_COCOA_MEDIAHARDWAREKEYSEVENTSOURCEMACMEDIACENTER_H_ #define WIDGET_COCOA_MEDIAHARDWAREKEYSEVENTSOURCEMACMEDIACENTER_H_ +#include "mozilla/dom/FetchImageHelper.h" #include "mozilla/dom/MediaControlKeySource.h" #ifdef __OBJC__ @@ -51,7 +52,15 @@ class MediaHardwareKeysEventSourceMacMediaCenter final bool mOpened = false; dom::MediaMetadataBase mMediaMetadata; + // Should only be used on main thread + UniquePtr<dom::FetchImageHelper> mImageFetcher; + MozPromiseRequestHolder<dom::ImagePromise> mImageFetchRequest; + + nsString mFetchingUrl; nsString mCurrentImageUrl; + size_t mNextImageIndex = 0; + + void LoadImageAtIndex(const size_t aIndex); MediaCenterEventHandler mPlayPauseHandler; MediaCenterEventHandler mNextTrackHandler; diff --git a/widget/cocoa/MediaHardwareKeysEventSourceMacMediaCenter.mm b/widget/cocoa/MediaHardwareKeysEventSourceMacMediaCenter.mm @@ -156,7 +156,10 @@ bool MediaHardwareKeysEventSourceMacMediaCenter::Open() { void MediaHardwareKeysEventSourceMacMediaCenter::Close() { LOG("Close MediaHardwareKeysEventSourceMacMediaCenter"); SetPlaybackState(MediaSessionPlaybackState::None); + mImageFetchRequest.DisconnectIfExists(); mCurrentImageUrl.Truncate(); + mFetchingUrl.Truncate(); + mNextImageIndex = 0; EndListeningForEvents(); mOpened = false; MediaControlKeySource::Close(); @@ -202,50 +205,15 @@ void MediaHardwareKeysEventSourceMacMediaCenter::SetMediaMetadata( forKey:MPMediaItemPropertyArtist]; [nowPlayingInfo setObject:nsCocoaUtils::ToNSString(aMetadata.mAlbum) forKey:MPMediaItemPropertyAlbumTitle]; + if (mCurrentImageUrl.IsEmpty() || + !IsImageIn(aMetadata.mArtwork, mCurrentImageUrl)) { + [nowPlayingInfo removeObjectForKey:MPMediaItemPropertyArtwork]; - bool remove = true; - for (const dom::MediaImageData& imageData : aMetadata.mArtwork) { - if (!imageData.mDataSurface) { - continue; - } - - if (mCurrentImageUrl == imageData.mSrc) { - LOG("Artwork image url did not change."); - remove = false; - break; - } - - RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable( - imageData.mDataSurface, imageData.mDataSurface->GetSize()); - nsCOMPtr<imgIContainer> imageContainer = - image::ImageOps::CreateFromDrawable(drawable); - - NSImage* image; - nsresult rv = - nsCocoaUtils::CreateDualRepresentationNSImageFromImageContainer( - imageContainer, imgIContainer::FRAME_CURRENT, nullptr, - NSMakeSize(0, 0), &image); - if (NS_FAILED(rv) || !image) { - LOG("Failed to create cocoa image. Try next image"); - continue; + if (mFetchingUrl.IsEmpty() || + !IsImageIn(aMetadata.mArtwork, mFetchingUrl)) { + mNextImageIndex = 0; + LoadImageAtIndex(mNextImageIndex++); } - - MPMediaItemArtwork* artwork = [[MPMediaItemArtwork alloc] - initWithBoundsSize:image.size - requestHandler:^NSImage* _Nonnull(CGSize aSize) { - return image; - }]; - [nowPlayingInfo setObject:artwork forKey:MPMediaItemPropertyArtwork]; - [artwork release]; - [image release]; - - mCurrentImageUrl = imageData.mSrc; - remove = false; - break; - } - - if (remove) { - [nowPlayingInfo removeObjectForKey:MPMediaItemPropertyArtwork]; } // The procedure of updating `nowPlayingInfo` is actually an async operation @@ -296,5 +264,77 @@ void MediaHardwareKeysEventSourceMacMediaCenter::SetPositionState( } } +void MediaHardwareKeysEventSourceMacMediaCenter::LoadImageAtIndex( + const size_t aIndex) { + MOZ_ASSERT(NS_IsMainThread()); + + if (aIndex >= mMediaMetadata.mArtwork.Length()) { + LOG("Stop loading image. No available image"); + mImageFetchRequest.DisconnectIfExists(); + mFetchingUrl.Truncate(); + return; + } + + const MediaImage& image = mMediaMetadata.mArtwork[aIndex]; + + if (!IsValidImageUrl(image.mSrc)) { + LOG("Skip the image with invalid URL. Try next image"); + LoadImageAtIndex(mNextImageIndex++); + return; + } + + mImageFetchRequest.DisconnectIfExists(); + mFetchingUrl = image.mSrc; + + mImageFetcher = MakeUnique<dom::FetchImageHelper>(image); + RefPtr<MediaHardwareKeysEventSourceMacMediaCenter> self = this; + mImageFetcher->FetchImage() + ->Then( + AbstractThread::MainThread(), __func__, + [this, self](const nsCOMPtr<imgIContainer>& aImage) { + LOG("The image is fetched successfully"); + mImageFetchRequest.Complete(); + + NSImage* image; + nsresult rv = + nsCocoaUtils::CreateDualRepresentationNSImageFromImageContainer( + aImage, imgIContainer::FRAME_CURRENT, nullptr, + NSMakeSize(0, 0), &image); + if (NS_FAILED(rv) || !image) { + LOG("Failed to create cocoa image. Try next image"); + LoadImageAtIndex(mNextImageIndex++); + return; + } + mCurrentImageUrl = mFetchingUrl; + + MPNowPlayingInfoCenter* center = + [MPNowPlayingInfoCenter defaultCenter]; + NSMutableDictionary* nowPlayingInfo = + [[center.nowPlayingInfo mutableCopy] autorelease] + ?: [NSMutableDictionary dictionary]; + + MPMediaItemArtwork* artwork = [[MPMediaItemArtwork alloc] + initWithBoundsSize:image.size + requestHandler:^NSImage* _Nonnull(CGSize aSize) { + return image; + }]; + [nowPlayingInfo setObject:artwork + forKey:MPMediaItemPropertyArtwork]; + [artwork release]; + [image release]; + + center.nowPlayingInfo = nowPlayingInfo; + + mFetchingUrl.Truncate(); + }, + [this, self](bool) { + LOG("Failed to fetch image. Try next image"); + mImageFetchRequest.Complete(); + mFetchingUrl.Truncate(); + LoadImageAtIndex(mNextImageIndex++); + }) + ->Track(mImageFetchRequest); +} + } // namespace widget } // namespace mozilla diff --git a/widget/gtk/MPRISServiceHandler.cpp b/widget/gtk/MPRISServiceHandler.cpp @@ -531,38 +531,38 @@ GVariant* MPRISServiceHandler::GetPlaybackStatus() const { void MPRISServiceHandler::SetMediaMetadata( const dom::MediaMetadataBase& aMetadata) { - SetMediaMetadataInternal(aMetadata); - - for (const dom::MediaImageData& image : aMetadata.mArtwork) { - if (!image.mDataSurface) { - continue; - } - - if (mCurrentImageUrl == image.mSrc) { - LOGMPRIS("Artwork image URL did not change"); - break; - } - - uint32_t size = 0; - char* data = nullptr; - // Only used to hold the image data - nsCOMPtr<nsIInputStream> inputStream; - - nsresult rv = - dom::GetEncodedImageBuffer(image.mDataSurface, mMimeType, - getter_AddRefs(inputStream), &size, &data); - if (NS_FAILED(rv) || !inputStream || size == 0 || !data) { - LOGMPRIS("Failed to get the image buffer info. Try next image"); - continue; + // Reset the index of the next available image to be fetched in the artwork, + // before checking the fetching process should be started or not. The image + // fetching process could be skipped if the image being fetching currently is + // in the artwork. If the current image fetching fails, the next availabe + // candidate should be the first image in the latest artwork + mNextImageIndex = 0; + + // No need to fetch a MPRIS image if + // 1) MPRIS image is being fetched, and the one in fetching is in the artwork + // 2) MPRIS image is not being fetched, and the one in use is in the artwork + if (!mFetchingUrl.IsEmpty()) { + if (dom::IsImageIn(aMetadata.mArtwork, mFetchingUrl)) { + LOGMPRIS( + "No need to load MPRIS image. The one being processed is in the " + "artwork"); + // Set MPRIS without the image first. The image will be loaded to MPRIS + // asynchronously once it's fetched and saved into a local file + SetMediaMetadataInternal(aMetadata); + return; } - - if (SetImageToDisplay(data, size)) { - mCurrentImageUrl = image.mSrc; - LOGMPRIS("The MPRIS image is updated to the image from: %s", - NS_ConvertUTF16toUTF8(mCurrentImageUrl).get()); - break; + } else if (!mCurrentImageUrl.IsEmpty()) { + if (dom::IsImageIn(aMetadata.mArtwork, mCurrentImageUrl)) { + LOGMPRIS("No need to load MPRIS image. The one in use is in the artwork"); + SetMediaMetadataInternal(aMetadata, false); + return; } } + + // Set MPRIS without the image first then load the image to MPRIS + // asynchronously + SetMediaMetadataInternal(aMetadata); + LoadImageAtIndex(mNextImageIndex++); } bool MPRISServiceHandler::EmitMetadataChanged() const { @@ -588,12 +588,76 @@ void MPRISServiceHandler::SetMediaMetadataInternal( void MPRISServiceHandler::ClearMetadata() { mMPRISMetadata.Clear(); + mImageFetchRequest.DisconnectIfExists(); RemoveAllLocalImages(); mCurrentImageUrl.Truncate(); + mFetchingUrl.Truncate(); + mNextImageIndex = 0; mSupportedKeys = 0; EmitMetadataChanged(); } +void MPRISServiceHandler::LoadImageAtIndex(const size_t aIndex) { + MOZ_ASSERT(NS_IsMainThread()); + + if (aIndex >= mMPRISMetadata.mArtwork.Length()) { + LOGMPRIS("Stop loading image to MPRIS. No available image"); + mImageFetchRequest.DisconnectIfExists(); + return; + } + + const dom::MediaImage& image = mMPRISMetadata.mArtwork[aIndex]; + + if (!dom::IsValidImageUrl(image.mSrc)) { + LOGMPRIS("Skip the image with invalid URL. Try next image"); + LoadImageAtIndex(mNextImageIndex++); + return; + } + + mImageFetchRequest.DisconnectIfExists(); + mFetchingUrl = image.mSrc; + + mImageFetcher = MakeUnique<dom::FetchImageHelper>(image); + RefPtr<MPRISServiceHandler> self = this; + mImageFetcher->FetchImage() + ->Then( + AbstractThread::MainThread(), __func__, + [this, self](const nsCOMPtr<imgIContainer>& aImage) { + LOGMPRIS("The image is fetched successfully"); + mImageFetchRequest.Complete(); + + uint32_t size = 0; + char* data = nullptr; + // Only used to hold the image data + nsCOMPtr<nsIInputStream> inputStream; + nsresult rv = dom::GetEncodedImageBuffer( + aImage, mMimeType, getter_AddRefs(inputStream), &size, &data); + if (NS_FAILED(rv) || !inputStream || size == 0 || !data) { + LOGMPRIS("Failed to get the image buffer info. Try next image"); + LoadImageAtIndex(mNextImageIndex++); + return; + } + + if (SetImageToDisplay(data, size)) { + mCurrentImageUrl = mFetchingUrl; + LOGMPRIS("The MPRIS image is updated to the image from: %s", + NS_ConvertUTF16toUTF8(mCurrentImageUrl).get()); + } else { + LOGMPRIS("Failed to set image to MPRIS"); + mCurrentImageUrl.Truncate(); + } + + mFetchingUrl.Truncate(); + }, + [this, self](bool) { + LOGMPRIS("Failed to fetch image. Try next image"); + mImageFetchRequest.Complete(); + mFetchingUrl.Truncate(); + LoadImageAtIndex(mNextImageIndex++); + }) + ->Track(mImageFetchRequest); +} + bool MPRISServiceHandler::SetImageToDisplay(const char* aImageData, uint32_t aDataSize) { if (!RenewLocalImageFile(aImageData, aDataSize)) { diff --git a/widget/gtk/MPRISServiceHandler.h b/widget/gtk/MPRISServiceHandler.h @@ -8,6 +8,7 @@ #define WIDGET_GTK_MPRIS_SERVICE_HANDLER_H_ #include <gio/gio.h> +#include "mozilla/dom/FetchImageHelper.h" #include "mozilla/dom/MediaControlKeySource.h" #include "mozilla/UniquePtr.h" #include "nsIFile.h" @@ -139,8 +140,17 @@ class MPRISServiceHandler final : public dom::MediaControlKeySource { nsCOMPtr<nsIFile> mLocalImageFile; nsCOMPtr<nsIFile> mLocalImageFolder; + UniquePtr<dom::FetchImageHelper> mImageFetcher; + MozPromiseRequestHolder<dom::ImagePromise> mImageFetchRequest; + + nsString mFetchingUrl; nsString mCurrentImageUrl; + size_t mNextImageIndex = 0; + + // Load the image at index aIndex of the metadta's artwork to MPRIS + // asynchronously + void LoadImageAtIndex(const size_t aIndex); bool SetImageToDisplay(const char* aImageData, uint32_t aDataSize); bool RenewLocalImageFile(const char* aImageData, uint32_t aDataSize); diff --git a/widget/windows/WindowsSMTCProvider.cpp b/widget/windows/WindowsSMTCProvider.cpp @@ -224,6 +224,7 @@ void WindowsSMTCProvider::ClearMetadata() { if (FAILED(mDisplay->ClearAll())) { LOG("Failed to clear SMTC display"); } + mImageFetchRequest.DisconnectIfExists(); CancelPendingStoreAsyncOperation(); mThumbnailUrl.Truncate(); mProcessingUrl.Truncate(); @@ -595,46 +596,102 @@ void WindowsSMTCProvider::SetPositionState( } void WindowsSMTCProvider::LoadThumbnail( - const nsTArray<mozilla::dom::MediaImageData>& aArtwork) { + const nsTArray<mozilla::dom::MediaImage>& aArtwork) { MOZ_ASSERT(NS_IsMainThread()); - for (const dom::MediaImageData& image : aArtwork) { - if (!image.mDataSurface) { - continue; - } + // TODO: Sort the images by the preferred size or format. + mArtwork = aArtwork; + mNextImageIndex = 0; - if (mThumbnailUrl == image.mSrc) { - LOG("Artwork image URL did not change"); + // Abort the loading if + // 1) thumbnail is being updated, and one in processing is in the artwork + // 2) thumbnail is not being updated, and one in use is in the artwork + if (!mProcessingUrl.IsEmpty()) { + LOG("Load thumbnail while image: %s is being processed", + NS_ConvertUTF16toUTF8(mProcessingUrl).get()); + if (mozilla::dom::IsImageIn(mArtwork, mProcessingUrl)) { + LOG("No need to load thumbnail. The one being processed is in the " + "artwork"); return; } - - // Although IMAGE_JPEG or IMAGE_BMP are valid types as well, but a - // png image with transparent background will be converted into a - // jpeg/bmp file with a colored background. IMAGE_PNG format seems - // to be the best choice for now. - uint32_t size = 0; - char* src = nullptr; - // Only used to hold the image data - nsCOMPtr<nsIInputStream> inputStream; - nsresult rv = mozilla::dom::GetEncodedImageBuffer( - image.mDataSurface, nsLiteralCString(IMAGE_PNG), - getter_AddRefs(inputStream), &size, &src); - if (NS_FAILED(rv) || !inputStream || size == 0 || !src) { - LOG("Failed to get the image buffer info. Try next image"); - continue; + } else if (!mThumbnailUrl.IsEmpty()) { + if (mozilla::dom::IsImageIn(mArtwork, mThumbnailUrl)) { + LOG("No need to load thumbnail. The one in use is in the artwork"); + return; } + } + + // If there is a pending image store operation, that image must be different + // from the new image will be loaded below, so the pending one should be + // cancelled. + CancelPendingStoreAsyncOperation(); + // Remove the current thumbnail on the interface + ClearThumbnail(); + // Then load the new thumbnail asynchronously + LoadImageAtIndex(mNextImageIndex++); +} + +void WindowsSMTCProvider::LoadImageAtIndex(const size_t aIndex) { + MOZ_ASSERT(NS_IsMainThread()); + + if (aIndex >= mArtwork.Length()) { + LOG("Stop loading thumbnail. No more available images"); + mImageFetchRequest.DisconnectIfExists(); + mProcessingUrl.Truncate(); + return; + } + + const mozilla::dom::MediaImage& image = mArtwork[aIndex]; - // If there is a pending image store operation, that image must be different - // from the new image will be loaded below, so the pending one should be - // cancelled. - CancelPendingStoreAsyncOperation(); - // Remove the current thumbnail on the interface - ClearThumbnail(); + // TODO: No need to fetch the default image and do image processing since the + // the default image is local file and it's trustworthy. For the default + // image, we can use `CreateFromFile` to create the IRandomAccessStream. We + // should probably cache it since it could be used very often (Bug 1643102) - mProcessingUrl = image.mSrc; - LoadImage(src, size); - break; + if (!mozilla::dom::IsValidImageUrl(image.mSrc)) { + LOG("Skip the image with invalid URL. Try next image"); + mImageFetchRequest.DisconnectIfExists(); + LoadImageAtIndex(mNextImageIndex++); + return; } + + mImageFetchRequest.DisconnectIfExists(); + mProcessingUrl = image.mSrc; + + mImageFetcher = mozilla::MakeUnique<mozilla::dom::FetchImageHelper>(image); + RefPtr<WindowsSMTCProvider> self = this; + mImageFetcher->FetchImage() + ->Then( + AbstractThread::MainThread(), __func__, + [this, self](const nsCOMPtr<imgIContainer>& aImage) { + LOG("The image is fetched successfully"); + mImageFetchRequest.Complete(); + + // Although IMAGE_JPEG or IMAGE_BMP are valid types as well, but a + // png image with transparent background will be converted into a + // jpeg/bmp file with a colored background. IMAGE_PNG format seems + // to be the best choice for now. + uint32_t size = 0; + char* src = nullptr; + // Only used to hold the image data + nsCOMPtr<nsIInputStream> inputStream; + nsresult rv = mozilla::dom::GetEncodedImageBuffer( + aImage, nsLiteralCString(IMAGE_PNG), + getter_AddRefs(inputStream), &size, &src); + if (NS_FAILED(rv) || !inputStream || size == 0 || !src) { + LOG("Failed to get the image buffer info. Try next image"); + LoadImageAtIndex(mNextImageIndex++); + return; + } + + LoadImage(src, size); + }, + [this, self](bool) { + LOG("Failed to fetch image. Try next image"); + mImageFetchRequest.Complete(); + LoadImageAtIndex(mNextImageIndex++); + }) + ->Track(mImageFetchRequest); } void WindowsSMTCProvider::LoadImage(const char* aImageData, diff --git a/widget/windows/WindowsSMTCProvider.h b/widget/windows/WindowsSMTCProvider.h @@ -12,6 +12,7 @@ # include <Windows.Media.h> # include <wrl.h> +# include "mozilla/dom/FetchImageHelper.h" # include "mozilla/dom/MediaController.h" # include "mozilla/dom/MediaControlKeySource.h" # include "mozilla/UniquePtr.h" @@ -71,7 +72,10 @@ class WindowsSMTCProvider final : public mozilla::dom::MediaControlKeySource { bool SetMusicMetadata(const nsString& aArtist, const nsString& aTitle); // Sets one of the artwork to the SMTC interface asynchronously - void LoadThumbnail(const nsTArray<mozilla::dom::MediaImageData>& aArtwork); + void LoadThumbnail(const nsTArray<mozilla::dom::MediaImage>& aArtwork); + // Stores the image at index aIndex of the mArtwork to the Thumbnail + // asynchronously + void LoadImageAtIndex(const size_t aIndex); // Stores the raw binary data of an image to mImageStream and set it to the // Thumbnail asynchronously void LoadImage(const char* aImageData, uint32_t aDataSize); @@ -114,6 +118,10 @@ class WindowsSMTCProvider final : public mozilla::dom::MediaControlKeySource { CopyableTArray<mozilla::dom::MediaImage> mArtwork; size_t mNextImageIndex; + mozilla::UniquePtr<mozilla::dom::FetchImageHelper> mImageFetcher; + mozilla::MozPromiseRequestHolder<mozilla::dom::ImagePromise> + mImageFetchRequest; + HWND mWindow; // handle to the invisible window // EventRegistrationTokens are used to have a handle on a callback (to remove