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);