diff --git a/app/build.sh b/app/build.sh index 576a2721d6..8b9dde7609 100755 --- a/app/build.sh +++ b/app/build.sh @@ -709,9 +709,9 @@ 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 + # Overlay custom Firefox components (e.g., XUL or libmozglue.dylib) 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..6cff315439 100755 --- a/app/mac/build-and-unify +++ b/app/mac/build-and-unify @@ -56,8 +56,20 @@ 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 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/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 diff --git a/app/scripts/fetch_xulrunner b/app/scripts/fetch_xulrunner index 60cd2f90ec..f49db0c0c4 100755 --- a/app/scripts/fetch_xulrunner +++ b/app/scripts/fetch_xulrunner @@ -617,26 +617,41 @@ if [ $BUILD_MAC == 1 ]; then 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" + tarfile="Firefox $GECKO_VERSION MacOS.tar.xz" + if [[ "$(uname)" = "Darwin" ]]; then + shasum="shasum -a 256" else + shasum=sha256sum + fi + + download_components() { 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 + curl -fo "$tarfile" "${custom_components_url}mac/$custom_components_hash_mac.tar.xz" + } + + verify_components_hash() { + echo "$custom_components_hash_mac $tarfile" | $shasum -c - + } + + if [ ! -e "$tarfile" ]; then + download_components fi + + # Verify hash + echo + if ! verify_components_hash; then + echo "SHA hash mismatch. Redownloading custom Firefox components…" + rm -f "$tarfile" + download_components + echo + if ! verify_components_hash; then + echo "Hash still doesn't match. Aborting." + exit 1 + fi + fi + echo + tar xvf "$tarfile" echo fi