commit 7b5c8a2baa22f0fabd23f78542b94afc1bc36e53
parent 70238397ad9d0d7543f547312b7590312146b142
Author: Tom Schuster <tschuster@mozilla.com>
Date: Mon, 17 Nov 2025 11:19:43 +0000
Bug 1985987 - Fetch and decode images in a content process. r=tnikkel,nika,layout-reviewers
Differential Revision: https://phabricator.services.mozilla.com/D264427
Diffstat:
7 files changed, 211 insertions(+), 25 deletions(-)
diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp
@@ -15,6 +15,7 @@
#include "Geolocation.h"
#include "HandlerServiceChild.h"
#include "ScrollingMetrics.h"
+#include "gfxUtils.h"
#include "imgLoader.h"
#include "mozilla/AppShutdown.h"
#include "mozilla/Attributes.h"
@@ -91,9 +92,11 @@
#include "mozilla/dom/ipc/SharedMap.h"
#include "mozilla/extensions/ExtensionsChild.h"
#include "mozilla/extensions/StreamFilterParent.h"
+#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/Logging.h"
#include "mozilla/gfx/gfxVars.h"
#include "mozilla/hal_sandbox/PHalChild.h"
+#include "mozilla/image/FetchDecodedImage.h"
#include "mozilla/intl/L10nRegistry.h"
#include "mozilla/intl/LocaleService.h"
#include "mozilla/intl/OSPreferences.h"
@@ -1475,6 +1478,58 @@ mozilla::ipc::IPCResult ContentChild::RecvRequestMemoryReport(
return IPC_OK();
}
+mozilla::ipc::IPCResult ContentChild::RecvDecodeImage(
+ NotNull<nsIURI*> aURI, const ImageIntSize& aSize,
+ DecodeImageResolver&& aResolver) {
+ auto size = aSize.ToUnknownSize();
+ // TODO(Bug 1999930): Investigate using a content-principal for
+ // moz-remote-image: requests
+ image::FetchDecodedImage(aURI, size, nsContentUtils::GetSystemPrincipal())
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [size, aResolver](already_AddRefed<imgIContainer> aImage) {
+ using Result = std::tuple<nsresult, mozilla::Maybe<IPCImage>>;
+
+ nsCOMPtr<imgIContainer> image(std::move(aImage));
+
+ const int32_t flags = imgIContainer::FLAG_SYNC_DECODE |
+ imgIContainer::FLAG_ASYNC_NOTIFY;
+ RefPtr<gfx::SourceSurface> surface;
+ if (size.Width() && size.Height()) {
+ surface = image->GetFrameAtSize(size, imgIContainer::FRAME_FIRST,
+ flags);
+ if (surface && surface->GetSize() != size) {
+ surface = gfxUtils::ScaleSourceSurface(*surface, size);
+ }
+ } else {
+ surface = image->GetFrame(imgIContainer::FRAME_FIRST, flags);
+ }
+
+ if (!surface) {
+ aResolver(Result(NS_ERROR_FAILURE, Nothing()));
+ return;
+ }
+
+ if (RefPtr<gfx::DataSourceSurface> dataSurface =
+ surface->GetDataSurface()) {
+ if (Maybe<IPCImage> image =
+ nsContentUtils::SurfaceToIPCImage(*dataSurface)) {
+ aResolver(Result(NS_OK, std::move(image)));
+ return;
+ }
+ }
+
+ aResolver(Result(NS_ERROR_FAILURE, Nothing()));
+ return;
+ },
+ [aResolver](nsresult aStatus) {
+ aResolver(std::tuple<nsresult, mozilla::Maybe<IPCImage>>(
+ aStatus, Nothing()));
+ });
+
+ return IPC_OK();
+}
+
#if defined(XP_WIN)
mozilla::ipc::IPCResult ContentChild::RecvGetUntrustedModulesData(
GetUntrustedModulesDataResolver&& aResolver) {
diff --git a/dom/ipc/ContentChild.h b/dom/ipc/ContentChild.h
@@ -491,6 +491,10 @@ class ContentChild final : public PContentChild,
const bool& minimizeMemoryUsage, const Maybe<FileDescriptor>& DMDFile,
const RequestMemoryReportResolver& aResolver);
+ mozilla::ipc::IPCResult RecvDecodeImage(NotNull<nsIURI*> aURI,
+ const ImageIntSize& aSize,
+ DecodeImageResolver&& aResolver);
+
#if defined(XP_WIN)
mozilla::ipc::IPCResult RecvGetUntrustedModulesData(
GetUntrustedModulesDataResolver&& aResolver);
diff --git a/dom/ipc/PContent.ipdl b/dom/ipc/PContent.ipdl
@@ -627,6 +627,8 @@ child:
FileDescriptor? DMDFile)
returns (uint32_t aGeneration);
+ async DecodeImage(nsIURI aURI, ImageIntSize aSize) returns (nsresult rv, IPCImage? image);
+
#if defined(XP_WIN)
/**
* Used by third-party modules telemetry (aka "untrusted modules" telemetry)
@@ -1793,7 +1795,7 @@ parent:
returns(int32_t? requestedIndex);
async NavigationTraverse(MaybeDiscardedBrowsingContext aContext, nsID aKey,
- uint64_t aHistoryEpoch, bool aUserActivation,
+ uint64_t aHistoryEpoch, bool aUserActivation,
bool aCheckForCancelation)
returns(nsresult result);
diff --git a/gfx/thebes/gfxUtils.cpp b/gfx/thebes/gfxUtils.cpp
@@ -956,6 +956,27 @@ gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(SourceSurface* aSurface,
return dataSurface.forget();
}
+/* static */
+already_AddRefed<SourceSurface> gfxUtils::ScaleSourceSurface(
+ SourceSurface& aSurface, const IntSize& aTargetSize) {
+ const IntSize surfaceSize = aSurface.GetSize();
+
+ MOZ_ASSERT(surfaceSize != aTargetSize);
+ MOZ_ASSERT(!surfaceSize.IsEmpty());
+ MOZ_ASSERT(!aTargetSize.IsEmpty());
+
+ RefPtr<DrawTarget> dt = Factory::CreateDrawTarget(
+ gfxVars::ContentBackend(), aTargetSize, aSurface.GetFormat());
+
+ if (!dt || !dt->IsValid()) {
+ return nullptr;
+ }
+
+ dt->DrawSurface(&aSurface, Rect(Point(), Size(aTargetSize)),
+ Rect(Point(), Size(surfaceSize)));
+ return dt->GetBackingSurface();
+}
+
const uint32_t gfxUtils::sNumFrameColors = 8;
/* static */
diff --git a/gfx/thebes/gfxUtils.h b/gfx/thebes/gfxUtils.h
@@ -285,6 +285,13 @@ class gfxUtils {
CopySurfaceToDataSourceSurfaceWithFormat(SourceSurface* aSurface,
SurfaceFormat aFormat);
+ // Scales a SourceSurface to the new requested size.
+ //
+ // Asserts when the requested size is equal to the current size of the
+ // surface.
+ static already_AddRefed<SourceSurface> ScaleSourceSurface(
+ SourceSurface& aSurface, const mozilla::gfx::IntSize& aTargetSize);
+
/**
* Return a color that can be used to identify a frame with a given frame
* number. The colors will cycle after sNumFrameColors. You can query colors
diff --git a/image/remote/RemoteImageProtocolHandler.cpp b/image/remote/RemoteImageProtocolHandler.cpp
@@ -5,11 +5,20 @@
#include "RemoteImageProtocolHandler.h"
+#include "gfxDrawable.h"
+#include "ImageOps.h"
+#include "imgITools.h"
+#include "nsContentUtils.h"
+#include "nsIPipe.h"
#include "nsIURI.h"
+#include "nsMimeTypes.h"
#include "nsNetUtil.h"
+#include "nsStreamUtils.h"
#include "nsURLHelper.h"
#include "mozilla/dom/ipc/IdType.h"
+#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/ContentProcessManager.h"
+#include "mozilla/gfx/2D.h"
namespace mozilla::image {
@@ -53,6 +62,95 @@ static UniqueContentParentKeepAlive GetLaunchingContentParentForDecode(
/* aPreferUsed */ true);
}
+static nsresult EncodeImage(const dom::IPCImage& aImage,
+ nsIAsyncOutputStream* aOutputStream) {
+ // TODO(Bug 1997538): Use the internal image/icon format for the
+ // moz-remote-image: protocol
+ nsresult rv;
+ nsCOMPtr<imgITools> imgTools =
+ do_GetService("@mozilla.org/image/tools;1", &rv);
+ MOZ_TRY(rv);
+
+ RefPtr<gfx::DataSourceSurface> surface =
+ nsContentUtils::IPCImageToSurface(aImage);
+ if (!surface) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<gfxDrawable> drawable =
+ new gfxSurfaceDrawable(surface, surface->GetSize());
+ nsCOMPtr<imgIContainer> imgContainer =
+ image::ImageOps::CreateFromDrawable(drawable);
+
+ nsCOMPtr<nsIInputStream> stream;
+ MOZ_TRY(imgTools->EncodeImage(imgContainer, nsLiteralCString(IMAGE_PNG),
+ u"png-zlib-level=0"_ns,
+ getter_AddRefs(stream)));
+
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ MOZ_TRY(rv);
+
+ return NS_AsyncCopy(stream, aOutputStream, target);
+}
+
+static void AsyncReEncodeImage(nsIURI* aRemoteURI, ImageIntSize aSize,
+ const Maybe<ContentParentId> aContentParentId,
+ nsIAsyncOutputStream* aOutputStream) {
+ UniqueContentParentKeepAlive cp =
+ GetLaunchingContentParentForDecode(aContentParentId);
+ if (NS_WARN_IF(!cp)) {
+ aOutputStream->CloseWithStatus(NS_ERROR_FAILURE);
+ return;
+ }
+
+ cp->WaitForLaunchAsync()
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [remoteURI = nsCOMPtr{aRemoteURI},
+ aSize](UniqueContentParentKeepAlive&& aCp) {
+ return aCp->SendDecodeImage(WrapNotNull(remoteURI), aSize);
+ },
+ [](nsresult aError) {
+ return ContentParent::DecodeImagePromise::CreateAndReject(
+ ipc::ResponseRejectReason::SendError, __func__);
+ })
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [cp = std::move(cp), outputStream = nsCOMPtr{aOutputStream},
+ aSize](const std::tuple<nsresult, mozilla::Maybe<dom::IPCImage>>&
+ aResult) {
+ nsresult rv = std::get<0>(aResult);
+ const mozilla::Maybe<dom::IPCImage>& image = std::get<1>(aResult);
+
+ if (NS_FAILED(rv)) {
+ outputStream->CloseWithStatus(rv);
+ return;
+ }
+
+ if (image.isNothing()) {
+ outputStream->CloseWithStatus(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // Make sure the image size matches if a specific size was
+ // requested.
+ if (aSize.Width() && aSize.Height() && image->size() != aSize) {
+ outputStream->CloseWithStatus(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ rv = EncodeImage(*image, outputStream);
+ if (NS_FAILED(rv)) {
+ outputStream->CloseWithStatus(rv);
+ }
+ },
+ [outputStream =
+ nsCOMPtr{aOutputStream}](mozilla::ipc::ResponseRejectReason) {
+ outputStream->CloseWithStatus(NS_ERROR_FAILURE);
+ });
+}
+
// Parse out the relevant parts of the moz-remote-image URL
static nsresult ParseURI(nsIURI* aURI, nsIURI** aRemoteURI, ImageIntSize* aSize,
Maybe<ContentParentId>& aContentParentId) {
@@ -104,12 +202,29 @@ static nsresult ParseURI(nsIURI* aURI, nsIURI** aRemoteURI, ImageIntSize* aSize,
NS_IMETHODIMP RemoteImageProtocolHandler::NewChannel(nsIURI* aURI,
nsILoadInfo* aLoadInfo,
nsIChannel** aOutChannel) {
+ if (!aLoadInfo->TriggeringPrincipal()->IsSystemPrincipal()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
nsCOMPtr<nsIURI> remoteURI;
ImageIntSize size;
Maybe<ContentParentId> contentParentId;
MOZ_TRY(ParseURI(aURI, getter_AddRefs(remoteURI), &size, contentParentId));
- return NS_ERROR_NOT_IMPLEMENTED;
+ nsCOMPtr<nsIAsyncInputStream> pipeIn;
+ nsCOMPtr<nsIAsyncOutputStream> pipeOut;
+ NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut), true, true);
+
+ nsCOMPtr<nsIChannel> channel;
+ MOZ_TRY(NS_NewInputStreamChannelInternal(
+ getter_AddRefs(channel), aURI, pipeIn.forget(),
+ /* aContentType */ nsLiteralCString(IMAGE_PNG),
+ /* aContentCharset */ ""_ns, aLoadInfo));
+
+ AsyncReEncodeImage(remoteURI, size, contentParentId, pipeOut);
+
+ channel.forget(aOutChannel);
+ return NS_OK;
}
} // namespace mozilla::image
diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp
@@ -6817,26 +6817,6 @@ bool nsLayoutUtils::IsInPositionFixedSubtree(const nsIFrame* aFrame) {
return false;
}
-static RefPtr<SourceSurface> ScaleSourceSurface(SourceSurface& aSurface,
- const IntSize& aTargetSize) {
- const IntSize surfaceSize = aSurface.GetSize();
-
- MOZ_ASSERT(surfaceSize != aTargetSize);
- MOZ_ASSERT(!surfaceSize.IsEmpty());
- MOZ_ASSERT(!aTargetSize.IsEmpty());
-
- RefPtr<DrawTarget> dt = Factory::CreateDrawTarget(
- gfxVars::ContentBackend(), aTargetSize, aSurface.GetFormat());
-
- if (!dt || !dt->IsValid()) {
- return nullptr;
- }
-
- dt->DrawSurface(&aSurface, Rect(Point(), Size(aTargetSize)),
- Rect(Point(), Size(surfaceSize)));
- return dt->GetBackingSurface();
-}
-
SurfaceFromElementResult nsLayoutUtils::SurfaceFromOffscreenCanvas(
OffscreenCanvas* aOffscreenCanvas, uint32_t aSurfaceFlags,
RefPtr<DrawTarget>& aTarget) {
@@ -6872,7 +6852,8 @@ SurfaceFromElementResult nsLayoutUtils::SurfaceFromOffscreenCanvas(
const bool exactSize = aSurfaceFlags & SFE_EXACT_SIZE_SURFACE;
if (exactSize && size != result.mSize) {
result.mSize = size;
- result.mSourceSurface = ScaleSourceSurface(*result.mSourceSurface, size);
+ result.mSourceSurface =
+ gfxUtils::ScaleSourceSurface(*result.mSourceSurface, size);
}
if (aTarget && result.mSourceSurface) {
@@ -7137,7 +7118,7 @@ SurfaceFromElementResult nsLayoutUtils::SurfaceFromElement(
IntSize surfSize = result.mSourceSurface->GetSize();
if (exactSize && surfSize != result.mSize) {
result.mSourceSurface =
- ScaleSourceSurface(*result.mSourceSurface, result.mSize);
+ gfxUtils::ScaleSourceSurface(*result.mSourceSurface, result.mSize);
if (!result.mSourceSurface) {
return result;
}
@@ -7231,7 +7212,8 @@ SurfaceFromElementResult nsLayoutUtils::SurfaceFromElement(
const bool exactSize = aSurfaceFlags & SFE_EXACT_SIZE_SURFACE;
if (exactSize && size != result.mSize) {
result.mSize = size;
- result.mSourceSurface = ScaleSourceSurface(*result.mSourceSurface, size);
+ result.mSourceSurface =
+ gfxUtils::ScaleSourceSurface(*result.mSourceSurface, size);
}
if (aTarget && result.mSourceSurface) {