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
-
3c493b1e
by Ting-Yu Lin at 2026-09-01T16:47:32+02:00
-
a6141d0a
by Ting-Yu Lin at 2026-09-01T16:47:32+02:00
-
23f5704c
by Iain Ireland at 2026-09-01T16:47:33+02:00
-
b781a92b
by Gela at 2026-09-01T16:47:34+02:00
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:
| ... | ... | @@ -999,6 +999,7 @@ class GCRuntime { |
| 999 | 999 | void updateAtomsBitmap();
|
| 1000 | 1000 | void sweepCCWrappers();
|
| 1001 | 1001 | void sweepRealmGlobals();
|
| 1002 | + void sweepWasmInstances();
|
|
| 1002 | 1003 | void sweepEmbeddingWeakPointers(JS::GCContext* gcx);
|
| 1003 | 1004 | void sweepMisc();
|
| 1004 | 1005 | void sweepCompressionTasks();
|
| ... | ... | @@ -1387,6 +1387,13 @@ void GCRuntime::sweepRealmGlobals() { |
| 1387 | 1387 | }
|
| 1388 | 1388 | }
|
| 1389 | 1389 | |
| 1390 | +void GCRuntime::sweepWasmInstances() {
|
|
| 1391 | + for (SweepGroupRealmsIter r(this); !r.done(); r.next()) {
|
|
| 1392 | + AutoSetThreadIsSweeping threadIsSweeping(r->zone());
|
|
| 1393 | + r->wasm.traceWeakInstances();
|
|
| 1394 | + }
|
|
| 1395 | +}
|
|
| 1396 | + |
|
| 1390 | 1397 | void GCRuntime::sweepMisc() {
|
| 1391 | 1398 | SweepingTracer trc(rt);
|
| 1392 | 1399 | for (SweepGroupRealmsIter r(this); !r.done(); r.next()) {
|
| ... | ... | @@ -1743,6 +1750,11 @@ IncrementalProgress GCRuntime::beginSweepingSweepGroup(JS::GCContext* gcx, |
| 1743 | 1750 | // This must happen before updating embedding weak pointers.
|
| 1744 | 1751 | sweepRealmGlobals();
|
| 1745 | 1752 | |
| 1753 | + // Prune dying wasm instances from each realm's weak instance list now, at the
|
|
| 1754 | + // start of sweeping, before the mutator can observe them via the (now no-op)
|
|
| 1755 | + // instances() read barrier during later incremental slices.
|
|
| 1756 | + sweepWasmInstances();
|
|
| 1757 | + |
|
| 1746 | 1758 | sweepEmbeddingWeakPointers(gcx);
|
| 1747 | 1759 | |
| 1748 | 1760 | maybeWriteCoverageAndSpew();
|
| 1 | +function forge(pattern, srcFlags, flagsByte) {
|
|
| 2 | + var cb = serialize(new RegExp(pattern, srcFlags), undefined, { scope: "DifferentProcess" });
|
|
| 3 | + var u8 = new Uint8Array(cb.arraybuffer);
|
|
| 4 | + for (var i = 0; i + 8 <= u8.length; i += 4) {
|
|
| 5 | + var tag = u8[i+4] | (u8[i+5] << 8) | (u8[i+6] << 16) | (u8[i+7] << 24);
|
|
| 6 | + if ((tag >>> 0) === 0xFFFF0006) { u8[i] = flagsByte; break; }
|
|
| 7 | + }
|
|
| 8 | + cb.clonebuffer = u8.buffer;
|
|
| 9 | + return deserialize(cb, { scope: "DifferentProcess" });
|
|
| 10 | +}
|
|
| 11 | +try {
|
|
| 12 | + var forged = forge("[\\q{abc|de}]", "v", 0x90);
|
|
| 13 | + var bad = new RegExp(forged, "u");
|
|
| 14 | + try { bad.exec("abc"); } catch {}
|
|
| 15 | + var good = new RegExp("[\\q{abc|de}]", "u");
|
|
| 16 | +} catch {} |
| ... | ... | @@ -3234,7 +3234,9 @@ bool JSStructuredCloneReader::startReadUnchecked( |
| 3234 | 3234 | }
|
| 3235 | 3235 | |
| 3236 | 3236 | case SCTAG_REGEXP_OBJECT: {
|
| 3237 | - if ((data & RegExpFlag::AllFlags) != data) {
|
|
| 3237 | + // Reject invalid flags. /u and /v are mutually exclusive.
|
|
| 3238 | + if ((data & RegExpFlag::AllFlags) != data ||
|
|
| 3239 | + ((data & RegExpFlag::Unicode) && (data & RegExpFlag::UnicodeSets))) {
|
|
| 3238 | 3240 | JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
|
| 3239 | 3241 | JSMSG_SC_BAD_SERIALIZED_DATA, "regexp");
|
| 3240 | 3242 | return false;
|
| ... | ... | @@ -16,6 +16,7 @@ |
| 16 | 16 | |
| 17 | 17 | #include "wasm/WasmRealm.h"
|
| 18 | 18 | |
| 19 | +#include "gc/Marking.h"
|
|
| 19 | 20 | #include "vm/GlobalObject.h"
|
| 20 | 21 | #include "vm/Realm.h"
|
| 21 | 22 | #include "wasm/WasmDebug.h"
|
| ... | ... | @@ -111,6 +112,19 @@ void wasm::Realm::unregisterInstance(Instance& instance) { |
| 111 | 112 | }
|
| 112 | 113 | }
|
| 113 | 114 | |
| 115 | +void wasm::Realm::traceWeakInstances() {
|
|
| 116 | + // Registration/unregistration of instances_ is tied to Instance lifetime, so
|
|
| 117 | + // an instance whose owning object is about to be finalized is still present
|
|
| 118 | + // here until ~Instance runs. Remove such entries now, at the start of zone
|
|
| 119 | + // sweeping, because the instances() read barrier that otherwise protects
|
|
| 120 | + // readers is a no-op once the zone is being swept. erase order is preserved,
|
|
| 121 | + // so the pointer-sorted invariant used by BinarySearchIf holds.
|
|
| 122 | + instances_.eraseIf([](Instance* instance) {
|
|
| 123 | + return js::gc::IsAboutToBeFinalizedUnbarriered(
|
|
| 124 | + instance->objectUnbarriered());
|
|
| 125 | + });
|
|
| 126 | +}
|
|
| 127 | + |
|
| 114 | 128 | void wasm::Realm::ensureProfilingLabels(bool profilingEnabled) {
|
| 115 | 129 | for (Instance* instance : instances_) {
|
| 116 | 130 | instance->ensureProfilingLabels(profilingEnabled);
|
| ... | ... | @@ -51,10 +51,17 @@ class Realm { |
| 51 | 51 | // Return a vector of all live instances in the realm. The lifetime of
|
| 52 | 52 | // these Instances is determined by their owning WasmInstanceObject.
|
| 53 | 53 | // Note that accessing instances()[i]->object() triggers a read barrier
|
| 54 | - // since instances() is effectively a weak list.
|
|
| 54 | + // since instances() is effectively a weak list. This read barrier is only
|
|
| 55 | + // effective while the owning zone is being marked; traceWeakInstances()
|
|
| 56 | + // prunes dying entries at the start of sweeping so that the list never
|
|
| 57 | + // exposes an about-to-be-finalized instance to the mutator.
|
|
| 55 | 58 | |
| 56 | 59 | const InstanceVector& instances() const { return instances_; }
|
| 57 | 60 | |
| 61 | + // Remove instances whose owning object is about to be finalized. Called at
|
|
| 62 | + // the start of zone sweeping, when the instances() read barrier is a no-op.
|
|
| 63 | + void traceWeakInstances();
|
|
| 64 | + |
|
| 58 | 65 | // Ensure all Instances in this Realm have profiling labels created.
|
| 59 | 66 | |
| 60 | 67 | void ensureProfilingLabels(bool profilingEnabled);
|
| ... | ... | @@ -9,6 +9,7 @@ |
| 9 | 9 | |
| 10 | 10 | #include "nsSplittableFrame.h"
|
| 11 | 11 | |
| 12 | +#include "mozilla/DebugOnly.h"
|
|
| 12 | 13 | #include "mozilla/ReflowInput.h"
|
| 13 | 14 | #include "nsContainerFrame.h"
|
| 14 | 15 | #include "nsFieldSetFrame.h"
|
| ... | ... | @@ -214,7 +215,7 @@ void nsSplittableFrame::UpdateFirstContinuationAndFirstInFlowCache() { |
| 214 | 215 | if (oldCachedFirstContinuation != newFirstContinuation) {
|
| 215 | 216 | // Update the first-continuation cache for us and our next-continuations.
|
| 216 | 217 | for (nsSplittableFrame* f = this; f;
|
| 217 | - f = reinterpret_cast<nsSplittableFrame*>(f->GetNextContinuation())) {
|
|
| 218 | + f = static_cast<nsSplittableFrame*>(f->GetNextContinuation())) {
|
|
| 218 | 219 | f->mFirstContinuation = newFirstContinuation;
|
| 219 | 220 | }
|
| 220 | 221 | }
|
| ... | ... | @@ -227,7 +228,7 @@ void nsSplittableFrame::UpdateFirstContinuationAndFirstInFlowCache() { |
| 227 | 228 | // behavior when a frame list is destroyed from the front. To avoid that
|
| 228 | 229 | // pathological behavior, we simply purge the cached values.
|
| 229 | 230 | for (nsSplittableFrame* f = this; f;
|
| 230 | - f = reinterpret_cast<nsSplittableFrame*>(f->GetNextContinuation())) {
|
|
| 231 | + f = static_cast<nsSplittableFrame*>(f->GetNextContinuation())) {
|
|
| 231 | 232 | f->mFirstContinuation = nullptr;
|
| 232 | 233 | }
|
| 233 | 234 | }
|
| ... | ... | @@ -239,22 +240,41 @@ void nsSplittableFrame::UpdateFirstContinuationAndFirstInFlowCache() { |
| 239 | 240 | if (oldCachedFirstInFlow != newFirstInFlow) {
|
| 240 | 241 | // Update the first-in-flow cache for us and our next-in-flows.
|
| 241 | 242 | for (nsSplittableFrame* f = this; f;
|
| 242 | - f = reinterpret_cast<nsSplittableFrame*>(f->GetNextInFlow())) {
|
|
| 243 | + f = static_cast<nsSplittableFrame*>(f->GetNextInFlow())) {
|
|
| 243 | 244 | f->mFirstInFlow = newFirstInFlow;
|
| 244 | 245 | }
|
| 245 | 246 | }
|
| 246 | 247 | } else {
|
| 247 | - // We become the new first-in-flow due to our prev-in-flow being removed.
|
|
| 248 | - if (oldCachedFirstInFlow) {
|
|
| 249 | - // It's tempting to update the first-in-flow cache for our
|
|
| 250 | - // next-in-flows here, but that would result in overall O(n^2)
|
|
| 251 | - // behavior when a frame list is destroyed from the front. To avoid that
|
|
| 252 | - // pathological behavior, we simply purge the cached values.
|
|
| 248 | + if (GetPrevContinuation()) {
|
|
| 249 | + // We become the new first-in-flow after changing from fluid to non-fluid.
|
|
| 250 | + // Update the stale first-in-flow cache for us and all next-in-flows.
|
|
| 251 | + //
|
|
| 252 | + // Note that this has no counterpart in the above mFirstContinuation cache
|
|
| 253 | + // since GetPrevContinuation() does not depend on the
|
|
| 254 | + // NS_FRAME_IS_FLUID_CONTINUATION bit.
|
|
| 253 | 255 | for (nsSplittableFrame* f = this; f;
|
| 254 | - f = reinterpret_cast<nsSplittableFrame*>(f->GetNextInFlow())) {
|
|
| 255 | - f->mFirstInFlow = nullptr;
|
|
| 256 | + f = static_cast<nsSplittableFrame*>(f->GetNextInFlow())) {
|
|
| 257 | + f->mFirstInFlow = this;
|
|
| 258 | + }
|
|
| 259 | + } else {
|
|
| 260 | + // We become the new first-in-flow due to our prev-in-flow being removed.
|
|
| 261 | + if (oldCachedFirstInFlow) {
|
|
| 262 | + // It's tempting to update the first-in-flow cache for our
|
|
| 263 | + // next-in-flows here, but that would result in overall O(n^2)
|
|
| 264 | + // behavior when a frame list is destroyed from the front. To avoid that
|
|
| 265 | + // pathological behavior, we simply purge the cached values.
|
|
| 266 | + for (nsSplittableFrame* f = this; f;
|
|
| 267 | + f = static_cast<nsSplittableFrame*>(f->GetNextInFlow())) {
|
|
| 268 | + f->mFirstInFlow = nullptr;
|
|
| 269 | + }
|
|
| 256 | 270 | }
|
| 257 | 271 | }
|
| 272 | + |
|
| 273 | + DebugOnly<nsSplittableFrame*> nextInFlow =
|
|
| 274 | + static_cast<nsSplittableFrame*>(GetNextInFlow());
|
|
| 275 | + MOZ_ASSERT(!nextInFlow || !nextInFlow->mFirstInFlow ||
|
|
| 276 | + nextInFlow->mFirstInFlow == this,
|
|
| 277 | + "Our next-in-flow caches a stale first-in-flow!");
|
|
| 258 | 278 | }
|
| 259 | 279 | }
|
| 260 | 280 |
| ... | ... | @@ -820,6 +820,16 @@ |
| 820 | 820 | <action android:name="org.mozilla.fenix.TRIGGER_MESSAGE_WORKER" />
|
| 821 | 821 | </intent-filter>
|
| 822 | 822 | </receiver>
|
| 823 | + |
|
| 824 | + <receiver
|
|
| 825 | + android:name="org.mozilla.fenix.experiments.QANimbusToolingReceiver"
|
|
| 826 | + android:exported="true"
|
|
| 827 | + android:enabled="true"
|
|
| 828 | + android:permission="android.permission.DUMP">
|
|
| 829 | + <intent-filter>
|
|
| 830 | + <action android:name="org.mozilla.fenix.NIMBUS_TOOLING" />
|
|
| 831 | + </intent-filter>
|
|
| 832 | + </receiver>
|
|
| 823 | 833 | </application>
|
| 824 | 834 | |
| 825 | 835 | </manifest> |
| ... | ... | @@ -99,7 +99,6 @@ import mozilla.components.support.utils.toSafeIntent |
| 99 | 99 | import mozilla.components.support.webextensions.WebExtensionOptionsPageObserver
|
| 100 | 100 | import mozilla.components.support.webextensions.WebExtensionPopupObserver
|
| 101 | 101 | import mozilla.telemetry.glean.private.NoExtras
|
| 102 | -import org.mozilla.experiments.nimbus.initializeTooling
|
|
| 103 | 102 | import org.mozilla.fenix.GleanMetrics.AppIcon
|
| 104 | 103 | import org.mozilla.fenix.GleanMetrics.Events
|
| 105 | 104 | import org.mozilla.fenix.GleanMetrics.Metrics
|
| ... | ... | @@ -463,9 +462,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity, Crash |
| 463 | 462 | }
|
| 464 | 463 | }
|
| 465 | 464 | }
|
| 466 | - |
|
| 467 | - // Setup nimbus-cli tooling. This is a NOOP when launching normally.
|
|
| 468 | - components.nimbus.sdk.initializeTooling(applicationContext, intent)
|
|
| 469 | 465 | components.strictMode.attachListenerToDisablePenaltyDeath(supportFragmentManager)
|
| 470 | 466 | MarkersFragmentLifecycleCallbacks.register(supportFragmentManager, components.core.engine)
|
| 471 | 467 |
| 1 | +/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
| 2 | + * License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
| 3 | + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
| 4 | + |
|
| 5 | +package org.mozilla.fenix.experiments
|
|
| 6 | + |
|
| 7 | +import android.content.BroadcastReceiver
|
|
| 8 | +import android.content.Context
|
|
| 9 | +import android.content.Intent
|
|
| 10 | +import kotlinx.coroutines.CoroutineDispatcher
|
|
| 11 | +import kotlinx.coroutines.CoroutineScope
|
|
| 12 | +import kotlinx.coroutines.Dispatchers
|
|
| 13 | +import kotlinx.coroutines.launch
|
|
| 14 | +import mozilla.components.support.base.log.logger.Logger
|
|
| 15 | +import org.mozilla.experiments.nimbus.initializeTooling
|
|
| 16 | +import org.mozilla.fenix.ext.components
|
|
| 17 | + |
|
| 18 | +private val logger = Logger("QANimbusToolingReceiver")
|
|
| 19 | + |
|
| 20 | +/**
|
|
| 21 | + * Receiver triggered on demand via `nimbus-cli` to manually enroll into Nimbus experiments.
|
|
| 22 | + *
|
|
| 23 | + * ```
|
|
| 24 | + * adb shell am broadcast -a org.mozilla.fenix.NIMBUS_TOOLING \
|
|
| 25 | + * -p org.mozilla.fenix
|
|
| 26 | + * ```
|
|
| 27 | + *
|
|
| 28 | + * `-p org.mozilla.fenix` is the package name, so adjust that value for release/beta/nightly/debug.
|
|
| 29 | + *
|
|
| 30 | + * @param dispatcher the [CoroutineDispatcher] the tooling commands are applied on.
|
|
| 31 | + */
|
|
| 32 | +class QANimbusToolingReceiver(private val dispatcher: CoroutineDispatcher = Dispatchers.IO) : BroadcastReceiver() {
|
|
| 33 | + override fun onReceive(context: Context, intent: Intent) {
|
|
| 34 | + if (intent.action != ACTION_NIMBUS_TOOLING) return
|
|
| 35 | + |
|
| 36 | + logger.info("Enqueueing QANimbusToolingReceiver via debug trigger")
|
|
| 37 | + |
|
| 38 | + val applicationContext = context.applicationContext
|
|
| 39 | + |
|
| 40 | + val pendingResult: PendingResult? = goAsync()
|
|
| 41 | + CoroutineScope(dispatcher).launch {
|
|
| 42 | + try {
|
|
| 43 | + applicationContext.components.nimbus.sdk.initializeTooling(
|
|
| 44 | + applicationContext,
|
|
| 45 | + intent,
|
|
| 46 | + )
|
|
| 47 | + } finally {
|
|
| 48 | + logger.info("Nimbus tooling command processed")
|
|
| 49 | + pendingResult?.finish()
|
|
| 50 | + }
|
|
| 51 | + }
|
|
| 52 | + }
|
|
| 53 | + |
|
| 54 | + companion object {
|
|
| 55 | + const val ACTION_NIMBUS_TOOLING = "org.mozilla.fenix.NIMBUS_TOOLING"
|
|
| 56 | + }
|
|
| 57 | +} |
| 1 | +/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
| 2 | + * License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
| 3 | + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
| 4 | + |
|
| 5 | +package org.mozilla.fenix.experiments
|
|
| 6 | + |
|
| 7 | +import android.content.Context
|
|
| 8 | +import android.content.Intent
|
|
| 9 | +import io.mockk.every
|
|
| 10 | +import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
| 11 | +import kotlinx.coroutines.Job
|
|
| 12 | +import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
|
| 13 | +import mozilla.components.support.test.robolectric.testContext
|
|
| 14 | +import org.junit.Assert.assertEquals
|
|
| 15 | +import org.junit.Assert.assertFalse
|
|
| 16 | +import org.junit.Assert.assertNull
|
|
| 17 | +import org.junit.Assert.assertTrue
|
|
| 18 | +import org.junit.Before
|
|
| 19 | +import org.junit.Test
|
|
| 20 | +import org.junit.runner.RunWith
|
|
| 21 | +import org.mozilla.fenix.experiments.QANimbusToolingReceiver.Companion.ACTION_NIMBUS_TOOLING
|
|
| 22 | +import org.mozilla.fenix.ext.components
|
|
| 23 | +import org.mozilla.fenix.nimbus.TestNimbusApi
|
|
| 24 | +import org.robolectric.RobolectricTestRunner
|
|
| 25 | + |
|
| 26 | +@OptIn(ExperimentalCoroutinesApi::class)
|
|
| 27 | +@RunWith(RobolectricTestRunner::class)
|
|
| 28 | +class QANimbusToolingReceiverTest {
|
|
| 29 | + |
|
| 30 | + private val nimbusApi = FakeNimbusApi(testContext)
|
|
| 31 | + private val receiver = QANimbusToolingReceiver(UnconfinedTestDispatcher())
|
|
| 32 | + |
|
| 33 | + @Before
|
|
| 34 | + fun setUp() {
|
|
| 35 | + every { testContext.components.nimbus.sdk } returns nimbusApi
|
|
| 36 | + }
|
|
| 37 | + |
|
| 38 | + @Test
|
|
| 39 | + fun `GIVEN a tooling command WHEN the tooling action is received THEN the command is applied`() {
|
|
| 40 | + receiver.onReceive(testContext, toolingIntent(ACTION_NIMBUS_TOOLING))
|
|
| 41 | + |
|
| 42 | + assertEquals(EXPERIMENTS, nimbusApi.appliedExperiments)
|
|
| 43 | + assertEquals(false, nimbusApi.fetchEnabled)
|
|
| 44 | + assertTrue(nimbusApi.databaseReset)
|
|
| 45 | + assertTrue(nimbusApi.stateDumped)
|
|
| 46 | + }
|
|
| 47 | + |
|
| 48 | + @Test
|
|
| 49 | + fun `GIVEN a tooling command WHEN another action is received THEN the command is ignored`() {
|
|
| 50 | + receiver.onReceive(testContext, toolingIntent("org.mozilla.fenix.ACTION_PRINT"))
|
|
| 51 | + |
|
| 52 | + assertNull(nimbusApi.appliedExperiments)
|
|
| 53 | + assertNull(nimbusApi.fetchEnabled)
|
|
| 54 | + assertFalse(nimbusApi.databaseReset)
|
|
| 55 | + assertFalse(nimbusApi.stateDumped)
|
|
| 56 | + }
|
|
| 57 | + |
|
| 58 | + @Test
|
|
| 59 | + fun `GIVEN no tooling command WHEN the tooling action is received THEN nothing is applied`() {
|
|
| 60 | + receiver.onReceive(testContext, Intent(ACTION_NIMBUS_TOOLING))
|
|
| 61 | + |
|
| 62 | + assertNull(nimbusApi.appliedExperiments)
|
|
| 63 | + assertNull(nimbusApi.fetchEnabled)
|
|
| 64 | + assertFalse(nimbusApi.databaseReset)
|
|
| 65 | + assertFalse(nimbusApi.stateDumped)
|
|
| 66 | + }
|
|
| 67 | + |
|
| 68 | + @Test
|
|
| 69 | + fun `GIVEN a tooling command without the version extra WHEN the tooling action is received THEN nothing is applied`() {
|
|
| 70 | + val intent = toolingIntent(ACTION_NIMBUS_TOOLING).apply { removeExtra("version") }
|
|
| 71 | + |
|
| 72 | + receiver.onReceive(testContext, intent)
|
|
| 73 | + |
|
| 74 | + assertNull(nimbusApi.appliedExperiments)
|
|
| 75 | + assertFalse(nimbusApi.stateDumped)
|
|
| 76 | + }
|
|
| 77 | + |
|
| 78 | + private fun toolingIntent(action: String) =
|
|
| 79 | + Intent(action).apply {
|
|
| 80 | + putExtra("nimbus-cli", null as String?)
|
|
| 81 | + putExtra("version", 1)
|
|
| 82 | + putExtra("experiments", EXPERIMENTS)
|
|
| 83 | + putExtra("reset-db", true)
|
|
| 84 | + putExtra("log-state", true)
|
|
| 85 | + }
|
|
| 86 | + |
|
| 87 | + private class FakeNimbusApi(context: Context) : TestNimbusApi(context) {
|
|
| 88 | + var appliedExperiments: String? = null
|
|
| 89 | + var fetchEnabled: Boolean? = null
|
|
| 90 | + var databaseReset = false
|
|
| 91 | + var stateDumped = false
|
|
| 92 | + |
|
| 93 | + override fun applyLocalExperiments(experimentsJson: String): Job {
|
|
| 94 | + appliedExperiments = experimentsJson
|
|
| 95 | + return completedJob()
|
|
| 96 | + }
|
|
| 97 | + |
|
| 98 | + override fun resetEnrollmentsDatabase(): Job {
|
|
| 99 | + databaseReset = true
|
|
| 100 | + return completedJob()
|
|
| 101 | + }
|
|
| 102 | + |
|
| 103 | + override fun setFetchEnabled(enabled: Boolean) {
|
|
| 104 | + fetchEnabled = enabled
|
|
| 105 | + }
|
|
| 106 | + |
|
| 107 | + override fun dumpStateToLog() {
|
|
| 108 | + stateDumped = true
|
|
| 109 | + }
|
|
| 110 | + |
|
| 111 | + private fun completedJob() = Job().apply { complete() }
|
|
| 112 | + }
|
|
| 113 | + |
|
| 114 | + companion object {
|
|
| 115 | + private const val EXPERIMENTS = """{"data":[]}"""
|
|
| 116 | + }
|
|
| 117 | +} |
| 1 | +<!DOCTYPE html>
|
|
| 2 | +<meta charset="utf-8">
|
|
| 3 | +<link rel="author" title="Ting-Yu Lin" href="">"mailto:tlin@xxxxxxxxxxx">
|
|
| 4 | +<link rel="help" href="">"https://bugzilla.mozilla.org/show_bug.cgi?id=2053578">
|
|
| 5 | + |
|
| 6 | +<!-- The operations in <script> are generated from one of the runs from
|
|
| 7 | + the original testcase (Bug 2053578 Comment 5) that triggers
|
|
| 8 | + the assertion. -->
|
|
| 9 | + |
|
| 10 | +<body></body>
|
|
| 11 | + |
|
| 12 | +<script>
|
|
| 13 | +var n1 = document.createElement("div");
|
|
| 14 | +n1.style.width = "50px";
|
|
| 15 | +var n2 = document.createElement("bdo");
|
|
| 16 | +n2.setAttribute("dir", "rtl");
|
|
| 17 | +var n3 = document.createTextNode("كلمة");
|
|
| 18 | +n2.appendChild(n3);
|
|
| 19 | +n1.appendChild(n2);
|
|
| 20 | +var n4 = document.createElement("em");
|
|
| 21 | +var n5 = document.createElement("span");
|
|
| 22 | +var n6 = document.createElement("bdo");
|
|
| 23 | +var n7 = document.createTextNode("word m mix");
|
|
| 24 | +n6.appendChild(n7);
|
|
| 25 | +n5.appendChild(n6);
|
|
| 26 | +var n8 = document.createTextNode("מלל");
|
|
| 27 | +n5.appendChild(n8);
|
|
| 28 | +var n9 = document.createElement("b");
|
|
| 29 | +n9.setAttribute("dir", "ltr");
|
|
| 30 | +n9.style.unicodeBidi = "bidi-override";
|
|
| 31 | +var n10 = document.createTextNode("alpha");
|
|
| 32 | +n9.appendChild(n10);
|
|
| 33 | +var n11 = document.createTextNode("نص مرحبا كلمة");
|
|
| 34 | +n9.appendChild(n11);
|
|
| 35 | +var n12 = document.createTextNode("مرحبا نص نص");
|
|
| 36 | +n9.appendChild(n12);
|
|
| 37 | +n5.appendChild(n9);
|
|
| 38 | +n4.appendChild(n5);
|
|
| 39 | +var n13 = document.createElement("span");
|
|
| 40 | +n13.style.unicodeBidi = "bidi-override";
|
|
| 41 | +var n14 = document.createElement("bdi");
|
|
| 42 | +var n15 = document.createTextNode("اختبار");
|
|
| 43 | +n14.appendChild(n15);
|
|
| 44 | +n13.appendChild(n14);
|
|
| 45 | +var n16 = document.createElement("em");
|
|
| 46 | +var n17 = document.createTextNode("בדיקה מלל m نص");
|
|
| 47 | +n16.appendChild(n17);
|
|
| 48 | +var n18 = document.createTextNode("كلمة");
|
|
| 49 | +n16.appendChild(n18);
|
|
| 50 | +var n19 = document.createTextNode("نص مرحبا كلمة");
|
|
| 51 | +n16.appendChild(n19);
|
|
| 52 | +n13.appendChild(n16);
|
|
| 53 | +var n20 = document.createElement("span");
|
|
| 54 | +var n21 = document.createTextNode("שלום מלל בדיקה שלום");
|
|
| 55 | +n20.appendChild(n21);
|
|
| 56 | +n13.appendChild(n20);
|
|
| 57 | +n4.appendChild(n13);
|
|
| 58 | +n1.appendChild(n4);
|
|
| 59 | +document.body.appendChild(n1);
|
|
| 60 | +var n22 = document.createElement("bdo");
|
|
| 61 | +n22.style.unicodeBidi = "plaintext";
|
|
| 62 | +var n23 = document.createElement("span");
|
|
| 63 | +var n24 = document.createTextNode("עברית שלום مرحبا alpha");
|
|
| 64 | +n23.appendChild(n24);
|
|
| 65 | +n22.appendChild(n23);
|
|
| 66 | +n14.style.direction = "ltr";
|
|
| 67 | +var n25 = document.createTextNode("كلمة שלום");
|
|
| 68 | +n2.insertBefore(n25, n3);
|
|
| 69 | +n21.remove();
|
|
| 70 | +n9.setAttribute("dir", "ltr");
|
|
| 71 | +var n26 = document.createTextNode("בדיקה mm");
|
|
| 72 | +n4.appendChild(n26);
|
|
| 73 | +n12.data = "שלום עברית שלום עברית";
|
|
| 74 | +n7.remove();
|
|
| 75 | +n2.style.direction = "ltr";
|
|
| 76 | +n1.style.width = "102px";
|
|
| 77 | +var n27 = document.createTextNode("עברית mm m");
|
|
| 78 | +n22.appendChild(n27);
|
|
| 79 | +n22.setAttribute("dir", "rtl");
|
|
| 80 | +n4.style.direction = "";
|
|
| 81 | +n14.style.direction = "rtl";
|
|
| 82 | +n22.insertBefore(n9, n23);
|
|
| 83 | +var n28 = document.createElement("br");
|
|
| 84 | +n9.insertBefore(n28, n12);
|
|
| 85 | +n14.style.unicodeBidi = "embed";
|
|
| 86 | +n5.insertBefore(n22, n6);
|
|
| 87 | +n9.style.direction = "";
|
|
| 88 | +n9.style.unicodeBidi = "bidi-override";
|
|
| 89 | +n22.remove();
|
|
| 90 | +var n29 = document.createTextNode("עברית שלום");
|
|
| 91 | +n5.insertBefore(n29, n6);
|
|
| 92 | +n18.data = "mix";
|
|
| 93 | +n19.data = "alpha m word";
|
|
| 94 | +n20.style.direction = "ltr";
|
|
| 95 | +var n30 = document.createTextNode("mm עברית עברית نص");
|
|
| 96 | +n6.appendChild(n30);
|
|
| 97 | +n6.style.unicodeBidi = "isolate";
|
|
| 98 | +n20.style.unicodeBidi = "embed";
|
|
| 99 | +n16.setAttribute("dir", "rtl");
|
|
| 100 | +n2.insertBefore(n20, n25);
|
|
| 101 | +var n31 = document.createTextNode("שלום مرحبا mix mm");
|
|
| 102 | +n2.insertBefore(n31, n20);
|
|
| 103 | +var n32 = document.createTextNode("m בדיקה word mix");
|
|
| 104 | +n20.appendChild(n32);
|
|
| 105 | +n16.removeAttribute("dir");
|
|
| 106 | +n16.setAttribute("dir", "auto");
|
|
| 107 | +n5.remove();
|
|
| 108 | +n17.remove();
|
|
| 109 | +n31.data = "בדיקה mm";
|
|
| 110 | +n14.style.unicodeBidi = "isolate";
|
|
| 111 | +n4.remove();
|
|
| 112 | +n20.style.direction = "";
|
|
| 113 | +n20.style.direction = "rtl";
|
|
| 114 | +n3.data = "בדיקה שלום word";
|
|
| 115 | +n25.data = "mm";
|
|
| 116 | +n25.data = "m mix word";
|
|
| 117 | +n1.style.direction = "rtl";
|
|
| 118 | +n31.data = "كلمة اختبار";
|
|
| 119 | +n20.remove();
|
|
| 120 | +n3.data = "שלום نص";
|
|
| 121 | +n31.remove();
|
|
| 122 | +n2.setAttribute("dir", "ltr");
|
|
| 123 | +n2.removeAttribute("dir");
|
|
| 124 | +n1.insertBefore(n4, n2);
|
|
| 125 | +n19.data = "מלל word";
|
|
| 126 | +n15.remove();
|
|
| 127 | +n13.setAttribute("dir", "ltr");
|
|
| 128 | +n25.remove();
|
|
| 129 | +n2.setAttribute("dir", "ltr");
|
|
| 130 | +n14.appendChild(n2);
|
|
| 131 | +n3.data = "مرحبا اختبار مرحبا";
|
|
| 132 | +n18.data = "שלום";
|
|
| 133 | +n16.insertBefore(n20, n18);
|
|
| 134 | +var n33 = document.createTextNode("كلمة نص mm");
|
|
| 135 | +n2.appendChild(n33);
|
|
| 136 | +n19.data = "اختبار مرحبا كلمة كلمة";
|
|
| 137 | +n18.data = "mm mix mix";
|
|
| 138 | +var n34 = document.createElement("br");
|
|
| 139 | +n4.insertBefore(n34, n13);
|
|
| 140 | +n13.style.unicodeBidi = "isolate-override";
|
|
| 141 | +n13.remove();
|
|
| 142 | +n34.remove();
|
|
| 143 | +n26.data = "كلمة كلمة مرحبا";
|
|
| 144 | +n4.setAttribute("dir", "rtl");
|
|
| 145 | +n26.data = "mm word m mm";
|
|
| 146 | +n26.data = "اختبار نص";
|
|
| 147 | +n4.setAttribute("dir", "auto");
|
|
| 148 | +var n35 = document.createTextNode("مرحبا מלל كلمة שלום");
|
|
| 149 | +n4.insertBefore(n35, n26);
|
|
| 150 | +n4.style.unicodeBidi = "embed";
|
|
| 151 | +n4.style.direction = "";
|
|
| 152 | +var n36 = document.createTextNode("كلمة mix בדיקה نص");
|
|
| 153 | +n4.insertBefore(n36, n35);
|
|
| 154 | +n36.remove();
|
|
| 155 | +n4.removeAttribute("dir");
|
|
| 156 | +n4.style.unicodeBidi = "";
|
|
| 157 | +n1.style.width = "87px";
|
|
| 158 | +n26.data = "שלום מלל מלל עברית";
|
|
| 159 | +var n37 = document.createElement("br");
|
|
| 160 | +n4.insertBefore(n37, n35);
|
|
| 161 | +n35.data = "مرحبا نص اختبار";
|
|
| 162 | +n4.setAttribute("dir", "rtl");
|
|
| 163 | +n33.data = "word alpha mm";
|
|
| 164 | +var n38 = document.createTextNode("שלום word");
|
|
| 165 | +n16.insertBefore(n38, n20);
|
|
| 166 | +n2.setAttribute("dir", "auto");
|
|
| 167 | +n2.remove();
|
|
| 168 | +n37.remove();
|
|
| 169 | +var n39 = document.createTextNode("עברית اختبار בדיקה");
|
|
| 170 | +n13.appendChild(n39);
|
|
| 171 | +var n40 = document.createTextNode("בדיקה m نص نص");
|
|
| 172 | +n13.insertBefore(n40, n14);
|
|
| 173 | +var n41 = document.createElement("br");
|
|
| 174 | +n20.appendChild(n41);
|
|
| 175 | +n35.data = "alpha";
|
|
| 176 | +n16.remove();
|
|
| 177 | +n39.data = "نص mm בדיקה اختبار";
|
|
| 178 | +n40.data = "mix mix word m";
|
|
| 179 | +n35.data = "שלום בדיקה";
|
|
| 180 | +n35.data = "m mix mm m";
|
|
| 181 | +n40.data = "مرحبا اختبار";
|
|
| 182 | +n13.style.unicodeBidi = "embed";
|
|
| 183 | +n4.insertBefore(n16, n26);
|
|
| 184 | +var n42 = document.createTextNode("نص");
|
|
| 185 | +n13.insertBefore(n42, n40);
|
|
| 186 | +n41.remove();
|
|
| 187 | +n20.insertBefore(n2, n32);
|
|
| 188 | +document.body.offsetHeight;
|
|
| 189 | +n39.data = "مرحبا alpha עברית word";
|
|
| 190 | +n18.data = "שלום";
|
|
| 191 | +n2.setAttribute("dir", "auto");
|
|
| 192 | +document.body.offsetHeight;
|
|
| 193 | +n20.style.direction = "ltr";
|
|
| 194 | +</script> |