diff --git a/src/Cargo-lock.patch b/src/Cargo-lock.patch
new file mode 100644
index 000000000..fe2a60421
--- /dev/null
+++ b/src/Cargo-lock.patch
@@ -0,0 +1,13 @@
+diff --git a/Cargo.lock b/Cargo.lock
+index 0609d96f01ded85dccf2c931d3c553be61432e6a..8ce4374dd31b4ce818c8bb548fbfb2669014b05b 100644
+--- a/Cargo.lock
++++ b/Cargo.lock
+@@ -3919,8 +3919,6 @@ dependencies = [
+ [[package]]
+ name = "mime_guess"
+ version = "2.0.4"
+-source = "registry+https://github.com/rust-lang/crates.io-index"
+-checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
+ dependencies = [
+ "mime",
+ "unicase",
diff --git a/src/Cargo-toml.patch b/src/Cargo-toml.patch
new file mode 100644
index 000000000..298cb4263
--- /dev/null
+++ b/src/Cargo-toml.patch
@@ -0,0 +1,14 @@
+diff --git a/Cargo.toml b/Cargo.toml
+index 29942b65f62497b80d63675501ae063c393943a7..a9aa4641421038fbe5b0ecd9b7353611eecefffe 100644
+--- a/Cargo.toml
++++ b/Cargo.toml
+@@ -198,6 +198,9 @@ rure = { path = "third_party/rust/rure" }
+ # Patch `plist` to work with `indexmap` 2.*
+ plist = { path = "third_party/rust/plist" }
+
++# Patch mime_guess to add missing mime types
++mime_guess = { path = "third_party/rust/mime_guess" }
++
+ # To-be-published changes.
+ unicode-bidi = { git = "https://github.com/servo/unicode-bidi", rev = "ca612daf1c08c53abe07327cb3e6ef6e0a760f0c" }
+ nss-gk-api = { git = "https://github.com/beurdouche/nss-gk-api", rev = "e48a946811ffd64abc78de3ee284957d8d1c0d63" }
diff --git a/src/browser/components/BrowserContentHandler-sys-mjs.patch b/src/browser/components/BrowserContentHandler-sys-mjs.patch
new file mode 100644
index 000000000..b69095199
--- /dev/null
+++ b/src/browser/components/BrowserContentHandler-sys-mjs.patch
@@ -0,0 +1,12 @@
+diff --git a/browser/components/BrowserContentHandler.sys.mjs b/browser/components/BrowserContentHandler.sys.mjs
+index 7aef091c0be1cb0ea0be52268949db17032f96d9..5e9105fa671d1b1979f204fc8d3be22771998ad7 100644
+--- a/browser/components/BrowserContentHandler.sys.mjs
++++ b/browser/components/BrowserContentHandler.sys.mjs
+@@ -1278,6 +1278,7 @@ function maybeRecordToHandleTelemetry(uri, isLaunch) {
+ ".avif",
+ ".htm",
+ ".html",
++ ".jxl",
+ ".pdf",
+ ".shtml",
+ ".xht",
diff --git a/src/browser/installer/windows/msix/AppxManifest-xml-in.patch b/src/browser/installer/windows/msix/AppxManifest-xml-in.patch
new file mode 100644
index 000000000..1c638f2df
--- /dev/null
+++ b/src/browser/installer/windows/msix/AppxManifest-xml-in.patch
@@ -0,0 +1,12 @@
+diff --git a/browser/installer/windows/msix/AppxManifest.xml.in b/browser/installer/windows/msix/AppxManifest.xml.in
+index b81a73518a183b7b1d178793886c66f44651058d..89690a4177229b70013bcf35ec1d805fff7e1b26 100644
+--- a/browser/installer/windows/msix/AppxManifest.xml.in
++++ b/browser/installer/windows/msix/AppxManifest.xml.in
+@@ -61,6 +61,7 @@
+ .avif
+ .htm
+ .html
++ .jxl
+ .pdf
+ .shtml
+ .xht
diff --git a/src/browser/installer/windows/nsis/shared-nsh.patch b/src/browser/installer/windows/nsis/shared-nsh.patch
new file mode 100644
index 000000000..f8da06cfe
--- /dev/null
+++ b/src/browser/installer/windows/nsis/shared-nsh.patch
@@ -0,0 +1,46 @@
+diff --git a/browser/installer/windows/nsis/shared.nsh b/browser/installer/windows/nsis/shared.nsh
+index b7f8e1453089ab5f1945e1a65f038e17b5273571..5297f5ed70fe3446e55be37df486fb4ad791a446 100644
+--- a/browser/installer/windows/nsis/shared.nsh
++++ b/browser/installer/windows/nsis/shared.nsh
+@@ -513,6 +513,7 @@ ${RemoveDefaultBrowserAgentShortcut}
+ ${AddAssociationIfNoneExist} ".svg" "FirefoxHTML$5"
+ ${AddAssociationIfNoneExist} ".webp" "FirefoxHTML$5"
+ ${AddAssociationIfNoneExist} ".avif" "FirefoxHTML$5"
++ ${AddAssociationIfNoneExist} ".jxl" "FirefoxHTML$5"
+
+ ${AddAssociationIfNoneExist} ".pdf" "FirefoxPDF$5"
+
+@@ -609,6 +610,7 @@ ${RemoveDefaultBrowserAgentShortcut}
+ WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".svg" "FirefoxHTML$2"
+ WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".webp" "FirefoxHTML$2"
+ WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".avif" "FirefoxHTML$2"
++ WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".jxl" "FirefoxHTML$2"
+
+ WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".pdf" "FirefoxPDF$2"
+
+@@ -681,6 +683,7 @@ ${RemoveDefaultBrowserAgentShortcut}
+ ${WriteApplicationsSupportedType} ${RegKey} ".webm"
+ ${WriteApplicationsSupportedType} ${RegKey} ".webp"
+ ${WriteApplicationsSupportedType} ${RegKey} ".avif"
++ ${WriteApplicationsSupportedType} ${RegKey} ".jxl"
+ ${WriteApplicationsSupportedType} ${RegKey} ".xht"
+ ${WriteApplicationsSupportedType} ${RegKey} ".xhtml"
+ ${WriteApplicationsSupportedType} ${RegKey} ".xml"
+@@ -1728,6 +1731,8 @@ Function SetAsDefaultAppUserHKCU
+ Pop $0
+ AppAssocReg::SetAppAsDefault "$R9" ".avif" "file"
+ Pop $0
++ AppAssocReg::SetAppAsDefault "$R9" ".jxl" "file"
++ Pop $0
+ AppAssocReg::SetAppAsDefault "$R9" ".xht" "file"
+ Pop $0
+ AppAssocReg::SetAppAsDefault "$R9" ".xhtml" "file"
+@@ -1857,7 +1862,7 @@ FunctionEnd
+ ; uninstalled.
+
+ ; Do all of that twice, once for the local machine and once for the current user
+-
++
+ ; Remove protocol handlers
+ ClearErrors
+ ReadRegStr $0 HKLM "Software\Classes\${_PROTOCOL}\DefaultIcon" ""
diff --git a/src/browser/installer/windows/nsis/uninstaller-nsi.patch b/src/browser/installer/windows/nsis/uninstaller-nsi.patch
new file mode 100644
index 000000000..b2de9cb9a
--- /dev/null
+++ b/src/browser/installer/windows/nsis/uninstaller-nsi.patch
@@ -0,0 +1,12 @@
+diff --git a/browser/installer/windows/nsis/uninstaller.nsi b/browser/installer/windows/nsis/uninstaller.nsi
+index 559c8b46ee06bc42c91da49b5d9e397fe8ff6126..62094a5d98712a41a607ba01ca2adfa1e4f51ccd 100644
+--- a/browser/installer/windows/nsis/uninstaller.nsi
++++ b/browser/installer/windows/nsis/uninstaller.nsi
+@@ -507,6 +507,7 @@ Section "Uninstall"
+ ${un.RegCleanFileHandler} ".svg" "FirefoxHTML-$AppUserModelID"
+ ${un.RegCleanFileHandler} ".webp" "FirefoxHTML-$AppUserModelID"
+ ${un.RegCleanFileHandler} ".avif" "FirefoxHTML-$AppUserModelID"
++ ${un.RegCleanFileHandler} ".jxl" "FirefoxHTML-$AppUserModelID"
+
+ ${un.RegCleanFileHandler} ".pdf" "FirefoxPDF-$AppUserModelID"
+
diff --git a/src/image/DecoderFactory-cpp.patch b/src/image/DecoderFactory-cpp.patch
new file mode 100644
index 000000000..8d3df598e
--- /dev/null
+++ b/src/image/DecoderFactory-cpp.patch
@@ -0,0 +1,32 @@
+diff --git a/image/DecoderFactory.cpp b/image/DecoderFactory.cpp
+index f36f03c7f2..d2cdd79f70 100644
+--- a/image/DecoderFactory.cpp
++++ b/image/DecoderFactory.cpp
+@@ -244,7 +244,12 @@ nsresult DecoderFactory::CreateAnimationDecoder(
+ }
+
+ MOZ_ASSERT(aType == DecoderType::GIF || aType == DecoderType::PNG ||
+- aType == DecoderType::WEBP || aType == DecoderType::AVIF,
++ aType == DecoderType::WEBP || aType == DecoderType::AVIF
++#ifdef MOZ_JXL
++ || aType == DecoderType::JXL,
++#else
++ ,
++#endif
+ "Calling CreateAnimationDecoder for non-animating DecoderType");
+
+ // Create an anonymous decoder. Interaction with the SurfaceCache and the
+@@ -299,7 +304,12 @@ already_AddRefed DecoderFactory::CloneAnimationDecoder(
+ // rediscover it is animated).
+ DecoderType type = aDecoder->GetType();
+ MOZ_ASSERT(type == DecoderType::GIF || type == DecoderType::PNG ||
+- type == DecoderType::WEBP || type == DecoderType::AVIF,
++ type == DecoderType::WEBP || type == DecoderType::AVIF
++#ifdef MOZ_JXL
++ || aType == DecoderType::JXL,
++#else
++ ,
++#endif
+ "Calling CloneAnimationDecoder for non-animating DecoderType");
+
+ RefPtr decoder = GetDecoder(type, nullptr, /* aIsRedecode = */ true);
diff --git a/src/image/decoders/nsJXLDecoder-cpp.patch b/src/image/decoders/nsJXLDecoder-cpp.patch
new file mode 100644
index 000000000..91f45420c
--- /dev/null
+++ b/src/image/decoders/nsJXLDecoder-cpp.patch
@@ -0,0 +1,308 @@
+diff --git a/image/decoders/nsJXLDecoder.cpp b/image/decoders/nsJXLDecoder.cpp
+index ffb7f3cd51e1d0e480bf8ac5e936a90c11f0c93d..282472afe4fbab7d41adaf15928fa93c99e74452 100644
+--- a/image/decoders/nsJXLDecoder.cpp
++++ b/image/decoders/nsJXLDecoder.cpp
+@@ -45,9 +45,20 @@ nsJXLDecoder::nsJXLDecoder(RasterImage* aImage)
+ Transition::TerminateSuccess()),
+ mDecoder(JxlDecoderMake(nullptr)),
+ mParallelRunner(
+- JxlThreadParallelRunnerMake(nullptr, PreferredThreadCount())) {
+- JxlDecoderSubscribeEvents(mDecoder.get(),
+- JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE);
++ JxlThreadParallelRunnerMake(nullptr, PreferredThreadCount())),
++ mUsePipeTransform(true),
++ mCMSLine(nullptr),
++ mNumFrames(0),
++ mTimeout(FrameTimeout::Forever()),
++ mSurfaceFormat(SurfaceFormat::OS_RGBX),
++ mContinue(false) {
++ int events = JXL_DEC_BASIC_INFO | JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE;
++
++ if (mCMSMode != CMSMode::Off) {
++ events |= JXL_DEC_COLOR_ENCODING;
++ }
++
++ JxlDecoderSubscribeEvents(mDecoder.get(), events);
+ JxlDecoderSetParallelRunner(mDecoder.get(), JxlThreadParallelRunner,
+ mParallelRunner.get());
+
+@@ -58,6 +69,10 @@ nsJXLDecoder::nsJXLDecoder(RasterImage* aImage)
+ nsJXLDecoder::~nsJXLDecoder() {
+ MOZ_LOG(sJXLLog, LogLevel::Debug,
+ ("[this=%p] nsJXLDecoder::~nsJXLDecoder", this));
++
++ if (mCMSLine) {
++ free(mCMSLine);
++ }
+ }
+
+ size_t nsJXLDecoder::PreferredThreadCount() {
+@@ -86,14 +101,20 @@ LexerResult nsJXLDecoder::DoDecode(SourceBufferIterator& aIterator,
+
+ 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();
++ // Ignore data we have already read.
++ // This will only occur as a result of a yield for animation.
++ if (!mContinue) {
++ 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));
+ }
+- JXL_TRY(JxlDecoderSetInput(mDecoder.get(), input, length));
++ mContinue = false;
+
+ while (true) {
+ JxlDecoderStatus status = JxlDecoderProcessInput(mDecoder.get());
+@@ -106,51 +127,229 @@ LexerTransition nsJXLDecoder::ReadJXLData(
+ size_t remaining = JxlDecoderReleaseInput(mDecoder.get());
+ mBuffer.clear();
+ JXL_TRY_BOOL(mBuffer.append(aData + aLength - remaining, remaining));
++
++ if (mNumFrames == 0 && InFrame()) {
++ // If an image was flushed by JxlDecoderFlushImage, then we know that
++ // JXL_DEC_FRAME has already been run and there is a pipe.
++ if (JxlDecoderFlushImage(mDecoder.get()) == JXL_DEC_SUCCESS) {
++ // A full frame partial image is written to the buffer.
++ mPipe.ResetToFirstRow();
++ for (uint8_t* rowPtr = mOutBuffer.begin();
++ rowPtr < mOutBuffer.end(); rowPtr += mInfo.xsize * mChannels) {
++ uint8_t* rowToWrite = rowPtr;
++
++ if (!mUsePipeTransform && mTransform) {
++ qcms_transform_data(mTransform, rowToWrite, mCMSLine,
++ mInfo.xsize);
++ rowToWrite = mCMSLine;
++ }
++
++ mPipe.WriteBuffer(reinterpret_cast(rowToWrite));
++ }
++
++ if (Maybe invalidRect =
++ mPipe.TakeInvalidRect()) {
++ PostInvalidation(invalidRect->mInputSpaceRect,
++ Some(invalidRect->mOutputSpaceRect));
++ }
++ }
++ }
++
+ return Transition::ContinueUnbuffered(State::JXL_DATA);
+ }
+
+ case JXL_DEC_BASIC_INFO: {
+ JXL_TRY(JxlDecoderGetBasicInfo(mDecoder.get(), &mInfo));
+ PostSize(mInfo.xsize, mInfo.ysize);
++
+ if (WantsFrameCount()) {
+ PostFrameCount(/* aFrameCount */ 1);
+ }
++
+ if (mInfo.alpha_bits > 0) {
++ mSurfaceFormat = SurfaceFormat::OS_RGBA;
+ PostHasTransparency();
+ }
++
++ if (!mInfo.have_animation && IsMetadataDecode()) {
++ return Transition::TerminateSuccess();
++ }
++
++ // If CMS is off or the image is RGB, always output in RGBA.
++ // If the image is grayscale, then the pipe transform can't be used.
++ if (mCMSMode != CMSMode::Off) {
++ mChannels = mInfo.num_color_channels == 1
++ ? 1 + (mInfo.alpha_bits > 0 ? 1 : 0)
++ : 4;
++ } else {
++ mChannels = 4;
++ }
++
++ mFormat = {mChannels, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, 0};
++
++ break;
++ }
++
++ case JXL_DEC_COLOR_ENCODING: {
++ size_t size = 0;
++ JXL_TRY(JxlDecoderGetICCProfileSize(
++ mDecoder.get(), JXL_COLOR_PROFILE_TARGET_DATA, &size))
++ std::vector icc_profile(size);
++ JXL_TRY(JxlDecoderGetColorAsICCProfile(mDecoder.get(),
++ JXL_COLOR_PROFILE_TARGET_DATA,
++ icc_profile.data(), size))
++
++ mInProfile = qcms_profile_from_memory((char*)icc_profile.data(), size);
++
++ uint32_t profileSpace = qcms_profile_get_color_space(mInProfile);
++
++ // Skip color management if color profile is not compatible with number
++ // of channels.
++ if (profileSpace != icSigRgbData &&
++ (mInfo.num_color_channels == 3 || profileSpace != icSigGrayData)) {
++ break;
++ }
++
++ mUsePipeTransform =
++ profileSpace == icSigRgbData && mInfo.num_color_channels == 3;
++
++ qcms_data_type inType;
++ if (mInfo.num_color_channels == 3) {
++ inType = QCMS_DATA_RGBA_8;
++ } else if (mInfo.alpha_bits > 0) {
++ inType = QCMS_DATA_GRAYA_8;
++ } else {
++ inType = QCMS_DATA_GRAY_8;
++ }
++
++ if (!mUsePipeTransform) {
++ mCMSLine =
++ static_cast(malloc(sizeof(uint32_t) * mInfo.xsize));
++ }
++
++ int intent = gfxPlatform::GetRenderingIntent();
++ if (intent == -1) {
++ intent = qcms_profile_get_rendering_intent(mInProfile);
++ }
++
++ mTransform =
++ qcms_transform_create(mInProfile, inType, GetCMSOutputProfile(),
++ QCMS_DATA_RGBA_8, (qcms_intent)intent);
++
++ break;
++ }
++
++ case JXL_DEC_FRAME: {
++ if (mInfo.have_animation) {
++ JXL_TRY(JxlDecoderGetFrameHeader(mDecoder.get(), &mFrameHeader));
++ int32_t duration = (int32_t)(1000.0 * mFrameHeader.duration *
++ mInfo.animation.tps_denominator /
++ mInfo.animation.tps_numerator);
++
++ mTimeout = FrameTimeout::FromRawMilliseconds(duration);
++
++ if (!HasAnimation()) {
++ PostIsAnimated(mTimeout);
++ }
++ }
++
++ bool is_last = mInfo.have_animation ? mFrameHeader.is_last : true;
++ MOZ_LOG(sJXLLog, LogLevel::Debug,
++ ("[this=%p] nsJXLDecoder::ReadJXLData - frame %d, is_last %d, "
++ "metadata decode %d, first frame decode %d\n",
++ this, mNumFrames, is_last, IsMetadataDecode(),
++ IsFirstFrameDecode()));
++
+ if (IsMetadataDecode()) {
+ return Transition::TerminateSuccess();
+ }
++
++ OrientedIntSize size(mInfo.xsize, mInfo.ysize);
++
++ Maybe animParams;
++ if (!IsFirstFrameDecode()) {
++ animParams.emplace(FullFrame().ToUnknownRect(), mTimeout, mNumFrames,
++ BlendMethod::SOURCE, DisposalMethod::CLEAR);
++ }
++
++ SurfacePipeFlags pipeFlags = SurfacePipeFlags();
++ if (mNumFrames == 0) {
++ // The first frame may be displayed progressively.
++ pipeFlags |= SurfacePipeFlags::PROGRESSIVE_DISPLAY;
++ }
++ if (mSurfaceFormat == SurfaceFormat::OS_RGBA &&
++ !(GetSurfaceFlags() & SurfaceFlags::NO_PREMULTIPLY_ALPHA)) {
++ pipeFlags |= SurfacePipeFlags::PREMULTIPLY_ALPHA;
++ }
++
++ qcms_transform* pipeTransform =
++ mUsePipeTransform ? mTransform : nullptr;
++
++ Maybe pipe = SurfacePipeFactory::CreateSurfacePipe(
++ this, size, OutputSize(), FullFrame(), SurfaceFormat::R8G8B8A8,
++ mSurfaceFormat, animParams, pipeTransform, pipeFlags);
++
++ if (!pipe) {
++ MOZ_LOG(sJXLLog, LogLevel::Debug,
++ ("[this=%p] nsJXLDecoder::ReadJXLData - no pipe\n", this));
++ return Transition::TerminateFailure();
++ }
++
++ mPipe = std::move(*pipe);
++
+ 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));
++ JXL_TRY(JxlDecoderImageOutBufferSize(mDecoder.get(), &mFormat, &size));
+
+ mOutBuffer.clear();
+ JXL_TRY_BOOL(mOutBuffer.growBy(size));
+- JXL_TRY(JxlDecoderSetImageOutBuffer(mDecoder.get(), &format,
++ JXL_TRY(JxlDecoderSetImageOutBuffer(mDecoder.get(), &mFormat,
+ mOutBuffer.begin(), size));
+ break;
+ }
+
+ case JXL_DEC_FULL_IMAGE: {
+- OrientedIntSize size(mInfo.xsize, mInfo.ysize);
+- Maybe pipe = SurfacePipeFactory::CreateSurfacePipe(
+- this, size, OutputSize(), FullFrame(), SurfaceFormat::R8G8B8A8,
+- SurfaceFormat::OS_RGBA, Nothing(), nullptr, SurfacePipeFlags());
++ mPipe.ResetToFirstRow();
++
+ for (uint8_t* rowPtr = mOutBuffer.begin(); rowPtr < mOutBuffer.end();
+- rowPtr += mInfo.xsize * 4) {
+- pipe->WriteBuffer(reinterpret_cast(rowPtr));
++ rowPtr += mInfo.xsize * mChannels) {
++ uint8_t* rowToWrite = rowPtr;
++
++ if (!mUsePipeTransform && mTransform) {
++ qcms_transform_data(mTransform, rowToWrite, mCMSLine, mInfo.xsize);
++ rowToWrite = mCMSLine;
++ }
++
++ mPipe.WriteBuffer(reinterpret_cast(rowToWrite));
+ }
+
+- if (Maybe invalidRect = pipe->TakeInvalidRect()) {
++ if (Maybe invalidRect = mPipe.TakeInvalidRect()) {
+ PostInvalidation(invalidRect->mInputSpaceRect,
+ Some(invalidRect->mOutputSpaceRect));
+ }
+- PostFrameStop();
++
++ Opacity opacity = (mSurfaceFormat == SurfaceFormat::OS_RGBA)
++ ? Opacity::SOME_TRANSPARENCY
++ : Opacity::FULLY_OPAQUE;
++ PostFrameStop(opacity);
++
++ if (!IsFirstFrameDecode() && mInfo.have_animation &&
++ !mFrameHeader.is_last) {
++ mNumFrames++;
++ mContinue = true;
++ // Notify for a new frame but there may be data in the current buffer
++ // that can immediately be processed.
++ return Transition::ToAfterYield(State::JXL_DATA);
++ }
++ [[fallthrough]]; // We are done.
++ }
++
++ case JXL_DEC_SUCCESS: {
++ PostLoopCount(HasAnimation() ? (int32_t)mInfo.animation.num_loops - 1
++ : 0);
+ PostDecodeDone();
+ return Transition::TerminateSuccess();
+ }
diff --git a/src/image/decoders/nsJXLDecoder-h.patch b/src/image/decoders/nsJXLDecoder-h.patch
new file mode 100644
index 000000000..779ccc634
--- /dev/null
+++ b/src/image/decoders/nsJXLDecoder-h.patch
@@ -0,0 +1,23 @@
+diff --git a/image/decoders/nsJXLDecoder.h b/image/decoders/nsJXLDecoder.h
+index 6cde7456ca03f79e74401c1d215b9d50453ebf41..2f593ca3b70100c600b86e753d7a458c83b4f15c 100644
+--- a/image/decoders/nsJXLDecoder.h
++++ b/image/decoders/nsJXLDecoder.h
+@@ -48,6 +48,18 @@ class nsJXLDecoder final : public Decoder {
+ Vector mBuffer;
+ Vector mOutBuffer;
+ JxlBasicInfo mInfo{};
++ JxlPixelFormat mFormat;
++ JxlFrameHeader mFrameHeader;
++
++ bool mUsePipeTransform;
++ uint8_t mChannels;
++ uint8_t* mCMSLine;
++
++ uint32_t mNumFrames;
++ FrameTimeout mTimeout;
++ gfx::SurfaceFormat mSurfaceFormat;
++ SurfacePipe mPipe;
++ bool mContinue;
+ };
+
+ } // namespace mozilla::image
diff --git a/src/third_party/rust/mime_guess/src/mime_types-rs.patch b/src/third_party/rust/mime_guess/src/mime_types-rs.patch
new file mode 100644
index 000000000..9568fa141
--- /dev/null
+++ b/src/third_party/rust/mime_guess/src/mime_types-rs.patch
@@ -0,0 +1,31 @@
+diff --git a/third_party/rust/mime_guess/src/mime_types.rs b/third_party/rust/mime_guess/src/mime_types.rs
+index 13c91b7bee77a0c0a4b45b8e05a25bb89daac66e..1521cd729ec78dbc51b86cf04546c4cd4ceb1163 100644
+--- a/third_party/rust/mime_guess/src/mime_types.rs
++++ b/third_party/rust/mime_guess/src/mime_types.rs
+@@ -103,6 +103,7 @@ pub static MIME_TYPES: &[(&str, &[&str])] = &[
+ ("au", &["audio/basic"]),
+ ("avi", &["video/x-msvideo"]),
+ ("avif", &["image/avif"]),
++ ("avifs", &["image/avif-sequence"]),
+ ("aw", &["application/applixware"]),
+ ("axa", &["audio/annodex"]),
+ ("axs", &["application/olescript"]),
+@@ -449,6 +450,10 @@ pub static MIME_TYPES: &[(&str, &[&str])] = &[
+ ("hdf", &["application/x-hdf"]),
+ ("hdml", &["text/x-hdml"]),
+ ("hdr", &["image/vnd.radiance"]),
++ ("heic", &["image/heic"]),
++ ("heics", &["image/heic-sequence"]),
++ ("heif", &["image/heif"]),
++ ("heifs", &["image/heif-sequence"]),
+ ("hh", &["text/plain"]),
+ ("hhc", &["application/x-oleobject"]),
+ ("hhk", &["application/octet-stream"]),
+@@ -567,6 +572,7 @@ pub static MIME_TYPES: &[(&str, &[&str])] = &[
+ ("jsonml", &["application/jsonml+json"]),
+ ("jsx", &["text/jscript"]),
+ ("jsxbin", &["text/plain"]),
++ ("jxl", &["image/jxl"]),
+ ("kar", &["audio/midi"]),
+ ("karbon", &["application/vnd.kde.karbon"]),
+ ("kfo", &["application/vnd.kde.kformula"]),
diff --git a/src/toolkit/components/downloads/DownloadList-sys-mjs.patch b/src/toolkit/components/downloads/DownloadList-sys-mjs.patch
new file mode 100644
index 000000000..f27368c52
--- /dev/null
+++ b/src/toolkit/components/downloads/DownloadList-sys-mjs.patch
@@ -0,0 +1,12 @@
+diff --git a/toolkit/components/downloads/DownloadList.sys.mjs b/toolkit/components/downloads/DownloadList.sys.mjs
+index c4e5776940c4cdca731a82ba91d41620c4c7b75a..d580d193bda5e932cebc849c4487de504f17b6fe 100644
+--- a/toolkit/components/downloads/DownloadList.sys.mjs
++++ b/toolkit/components/downloads/DownloadList.sys.mjs
+@@ -50,6 +50,7 @@ const FILE_EXTENSIONS = [
+ "jpg",
+ "jpeg",
+ "json",
++ "jxl",
+ "m4a",
+ "mdb",
+ "mid",
diff --git a/src/toolkit/components/extensions/parent/ext-downloads-js.patch b/src/toolkit/components/extensions/parent/ext-downloads-js.patch
new file mode 100644
index 000000000..f867d171d
--- /dev/null
+++ b/src/toolkit/components/extensions/parent/ext-downloads-js.patch
@@ -0,0 +1,20 @@
+diff --git a/toolkit/components/extensions/parent/ext-downloads.js b/toolkit/components/extensions/parent/ext-downloads.js
+index ea6929d23d432958def5be46e42e329bc224d3aa..942cfddc090399ef239cc34ab47682cab6a33cd4 100644
+--- a/toolkit/components/extensions/parent/ext-downloads.js
++++ b/toolkit/components/extensions/parent/ext-downloads.js
+@@ -87,6 +87,7 @@ const FILTER_IMAGES_EXTENSIONS = [
+ "jpe",
+ "jpg",
+ "jpeg",
++ "jxl",
+ "gif",
+ "png",
+ "bmp",
+@@ -104,6 +105,7 @@ const FILTER_IMAGES_EXTENSIONS = [
+ "raw",
+ "webp",
+ "heic",
++ "avif",
+ ];
+
+ const FILTER_XML_EXTENSIONS = ["xml"];
diff --git a/src/toolkit/content/filepicker-properties.patch b/src/toolkit/content/filepicker-properties.patch
new file mode 100644
index 000000000..c3abaca23
--- /dev/null
+++ b/src/toolkit/content/filepicker-properties.patch
@@ -0,0 +1,13 @@
+diff --git a/toolkit/content/filepicker.properties b/toolkit/content/filepicker.properties
+index 03daec114c2882ed5ab7899b9b435d1cce936838..b6bd09c3c5625a1649b31dc99935bc90773d4133 100644
+--- a/toolkit/content/filepicker.properties
++++ b/toolkit/content/filepicker.properties
+@@ -5,7 +5,7 @@
+ allFilter=*
+ htmlFilter=*.html; *.htm; *.shtml; *.xhtml
+ textFilter=*.txt; *.text
+-imageFilter=*.jpe; *.jpg; *.jpeg; *.gif; *.png; *.bmp; *.ico; *.svg; *.svgz; *.tif; *.tiff; *.ai; *.drw; *.pct; *.psp; *.xcf; *.psd; *.raw; *.webp; *.heic
++imageFilter=*.jpe; *.jpg; *.jpeg; *.gif; *.png; *.bmp; *.ico; *.svg; *.svgz; *.tif; *.tiff; *.ai; *.drw; *.pct; *.psp; *.xcf; *.psd; *.raw; *.webp; *.heic; *.avif; *.jxl
+ xmlFilter=*.xml
+ xulFilter=*.xul
+ audioFilter=*.aac; *.aif; *.flac; *.iff; *.m4a; *.m4b; *.mid; *.midi; *.mp3; *.mpa; *.mpc; *.oga; *.ogg; *.opus; *.ra; *.ram; *.snd; *.wav; *.wma
diff --git a/src/toolkit/locales/en-US/chrome/mozapps/downloads/unknownContentType-properties.patch b/src/toolkit/locales/en-US/chrome/mozapps/downloads/unknownContentType-properties.patch
new file mode 100644
index 000000000..c8c23196b
--- /dev/null
+++ b/src/toolkit/locales/en-US/chrome/mozapps/downloads/unknownContentType-properties.patch
@@ -0,0 +1,12 @@
+diff --git a/toolkit/locales/en-US/chrome/mozapps/downloads/unknownContentType.properties b/toolkit/locales/en-US/chrome/mozapps/downloads/unknownContentType.properties
+index fa3c5e389bad5abb05c86a3cb08d6c7abf34166c..1bb1f48c4d3964e4637462bb0b3d4a1e965ca5ec 100644
+--- a/toolkit/locales/en-US/chrome/mozapps/downloads/unknownContentType.properties
++++ b/toolkit/locales/en-US/chrome/mozapps/downloads/unknownContentType.properties
+@@ -17,6 +17,7 @@ fileType=%S file
+ # LOCALIZATION NOTE (orderedFileSizeWithType): first %S is type, second %S is size, and third %S is unit
+ orderedFileSizeWithType=%1$S (%2$S %3$S)
+ avifExtHandlerDescription=AV1 Image File (AVIF)
++jxlExtHandlerDescription=JPEG XL Image (JXL)
+ pdfExtHandlerDescription=Portable Document Format (PDF)
+ svgExtHandlerDescription=Scalable Vector Graphics (SVG)
+ webpExtHandlerDescription=WebP Image
diff --git a/src/widget/gtk/nsAppShell-cpp.patch b/src/widget/gtk/nsAppShell-cpp.patch
new file mode 100644
index 000000000..5e170a9db
--- /dev/null
+++ b/src/widget/gtk/nsAppShell-cpp.patch
@@ -0,0 +1,14 @@
+diff --git a/widget/gtk/nsAppShell.cpp b/widget/gtk/nsAppShell.cpp
+index eef6e76a26341d30748c6c4f054092ba0bfdd865..65b6e2583e6e6891dcbf9faeeefed21cc2d40d15 100644
+--- a/widget/gtk/nsAppShell.cpp
++++ b/widget/gtk/nsAppShell.cpp
+@@ -419,7 +419,8 @@ nsresult nsAppShell::Init() {
+ gchar* name = gdk_pixbuf_format_get_name(format);
+ if (strcmp(name, "jpeg") && strcmp(name, "png") && strcmp(name, "gif") &&
+ strcmp(name, "bmp") && strcmp(name, "ico") && strcmp(name, "xpm") &&
+- strcmp(name, "svg") && strcmp(name, "webp") && strcmp(name, "avif")) {
++ strcmp(name, "svg") && strcmp(name, "webp") && strcmp(name, "avif") &&
++ strcmp(name, "jxl")) {
+ gdk_pixbuf_format_set_disabled(format, TRUE);
+ }
+ g_free(name);