diff --git a/configs/common/mozconfig b/configs/common/mozconfig
index 00c55a046..11e8d91cb 100644
--- a/configs/common/mozconfig
+++ b/configs/common/mozconfig
@@ -59,6 +59,7 @@ if test "$ZEN_RELEASE"; then
ac_add_options --disable-rust-tests
ac_add_options --disable-default-browser-agent
+# ac_add_options --enable-minify=js,properties
if ! test "$ZEN_DISABLE_LTO"; then
# only enable full LTO when ZEN_RELEASE_BRANCH is 'release'
diff --git a/src/Cargo-lock.patch b/src/Cargo-lock.patch
new file mode 100644
index 000000000..72aa68fea
--- /dev/null
+++ b/src/Cargo-lock.patch
@@ -0,0 +1,13 @@
+diff --git a/Cargo.lock b/Cargo.lock
+index da2fbe8c40fa40a86c350f8adb33e26915fecc7b..e5a571fc41cd4fa8d2cdffdc15f9ad083e6d36fb 100644
+--- a/Cargo.lock
++++ b/Cargo.lock
+@@ -3912,8 +3912,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..b7c23b97e
--- /dev/null
+++ b/src/Cargo-toml.patch
@@ -0,0 +1,14 @@
+diff --git a/Cargo.toml b/Cargo.toml
+index 19a470608b7de28a946353d4c09a56b4dd3fd69a..37e9687c86b5725ee5d5071b632b5927ffb6bd27 100644
+--- a/Cargo.toml
++++ b/Cargo.toml
+@@ -201,6 +201,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/base/content/zen-sidebar-panel.inc.xhtml b/src/browser/base/content/zen-sidebar-panel.inc.xhtml
index e1137b013..e089825f5 100644
--- a/src/browser/base/content/zen-sidebar-panel.inc.xhtml
+++ b/src/browser/base/content/zen-sidebar-panel.inc.xhtml
@@ -10,7 +10,7 @@
-
+
diff --git a/src/browser/base/content/zen-styles/zen-folders.css b/src/browser/base/content/zen-styles/zen-folders.css
index 2f8df029f..29be10fc5 100644
--- a/src/browser/base/content/zen-styles/zen-folders.css
+++ b/src/browser/base/content/zen-styles/zen-folders.css
@@ -28,7 +28,12 @@ tab-group[split-view-group] {
--tab-min-height: 28px;
}
+ container-type: inline-size;
+ container-name: browser-tab;
+
flex: 1 !important;
+ padding-inline: 2px !important;
+ overflow: clip;
&:not(:last-child)::after {
content: '';
@@ -75,10 +80,18 @@ tab-group[split-view-group] {
background-color: var(--zen-toolbar-element-bg);
}
- & .tab-close-button {
+ & .tab-close-button,
+ & .tab-reset-button {
+ margin-inline-end: -3px !important;
display: none !important;
}
+ @container browser-tab (min-width: 70px) {
+ :root[zen-sidebar-expanded='true'] &:hover > .tabbrowser-tab:not([pinned]) .tab-close-button {
+ display: block !important;
+ }
+ }
+
@media (prefers-reduced-motion: no-preference) {
#tabbrowser-tabs[movingtab] & {
transition: var(--tab-dragover-transition);
diff --git a/src/browser/base/content/zen-styles/zen-tabs/vertical-tabs.css b/src/browser/base/content/zen-styles/zen-tabs/vertical-tabs.css
index 0098d1d03..b2f7e206b 100644
--- a/src/browser/base/content/zen-styles/zen-tabs/vertical-tabs.css
+++ b/src/browser/base/content/zen-styles/zen-tabs/vertical-tabs.css
@@ -600,7 +600,7 @@
&:is(:hover, [visuallyselected]) .tab-close-button {
display: block;
--tab-inline-padding: 0; /* Avoid weird padding */
- margin-inline-end: 0 !important;
+ margin-inline-end: 0;
}
.tab-throbber,
@@ -888,7 +888,6 @@
.reset-icon,
.tab-reset-pin-button {
appearance: none;
-
}
@media not (forced-colors) {
diff --git a/src/browser/base/zen-components/ZenFolders.mjs b/src/browser/base/zen-components/ZenFolders.mjs
index 183d0d327..976598bef 100644
--- a/src/browser/base/zen-components/ZenFolders.mjs
+++ b/src/browser/base/zen-components/ZenFolders.mjs
@@ -8,20 +8,49 @@
document.addEventListener('TabGrouped', this.#onTabGrouped.bind(this));
document.addEventListener('TabUngrouped', this.#onTabUngrouped.bind(this));
document.addEventListener('TabGroupRemoved', this.#onTabGroupRemoved.bind(this));
+ document.addEventListener('TabGroupCreate', this.#onTabGroupCreate.bind(this));
}
#onTabGrouped(event) {
const tab = event.target;
const group = tab.group;
group.pinned = tab.pinned;
+
+ if (group.hasAttribute('split-view-group') && group.hasAttribute('zen-pinned-changed')) {
+ // zen-pinned-changed remove it and set it to had-zen-pinned-changed to keep
+ // track of the original pinned state
+ group.removeAttribute('zen-pinned-changed');
+ group.setAttribute('had-zen-pinned-changed', true);
+ }
}
- #onTabUngrouped(event) {}
+ #onTabUngrouped(event) {
+ const tab = event.target;
+ const group = event.detail;
+ if (group.hasAttribute('split-view-group') && tab.hasAttribute('had-zen-pinned-changed')) {
+ tab.setAttribute('zen-pinned-changed', true);
+ tab.removeAttribute('had-zen-pinned-changed');
+ }
+ }
+
+ #onTabGroupCreate(event) {
+ const group = event.target;
+ const tabs = group.tabs;
+ if (!group.pinned) {
+ return;
+ }
+ for (const tab of tabs) {
+ if (tab.hasAttribute('zen-pinned-changed')) {
+ tab.removeAttribute('zen-pinned-changed');
+ tab.setAttribute('had-zen-pinned-changed', true);
+ }
+ }
+ }
#onTabGroupRemoved(event) {}
expandGroupTabs(group) {
- for (const tab of group.tabs) {
+ for (const tab of group.tabs.reverse()) {
gBrowser.ungroupTab(tab);
}
}
@@ -31,15 +60,20 @@
if (!group) {
return false;
}
- if (group.hasAttribute('split-view-group')) {
- for (const tab of group.tabs) {
- tab.setAttribute('pinned', 'true');
+ if (group.hasAttribute('split-view-group') && !this._piningFolder) {
+ this._piningFolder = true;
+ for (const otherTab of group.tabs) {
+ if (tab === otherTab) {
+ continue;
+ }
+ gBrowser.pinTab(otherTab);
}
+ this._piningFolder = false;
gBrowser.verticalPinnedTabsContainer.insertBefore(group, gBrowser.verticalPinnedTabsContainer.lastChild);
gBrowser.tabContainer._invalidateCachedTabs();
return true;
}
- return false;
+ return this._piningFolder;
}
handleTabUnpin(tab) {
@@ -47,15 +81,20 @@
if (!group) {
return false;
}
- if (group.hasAttribute('split-view-group')) {
- for (const tab of group.tabs) {
- tab.removeAttribute('pinned');
+ if (group.hasAttribute('split-view-group') && !this._piningFolder) {
+ this._piningFolder = true;
+ for (const otherTab of group.tabs) {
+ if (tab === otherTab) {
+ continue;
+ }
+ gBrowser.unpinTab(otherTab);
}
+ this._piningFolder = false;
ZenWorkspaces.activeWorkspaceStrip.prepend(group);
gBrowser.tabContainer._invalidateCachedTabs();
return true;
}
- return false;
+ return this._piningFolder;
}
}
diff --git a/src/browser/base/zen-components/ZenPinnedTabManager.mjs b/src/browser/base/zen-components/ZenPinnedTabManager.mjs
index 3cd881b9a..82f90009a 100644
--- a/src/browser/base/zen-components/ZenPinnedTabManager.mjs
+++ b/src/browser/base/zen-components/ZenPinnedTabManager.mjs
@@ -94,7 +94,7 @@
//const pin = this._pinsCache.find((pin) => pin.uuid === tab.getAttribute('zen-pin-id'));
//if (pin) {
// pin.iconUrl = iconUrl;
- // ZenPinnedTabsStorage.savePin(pin);
+ // this.savePin(pin);
//}
}
@@ -289,7 +289,7 @@
container.insertBefore(newTab, container.lastChild);
}
} else {
- document.getElementById('zen-essentials-container').prepend(newTab);
+ document.getElementById('zen-essentials-container').appendChild(newTab);
}
gBrowser.tabContainer._invalidateCachedTabs();
newTab.initialize();
@@ -339,13 +339,13 @@
tab.position = tab._tPos;
for (let otherTab of gBrowser.tabs) {
- if (otherTab.pinned && otherTab._tPos > tab.position) {
+ if (otherTab.pinned) {
const actualPin = this._pinsCache.find((pin) => pin.uuid === otherTab.getAttribute('zen-pin-id'));
if (!actualPin) {
continue;
}
actualPin.position = otherTab._tPos;
- await ZenPinnedTabsStorage.savePin(actualPin, false);
+ await this.savePin(actualPin, false);
}
}
@@ -355,7 +355,8 @@
return;
}
actualPin.position = tab.position;
- await ZenPinnedTabsStorage.savePin(actualPin);
+ actualPin.isEssential = tab.hasAttribute('zen-essential');
+ await this.savePin(actualPin);
}
_onTabClick(e) {
@@ -398,7 +399,7 @@
pin.workspaceUuid = tab.getAttribute('zen-workspace-id');
pin.userContextId = userContextId ? parseInt(userContextId, 10) : 0;
- await ZenPinnedTabsStorage.savePin(pin);
+ await this.savePin(pin);
this.resetPinChangedUrl(tab);
await this._refreshPinnedTabs();
gZenUIManager.showToast('zen-pinned-tab-replaced');
@@ -421,7 +422,7 @@
entry = JSON.parse(tab.getAttribute('zen-pinned-entry'));
}
- await ZenPinnedTabsStorage.savePin({
+ await this.savePin({
uuid,
title: entry?.title || tab.label || browser.contentTitle,
url: entry?.url || browser.currentURI.spec,
@@ -474,6 +475,15 @@
}
}
+ async savePin(pin, notifyObservers = true) {
+ await ZenPinnedTabsStorage.savePin(pin, notifyObservers);
+ // Update the cache
+ const existingPin = this._pinsCache.find((p) => p.uuid === pin.uuid);
+ if (existingPin) {
+ Object.assign(existingPin, pin);
+ }
+ }
+
_onCloseTabShortcut(event, selectedTab = gBrowser.selectedTab, behavior = lazy.zenPinnedTabCloseShortcutBehavior) {
if (!selectedTab?.pinned) {
return;
@@ -604,7 +614,7 @@
const pin = this._pinsCache.find((pin) => pin.uuid === tab.getAttribute('zen-pin-id'));
if (pin) {
pin.isEssential = true;
- ZenPinnedTabsStorage.savePin(pin);
+ this.savePin(pin);
}
document.getElementById('zen-essentials-container').appendChild(tab);
gBrowser.tabContainer._invalidateCachedTabs();
@@ -622,8 +632,8 @@
for (let i = 0; i < tabs.length; i++) {
const tab = tabs[i];
tab.removeAttribute('zen-essential');
- if (ZenWorkspaces.workspaceEnabled && ZenWorkspaces.getActiveWorkspaceFromCache.uuid) {
- tab.setAttribute('zen-workspace-id', ZenWorkspaces.getActiveWorkspaceFromCache.uuid);
+ if (ZenWorkspaces.workspaceEnabled && ZenWorkspaces.getActiveWorkspaceFromCache().uuid) {
+ tab.setAttribute('zen-workspace-id', ZenWorkspaces.getActiveWorkspaceFromCache().uuid);
}
if (unpin) {
gBrowser.unpinTab(tab);
@@ -781,6 +791,7 @@
return;
}
tab.removeAttribute('zen-pinned-changed');
+ tab.removeAttribute('had-zen-pinned-changed');
tab.style.removeProperty('--zen-original-tab-icon');
}
@@ -788,7 +799,11 @@
if (tab.hasAttribute('zen-pinned-changed')) {
return;
}
- tab.setAttribute('zen-pinned-changed', 'true');
+ if (tab.group?.hasAttribute('split-view-group')) {
+ tab.setAttribute('had-zen-pinned-changed', 'true');
+ } else {
+ tab.setAttribute('zen-pinned-changed', 'true');
+ }
tab.style.setProperty('--zen-original-tab-icon', `url(${pin.iconUrl})`);
}
diff --git a/src/browser/base/zen-components/ZenSidebarManager.mjs b/src/browser/base/zen-components/ZenSidebarManager.mjs
index dd891d07f..f4b2c4ab0 100644
--- a/src/browser/base/zen-components/ZenSidebarManager.mjs
+++ b/src/browser/base/zen-components/ZenSidebarManager.mjs
@@ -33,6 +33,7 @@ class ZenBrowserManagerSidebar extends ZenDOMOperatedFeature {
this.listenForPrefChanges();
this.insertIntoContextMenu();
this.addPositioningListeners();
+ this.syncPinnedState();
}
onlySafeWidthAndHeight() {
@@ -98,6 +99,17 @@ class ZenBrowserManagerSidebar extends ZenDOMOperatedFeature {
window.addEventListener('resize', this.onWindowResize.bind(this));
}
+ syncPinnedState() {
+ const sidebar = document.getElementById('zen-sidebar-web-panel');
+ const pinButton = document.getElementById('zen-sidebar-web-panel-pinned');
+
+ if (sidebar.hasAttribute('pinned')) {
+ pinButton.setAttribute('pinned', 'true');
+ } else {
+ pinButton.removeAttribute('pinned');
+ }
+ }
+
handleSplitterMouseDown(mouseDownEvent) {
if (this._isDragging) return;
this._isDragging = true;
diff --git a/src/browser/base/zen-components/ZenViewSplitter.mjs b/src/browser/base/zen-components/ZenViewSplitter.mjs
index 6e3f5ec16..a863ec023 100644
--- a/src/browser/base/zen-components/ZenViewSplitter.mjs
+++ b/src/browser/base/zen-components/ZenViewSplitter.mjs
@@ -709,7 +709,9 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
*/
splitTabs(tabs, gridType) {
const firstisPinned = tabs[0].pinned;
- tabs = tabs.filter((t) => t.pinned === firstisPinned && !t.hidden && !t.hasAttribute('zen-empty-tab'));
+ tabs = tabs.filter(
+ (t) => t.pinned === firstisPinned && !t.hidden && !t.hasAttribute('zen-empty-tab') && !t.hasAttribute('zen-essential')
+ );
if (tabs.length < 2 || tabs.length > this.MAX_TABS) {
return;
}
diff --git a/src/browser/base/zen-components/ZenWorkspaces.mjs b/src/browser/base/zen-components/ZenWorkspaces.mjs
index 769e11964..e1cf0a825 100644
--- a/src/browser/base/zen-components/ZenWorkspaces.mjs
+++ b/src/browser/base/zen-components/ZenWorkspaces.mjs
@@ -1493,9 +1493,10 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
this._inChangingWorkspace = true;
try {
await this._performWorkspaceChange(window, ...args);
- } finally {
- this._inChangingWorkspace = false;
+ } catch (e) {
+ console.error('ZenWorkspaces: Error changing workspace', e);
}
+ this._inChangingWorkspace = false;
}
_cancelSwipeAnimation() {
@@ -1630,7 +1631,6 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
const elementWorkspaceIndex = workspaces.workspaces.findIndex((w) => w.uuid === elementWorkspaceId);
const offset = -(newWorkspaceIndex - elementWorkspaceIndex) * 100;
const newTransform = `translateX(${offset}%)`;
- const isCurrent = offset === 0;
if (shouldAnimate) {
element.removeAttribute('hidden');
animations.push(
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/components/places/PlacesUIUtils-sys-mjs.patch b/src/browser/components/places/PlacesUIUtils-sys-mjs.patch
index fc32aa200..19b4f1ce8 100644
--- a/src/browser/components/places/PlacesUIUtils-sys-mjs.patch
+++ b/src/browser/components/places/PlacesUIUtils-sys-mjs.patch
@@ -1,5 +1,5 @@
diff --git a/browser/components/places/PlacesUIUtils.sys.mjs b/browser/components/places/PlacesUIUtils.sys.mjs
-index fbdd6a34b12d4d957f7a2d9d95df0bfd65ba3f61..4d27f7fc108ca76e071707a737209bf5ea4ea07e 100644
+index fbdd6a34b12d4d957f7a2d9d95df0bfd65ba3f61..baaf34536f557c69fce3cc43e6f12658514db39f 100644
--- a/browser/components/places/PlacesUIUtils.sys.mjs
+++ b/browser/components/places/PlacesUIUtils.sys.mjs
@@ -58,6 +58,7 @@ class BookmarkState {
@@ -95,7 +95,7 @@ index fbdd6a34b12d4d957f7a2d9d95df0bfd65ba3f61..4d27f7fc108ca76e071707a737209bf5
+ const placeholders = workspacesToRemove.map(() => '?').join(',');
+ await db.execute(`
+ DELETE FROM zen_bookmarks_workspaces
-+ WHERE bookmark_guid = :bookmark_guid
++ WHERE bookmark_guid = :bookmark_guid
+ AND workspace_uuid IN (${placeholders})
+ `, [bookmarkGuid, ...workspacesToRemove]);
+
@@ -157,12 +157,20 @@ index fbdd6a34b12d4d957f7a2d9d95df0bfd65ba3f61..4d27f7fc108ca76e071707a737209bf5
/**
* Append transactions to update tags by given information.
*
-@@ -902,7 +1011,7 @@ export var PlacesUIUtils = {
+@@ -902,8 +1011,15 @@ export var PlacesUIUtils = {
aNode,
aWhere,
aWindow,
- { aPrivate = false, userContextId = 0 } = {}
-+ { aPrivate = false, userContextId = aWindow.ZenWorkspaces.getDefaultContainer() } = {}
++ { aPrivate = false, userContextId = undefined } = {}
) {
++ if (typeof userContextId == "undefined") {
++ try {
++ let browserWindow = getBrowserWindow(aWindow);
++ userContextId = browserWindow.ZenWorkspaces.getDefaultContainer();
++ } catch {}
++ }
++
if (
aNode &&
+ lazy.PlacesUtils.nodeIsURI(aNode) &&
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);