From b5bd9f27ae9abfe2b4734372d806759d4509014c Mon Sep 17 00:00:00 2001 From: Kagami Sascha Rosylight Date: Thu, 6 May 2021 01:14:21 +0000 Subject: [PATCH] Bug 1707590 - Part 2: Implement nsJXLDecoder r=tnikkel Differential Revision: https://phabricator.services.mozilla.com/D113359 --- image/DecoderFactory.cpp | 13 ++ image/DecoderFactory.h | 7 +- image/build/nsImageModule.cpp | 4 + image/decoders/moz.build | 5 + image/decoders/nsJXLDecoder.cpp | 163 ++++++++++++++++++ image/decoders/nsJXLDecoder.h | 55 ++++++ image/imgLoader.cpp | 5 + image/test/fuzzing/TestDecoders.cpp | 8 + image/test/gtest/Common.cpp | 23 +++ image/test/gtest/Common.h | 4 + image/test/gtest/TestDecoders.cpp | 5 + image/test/gtest/TestLoader.cpp | 13 ++ image/test/gtest/TestMetadata.cpp | 4 + image/test/gtest/downscaled.jxl | 0 image/test/gtest/green.jxl | 0 image/test/gtest/large.jxl | 0 image/test/gtest/moz.build | 4 + image/test/gtest/transparent.jxl | 0 image/test/reftest/jxl/jxl-size-33x33.jxl | 0 image/test/reftest/jxl/jxl-size-33x33.png | 0 image/test/reftest/jxl/reftest.list | 3 + image/test/reftest/reftest.list | 3 + modules/libpref/init/StaticPrefList.yaml | 6 + netwerk/mime/nsMimeTypes.h | 1 + toolkit/content/contentAreaUtils.js | 1 + .../exthandler/nsExternalHelperAppService.cpp | 3 +- 26 files changed, 325 insertions(+), 5 deletions(-) create mode 100644 image/decoders/nsJXLDecoder.cpp create mode 100644 image/decoders/nsJXLDecoder.h create mode 100644 image/test/gtest/downscaled.jxl create mode 100644 image/test/gtest/green.jxl create mode 100644 image/test/gtest/large.jxl create mode 100644 image/test/gtest/transparent.jxl create mode 100644 image/test/reftest/jxl/jxl-size-33x33.jxl create mode 100644 image/test/reftest/jxl/jxl-size-33x33.png create mode 100644 image/test/reftest/jxl/reftest.list diff --git a/image/DecoderFactory.cpp b/image/DecoderFactory.cpp index a65166c510e8..96cc2b775913 100644 --- a/image/DecoderFactory.cpp +++ b/image/DecoderFactory.cpp @@ -23,6 +23,9 @@ #ifdef MOZ_AV1 # include "nsAVIFDecoder.h" #endif +#ifdef MOZ_JXL +# include "nsJXLDecoder.h" +#endif namespace mozilla { @@ -88,6 +91,11 @@ DecoderType DecoderFactory::GetDecoderType(const char* aMimeType) { type = DecoderType::AVIF; } #endif +#ifdef MOZ_JXL + else if (!strcmp(aMimeType, IMAGE_JXL) && StaticPrefs::image_jxl_enabled()) { + type = DecoderType::JXL; + } +#endif return type; } @@ -130,6 +138,11 @@ already_AddRefed DecoderFactory::GetDecoder(DecoderType aType, case DecoderType::AVIF: decoder = new nsAVIFDecoder(aImage); break; +#endif +#ifdef MOZ_JXL + case DecoderType::JXL: + decoder = new nsJXLDecoder(aImage); + break; #endif default: MOZ_ASSERT_UNREACHABLE("Unknown decoder type"); diff --git a/image/DecoderFactory.h b/image/DecoderFactory.h index 494eab71cb35..b1ad5f0ad7a9 100644 --- a/image/DecoderFactory.h +++ b/image/DecoderFactory.h @@ -15,8 +15,7 @@ #include "nsCOMPtr.h" #include "SurfaceFlags.h" -namespace mozilla { -namespace image { +namespace mozilla::image { class Decoder; class IDecodingTask; @@ -39,6 +38,7 @@ enum class DecoderType { ICON, WEBP, AVIF, + JXL, UNKNOWN }; @@ -201,7 +201,6 @@ class DecoderFactory { bool aIsRedecode); }; -} // namespace image -} // namespace mozilla +} // namespace mozilla::image #endif // mozilla_image_DecoderFactory_h diff --git a/image/build/nsImageModule.cpp b/image/build/nsImageModule.cpp index 0d6327e0d94f..28fb8bb33086 100644 --- a/image/build/nsImageModule.cpp +++ b/image/build/nsImageModule.cpp @@ -58,10 +58,14 @@ nsresult mozilla::image::EnsureModuleInitialized() { static ImageEnablementCookie kAVIFCookie = { mozilla::StaticPrefs::image_avif_enabled, "image/avif"_ns}; + static ImageEnablementCookie kJXLCookie = { + mozilla::StaticPrefs::image_jxl_enabled, "image/jxl"_ns}; static ImageEnablementCookie kWebPCookie = { mozilla::StaticPrefs::image_webp_enabled, "image/webp"_ns}; Preferences::RegisterCallbackAndCall(UpdateContentViewerRegistration, "image.avif.enabled", &kAVIFCookie); + Preferences::RegisterCallbackAndCall(UpdateContentViewerRegistration, + "image.jxl.enabled", &kJXLCookie); Preferences::RegisterCallbackAndCall(UpdateContentViewerRegistration, "image.webp.enabled", &kWebPCookie); diff --git a/image/decoders/moz.build b/image/decoders/moz.build index 16435e3eb8e1..946f947bb8bc 100644 --- a/image/decoders/moz.build +++ b/image/decoders/moz.build @@ -36,6 +36,11 @@ if CONFIG["MOZ_AV1"]: "nsAVIFDecoder.cpp", ] +if CONFIG["MOZ_JXL"]: + UNIFIED_SOURCES += [ + "nsJXLDecoder.cpp", + ] + include("/ipc/chromium/chromium-config.mozbuild") LOCAL_INCLUDES += [ diff --git a/image/decoders/nsJXLDecoder.cpp b/image/decoders/nsJXLDecoder.cpp new file mode 100644 index 000000000000..f03d8307611e --- /dev/null +++ b/image/decoders/nsJXLDecoder.cpp @@ -0,0 +1,163 @@ +/* -*- 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 "ImageLogging.h" // Must appear first +#include "gfxPlatform.h" +#include "jxl/codestream_header.h" +#include "jxl/decode_cxx.h" +#include "jxl/types.h" +#include "mozilla/TelemetryHistogramEnums.h" +#include "mozilla/gfx/Point.h" +#include "nsJXLDecoder.h" + +#include "RasterImage.h" +#include "SurfacePipeFactory.h" + +using namespace mozilla::gfx; + +namespace mozilla::image { + +#define JXL_TRY(expr) \ + do { \ + JxlDecoderStatus status = (expr); \ + if (status != JXL_DEC_SUCCESS) { \ + return Transition::TerminateFailure(); \ + } \ + } while (0); + +#define JXL_TRY_BOOL(expr) \ + do { \ + bool succeeded = (expr); \ + if (!succeeded) { \ + return Transition::TerminateFailure(); \ + } \ + } while (0); + +static LazyLogModule sJXLLog("JXLDecoder"); + +nsJXLDecoder::nsJXLDecoder(RasterImage* aImage) + : Decoder(aImage), + mLexer(Transition::ToUnbuffered(State::FINISHED_JXL_DATA, State::JXL_DATA, + SIZE_MAX), + Transition::TerminateSuccess()), + mDecoder(JxlDecoderMake(nullptr)), + mParallelRunner( + JxlThreadParallelRunnerMake(nullptr, PreferredThreadCount())) { + JxlDecoderSubscribeEvents(mDecoder.get(), + JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE); + JxlDecoderSetParallelRunner(mDecoder.get(), JxlThreadParallelRunner, + mParallelRunner.get()); + + MOZ_LOG(sJXLLog, LogLevel::Debug, + ("[this=%p] nsJXLDecoder::nsJXLDecoder", this)); +} + +nsJXLDecoder::~nsJXLDecoder() { + MOZ_LOG(sJXLLog, LogLevel::Debug, + ("[this=%p] nsJXLDecoder::~nsJXLDecoder", this)); +} + +size_t nsJXLDecoder::PreferredThreadCount() { + if (IsMetadataDecode()) { + return 0; // no additional worker thread + } + return JxlThreadParallelRunnerDefaultNumWorkerThreads(); +} + +LexerResult nsJXLDecoder::DoDecode(SourceBufferIterator& aIterator, + IResumable* aOnResume) { + // return LexerResult(TerminalState::FAILURE); + MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!"); + + return mLexer.Lex(aIterator, aOnResume, + [=](State aState, const char* aData, size_t aLength) { + switch (aState) { + case State::JXL_DATA: + return ReadJXLData(aData, aLength); + case State::FINISHED_JXL_DATA: + return FinishedJXLData(); + } + MOZ_CRASH("Unknown State"); + }); +}; + +LexerTransition nsJXLDecoder::ReadJXLData( + const char* aData, size_t aLength) { + const uint8_t* input = (const uint8_t*)aData; + size_t length = aLength; + if (mBuffer.length() != 0) { + JXL_TRY_BOOL(mBuffer.append(aData, aLength)); + input = mBuffer.begin(); + length = mBuffer.length(); + } + JXL_TRY(JxlDecoderSetInput(mDecoder.get(), input, length)); + + while (true) { + JxlDecoderStatus status = JxlDecoderProcessInput(mDecoder.get()); + switch (status) { + case JXL_DEC_ERROR: + default: + return Transition::TerminateFailure(); + + case JXL_DEC_NEED_MORE_INPUT: { + size_t remaining = JxlDecoderReleaseInput(mDecoder.get()); + mBuffer.clear(); + JXL_TRY_BOOL(mBuffer.append(aData + aLength - remaining, remaining)); + return Transition::ContinueUnbuffered(State::JXL_DATA); + } + + case JXL_DEC_BASIC_INFO: { + JXL_TRY(JxlDecoderGetBasicInfo(mDecoder.get(), &mInfo)); + PostSize(mInfo.xsize, mInfo.ysize); + if (mInfo.alpha_bits > 0) { + PostHasTransparency(); + } + if (IsMetadataDecode()) { + return Transition::TerminateSuccess(); + } + break; + } + + case JXL_DEC_NEED_IMAGE_OUT_BUFFER: { + size_t size = 0; + JxlPixelFormat format{4, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, 0}; + JXL_TRY(JxlDecoderImageOutBufferSize(mDecoder.get(), &format, &size)); + + mOutBuffer.clear(); + JXL_TRY_BOOL(mOutBuffer.growBy(size)); + JXL_TRY(JxlDecoderSetImageOutBuffer(mDecoder.get(), &format, + mOutBuffer.begin(), size)); + break; + } + + case JXL_DEC_FULL_IMAGE: { + gfx::IntSize size(mInfo.xsize, mInfo.ysize); + Maybe pipe = SurfacePipeFactory::CreateSurfacePipe( + this, size, OutputSize(), FullFrame(), SurfaceFormat::R8G8B8A8, + SurfaceFormat::OS_RGBA, Nothing(), nullptr, SurfacePipeFlags()); + for (uint8_t* rowPtr = mOutBuffer.begin(); rowPtr < mOutBuffer.end(); + rowPtr += mInfo.xsize * 4) { + pipe->WriteBuffer(reinterpret_cast(rowPtr)); + } + + if (Maybe invalidRect = pipe->TakeInvalidRect()) { + PostInvalidation(invalidRect->mInputSpaceRect, + Some(invalidRect->mOutputSpaceRect)); + } + PostFrameStop(); + PostDecodeDone(); + return Transition::TerminateSuccess(); + } + } + } +} + +LexerTransition nsJXLDecoder::FinishedJXLData() { + MOZ_ASSERT_UNREACHABLE("Read the entire address space?"); + return Transition::TerminateFailure(); +} + +} // namespace mozilla::image diff --git a/image/decoders/nsJXLDecoder.h b/image/decoders/nsJXLDecoder.h new file mode 100644 index 000000000000..6cde7456ca03 --- /dev/null +++ b/image/decoders/nsJXLDecoder.h @@ -0,0 +1,55 @@ +/* -*- 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_decoders_nsJXLDecoder_h +#define mozilla_image_decoders_nsJXLDecoder_h + +#include "Decoder.h" +#include "mp4parse.h" +#include "SurfacePipe.h" + +#include "jxl/decode_cxx.h" +#include "jxl/thread_parallel_runner_cxx.h" + +#include "mozilla/Telemetry.h" + +namespace mozilla::image { +class RasterImage; + +class nsJXLDecoder final : public Decoder { + public: + virtual ~nsJXLDecoder(); + + DecoderType GetType() const override { return DecoderType::JXL; } + + protected: + LexerResult DoDecode(SourceBufferIterator& aIterator, + IResumable* aOnResume) override; + + private: + friend class DecoderFactory; + + // Decoders should only be instantiated via DecoderFactory. + explicit nsJXLDecoder(RasterImage* aImage); + + size_t PreferredThreadCount(); + + enum class State { JXL_DATA, FINISHED_JXL_DATA }; + + LexerTransition ReadJXLData(const char* aData, size_t aLength); + LexerTransition FinishedJXLData(); + + StreamingLexer mLexer; + JxlDecoderPtr mDecoder; + JxlThreadParallelRunnerPtr mParallelRunner; + Vector mBuffer; + Vector mOutBuffer; + JxlBasicInfo mInfo{}; +}; + +} // namespace mozilla::image + +#endif // mozilla_image_decoders_nsJXLDecoder_h diff --git a/image/imgLoader.cpp b/image/imgLoader.cpp index aa42730ddc12..b163178f366d 100644 --- a/image/imgLoader.cpp +++ b/image/imgLoader.cpp @@ -2758,6 +2758,11 @@ nsresult imgLoader::GetMimeTypeFromContent(const char* aContents, detected) && detected.Equals(IMAGE_AVIF)) { aContentType.AssignLiteral(IMAGE_AVIF); + } else if ((aLength >= 2 && !memcmp(aContents, "\xFF\x0A", 2)) || + (aLength >= 12 && + !memcmp(aContents, "\x00\x00\x00\x0CJXL \x0D\x0A\x87\x0A", 12))) { + // Each version is for containerless and containerful files respectively. + aContentType.AssignLiteral(IMAGE_JXL); } else { /* none of the above? I give up */ return NS_ERROR_NOT_AVAILABLE; diff --git a/image/test/fuzzing/TestDecoders.cpp b/image/test/fuzzing/TestDecoders.cpp index bf741f63f086..f08fd92d363a 100644 --- a/image/test/fuzzing/TestDecoders.cpp +++ b/image/test/fuzzing/TestDecoders.cpp @@ -129,8 +129,13 @@ static int RunDecodeToSurfaceFuzzingAVIF(nsCOMPtr inputStream) { return RunDecodeToSurfaceFuzzing(inputStream, "image/avif"); } +static int RunDecodeToSurfaceFuzzingJXL(nsCOMPtr inputStream) { + return RunDecodeToSurfaceFuzzing(inputStream, "image/jxl"); +} + int FuzzingInitImage(int* argc, char*** argv) { Preferences::SetBool("image.avif.enabled", true); + Preferences::SetBool("image.jxl.enabled", true); nsCOMPtr imgTools = do_CreateInstance("@mozilla.org/image/tools;1"); @@ -162,3 +167,6 @@ MOZ_FUZZING_INTERFACE_STREAM(FuzzingInitImage, RunDecodeToSurfaceFuzzingWebP, MOZ_FUZZING_INTERFACE_STREAM(FuzzingInitImage, RunDecodeToSurfaceFuzzingAVIF, ImageAVIF); + +MOZ_FUZZING_INTERFACE_STREAM(FuzzingInitImage, RunDecodeToSurfaceFuzzingJXL, + ImageJXL); diff --git a/image/test/gtest/Common.cpp b/image/test/gtest/Common.cpp index 012be2660282..c24d5f6275c3 100644 --- a/image/test/gtest/Common.cpp +++ b/image/test/gtest/Common.cpp @@ -47,6 +47,10 @@ AutoInitializeImageLib::AutoInitializeImageLib() { rv = Preferences::SetBool("image.avif.enabled", true); EXPECT_TRUE(rv == NS_OK); + // Ensure JXL is enabled to run decoder tests. + rv = Preferences::SetBool("image.jxl.enabled", true); + EXPECT_TRUE(rv == NS_OK); + // Ensure that ImageLib services are initialized. nsCOMPtr imgTools = do_CreateInstance("@mozilla.org/image/tools;1"); @@ -438,6 +442,10 @@ ImageTestCase GreenAVIFTestCase() { .WithSurfaceFlags(SurfaceFlags::TO_SRGB_COLORSPACE); } +ImageTestCase GreenJXLTestCase() { + return ImageTestCase("green.jxl", "image/jxl", IntSize(100, 100)); +} + // Forcing sRGB is required until nsAVIFDecoder supports ICC profiles // See bug 1634741 ImageTestCase Transparent10bit420AVIFTestCase() { @@ -565,6 +573,11 @@ ImageTestCase LargeAVIFTestCase() { TEST_CASE_IGNORE_OUTPUT); } +ImageTestCase LargeJXLTestCase() { + return ImageTestCase("large.jxl", "image/jxl", IntSize(1200, 660), + TEST_CASE_IGNORE_OUTPUT); +} + ImageTestCase GreenWebPIccSrgbTestCase() { return ImageTestCase("green.icc_srgb.webp", "image/webp", IntSize(100, 100)); } @@ -661,6 +674,11 @@ ImageTestCase TransparentWebPTestCase() { return test; } +ImageTestCase TransparentJXLTestCase() { + return ImageTestCase("transparent.jxl", "image/jxl", IntSize(1200, 1200), + TEST_CASE_IS_TRANSPARENT); +} + ImageTestCase TransparentNoAlphaHeaderWebPTestCase() { ImageTestCase test("transparent-no-alpha-header.webp", "image/webp", IntSize(100, 100), TEST_CASE_IS_FUZZY); @@ -750,6 +768,11 @@ ImageTestCase DownscaledAVIFTestCase() { IntSize(20, 20)); } +ImageTestCase DownscaledJXLTestCase() { + return ImageTestCase("downscaled.jxl", "image/jxl", IntSize(100, 100), + IntSize(20, 20)); +} + ImageTestCase DownscaledTransparentICOWithANDMaskTestCase() { // This test case is an ICO with AND mask transparency. We want to ensure that // we can downscale it without crashing or triggering ASAN failures, but its diff --git a/image/test/gtest/Common.h b/image/test/gtest/Common.h index bcdd248b51a8..b250a05c896b 100644 --- a/image/test/gtest/Common.h +++ b/image/test/gtest/Common.h @@ -476,6 +476,7 @@ ImageTestCase GreenICOTestCase(); ImageTestCase GreenIconTestCase(); ImageTestCase GreenWebPTestCase(); ImageTestCase GreenAVIFTestCase(); +ImageTestCase GreenJXLTestCase(); ImageTestCase Transparent10bit420AVIFTestCase(); ImageTestCase Transparent10bit422AVIFTestCase(); @@ -490,6 +491,7 @@ ImageTestCase Transparent8bit444AVIFTestCase(); ImageTestCase StackCheckAVIFTestCase(); ImageTestCase LargeWebPTestCase(); +ImageTestCase LargeJXLTestCase(); ImageTestCase GreenWebPIccSrgbTestCase(); ImageTestCase GreenFirstFrameAnimatedGIFTestCase(); @@ -509,6 +511,7 @@ ImageTestCase CorruptICOWithBadBppTestCase(); ImageTestCase TransparentPNGTestCase(); ImageTestCase TransparentGIFTestCase(); ImageTestCase TransparentWebPTestCase(); +ImageTestCase TransparentJXLTestCase(); ImageTestCase TransparentNoAlphaHeaderWebPTestCase(); ImageTestCase FirstFramePaddingGIFTestCase(); ImageTestCase TransparentIfWithinICOBMPTestCase(TestCaseFlags aFlags); @@ -526,6 +529,7 @@ ImageTestCase DownscaledBMPTestCase(); ImageTestCase DownscaledICOTestCase(); ImageTestCase DownscaledIconTestCase(); ImageTestCase DownscaledWebPTestCase(); +ImageTestCase DownscaledJXLTestCase(); ImageTestCase DownscaledTransparentICOWithANDMaskTestCase(); ImageTestCase TruncatedSmallGIFTestCase(); diff --git a/image/test/gtest/TestDecoders.cpp b/image/test/gtest/TestDecoders.cpp index 8b5ff32d8d8b..46ffbf17c603 100644 --- a/image/test/gtest/TestDecoders.cpp +++ b/image/test/gtest/TestDecoders.cpp @@ -680,6 +680,7 @@ IMAGE_GTEST_DECODER_BASE_F(BMP) IMAGE_GTEST_DECODER_BASE_F(ICO) IMAGE_GTEST_DECODER_BASE_F(Icon) IMAGE_GTEST_DECODER_BASE_F(WebP) +IMAGE_GTEST_DECODER_BASE_F(JXL) TEST_F(ImageDecoders, ICOWithANDMaskDownscaleDuringDecode) { CheckDownscaleDuringDecode(DownscaledTransparentICOWithANDMaskTestCase()); @@ -768,6 +769,10 @@ TEST_F(ImageDecoders, AVIFDownscaleDuringDecode) { CheckDownscaleDuringDecode(DownscaledAVIFTestCase()); } +TEST_F(ImageDecoders, JXLLargeMultiChunk) { + CheckDecoderMultiChunk(LargeJXLTestCase(), /* aChunkSize */ 64); +} + TEST_F(ImageDecoders, AnimatedGIFSingleChunk) { CheckDecoderSingleChunk(GreenFirstFrameAnimatedGIFTestCase()); } diff --git a/image/test/gtest/TestLoader.cpp b/image/test/gtest/TestLoader.cpp index 289cebae9c01..8f25b24ead26 100644 --- a/image/test/gtest/TestLoader.cpp +++ b/image/test/gtest/TestLoader.cpp @@ -87,6 +87,19 @@ TEST_F(ImageLoader, DetectAVIFCompatibleBrand) { CheckMimeType(buffer, sizeof(buffer), IMAGE_AVIF); } +TEST_F(ImageLoader, DetectJXLCodestream) { + const char buffer[] = "\xff\x0a"; + CheckMimeType(buffer, sizeof(buffer), IMAGE_JXL); +} + +TEST_F(ImageLoader, DetectJXLContainer) { + const char buffer[] = + "\x00\x00\x00\x0c" + "JXL " + "\x0d\x0a\x87\x0a"; + CheckMimeType(buffer, sizeof(buffer), IMAGE_JXL); +} + TEST_F(ImageLoader, DetectNonImageMP4) { const char buffer[] = "\x00\x00\x00\x1c" // box length diff --git a/image/test/gtest/TestMetadata.cpp b/image/test/gtest/TestMetadata.cpp index 209ad42d525b..0e8849ab87d3 100644 --- a/image/test/gtest/TestMetadata.cpp +++ b/image/test/gtest/TestMetadata.cpp @@ -153,6 +153,10 @@ TEST_F(ImageDecoderMetadata, BMP) { CheckMetadata(GreenBMPTestCase()); } TEST_F(ImageDecoderMetadata, ICO) { CheckMetadata(GreenICOTestCase()); } TEST_F(ImageDecoderMetadata, Icon) { CheckMetadata(GreenIconTestCase()); } TEST_F(ImageDecoderMetadata, WebP) { CheckMetadata(GreenWebPTestCase()); } +TEST_F(ImageDecoderMetadata, JXL) { CheckMetadata(GreenJXLTestCase()); } +TEST_F(ImageDecoderMetadata, TransparentJXL) { + CheckMetadata(TransparentJXLTestCase()); +} TEST_F(ImageDecoderMetadata, AnimatedGIF) { CheckMetadata(GreenFirstFrameAnimatedGIFTestCase()); diff --git a/image/test/gtest/downscaled.jxl b/image/test/gtest/downscaled.jxl new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/image/test/gtest/green.jxl b/image/test/gtest/green.jxl new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/image/test/gtest/large.jxl b/image/test/gtest/large.jxl new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/image/test/gtest/moz.build b/image/test/gtest/moz.build index adac135395fc..50531c516486 100644 --- a/image/test/gtest/moz.build +++ b/image/test/gtest/moz.build @@ -60,6 +60,7 @@ TEST_HARNESS_FILES.gtest += [ "downscaled.ico", "downscaled.icon", "downscaled.jpg", + "downscaled.jxl", "downscaled.png", "downscaled.webp", "exif_resolution.jpg", @@ -78,10 +79,12 @@ TEST_HARNESS_FILES.gtest += [ "green.ico", "green.icon", "green.jpg", + "green.jxl", "green.png", "green.webp", "invalid-truncated-metadata.bmp", "large.avif", + "large.jxl", "large.webp", "multilayer.avif", "no-frame-delay.gif", @@ -114,6 +117,7 @@ TEST_HARNESS_FILES.gtest += [ "transparent-no-alpha-header.webp", "transparent.avif", "transparent.gif", + "transparent.jxl", "transparent.png", "transparent.webp", ] diff --git a/image/test/gtest/transparent.jxl b/image/test/gtest/transparent.jxl new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/image/test/reftest/jxl/jxl-size-33x33.jxl b/image/test/reftest/jxl/jxl-size-33x33.jxl new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/image/test/reftest/jxl/jxl-size-33x33.png b/image/test/reftest/jxl/jxl-size-33x33.png new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/image/test/reftest/jxl/reftest.list b/image/test/reftest/jxl/reftest.list new file mode 100644 index 000000000000..04819b55439e --- /dev/null +++ b/image/test/reftest/jxl/reftest.list @@ -0,0 +1,3 @@ +# JXL tests + +pref(image.jxl.enabled,true) == jxl-size-33x33.jxl jxl-size-33x33.png diff --git a/image/test/reftest/reftest.list b/image/test/reftest/reftest.list index 131abb5b8dc4..2fa402d1193a 100644 --- a/image/test/reftest/reftest.list +++ b/image/test/reftest/reftest.list @@ -28,6 +28,9 @@ skip-if(Android&&webrender) include ico/reftest.list # JPEG tests include jpeg/reftest.list +# JXL tests +skip-if(Android) include jxl/reftest.list + # GIF tests include gif/reftest.list diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml index 63ce2b04bbe6..5ba1262fd35e 100644 --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -5488,6 +5488,12 @@ value: true mirror: always +# Whether we attempt to decode JXL images or not. +- name: image.jxl.enabled + type: RelaxedAtomicBool + value: false + mirror: always + #--------------------------------------------------------------------------- # Prefs starting with "intl." #--------------------------------------------------------------------------- diff --git a/netwerk/mime/nsMimeTypes.h b/netwerk/mime/nsMimeTypes.h index 14def4261556..c43c7fc73ea2 100644 --- a/netwerk/mime/nsMimeTypes.h +++ b/netwerk/mime/nsMimeTypes.h @@ -159,6 +159,7 @@ #define IMAGE_SVG_XML "image/svg+xml" #define IMAGE_WEBP "image/webp" #define IMAGE_AVIF "image/avif" +#define IMAGE_JXL "image/jxl" #define MESSAGE_EXTERNAL_BODY "message/external-body" #define MESSAGE_NEWS "message/news" diff --git a/toolkit/content/contentAreaUtils.js b/toolkit/content/contentAreaUtils.js index 0216e28aa9a5..bd749cff4b1e 100644 --- a/toolkit/content/contentAreaUtils.js +++ b/toolkit/content/contentAreaUtils.js @@ -1136,6 +1136,7 @@ const kImageExtensions = new Set([ "svg", "webp", "avif", + "jxl", ]); function getNormalizedLeafName(aFile, aDefaultExtension) { diff --git a/uriloader/exthandler/nsExternalHelperAppService.cpp b/uriloader/exthandler/nsExternalHelperAppService.cpp index 5086053a7602..37324a43c6e3 100644 --- a/uriloader/exthandler/nsExternalHelperAppService.cpp +++ b/uriloader/exthandler/nsExternalHelperAppService.cpp @@ -535,6 +535,7 @@ static const nsExtraMimeTypeEntry extraMimeEntries[] = { {IMAGE_SVG_XML, "svg", "Scalable Vector Graphics"}, {IMAGE_WEBP, "webp", "WebP Image"}, {IMAGE_AVIF, "avif", "AV1 Image File"}, + {IMAGE_JXL, "jxl", "JPEG XL Image File"}, {MESSAGE_RFC822, "eml", "RFC-822 data"}, {TEXT_PLAIN, "txt,text", "Text File"}, @@ -605,7 +606,7 @@ static const char* forcedExtensionMimetypes[] = { * NOTE: These MUST be lower-case and ASCII. */ static const char* descriptionOverwriteExtensions[] = { - "avif", "pdf", "svg", "webp", "xml", + "avif", "jxl", "pdf", "svg", "webp", "xml", }; static StaticRefPtr sExtHelperAppSvcSingleton;