ma1 pushed to branch tor-browser-140.15.0esr-15.0-1 at The Tor Project / Applications / Tor Browser Commits: 56736106 by Pier Angelo Vendrame at 2026-09-01T12:40:48+02:00 Bug 2039206 - Wire WindowDevicePixelRatio to image-set. r=firefox-style-system-reviewers,emilio,layout-reviewers Differential Revision: https://phabricator.services.mozilla.com/D300449 - - - - - 71ecc1ad by Yury Delendik at 2026-09-01T12:40:53+02:00 Bug 2045435 - Prune dying entries from wasm::Realm::instances_ during sweeping. r=jpages wasm::Realm::instances_ is a weak list. Readers rely on the instances()[i]->object() read barrier, but that barrier is a no-op once the owning zone is being incrementally swept, and entries are otherwise only removed at Instance finalization (~Instance -> unregisterInstance). So between marking a zone's instance objects dead and finalizing them, the list could still hand an about-to-be-finalized instance to a reader. Prune such entries at the start of zone sweeping via a new wasm::Realm::traceWeakInstances(), called from beginSweepingSweepGroup alongside the other per-realm weak-collection sweeps. This makes instances_ behave like the engine's other weak collections, so it never exposes an about-to-be-finalized instance to the mutator during sweep slices. Differential Revision: https://phabricator.services.mozilla.com/D314032 - - - - - b5de5efc by Gela at 2026-09-01T12:40:58+02:00 Bug 2053320 - Part 1: Don't tie Nimbus tooling to `HomeActivity` UI a=RyanVM Original Revision: https://phabricator.services.mozilla.com/D319628 Differential Revision: https://phabricator.services.mozilla.com/D320784 - - - - - 0b475830 by Ting-Yu Lin at 2026-09-01T12:41:02+02:00 Bug 2053578 - Use static_cast in nsSplittableFrame::UpdateFirstContinuationAndFirstInFlowCache(). r=layout-reviewers,jfkthame `nsSplittableFrame` is a subclass of `nsIFrame`, it is sufficient to use `static_cast`. Differential Revision: https://phabricator.services.mozilla.com/D315157 - - - - - 2c620a58 by Ting-Yu Lin at 2026-09-01T12:41:07+02:00 Bug 2053578 - Update first-in-flow cache when a continuation is changing from fluid to non-fluid. r=layout-reviewers,jfkthame The original case in bug 2053578 comment 5 can reproduce an ASAN use-after-poison with the patch bug 2053578 comment 6 applied. However, with unpatched code, the best we can do is using a DEBUG-only assertion to catch the error condition that detect a stale first-in-flow cache in next-in-flow. `bidi-inline-continuation-first-in-flow.html` is generated with the help of Claude code, and it can trigger the assertion without other fix in this patch. Differential Revision: https://phabricator.services.mozilla.com/D315158 - - - - - f14f9f44 by Jan Grulich at 2026-09-01T12:41:11+02:00 Bug 2054625 - WebRTC backport: Video capture: validate PipeWire camera frame size to prevent OOB read r=pehrsons Check spa_chunk::size is not larger than mmap'ed buffer, which would lead to an out-of-bounds read when the frame is copied. Validate that chunk offset + size does not exceed maxsize before processing. This is a simple backport of an WebRTC upstream change. Upstream commit: 612430e8266881eaff478b0000cacc90fb71c918 Differential Revision: https://phabricator.services.mozilla.com/D316000 - - - - - 6017bf80 by David Parks at 2026-09-01T12:41:16+02:00 Bug 2058006 - Call Destroy() unconditionally in ~StatusBarEntry. a=RyanVM DONTBUILD Allow Destroy() to tear down mIconLoader, even if Init() does not successfully complete initialization. Original Revision: https://phabricator.services.mozilla.com/D318206 Differential Revision: https://phabricator.services.mozilla.com/D319879 - - - - - 9cf002cf by David Parks at 2026-09-01T12:41:20+02:00 Bug 2058013 - Only resolve non-filesystem picker results as shell libraries a=RyanVM DONTBUILD A genuine shell library is a virtual item with no filesystem path. A .library-ms file is an ordinary file whose XML can name any local directory. filedialog::GetFolderResults was passing whatever IFileDialog::GetResult returned to IShellLibrary::LoadLibraryFromItem and, on success, replacing it with the library's embedded default save folder. Gating the resolution on the item being affirmatively not a filesystem object keeps real libraries selectable and resolving as before, while a .library-ms file "resolves" to itself -- the .library-ms file, not its target. Original Revision: https://phabricator.services.mozilla.com/D318246 Differential Revision: https://phabricator.services.mozilla.com/D319957 - - - - - 1cec2a2f by Iain Ireland at 2026-09-01T12:41:25+02:00 Bug 2058626: Check for mutually exclusive flags when deserializing cloned RegExp r=spidermonkey-reviewers,jonco Differential Revision: https://phabricator.services.mozilla.com/D316904 - - - - - 08795680 by Kui-Feng Lee at 2026-09-01T12:41:30+02:00 Bug 2058661 - Clear the drag source window's GdkDragContext pointer synchronously. r=stransky a=RyanVM DONTBUILD gtk_drag_begin_with_coordinates() returns a borrowed context: GtkDragSourceInfo holds the only reference, and gtk_drag_source_info_destroy() drops it as soon as the drag-end handler returns. nsWindow::mSourceDragContext was cleared only from an async runnable (EndDragSessionMainThread -> EndDragSessionImpl), so between GTK's unref and that runnable the pointer dangled, and nsWindow::OnUnmap() calls gtk_drag_cancel() on it. Content can land an unmap in that window with a window.close() task queued before the drag ended. EndDragSessionImpl keeps its own clear for drags torn down without a drag-end. Original Revision: https://phabricator.services.mozilla.com/D315509 Differential Revision: https://phabricator.services.mozilla.com/D319278 - - - - - 2f953af5 by Kershaw Chang at 2026-09-01T12:41:34+02:00 Bug 2059199 - Reject NUL bytes in chunked trailer and chunk-size lines a=RyanVM DONTBUILD A trailer field line whose first byte is NUL would look empty to the end-of-trailer check, ending the trailer section early. We would then stop reading the body before the server (or a shared proxy) does, and disagree with them about where the response ends. Original Revision: https://phabricator.services.mozilla.com/D317742 Differential Revision: https://phabricator.services.mozilla.com/D319931 - - - - - 553036bc by Sotaro Ikeda at 2026-09-01T12:41:39+02:00 Bug 2061532 a=RyanVM DONTBUILD ANGLE Metal backend usage is also going to disable RenderExternalTextureHost usage. Original Revision: https://phabricator.services.mozilla.com/D318624 Differential Revision: https://phabricator.services.mozilla.com/D320769 - - - - - 24 changed files: - dom/base/ResponsiveImageSelector.cpp - gfx/webrender_bindings/RenderExternalTextureHost.cpp - js/src/gc/GCRuntime.h - js/src/gc/Sweeping.cpp - + js/src/jit-test/tests/structured-clone/bug2058626.js - js/src/vm/StructuredClone.cpp - js/src/wasm/WasmRealm.cpp - js/src/wasm/WasmRealm.h - layout/base/nsPresContext.cpp - layout/generic/nsSplittableFrame.cpp - mobile/android/fenix/app/src/main/AndroidManifest.xml - mobile/android/fenix/app/src/main/java/org/mozilla/fenix/HomeActivity.kt - + mobile/android/fenix/app/src/main/java/org/mozilla/fenix/experiments/QANimbusToolingReceiver.kt - + mobile/android/fenix/app/src/test/java/org/mozilla/fenix/experiments/QANimbusToolingReceiverTest.kt - modules/libpref/init/StaticPrefList.yaml - netwerk/protocol/http/nsHttpChunkedDecoder.cpp - + testing/web-platform/tests/css/css-writing-modes/crashtests/bidi-inline-continuation-first-in-flow.html - third_party/libwebrtc/modules/video_capture/linux/video_capture_pipewire.cc - + third_party/libwebrtc/moz-patch-stack/612430e826.no-op-cherry-pick-msg - toolkit/components/resistfingerprinting/tests/browser/browser_srcset.js - toolkit/components/resistfingerprinting/tests/browser/srcset.html - widget/gtk/nsDragService.cpp - widget/windows/SystemStatusBar.cpp - widget/windows/filedialog/WinFileDialogCommands.cpp Changes: ===================================== dom/base/ResponsiveImageSelector.cpp ===================================== @@ -351,10 +351,6 @@ bool ResponsiveImageSelector::SelectImage(bool aReselect) { if (overrideDPPX > 0) { displayDensity = overrideDPPX; } - if (doc->ShouldResistFingerprinting(RFPTarget::WindowDevicePixelRatio)) { - displayDensity = - nsRFPService::GetDevicePixelRatioAtZoom(pctx->GetFullZoom()); - } // Per spec, "In a UA-specific manner, choose one image source" // - For now, select the lowest density greater than displayDensity, otherwise ===================================== gfx/webrender_bindings/RenderExternalTextureHost.cpp ===================================== @@ -126,7 +126,14 @@ bool RenderExternalTextureHost::IsReadyForDeletion() { auto& textureSource = mTextureSources[0]; if (textureSource) { - return textureSource->Sync(false); + if (textureSource->Sync(/* aBlocking */ true)) { + return true; + } + if (mGL && mGL->MakeCurrent() && !mGL->IsDestroyed()) { + mGL->fFinish(); + return true; + } + return false; } return true; @@ -153,7 +160,11 @@ wr::WrExternalImage RenderExternalTextureHost::Lock(uint8_t aChannelIndex, void RenderExternalTextureHost::PrepareForUse() { mTextureUpdateNeeded = true; } -void RenderExternalTextureHost::Unlock() {} +void RenderExternalTextureHost::Unlock() { + if (mInitialized && mTextureSources[0]) { + mTextureSources[0]->MaybeFenceTexture(); + } +} void RenderExternalTextureHost::UpdateTexture(size_t aIndex) { MOZ_ASSERT(mSurfaces[aIndex]); ===================================== js/src/gc/GCRuntime.h ===================================== @@ -924,6 +924,7 @@ class GCRuntime { void updateAtomsBitmap(); void sweepCCWrappers(); void sweepRealmGlobals(); + void sweepWasmInstances(); void sweepEmbeddingWeakPointers(JS::GCContext* gcx); void sweepMisc(); void sweepCompressionTasks(); ===================================== js/src/gc/Sweeping.cpp ===================================== @@ -1396,6 +1396,13 @@ void GCRuntime::sweepRealmGlobals() { } } +void GCRuntime::sweepWasmInstances() { + for (SweepGroupRealmsIter r(this); !r.done(); r.next()) { + AutoSetThreadIsSweeping threadIsSweeping(r->zone()); + r->wasm.traceWeakInstances(); + } +} + void GCRuntime::sweepMisc() { SweepingTracer trc(rt); for (SweepGroupRealmsIter r(this); !r.done(); r.next()) { @@ -1709,6 +1716,11 @@ IncrementalProgress GCRuntime::beginSweepingSweepGroup(JS::GCContext* gcx, // This must happen before updating embedding weak pointers. sweepRealmGlobals(); + // Prune dying wasm instances from each realm's weak instance list now, at the + // start of sweeping, before the mutator can observe them via the (now no-op) + // instances() read barrier during later incremental slices. + sweepWasmInstances(); + sweepEmbeddingWeakPointers(gcx); { ===================================== js/src/jit-test/tests/structured-clone/bug2058626.js ===================================== @@ -0,0 +1,16 @@ +function forge(pattern, srcFlags, flagsByte) { + var cb = serialize(new RegExp(pattern, srcFlags), undefined, { scope: "DifferentProcess" }); + var u8 = new Uint8Array(cb.arraybuffer); + for (var i = 0; i + 8 <= u8.length; i += 4) { + var tag = u8[i+4] | (u8[i+5] << 8) | (u8[i+6] << 16) | (u8[i+7] << 24); + if ((tag >>> 0) === 0xFFFF0006) { u8[i] = flagsByte; break; } + } + cb.clonebuffer = u8.buffer; + return deserialize(cb, { scope: "DifferentProcess" }); +} +try { + var forged = forge("[\\q{abc|de}]", "v", 0x90); + var bad = new RegExp(forged, "u"); + try { bad.exec("abc"); } catch {} + var good = new RegExp("[\\q{abc|de}]", "u"); +} catch {} ===================================== js/src/vm/StructuredClone.cpp ===================================== @@ -3189,7 +3189,9 @@ bool JSStructuredCloneReader::startRead(MutableHandleValue vp, } case SCTAG_REGEXP_OBJECT: { - if ((data & RegExpFlag::AllFlags) != data) { + // Reject invalid flags. /u and /v are mutually exclusive. + if ((data & RegExpFlag::AllFlags) != data || + ((data & RegExpFlag::Unicode) && (data & RegExpFlag::UnicodeSets))) { JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA, "regexp"); return false; ===================================== js/src/wasm/WasmRealm.cpp ===================================== @@ -18,6 +18,7 @@ #include "wasm/WasmRealm.h" +#include "gc/Marking.h" #include "vm/GlobalObject.h" #include "vm/Realm.h" #include "wasm/WasmDebug.h" @@ -111,6 +112,19 @@ void wasm::Realm::unregisterInstance(Instance& instance) { } } +void wasm::Realm::traceWeakInstances() { + // Registration/unregistration of instances_ is tied to Instance lifetime, so + // an instance whose owning object is about to be finalized is still present + // here until ~Instance runs. Remove such entries now, at the start of zone + // sweeping, because the instances() read barrier that otherwise protects + // readers is a no-op once the zone is being swept. erase order is preserved, + // so the pointer-sorted invariant used by BinarySearchIf holds. + instances_.eraseIf([](Instance* instance) { + return js::gc::IsAboutToBeFinalizedUnbarriered( + instance->objectUnbarriered()); + }); +} + void wasm::Realm::ensureProfilingLabels(bool profilingEnabled) { for (Instance* instance : instances_) { instance->ensureProfilingLabels(profilingEnabled); ===================================== js/src/wasm/WasmRealm.h ===================================== @@ -53,10 +53,17 @@ class Realm { // Return a vector of all live instances in the realm. The lifetime of // these Instances is determined by their owning WasmInstanceObject. // Note that accessing instances()[i]->object() triggers a read barrier - // since instances() is effectively a weak list. + // since instances() is effectively a weak list. This read barrier is only + // effective while the owning zone is being marked; traceWeakInstances() + // prunes dying entries at the start of sweeping so that the list never + // exposes an about-to-be-finalized instance to the mutator. const InstanceVector& instances() const { return instances_; } + // Remove instances whose owning object is about to be finalized. Called at + // the start of zone sweeping, when the instances() read barrier is a no-op. + void traceWeakInstances(); + // Ensure all Instances in this Realm have profiling labels created. void ensureProfilingLabels(bool profilingEnabled); ===================================== layout/base/nsPresContext.cpp ===================================== @@ -978,7 +978,11 @@ void nsPresContext::RecomputeBrowsingContextDependentData() { auto systemZoom = LookAndFeel::SystemZoomSettings(); SetFullZoom(browsingContext->FullZoom() * systemZoom.mFullZoom); SetTextZoom(browsingContext->TextZoom() * systemZoom.mTextZoom); - SetOverrideDPPX(browsingContext->OverrideDPPX()); + if (doc->ShouldResistFingerprinting(RFPTarget::WindowDevicePixelRatio)) { + SetOverrideDPPX(nsRFPService::GetDevicePixelRatioAtZoom(GetFullZoom())); + } else { + SetOverrideDPPX(browsingContext->OverrideDPPX()); + } } auto* top = browsingContext->Top(); ===================================== layout/generic/nsSplittableFrame.cpp ===================================== @@ -214,7 +214,7 @@ void nsSplittableFrame::UpdateFirstContinuationAndFirstInFlowCache() { if (oldCachedFirstContinuation != newFirstContinuation) { // Update the first-continuation cache for us and our next-continuations. for (nsSplittableFrame* f = this; f; - f = reinterpret_cast<nsSplittableFrame*>(f->GetNextContinuation())) { + f = static_cast<nsSplittableFrame*>(f->GetNextContinuation())) { f->mFirstContinuation = newFirstContinuation; } } @@ -227,7 +227,7 @@ void nsSplittableFrame::UpdateFirstContinuationAndFirstInFlowCache() { // behavior when a frame list is destroyed from the front. To avoid that // pathological behavior, we simply purge the cached values. for (nsSplittableFrame* f = this; f; - f = reinterpret_cast<nsSplittableFrame*>(f->GetNextContinuation())) { + f = static_cast<nsSplittableFrame*>(f->GetNextContinuation())) { f->mFirstContinuation = nullptr; } } @@ -239,22 +239,41 @@ void nsSplittableFrame::UpdateFirstContinuationAndFirstInFlowCache() { if (oldCachedFirstInFlow != newFirstInFlow) { // Update the first-in-flow cache for us and our next-in-flows. for (nsSplittableFrame* f = this; f; - f = reinterpret_cast<nsSplittableFrame*>(f->GetNextInFlow())) { + f = static_cast<nsSplittableFrame*>(f->GetNextInFlow())) { f->mFirstInFlow = newFirstInFlow; } } } else { - // We become the new first-in-flow due to our prev-in-flow being removed. - if (oldCachedFirstInFlow) { - // It's tempting to update the first-in-flow cache for our - // next-in-flows here, but that would result in overall O(n^2) - // behavior when a frame list is destroyed from the front. To avoid that - // pathological behavior, we simply purge the cached values. + if (GetPrevContinuation()) { + // We become the new first-in-flow after changing from fluid to non-fluid. + // Update the stale first-in-flow cache for us and all next-in-flows. + // + // Note that this has no counterpart in the above mFirstContinuation cache + // since GetPrevContinuation() does not depend on the + // NS_FRAME_IS_FLUID_CONTINUATION bit. for (nsSplittableFrame* f = this; f; - f = reinterpret_cast<nsSplittableFrame*>(f->GetNextInFlow())) { - f->mFirstInFlow = nullptr; + f = static_cast<nsSplittableFrame*>(f->GetNextInFlow())) { + f->mFirstInFlow = this; + } + } else { + // We become the new first-in-flow due to our prev-in-flow being removed. + if (oldCachedFirstInFlow) { + // It's tempting to update the first-in-flow cache for our + // next-in-flows here, but that would result in overall O(n^2) + // behavior when a frame list is destroyed from the front. To avoid that + // pathological behavior, we simply purge the cached values. + for (nsSplittableFrame* f = this; f; + f = static_cast<nsSplittableFrame*>(f->GetNextInFlow())) { + f->mFirstInFlow = nullptr; + } } } + + DebugOnly<nsSplittableFrame*> nextInFlow = + static_cast<nsSplittableFrame*>(GetNextInFlow()); + MOZ_ASSERT(!nextInFlow || !nextInFlow->mFirstInFlow || + nextInFlow->mFirstInFlow == this, + "Our next-in-flow caches a stale first-in-flow!"); } } ===================================== mobile/android/fenix/app/src/main/AndroidManifest.xml ===================================== @@ -770,6 +770,16 @@ android:value="androidx.startup" tools:node="remove" /> </provider> + + <receiver + android:name="org.mozilla.fenix.experiments.QANimbusToolingReceiver" + android:exported="true" + android:enabled="true" + android:permission="android.permission.DUMP"> + <intent-filter> + <action android:name="org.mozilla.fenix.NIMBUS_TOOLING" /> + </intent-filter> + </receiver> </application> </manifest> ===================================== mobile/android/fenix/app/src/main/java/org/mozilla/fenix/HomeActivity.kt ===================================== @@ -89,7 +89,6 @@ import mozilla.components.support.utils.ext.getParcelableExtraCompat import mozilla.components.support.utils.toSafeIntent import mozilla.components.support.webextensions.WebExtensionPopupObserver import mozilla.telemetry.glean.private.NoExtras -import org.mozilla.experiments.nimbus.initializeTooling import org.mozilla.fenix.GleanMetrics.AppIcon import org.mozilla.fenix.GleanMetrics.Events import org.mozilla.fenix.GleanMetrics.Metrics @@ -330,8 +329,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity, TorAn // DO NOT MOVE ANYTHING ABOVE THIS getProfilerTime CALL. val startTimeProfiler = components.core.engine.profiler?.getProfilerTime() - // Setup nimbus-cli tooling. This is a NOOP when launching normally. - components.nimbus.sdk.initializeTooling(applicationContext, intent) components.strictMode.attachListenerToDisablePenaltyDeath(supportFragmentManager) MarkersFragmentLifecycleCallbacks.register(supportFragmentManager, components.core.engine) ===================================== mobile/android/fenix/app/src/main/java/org/mozilla/fenix/experiments/QANimbusToolingReceiver.kt ===================================== @@ -0,0 +1,57 @@ +/* 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/. */ + +package org.mozilla.fenix.experiments + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import mozilla.components.support.base.log.logger.Logger +import org.mozilla.experiments.nimbus.initializeTooling +import org.mozilla.fenix.ext.components + +private val logger = Logger("QANimbusToolingReceiver") + +/** + * Receiver triggered on demand via `nimbus-cli` to manually enroll into Nimbus experiments. + * + * ``` + * adb shell am broadcast -a org.mozilla.fenix.NIMBUS_TOOLING \ + * -p org.mozilla.fenix + * ``` + * + * `-p org.mozilla.fenix` is the package name, so adjust that value for release/beta/nightly/debug. + * + * @param dispatcher the [CoroutineDispatcher] the tooling commands are applied on. + */ +class QANimbusToolingReceiver(private val dispatcher: CoroutineDispatcher = Dispatchers.IO) : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (intent.action != ACTION_NIMBUS_TOOLING) return + + logger.info("Enqueueing QANimbusToolingReceiver via debug trigger") + + val applicationContext = context.applicationContext + + val pendingResult: PendingResult? = goAsync() + CoroutineScope(dispatcher).launch { + try { + applicationContext.components.nimbus.sdk.initializeTooling( + applicationContext, + intent, + ) + } finally { + logger.info("Nimbus tooling command processed") + pendingResult?.finish() + } + } + } + + companion object { + const val ACTION_NIMBUS_TOOLING = "org.mozilla.fenix.NIMBUS_TOOLING" + } +} ===================================== mobile/android/fenix/app/src/test/java/org/mozilla/fenix/experiments/QANimbusToolingReceiverTest.kt ===================================== @@ -0,0 +1,117 @@ +/* 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/. */ + +package org.mozilla.fenix.experiments + +import android.content.Context +import android.content.Intent +import io.mockk.every +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import mozilla.components.support.test.robolectric.testContext +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.experiments.QANimbusToolingReceiver.Companion.ACTION_NIMBUS_TOOLING +import org.mozilla.fenix.ext.components +import org.mozilla.fenix.nimbus.TestNimbusApi +import org.robolectric.RobolectricTestRunner + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(RobolectricTestRunner::class) +class QANimbusToolingReceiverTest { + + private val nimbusApi = FakeNimbusApi(testContext) + private val receiver = QANimbusToolingReceiver(UnconfinedTestDispatcher()) + + @Before + fun setUp() { + every { testContext.components.nimbus.sdk } returns nimbusApi + } + + @Test + fun `GIVEN a tooling command WHEN the tooling action is received THEN the command is applied`() { + receiver.onReceive(testContext, toolingIntent(ACTION_NIMBUS_TOOLING)) + + assertEquals(EXPERIMENTS, nimbusApi.appliedExperiments) + assertEquals(false, nimbusApi.fetchEnabled) + assertTrue(nimbusApi.databaseReset) + assertTrue(nimbusApi.stateDumped) + } + + @Test + fun `GIVEN a tooling command WHEN another action is received THEN the command is ignored`() { + receiver.onReceive(testContext, toolingIntent("org.mozilla.fenix.ACTION_PRINT")) + + assertNull(nimbusApi.appliedExperiments) + assertNull(nimbusApi.fetchEnabled) + assertFalse(nimbusApi.databaseReset) + assertFalse(nimbusApi.stateDumped) + } + + @Test + fun `GIVEN no tooling command WHEN the tooling action is received THEN nothing is applied`() { + receiver.onReceive(testContext, Intent(ACTION_NIMBUS_TOOLING)) + + assertNull(nimbusApi.appliedExperiments) + assertNull(nimbusApi.fetchEnabled) + assertFalse(nimbusApi.databaseReset) + assertFalse(nimbusApi.stateDumped) + } + + @Test + fun `GIVEN a tooling command without the version extra WHEN the tooling action is received THEN nothing is applied`() { + val intent = toolingIntent(ACTION_NIMBUS_TOOLING).apply { removeExtra("version") } + + receiver.onReceive(testContext, intent) + + assertNull(nimbusApi.appliedExperiments) + assertFalse(nimbusApi.stateDumped) + } + + private fun toolingIntent(action: String) = + Intent(action).apply { + putExtra("nimbus-cli", null as String?) + putExtra("version", 1) + putExtra("experiments", EXPERIMENTS) + putExtra("reset-db", true) + putExtra("log-state", true) + } + + private class FakeNimbusApi(context: Context) : TestNimbusApi(context) { + var appliedExperiments: String? = null + var fetchEnabled: Boolean? = null + var databaseReset = false + var stateDumped = false + + override fun applyLocalExperiments(experimentsJson: String): Job { + appliedExperiments = experimentsJson + return completedJob() + } + + override fun resetEnrollmentsDatabase(): Job { + databaseReset = true + return completedJob() + } + + override fun setFetchEnabled(enabled: Boolean) { + fetchEnabled = enabled + } + + override fun dumpStateToLog() { + stateDumped = true + } + + private fun completedJob() = Job().apply { complete() } + } + + companion object { + private const val EXPERIMENTS = """{"data":[]}""" + } +} ===================================== modules/libpref/init/StaticPrefList.yaml ===================================== @@ -7834,7 +7834,7 @@ #ifdef XP_MACOSX - name: gfx.webrender.enable-client-storage type: bool - value: true + value: false mirror: once #endif ===================================== netwerk/protocol/http/nsHttpChunkedDecoder.cpp ===================================== @@ -106,8 +106,15 @@ nsresult nsHttpChunkedDecoder::ParseChunkRemaining(char* buf, uint32_t count, count = mLineBuf.Length(); } + // NUL is illegal in a chunk-size or trailer field line (RFC 9110 section + // 5.5). + if (memchr(buf, '\0', count)) { + LOG(("chunked line contains embedded NUL; rejecting\n")); + return NS_ERROR_UNEXPECTED; + } + if (mWaitEOF) { - if (*buf) { + if (count) { LOG(("got trailer: %s\n", buf)); // allocate a header array for the trailers on demand if (!mTrailers) { @@ -130,7 +137,7 @@ nsresult nsHttpChunkedDecoder::ParseChunkRemaining(char* buf, uint32_t count, mReachedEOF = true; LOG(("reached end of chunked-body\n")); } - } else if (*buf) { + } else if (count) { char* endptr; unsigned long parsedval; // could be 64 bit, could be 32 ===================================== testing/web-platform/tests/css/css-writing-modes/crashtests/bidi-inline-continuation-first-in-flow.html ===================================== @@ -0,0 +1,194 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com"> +<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=2053578"> + +<!-- The operations in <script> are generated from one of the runs from + the original testcase (Bug 2053578 Comment 5) that triggers + the assertion. --> + +<body></body> + +<script> +var n1 = document.createElement("div"); +n1.style.width = "50px"; +var n2 = document.createElement("bdo"); +n2.setAttribute("dir", "rtl"); +var n3 = document.createTextNode("كلمة"); +n2.appendChild(n3); +n1.appendChild(n2); +var n4 = document.createElement("em"); +var n5 = document.createElement("span"); +var n6 = document.createElement("bdo"); +var n7 = document.createTextNode("word m mix"); +n6.appendChild(n7); +n5.appendChild(n6); +var n8 = document.createTextNode("מלל"); +n5.appendChild(n8); +var n9 = document.createElement("b"); +n9.setAttribute("dir", "ltr"); +n9.style.unicodeBidi = "bidi-override"; +var n10 = document.createTextNode("alpha"); +n9.appendChild(n10); +var n11 = document.createTextNode("نص مرحبا كلمة"); +n9.appendChild(n11); +var n12 = document.createTextNode("مرحبا نص نص"); +n9.appendChild(n12); +n5.appendChild(n9); +n4.appendChild(n5); +var n13 = document.createElement("span"); +n13.style.unicodeBidi = "bidi-override"; +var n14 = document.createElement("bdi"); +var n15 = document.createTextNode("اختبار"); +n14.appendChild(n15); +n13.appendChild(n14); +var n16 = document.createElement("em"); +var n17 = document.createTextNode("בדיקה מלל m نص"); +n16.appendChild(n17); +var n18 = document.createTextNode("كلمة"); +n16.appendChild(n18); +var n19 = document.createTextNode("نص مرحبا كلمة"); +n16.appendChild(n19); +n13.appendChild(n16); +var n20 = document.createElement("span"); +var n21 = document.createTextNode("שלום מלל בדיקה שלום"); +n20.appendChild(n21); +n13.appendChild(n20); +n4.appendChild(n13); +n1.appendChild(n4); +document.body.appendChild(n1); +var n22 = document.createElement("bdo"); +n22.style.unicodeBidi = "plaintext"; +var n23 = document.createElement("span"); +var n24 = document.createTextNode("עברית שלום مرحبا alpha"); +n23.appendChild(n24); +n22.appendChild(n23); +n14.style.direction = "ltr"; +var n25 = document.createTextNode("كلمة שלום"); +n2.insertBefore(n25, n3); +n21.remove(); +n9.setAttribute("dir", "ltr"); +var n26 = document.createTextNode("בדיקה mm"); +n4.appendChild(n26); +n12.data = "שלום עברית שלום עברית"; +n7.remove(); +n2.style.direction = "ltr"; +n1.style.width = "102px"; +var n27 = document.createTextNode("עברית mm m"); +n22.appendChild(n27); +n22.setAttribute("dir", "rtl"); +n4.style.direction = ""; +n14.style.direction = "rtl"; +n22.insertBefore(n9, n23); +var n28 = document.createElement("br"); +n9.insertBefore(n28, n12); +n14.style.unicodeBidi = "embed"; +n5.insertBefore(n22, n6); +n9.style.direction = ""; +n9.style.unicodeBidi = "bidi-override"; +n22.remove(); +var n29 = document.createTextNode("עברית שלום"); +n5.insertBefore(n29, n6); +n18.data = "mix"; +n19.data = "alpha m word"; +n20.style.direction = "ltr"; +var n30 = document.createTextNode("mm עברית עברית نص"); +n6.appendChild(n30); +n6.style.unicodeBidi = "isolate"; +n20.style.unicodeBidi = "embed"; +n16.setAttribute("dir", "rtl"); +n2.insertBefore(n20, n25); +var n31 = document.createTextNode("שלום مرحبا mix mm"); +n2.insertBefore(n31, n20); +var n32 = document.createTextNode("m בדיקה word mix"); +n20.appendChild(n32); +n16.removeAttribute("dir"); +n16.setAttribute("dir", "auto"); +n5.remove(); +n17.remove(); +n31.data = "בדיקה mm"; +n14.style.unicodeBidi = "isolate"; +n4.remove(); +n20.style.direction = ""; +n20.style.direction = "rtl"; +n3.data = "בדיקה שלום word"; +n25.data = "mm"; +n25.data = "m mix word"; +n1.style.direction = "rtl"; +n31.data = "كلمة اختبار"; +n20.remove(); +n3.data = "שלום نص"; +n31.remove(); +n2.setAttribute("dir", "ltr"); +n2.removeAttribute("dir"); +n1.insertBefore(n4, n2); +n19.data = "מלל word"; +n15.remove(); +n13.setAttribute("dir", "ltr"); +n25.remove(); +n2.setAttribute("dir", "ltr"); +n14.appendChild(n2); +n3.data = "مرحبا اختبار مرحبا"; +n18.data = "שלום"; +n16.insertBefore(n20, n18); +var n33 = document.createTextNode("كلمة نص mm"); +n2.appendChild(n33); +n19.data = "اختبار مرحبا كلمة كلمة"; +n18.data = "mm mix mix"; +var n34 = document.createElement("br"); +n4.insertBefore(n34, n13); +n13.style.unicodeBidi = "isolate-override"; +n13.remove(); +n34.remove(); +n26.data = "كلمة كلمة مرحبا"; +n4.setAttribute("dir", "rtl"); +n26.data = "mm word m mm"; +n26.data = "اختبار نص"; +n4.setAttribute("dir", "auto"); +var n35 = document.createTextNode("مرحبا מלל كلمة שלום"); +n4.insertBefore(n35, n26); +n4.style.unicodeBidi = "embed"; +n4.style.direction = ""; +var n36 = document.createTextNode("كلمة mix בדיקה نص"); +n4.insertBefore(n36, n35); +n36.remove(); +n4.removeAttribute("dir"); +n4.style.unicodeBidi = ""; +n1.style.width = "87px"; +n26.data = "שלום מלל מלל עברית"; +var n37 = document.createElement("br"); +n4.insertBefore(n37, n35); +n35.data = "مرحبا نص اختبار"; +n4.setAttribute("dir", "rtl"); +n33.data = "word alpha mm"; +var n38 = document.createTextNode("שלום word"); +n16.insertBefore(n38, n20); +n2.setAttribute("dir", "auto"); +n2.remove(); +n37.remove(); +var n39 = document.createTextNode("עברית اختبار בדיקה"); +n13.appendChild(n39); +var n40 = document.createTextNode("בדיקה m نص نص"); +n13.insertBefore(n40, n14); +var n41 = document.createElement("br"); +n20.appendChild(n41); +n35.data = "alpha"; +n16.remove(); +n39.data = "نص mm בדיקה اختبار"; +n40.data = "mix mix word m"; +n35.data = "שלום בדיקה"; +n35.data = "m mix mm m"; +n40.data = "مرحبا اختبار"; +n13.style.unicodeBidi = "embed"; +n4.insertBefore(n16, n26); +var n42 = document.createTextNode("نص"); +n13.insertBefore(n42, n40); +n41.remove(); +n20.insertBefore(n2, n32); +document.body.offsetHeight; +n39.data = "مرحبا alpha עברית word"; +n18.data = "שלום"; +n2.setAttribute("dir", "auto"); +document.body.offsetHeight; +n20.style.direction = "ltr"; +</script> ===================================== third_party/libwebrtc/modules/video_capture/linux/video_capture_pipewire.cc ===================================== @@ -443,6 +443,14 @@ void VideoCaptureModulePipeWire::ProcessBuffers() { continue; } + if (static_cast<uint64_t>(spaBuffer->datas[0].chunk->offset) + + spaBuffer->datas[0].chunk->size > + spaBuffer->datas[0].maxsize) { + RTC_LOG(LS_ERROR) << "Dropping frame with invalid size"; + pw_stream_queue_buffer(stream_, buffer); + continue; + } + if (spaBuffer->datas[0].type == SPA_DATA_DmaBuf || spaBuffer->datas[0].type == SPA_DATA_MemFd) { ScopedBuf frame; ===================================== third_party/libwebrtc/moz-patch-stack/612430e826.no-op-cherry-pick-msg ===================================== @@ -0,0 +1 @@ +We cherry-picked this in bug 2054625. \ No newline at end of file ===================================== toolkit/components/resistfingerprinting/tests/browser/browser_srcset.js ===================================== @@ -4,8 +4,20 @@ "use strict"; +async function precacheAssets(tab, zoom) { + await SpecialPowers.spawn(tab.linkedBrowser, [zoom], async zoom => { + const { Layout } = ChromeUtils.importESModule( + "chrome://mochitests/content/browser/accessible/tests/browser/Layout.sys.mjs" + ); + Layout.zoomDocument(content.document, zoom); + const { promise, resolve } = Promise.withResolvers(); + content.requestAnimationFrame(resolve); + await promise; + }); +} + async function runForZoomLevel(tab, zoom) { - const [dpr, scale] = await SpecialPowers.spawn( + const [dpr, scale, cssScale] = await SpecialPowers.spawn( tab.linkedBrowser, [zoom], async zoom => { @@ -28,9 +40,16 @@ async function runForZoomLevel(tab, zoom) { // Workaround: content.devicePixelRatio has the unspoofed value. const dpr = content.wrappedJSObject.devicePixelRatio; const current = img.currentSrc; - const scale = - current.substring(current.length - 6, current.length - 4) / 10; - return [dpr, scale]; + const scale = parseFloat(current.match(/.*\/([0-9\.]+)\.png$/)[1]); + + const height = content.document + .getElementById("imgset") + .getBoundingClientRect().height; + // The images have been crafted so that it is possible to reverse their + // measured size (altered by the zoom) and find the scale. + const cssScale = Math.round((height * dpr - 50) / 10) / 10 + 0.3; + + return [dpr, scale, cssScale]; } ); @@ -39,6 +58,11 @@ async function runForZoomLevel(tab, zoom) { true, `Image scale (${scale}) is within DPR (${dpr})` ); + is( + Math.abs(dpr - cssScale) < 0.1, + true, + `CSS scale (${cssScale}) is within DPR (${dpr})` + ); } add_task(async () => { @@ -53,6 +77,9 @@ add_task(async () => { ) + "srcset.html"; const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, testPage); + for (let zoom = 0.3; zoom < 2.09; zoom += 0.1) { + await precacheAssets(tab, zoom); + } for (let zoom = 0.3; zoom < 2.09; zoom += 0.1) { await runForZoomLevel(tab, zoom); } ===================================== toolkit/components/resistfingerprinting/tests/browser/srcset.html ===================================== @@ -1,12 +1,57 @@ <!DOCTYPE html> <html> + <head> + <style> +#imgset { + content: image-set( + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50"></svg>') 0.3x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60"></svg>') 0.4x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="70" height="70"></svg>') 0.5x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="80" height="80"></svg>') 0.6x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="90" height="90"></svg>') 0.7x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"></svg>') 0.8x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="110" height="110"></svg>') 0.9x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120"></svg>') 1.0x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="130" height="130"></svg>') 1.1x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="140" height="140"></svg>') 1.2x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="150" height="150"></svg>') 1.3x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="160" height="160"></svg>') 1.4x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="170" height="170"></svg>') 1.5x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="180" height="180"></svg>') 1.6x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="190" height="190"></svg>') 1.7x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200"></svg>') 1.8x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="210" height="210"></svg>') 1.9x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="220" height="220"></svg>') 2.0x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="230" height="230"></svg>') 2.1x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="240" height="240"></svg>') 2.2x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="250" height="250"></svg>') 2.3x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="260" height="260"></svg>') 2.4x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="270" height="270"></svg>') 2.5x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="280" height="280"></svg>') 2.6x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="290" height="290"></svg>') 2.7x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="300" height="300"></svg>') 2.8x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="310" height="310"></svg>') 2.9x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="320" height="320"></svg>') 3.0x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="330" height="330"></svg>') 3.1x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="340" height="340"></svg>') 3.2x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="350" height="350"></svg>') 3.3x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="360" height="360"></svg>') 3.4x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="370" height="370"></svg>') 3.5x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="380" height="380"></svg>') 3.6x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="390" height="390"></svg>') 3.7x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="400" height="400"></svg>') 3.8x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="410" height="410"></svg>') 3.9x, + url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="420" height="420"></svg>') 4.0x + ); +} + </style> + </head> <body> <img id="srcset" srcset=" - 03.png 0.3x, 04.png 0.4x, 05.png 0.5x, 06.png 0.6x, 07.png 0.7x, 08.png 0.8x, 09.png 0.9x, - 10.png 1x, 11.png 1.1x, 12.png 1.2x, 13.png 1.3x, 14.png 1.4x, 15.png 1.5x, 16.png 1.6x, 17.png 1.7x, 18.png 1.8x, 19.png 1.9x, - 20.png 2x, 21.png 2.1x, 22.png 2.2x, 23.png 2.3x, 24.png 2.4x, 25.png 2.5x, 26.png 2.6x, 27.png 2.7x, 28.png 2.8x, 29.png 2.9x, - 30.png 3x, 31.png 3.1x, 32.png 3.2x, 33.png 3.3x, 34.png 3.4x, 35.png 3.5x, 36.png 3.6x, 37.png 3.7x, 38.png 3.8x, 39.png 3.9x, - 40.png 4x + 0.65.png 0.65x, 0.85.png 0.85x, 1.05.png 1.05x, 1.25.png 1.25x, 1.45.png 1.45x, 1.65.png 1.65x, 1.85.png 1.85x, + 2.05.png 2.05x, 2.25.png 2.25x, 2.45.png 2.45x, 2.65.png 2.65x, 2.9.png 2.9x, 3.0.png 3.0x, 3.2.png 3.2x, + 3.4.png 3.4x, 3.6.png 3.6x, 3.8.png 3.8x, 4.0.png 4.0x "> + <div id="imgset"></div> </body> </html> ===================================== widget/gtk/nsDragService.cpp ===================================== @@ -1741,6 +1741,14 @@ void nsDragSession::SourceEndDragSession(GdkDragContext* aContext, LOGDRAGSERVICE("SourceEndDragSession(%p) result %s\n", aContext, kGtkDragResults[aResult]); + // GTK drops its last reference right after drag-end, so reset ours now. + // + // Clearing only from EndDragSessionImpl is not enough (bug 2058661): that + // runs from an async runnable, by which point the pointer already dangles. + if (mSourceWindow) { + mSourceWindow->SetDragSource(nullptr); + } + // this just releases the list of data items that we provide mSourceDataItems = nullptr; ===================================== widget/windows/SystemStatusBar.cpp ===================================== @@ -91,10 +91,10 @@ StatusBarEntry::StatusBarEntry(Element* aMenu) : mMenu(aMenu), mInitted(false) { } StatusBarEntry::~StatusBarEntry() { + Destroy(); if (!mInitted) { return; } - Destroy(); ::Shell_NotifyIconW(NIM_DELETE, &mIconData); VERIFY(::DestroyWindow(mIconData.hWnd)); } ===================================== widget/windows/filedialog/WinFileDialogCommands.cpp ===================================== @@ -233,18 +233,24 @@ mozilla::Result<nsString, Error> GetFolderResults(::IFileDialog* dialog) { } // If the user chose a Win7 Library, resolve to the library's - // default save folder. - RefPtr<IShellLibrary> shellLib; - RefPtr<IShellItem> folderPath; - MOZ_ENSURE_HRESULT_OK( - "CoCreateInstance(CLSID_ShellLibrary)", - CoCreateInstance(CLSID_ShellLibrary, nullptr, CLSCTX_INPROC_SERVER, - IID_IShellLibrary, getter_AddRefs(shellLib))); - - if (shellLib && SUCCEEDED(shellLib->LoadLibraryFromItem(item, STGM_READ)) && - SUCCEEDED(shellLib->GetDefaultSaveFolder(DSFT_DETECT, IID_IShellItem, - getter_AddRefs(folderPath)))) { - item.swap(folderPath); + // default save folder. Only do this for items which are known not to be + // filesystem objects, like the user's Documents library (but not .library-ms + // files). + SFGAOF attrs = 0; + if (SUCCEEDED(item->GetAttributes(SFGAO_FILESYSTEM, &attrs)) && + !(attrs & SFGAO_FILESYSTEM)) { + RefPtr<IShellLibrary> shellLib; + RefPtr<IShellItem> folderPath; + MOZ_ENSURE_HRESULT_OK( + "CoCreateInstance(CLSID_ShellLibrary)", + CoCreateInstance(CLSID_ShellLibrary, nullptr, CLSCTX_INPROC_SERVER, + IID_IShellLibrary, getter_AddRefs(shellLib))); + + if (shellLib && SUCCEEDED(shellLib->LoadLibraryFromItem(item, STGM_READ)) && + SUCCEEDED(shellLib->GetDefaultSaveFolder(DSFT_DETECT, IID_IShellItem, + getter_AddRefs(folderPath)))) { + item.swap(folderPath); + } } // get the folder's file system path View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/339c1f8... -- View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/339c1f8... You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
participants (1)
-
ma1 (@ma1)