From 68ff79b91a5255e7da37d0c060254cbc9a2503c5 Mon Sep 17 00:00:00 2001 From: Abe Jellinek <1770299+AbeJellinek@users.noreply.github.com> Date: Wed, 23 Sep 2026 10:34:07 -0400 Subject: [PATCH 1/3] Patch libmozglue to prevent local voices from clicking on macOS 27 --- app/build.sh | 5 +- app/mac/build-and-unify | 11 +++- app/mac/mozilla-153.patch | 116 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 app/mac/mozilla-153.patch diff --git a/app/build.sh b/app/build.sh index 576a2721d6..9b024f76a4 100755 --- a/app/build.sh +++ b/app/build.sh @@ -709,9 +709,10 @@ if [ $BUILD_MAC == 1 ]; then xz -d --stdout "$CALLDIR/mac/zotero.xz" > "$CONTENTSDIR/MacOS/zotero" chmod 755 "$CONTENTSDIR/MacOS/zotero" - # TEMP: Custom version of XUL with some backported Mozilla bug fixes + # Custom versions of Firefox components (XUL and libmozglue.dylib) + # with fixes and workarounds missing in the base ESR build if [ -n "$custom_components_hash_mac" ]; then - cp "$MAC_RUNTIME_PATH/../MacOS/XUL" "$CONTENTSDIR/MacOS/" + cp -R "$MAC_RUNTIME_PATH/../MacOS/"* "$CONTENTSDIR/MacOS/" fi # Use our own updater, because Mozilla's requires updates signed by Mozilla diff --git a/app/mac/build-and-unify b/app/mac/build-and-unify index b3c8581557..6974d4846a 100755 --- a/app/mac/build-and-unify +++ b/app/mac/build-and-unify @@ -56,8 +56,15 @@ cp x64/$fx_app_name/Contents/MacOS/firefox zotero xz zotero mv zotero.xz "$APP_ROOT_DIR/mac/zotero.xz" -# Save a copy of XUL -#cp x64/$fx_app_name/Contents/MacOS/XUL "$APP_ROOT_DIR/mac/XUL" +# Package custom components for fetch_xulrunner. +# Upload MacOS-.tar.xz to ${custom_components_url}mac/.tar.xz and set +# custom_components_hash_mac in config.sh. +mkdir MacOS +cp x64/$fx_app_name/Contents/MacOS/{XUL,libmozglue.dylib} MacOS/ +tar cfJ MacOS.tar.xz MacOS +components_hash=$(shasum -a 256 MacOS.tar.xz | cut -d ' ' -f 1) +mv MacOS.tar.xz "$APP_ROOT_DIR/mac/MacOS-$components_hash.tar.xz" +echo "Custom components hash: $components_hash" # Save a copy of ChannelPrefs.framework # diff --git a/app/mac/mozilla-153.patch b/app/mac/mozilla-153.patch new file mode 100644 index 0000000000..272af67652 --- /dev/null +++ b/app/mac/mozilla-153.patch @@ -0,0 +1,116 @@ +diff --git a/mozglue/build/AudioQueueSanitizer.cpp b/mozglue/build/AudioQueueSanitizer.cpp +new file mode 100644 +index 000000000000..9efa3255c472 +--- /dev/null ++++ b/mozglue/build/AudioQueueSanitizer.cpp +@@ -0,0 +1,91 @@ ++/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ ++/* vim: set ts=8 sts=2 et sw=2 tw=80: */ ++/* This Source Code Form is subject to the terms of the Mozilla Public ++ * License, v. 2.0. If a copy of the MPL was not distributed with this ++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ ++ ++// Work around a use-after-free in the macOS 27 system speech synthesizer. ++// ++// When an utterance finishes, the AudioQueue player in the private ++// TextToSpeech framework (used by both NSSpeechSynthesizer and ++// AVSpeechSynthesizer) enqueues a short de-click ramp that starts from a ++// sample read out of a buffer it has already freed. With the system ++// allocator, which zeroes freed memory, that sample is 0 and the ramp is ++// silent. mozjemalloc poisons freed memory with 0xE5 instead, which reads ++// as -1.36e23 in float32, so every utterance ends with a ~10 ms full-scale ++// burst. ++// ++// We can't fix the framework, so we interpose AudioQueueEnqueueBuffer and ++// zero any float samples that are non-finite or far outside the nominal ++// [-1, 1] range before they reach CoreAudio. Real audio never gets close ++// to kMaxSampleMagnitude, so this only ever affects garbage. ++// ++// dyld only honors __interpose tuples in images loaded at launch, which is ++// why this needs to be in mozglue rather than in XUL (loaded with dlopen) ++// alongside OSXSpeechSynthesizerService. ++ ++#include ++#include ++ ++namespace { ++ ++constexpr float kMaxSampleMagnitude = 16.0f; ++ ++void SanitizeBuffer(AudioQueueRef aQueue, AudioQueueBufferRef aBuffer) { ++ AudioStreamBasicDescription format; ++ UInt32 size = sizeof(format); ++ if (AudioQueueGetProperty(aQueue, kAudioQueueProperty_StreamDescription, ++ &format, &size) != noErr) { ++ return; ++ } ++ if (format.mFormatID != kAudioFormatLinearPCM || ++ !(format.mFormatFlags & kAudioFormatFlagIsFloat) || ++ format.mBitsPerChannel != 32) { ++ return; ++ } ++ ++ float* samples = static_cast(aBuffer->mAudioData); ++ UInt32 count = aBuffer->mAudioDataByteSize / sizeof(float); ++ for (UInt32 i = 0; i < count; i++) { ++ // Written so that NaN also fails the comparison. ++ if (!(fabsf(samples[i]) <= kMaxSampleMagnitude)) { ++ samples[i] = 0.0f; ++ } ++ } ++} ++ ++OSStatus SanitizingEnqueueBuffer( ++ AudioQueueRef aQueue, AudioQueueBufferRef aBuffer, UInt32 aNumPackets, ++ const AudioStreamPacketDescription* aPacketDescs) { ++ SanitizeBuffer(aQueue, aBuffer); ++ return AudioQueueEnqueueBuffer(aQueue, aBuffer, aNumPackets, aPacketDescs); ++} ++ ++OSStatus SanitizingEnqueueBufferWithParameters( ++ AudioQueueRef aQueue, AudioQueueBufferRef aBuffer, UInt32 aNumPackets, ++ const AudioStreamPacketDescription* aPacketDescs, UInt32 aTrimFrames, ++ UInt32 aTrimFramesAtEnd, UInt32 aNumParamValues, ++ const AudioQueueParameterEvent* aParamValues, ++ const AudioTimeStamp* aStartTime, AudioTimeStamp* aActualStartTime) { ++ SanitizeBuffer(aQueue, aBuffer); ++ return AudioQueueEnqueueBufferWithParameters( ++ aQueue, aBuffer, aNumPackets, aPacketDescs, aTrimFrames, ++ aTrimFramesAtEnd, aNumParamValues, aParamValues, aStartTime, ++ aActualStartTime); ++} ++ ++struct Interpose { ++ const void* mReplacement; ++ const void* mReplacee; ++}; ++ ++__attribute__((used, section("__DATA,__interpose"))) const Interpose ++ kInterposes[] = { ++ {reinterpret_cast(&SanitizingEnqueueBuffer), ++ reinterpret_cast(&AudioQueueEnqueueBuffer)}, ++ {reinterpret_cast(&SanitizingEnqueueBufferWithParameters), ++ reinterpret_cast( ++ &AudioQueueEnqueueBufferWithParameters)}, ++}; ++ ++} // namespace +diff --git a/mozglue/build/moz.build b/mozglue/build/moz.build +index f8f01e999e54..111fe5af1b0d 100644 +--- a/mozglue/build/moz.build ++++ b/mozglue/build/moz.build +@@ -41,6 +41,14 @@ if CONFIG["OS_TARGET"] == "WINNT": + "winmm.dll", + ] + ++if CONFIG["OS_TARGET"] == "Darwin" and FORCE_SHARED_LIB: ++ SOURCES += [ ++ "AudioQueueSanitizer.cpp", ++ ] ++ OS_LIBS += [ ++ "-framework AudioToolbox", ++ ] ++ + if CONFIG["MOZ_WIDGET_TOOLKIT"]: + if CONFIG["MOZ_MEMORY"] and FORCE_SHARED_LIB: + pass From 82022caa655e888586c29c286cc7289b0661e038 Mon Sep 17 00:00:00 2001 From: Abe Jellinek <1770299+AbeJellinek@users.noreply.github.com> Date: Wed, 23 Sep 2026 13:47:24 -0400 Subject: [PATCH 2/3] Upload libmozglue by itself --- app/build.sh | 6 ---- app/mac/build-and-unify | 18 +++++------ app/scripts/fetch_xulrunner | 63 ++++++++++++++++++++++++------------- 3 files changed, 50 insertions(+), 37 deletions(-) diff --git a/app/build.sh b/app/build.sh index 9b024f76a4..8dcdefd9f3 100755 --- a/app/build.sh +++ b/app/build.sh @@ -709,12 +709,6 @@ if [ $BUILD_MAC == 1 ]; then xz -d --stdout "$CALLDIR/mac/zotero.xz" > "$CONTENTSDIR/MacOS/zotero" chmod 755 "$CONTENTSDIR/MacOS/zotero" - # Custom versions of Firefox components (XUL and libmozglue.dylib) - # with fixes and workarounds missing in the base ESR build - if [ -n "$custom_components_hash_mac" ]; then - cp -R "$MAC_RUNTIME_PATH/../MacOS/"* "$CONTENTSDIR/MacOS/" - fi - # Use our own updater, because Mozilla's requires updates signed by Mozilla cd "$CONTENTSDIR/MacOS" check_lfs_file "$CALLDIR/mac/updater.tar.xz" diff --git a/app/mac/build-and-unify b/app/mac/build-and-unify index 6974d4846a..19579864fd 100755 --- a/app/mac/build-and-unify +++ b/app/mac/build-and-unify @@ -56,15 +56,15 @@ cp x64/$fx_app_name/Contents/MacOS/firefox zotero xz zotero mv zotero.xz "$APP_ROOT_DIR/mac/zotero.xz" -# Package custom components for fetch_xulrunner. -# Upload MacOS-.tar.xz to ${custom_components_url}mac/.tar.xz and set -# custom_components_hash_mac in config.sh. -mkdir MacOS -cp x64/$fx_app_name/Contents/MacOS/{XUL,libmozglue.dylib} MacOS/ -tar cfJ MacOS.tar.xz MacOS -components_hash=$(shasum -a 256 MacOS.tar.xz | cut -d ' ' -f 1) -mv MacOS.tar.xz "$APP_ROOT_DIR/mac/MacOS-$components_hash.tar.xz" -echo "Custom components hash: $components_hash" +# Package custom libmozglue.dylib for fetch_xulrunner. +# Upload the file to ${custom_components_url}mac/ and set custom_components_hash_mac in +# config.sh. +xz --stdout x64/$fx_app_name/Contents/MacOS/libmozglue.dylib > libmozglue.dylib.xz +components_hash=$(shasum -a 256 libmozglue.dylib.xz | cut -d ' ' -f 1) +mozglue_file="${GECKO_VERSION_MAC//esr}-libmozglue-$components_hash.dylib.xz" +mv libmozglue.dylib.xz "$APP_ROOT_DIR/mac/$mozglue_file" +echo "Custom libmozglue.dylib: $APP_ROOT_DIR/mac/$mozglue_file" +echo "custom_components_hash_mac=\"$components_hash\"" # Save a copy of ChannelPrefs.framework # diff --git a/app/scripts/fetch_xulrunner b/app/scripts/fetch_xulrunner index 60cd2f90ec..f68f30d425 100755 --- a/app/scripts/fetch_xulrunner +++ b/app/scripts/fetch_xulrunner @@ -612,31 +612,50 @@ if [ $BUILD_MAC == 1 ]; then hdiutil detach -quiet /Volumes/Firefox fi - # Download custom components (e.g., XUL), as an xz-compressed tarball of the MacOS folder with - # custom files in it + # Download and extract custom libmozglue.dylib echo - rm -rf MacOS - if [ -n "$custom_components_hash_mac" ]; then - if [ -e "Firefox $GECKO_VERSION MacOS.zip" ]; then - echo "Using Firefox $GECKO_VERSION MacOS.zip" - unzip "Firefox $GECKO_VERSION MacOS.zip" + hash="$custom_components_hash_mac" + if [ -n "$hash" ]; then + xzfile="Firefox $GECKO_VERSION libmozglue.dylib.xz" + # Filename doesn't include "esr" + url="${custom_components_url}mac/${GECKO_VERSION//esr}-libmozglue-$hash.dylib.xz" + + if [[ "$(uname)" = "Darwin" ]]; then + shasum="shasum -a 256" else - echo "Downloading custom Firefox components" - echo - curl -o MacOS.tar.xz "${custom_components_url}mac/$custom_components_hash_mac.tar.xz" - - # Verify hash - if [[ "`uname`" = "Darwin" ]]; then - shasum="shasum -a 256" - else - shasum=sha256sum - fi - echo - echo "$custom_components_hash_mac MacOS.tar.xz" | $shasum -c - - echo - - tar xvf MacOS.tar.xz + shasum=sha256sum fi + + download_xz() { + echo "Downloading custom libmozglue.dylib" + echo + echo $url + curl -fo "$xzfile" "$url" + } + + verify_hash() { + echo "$hash $xzfile" | $shasum -c - + } + + # Download if file doesn't exist + if [ ! -e "$xzfile" ]; then + download_xz + fi + + echo + if ! verify_hash; then + echo "SHA hash mismatch. Redownloading libmozglue.dylib…" + rm -f "$xzfile" + download_xz + echo + if ! verify_hash; then + echo "Hash still doesn't match. Aborting." + exit 1 + fi + fi + echo + + xz -d --stdout "$xzfile" > Firefox.app/Contents/MacOS/libmozglue.dylib echo fi From bc2a74c233dd63b800697cefe9cf4a118c11d01c Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Wed, 23 Sep 2026 14:41:14 -0400 Subject: [PATCH 3/3] Restore separate macOS component staging and custom XUL support We still might want a custom XUL at some point in the future. --- app/build.sh | 5 +++++ app/mac/build-and-unify | 21 ++++++++++++------- app/scripts/fetch_xulrunner | 42 +++++++++++++++++-------------------- 3 files changed, 37 insertions(+), 31 deletions(-) diff --git a/app/build.sh b/app/build.sh index 8dcdefd9f3..8b9dde7609 100755 --- a/app/build.sh +++ b/app/build.sh @@ -709,6 +709,11 @@ if [ $BUILD_MAC == 1 ]; then xz -d --stdout "$CALLDIR/mac/zotero.xz" > "$CONTENTSDIR/MacOS/zotero" chmod 755 "$CONTENTSDIR/MacOS/zotero" + # Overlay custom Firefox components (e.g., XUL or libmozglue.dylib) + if [ -n "$custom_components_hash_mac" ]; then + cp -R "$MAC_RUNTIME_PATH/../MacOS/." "$CONTENTSDIR/MacOS/" + fi + # Use our own updater, because Mozilla's requires updates signed by Mozilla cd "$CONTENTSDIR/MacOS" check_lfs_file "$CALLDIR/mac/updater.tar.xz" diff --git a/app/mac/build-and-unify b/app/mac/build-and-unify index 19579864fd..6cff315439 100755 --- a/app/mac/build-and-unify +++ b/app/mac/build-and-unify @@ -56,14 +56,19 @@ cp x64/$fx_app_name/Contents/MacOS/firefox zotero xz zotero mv zotero.xz "$APP_ROOT_DIR/mac/zotero.xz" -# Package custom libmozglue.dylib for fetch_xulrunner. -# Upload the file to ${custom_components_url}mac/ and set custom_components_hash_mac in -# config.sh. -xz --stdout x64/$fx_app_name/Contents/MacOS/libmozglue.dylib > libmozglue.dylib.xz -components_hash=$(shasum -a 256 libmozglue.dylib.xz | cut -d ' ' -f 1) -mozglue_file="${GECKO_VERSION_MAC//esr}-libmozglue-$components_hash.dylib.xz" -mv libmozglue.dylib.xz "$APP_ROOT_DIR/mac/$mozglue_file" -echo "Custom libmozglue.dylib: $APP_ROOT_DIR/mac/$mozglue_file" +# Package custom Firefox components for fetch_xulrunner. Add XUL to this list if needed. +# Upload the archive to ${custom_components_url}mac/ and set custom_components_hash_mac +# in config.sh to the hash printed below. +custom_components=(libmozglue.dylib) +mkdir MacOS +for component in "${custom_components[@]}"; do + cp "x64/$fx_app_name/Contents/MacOS/$component" MacOS/ +done +tar cfJ MacOS.tar.xz MacOS +components_hash=$(shasum -a 256 MacOS.tar.xz | cut -d ' ' -f 1) +components_file="$APP_ROOT_DIR/mac/$components_hash.tar.xz" +mv MacOS.tar.xz "$components_file" +echo "Custom Firefox components: $components_file" echo "custom_components_hash_mac=\"$components_hash\"" # Save a copy of ChannelPrefs.framework diff --git a/app/scripts/fetch_xulrunner b/app/scripts/fetch_xulrunner index f68f30d425..f49db0c0c4 100755 --- a/app/scripts/fetch_xulrunner +++ b/app/scripts/fetch_xulrunner @@ -612,50 +612,46 @@ if [ $BUILD_MAC == 1 ]; then hdiutil detach -quiet /Volumes/Firefox fi - # Download and extract custom libmozglue.dylib + # Download custom components (e.g., XUL), as an xz-compressed tarball of the MacOS folder with + # custom files in it echo - hash="$custom_components_hash_mac" - if [ -n "$hash" ]; then - xzfile="Firefox $GECKO_VERSION libmozglue.dylib.xz" - # Filename doesn't include "esr" - url="${custom_components_url}mac/${GECKO_VERSION//esr}-libmozglue-$hash.dylib.xz" - + rm -rf MacOS + if [ -n "$custom_components_hash_mac" ]; then + tarfile="Firefox $GECKO_VERSION MacOS.tar.xz" if [[ "$(uname)" = "Darwin" ]]; then shasum="shasum -a 256" else shasum=sha256sum fi - download_xz() { - echo "Downloading custom libmozglue.dylib" + download_components() { + echo "Downloading custom Firefox components" echo - echo $url - curl -fo "$xzfile" "$url" + curl -fo "$tarfile" "${custom_components_url}mac/$custom_components_hash_mac.tar.xz" } - verify_hash() { - echo "$hash $xzfile" | $shasum -c - + verify_components_hash() { + echo "$custom_components_hash_mac $tarfile" | $shasum -c - } - # Download if file doesn't exist - if [ ! -e "$xzfile" ]; then - download_xz + if [ ! -e "$tarfile" ]; then + download_components fi + # Verify hash echo - if ! verify_hash; then - echo "SHA hash mismatch. Redownloading libmozglue.dylib…" - rm -f "$xzfile" - download_xz + if ! verify_components_hash; then + echo "SHA hash mismatch. Redownloading custom Firefox components…" + rm -f "$tarfile" + download_components echo - if ! verify_hash; then + if ! verify_components_hash; then echo "Hash still doesn't match. Aborting." exit 1 fi fi echo - - xz -d --stdout "$xzfile" > Firefox.app/Contents/MacOS/libmozglue.dylib + tar xvf "$tarfile" echo fi