commit 6dc346b92490dcac3b401a34ad7a346febc75193
parent 01d56417ef9c72252ffe694010c2d7cf96adb2b9
Author: Tom Schuster <tschuster@mozilla.com>
Date: Mon, 10 Nov 2025 11:36:56 +0000
Bug 1944033 - Load MediaSession artwork in the child. r=media-playback-reviewers,aosmond
Differential Revision: https://phabricator.services.mozilla.com/D271045
Diffstat:
5 files changed, 104 insertions(+), 10 deletions(-)
diff --git a/dom/media/mediasession/MediaMetadata.cpp b/dom/media/mediasession/MediaMetadata.cpp
@@ -10,6 +10,7 @@
#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 {
@@ -120,6 +121,54 @@ 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;
diff --git a/dom/media/mediasession/MediaMetadata.h b/dom/media/mediasession/MediaMetadata.h
@@ -10,6 +10,7 @@
#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"
@@ -31,6 +32,9 @@ class MediaImageData {
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 {
@@ -49,6 +53,9 @@ class MediaMetadataBase {
CopyableTArray<MediaImageData> mArtwork;
};
+using MediaMetadataBasePromise =
+ mozilla::MozPromise<MediaMetadataBase, nsresult, true>;
+
class MediaMetadata final : public nsISupports,
public nsWrapperCache,
private MediaMetadataBase {
@@ -85,10 +92,10 @@ class MediaMetadata final : public nsISupports,
void SetArtwork(JSContext* aCx, const Sequence<JSObject*>& aArtwork,
ErrorResult& aRv);
- // 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; }
+ // 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();
private:
MediaMetadata(nsIGlobalObject* aParent, const nsString& aTitle,
@@ -101,6 +108,10 @@ 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,6 +87,7 @@ void MediaSession::Shutdown() {
if (mParent) {
SetMediaSessionDocStatus(SessionDocStatus::eInactive);
}
+ mMetadataRequest.DisconnectIfExists();
}
void MediaSession::NotifyOwnerDocumentActivityChanged() {
@@ -315,13 +316,28 @@ void MediaSession::NotifyMetadataUpdated() {
RefPtr<BrowsingContext> currentBC = GetParentObject()->GetBrowsingContext();
MOZ_ASSERT(currentBC, "Update session metadata after context destroyed!");
- Maybe<MediaMetadataBase> metadata;
- if (GetMetadata()) {
- metadata.emplace(*(GetMetadata()->AsMetadataBase()));
- }
- if (RefPtr<IMediaInfoUpdater> updater = ContentMediaAgent::Get(currentBC)) {
- updater->UpdateMetadata(currentBC->Id(), metadata);
+ if (!mMediaMetadata) {
+ if (RefPtr<IMediaInfoUpdater> updater = ContentMediaAgent::Get(currentBC)) {
+ updater->UpdateMetadata(currentBC->Id(), Nothing());
+ }
+ return;
}
+
+ 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,6 +131,9 @@ 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,6 +11,7 @@
#include "mozilla/dom/BindingIPCUtils.h"
#include "mozilla/dom/MediaSession.h"
#include "mozilla/dom/MediaSessionBinding.h"
+#include "nsContentUtils.h"
namespace mozilla {
namespace dom {
@@ -30,6 +31,12 @@ struct ParamTraits<mozilla::dom::MediaImageData> {
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) {
@@ -38,6 +45,14 @@ 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;
}
};