commit bf5b1f91a7c01258560a3372120ec4d36be28d89
parent dddd088549bed24de7c5c4650ccb32a733814e2e
Author: Tom Schuster <tschuster@mozilla.com>
Date: Mon, 6 Oct 2025 09:35:41 +0000
Bug 1988124 - Add a FetchDecodedImage utility. r=tnikkel
Differential Revision: https://phabricator.services.mozilla.com/D264550
Diffstat:
5 files changed, 199 insertions(+), 1 deletion(-)
diff --git a/image/FetchDecodedImage.cpp b/image/FetchDecodedImage.cpp
@@ -0,0 +1,145 @@
+/* -*- 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 "FetchDecodedImage.h"
+
+#include "imgINotificationObserver.h"
+#include "imgITools.h"
+#include "nsIChannel.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+
+namespace mozilla::image {
+
+namespace {
+
+class FetchDecodedImageHelper;
+
+MOZ_RUNINIT
+HashSet<RefPtr<FetchDecodedImageHelper>,
+ PointerHasher<FetchDecodedImageHelper*>>
+ gDecodeRequests;
+
+class FetchDecodedImageHelper : public imgIContainerCallback,
+ public imgINotificationObserver {
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit FetchDecodedImageHelper(
+ gfx::IntSize aSize, RefPtr<FetchDecodedImagePromise::Private> aPromise)
+ : mSize(aSize), mPromise(aPromise) {
+ // Let's make sure we are alive until the request completes
+ MOZ_ALWAYS_TRUE(gDecodeRequests.putNew(this));
+ }
+
+ NS_IMETHOD
+ OnImageReady(imgIContainer* aImage, nsresult aStatus) override {
+ if (NS_FAILED(aStatus)) {
+ OnError(aStatus);
+ return NS_OK;
+ }
+
+ mImage = aImage;
+ return NS_OK;
+ }
+
+ void Notify(imgIRequest* aRequest, int32_t aType,
+ const nsIntRect* aData) override {
+ if (!mImage) {
+ return;
+ }
+
+ if (aType == imgINotificationObserver::LOAD_COMPLETE ||
+ aType == imgINotificationObserver::FRAME_UPDATE ||
+ aType == imgINotificationObserver::FRAME_COMPLETE) {
+ RequestDecode();
+ }
+
+ if (aType == imgINotificationObserver::DECODE_COMPLETE) {
+ OnDecodeComplete();
+ }
+ }
+
+ void OnError(nsresult aStatus) {
+ gDecodeRequests.remove(this);
+ mImage = nullptr;
+ mPromise->Reject(aStatus, __func__);
+ mPromise = nullptr;
+ }
+
+ private:
+ virtual ~FetchDecodedImageHelper() {}
+
+ void RequestDecode() {
+ if (mSize.Width() && mSize.Height()) {
+ if (NS_FAILED(mImage->RequestDecodeForSize(
+ mSize, imgIContainer::FLAG_ASYNC_NOTIFY,
+ imgIContainer::FRAME_FIRST))) {
+ OnError(NS_ERROR_DOM_IMAGE_BROKEN);
+ }
+
+ return;
+ }
+
+ switch (mImage->RequestDecodeWithResult(imgIContainer::FLAG_ASYNC_NOTIFY,
+ imgIContainer::FRAME_FIRST)) {
+ case imgIContainer::DecodeResult::DECODE_REQUEST_FAILED:
+ OnError(NS_ERROR_DOM_IMAGE_BROKEN);
+ break;
+ case imgIContainer::DecodeResult::DECODE_SURFACE_AVAILABLE:
+ OnDecodeComplete();
+ break;
+ case imgIContainer::DecodeResult::DECODE_REQUESTED:
+ break;
+ }
+ }
+
+ void OnDecodeComplete() {
+ gDecodeRequests.remove(this);
+ mPromise->Resolve(mImage.forget(), __func__);
+ mPromise = nullptr;
+ }
+
+ gfx::IntSize mSize;
+ RefPtr<FetchDecodedImagePromise::Private> mPromise;
+ nsCOMPtr<imgIContainer> mImage{};
+};
+
+NS_IMPL_ISUPPORTS(FetchDecodedImageHelper, imgIContainerCallback,
+ imgINotificationObserver)
+
+} // namespace
+
+RefPtr<FetchDecodedImagePromise> FetchDecodedImage(
+ nsIURI* aURI, gfx::IntSize aSize, nsIPrincipal* aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType) {
+ nsCOMPtr<nsIChannel> channel;
+ nsresult rv = NS_NewChannel(getter_AddRefs(channel), aURI, aLoadingPrincipal,
+ aSecurityFlags, aContentPolicyType);
+ if (NS_FAILED(rv)) {
+ return FetchDecodedImagePromise::CreateAndReject(rv, __func__);
+ }
+
+ nsCOMPtr<imgITools> imgTools =
+ do_GetService("@mozilla.org/image/tools;1", &rv);
+ if (NS_FAILED(rv)) {
+ return FetchDecodedImagePromise::CreateAndReject(rv, __func__);
+ }
+
+ auto promise = MakeRefPtr<FetchDecodedImagePromise::Private>(__func__);
+
+ RefPtr<FetchDecodedImageHelper> helper =
+ new FetchDecodedImageHelper(aSize, promise);
+
+ rv = imgTools->DecodeImageFromChannelAsync(aURI, channel, helper, helper);
+ if (NS_FAILED(rv)) {
+ helper->OnError(rv);
+ }
+
+ return promise;
+}
+
+} // namespace mozilla::image
diff --git a/image/FetchDecodedImage.h b/image/FetchDecodedImage.h
@@ -0,0 +1,40 @@
+/* -*- 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/. */
+
+#ifndef mozilla_image_FetchDecodedImage_h
+#define mozilla_image_FetchDecodedImage_h
+
+#include "mozilla/MozPromise.h"
+#include "mozilla/gfx/Point.h"
+#include "nsILoadInfo.h"
+#include "nsIContentPolicy.h"
+
+class imgIContainer;
+
+namespace mozilla::image {
+
+using FetchDecodedImagePromise =
+ mozilla::MozPromise<already_AddRefed<imgIContainer>, nsresult, true>;
+
+/*
+ * This method fetches and image URI and starts decoding the image soon as
+ * possible. Either resolves the promise with the decoded imgIContainer or
+ * rejects with an nsresult, for e.g. network failures or decoding errors.
+ *
+ * @param aURI URI of the image to download.
+ * @param aSize Size to decode the image at (if possible). Use zero width or
+ * height to prevent resizing.
+ */
+RefPtr<FetchDecodedImagePromise> FetchDecodedImage(
+ nsIURI* aURI, gfx::IntSize aSize, nsIPrincipal* aLoadingPrincipal,
+ nsSecurityFlags aSecurityFlags =
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsContentPolicyType aContentPolicyType =
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE);
+
+} // namespace mozilla::image
+
+#endif // mozilla_image_FetchDecodedImage_h
diff --git a/image/VectorImage.cpp b/image/VectorImage.cpp
@@ -1018,6 +1018,10 @@ imgIContainer::DecodeResult VectorImage::RequestDecodeWithResult(
NS_IMETHODIMP
VectorImage::RequestDecodeForSize(const nsIntSize& aSize, uint32_t aFlags,
uint32_t aWhichFrame) {
+ if (mError) {
+ return NS_ERROR_FAILURE;
+ }
+
// Nothing to do for SVG images, though in theory we could rasterize to the
// provided size ahead of time if we supported off-main-thread SVG
// rasterization...
diff --git a/image/imgTools.cpp b/image/imgTools.cpp
@@ -131,7 +131,14 @@ class ImageDecoderListener final : public nsIStreamListener,
}
}
- virtual void OnLoadComplete(bool aLastPart) override {}
+ virtual void OnLoadComplete(bool aLastPart) override {
+ // ProgressTracker dispatches LOAD_COMPLETE as OnLoadComplete, but *our*
+ // observers need the Notify invocation for it.
+ if (mObserver) {
+ mObserver->Notify(nullptr, imgINotificationObserver::LOAD_COMPLETE,
+ nullptr);
+ }
+ }
// Other notifications are ignored.
virtual void SetHasImage() override {}
diff --git a/image/moz.build b/image/moz.build
@@ -60,6 +60,7 @@ EXPORTS.mozilla.image += [
"encoders/ico/nsICOEncoder.h",
"encoders/jpeg/nsJPEGEncoder.h",
"encoders/png/nsPNGEncoder.h",
+ "FetchDecodedImage.h",
"ICOFileHeaders.h",
"ImageMemoryReporter.h",
"ImageUtils.h",
@@ -78,6 +79,7 @@ UNIFIED_SOURCES += [
"Decoder.cpp",
"DecoderFactory.cpp",
"DynamicImage.cpp",
+ "FetchDecodedImage.cpp",
"FrameAnimator.cpp",
"FrozenImage.cpp",
"IDecodingTask.cpp",