This commit is contained in:
Abe Jellinek 2026-09-25 13:49:23 +08:00 • committed by GitHub
commit 2baead9270
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 163 additions and 20 deletions

View file

@ -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

View file

@ -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
#

116
app/mac/mozilla-153.patch Normal file
View file

@ -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 <AudioToolbox/AudioToolbox.h>
+#include <math.h>
+
+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<float*>(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<const void*>(&SanitizingEnqueueBuffer),
+ reinterpret_cast<const void*>(&AudioQueueEnqueueBuffer)},
+ {reinterpret_cast<const void*>(&SanitizingEnqueueBufferWithParameters),
+ reinterpret_cast<const void*>(
+ &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

View file

@ -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