ma1 pushed to branch tor-browser-153.2.0esr-16.0-1 at The Tor Project / Applications / Tor Browser Commits: 3e75dcee by Yury Delendik at 2026-09-01T16:47:31+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 - - - - - 3c493b1e by Ting-Yu Lin at 2026-09-01T16:47:32+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 - - - - - a6141d0a by Ting-Yu Lin at 2026-09-01T16:47:32+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 - - - - - 23f5704c by Iain Ireland at 2026-09-01T16:47:33+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 - - - - - b781a92b by Gela at 2026-09-01T16:47:34+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 - - - - - 12 changed files: - 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/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 - + testing/web-platform/tests/css/css-writing-modes/crashtests/bidi-inline-continuation-first-in-flow.html Changes: ===================================== js/src/gc/GCRuntime.h ===================================== @@ -999,6 +999,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 ===================================== @@ -1387,6 +1387,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()) { @@ -1743,6 +1750,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); maybeWriteCoverageAndSpew(); ===================================== 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 ===================================== @@ -3234,7 +3234,9 @@ bool JSStructuredCloneReader::startReadUnchecked( } 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 ===================================== @@ -16,6 +16,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 ===================================== @@ -51,10 +51,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/generic/nsSplittableFrame.cpp ===================================== @@ -9,6 +9,7 @@ #include "nsSplittableFrame.h" +#include "mozilla/DebugOnly.h" #include "mozilla/ReflowInput.h" #include "nsContainerFrame.h" #include "nsFieldSetFrame.h" @@ -214,7 +215,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 +228,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 +240,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 ===================================== @@ -820,6 +820,16 @@ <action android:name="org.mozilla.fenix.TRIGGER_MESSAGE_WORKER" /> </intent-filter> </receiver> + + <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 ===================================== @@ -99,7 +99,6 @@ import mozilla.components.support.utils.toSafeIntent import mozilla.components.support.webextensions.WebExtensionOptionsPageObserver 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 @@ -463,9 +462,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity, Crash } } } - - // 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":[]}""" + } +} ===================================== 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> View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/1f8e95b... -- View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/1f8e95b... 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