tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 9c4eb6b6153d7f8effbf1ffa478ea56eb90fb4f2
parent e2c6ac5365305177a042b1962b07ac03f906bc30
Author: Ted Campbell <tcampbell@mozilla.com>
Date:   Wed,  7 Jan 2026 06:25:58 +0000

Bug 2005839 - Part 4: Stop using A-C CrashReporter in content processes r=mstange,android-reviewers,jonalmeida

Gecko handle their own crashes so stop using the A-C library. This also avoids
loading Nimbus, RemoteSettings and more through various lazy initializers.

There was a small window in content process startup that would have been covered
by this and not the GeckoView handler, but removing this handler also reduces
the amount of Kotlin code that was risky.

Some of the crash handling service processes still need access to this Fenix
configuration data, so provide a deferred initializer to CrashReporter that is
only run for those extra processes that need it.

Differential Revision: https://phabricator.services.mozilla.com/D276814

Diffstat:
Mmobile/android/android-components/components/lib/crash/src/main/java/mozilla/components/lib/crash/CrashReporter.kt | 36+++++++++++++++++++++++++++++++++---
Mmobile/android/android-components/components/lib/crash/src/test/java/mozilla/components/lib/crash/CrashReporterTest.kt | 22++++++++++++++++++++++
Mmobile/android/android-components/docs/changelog.md | 2++
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/FenixApplication.kt | 33++++++++++++++++++++-------------
4 files changed, 77 insertions(+), 16 deletions(-)

diff --git a/mobile/android/android-components/components/lib/crash/src/main/java/mozilla/components/lib/crash/CrashReporter.kt b/mobile/android/android-components/components/lib/crash/src/main/java/mozilla/components/lib/crash/CrashReporter.kt @@ -487,15 +487,45 @@ class CrashReporter internal constructor( @Volatile private var instance: CrashReporter? = null + private var deferredInitializer: (() -> CrashReporter)? = null + @VisibleForTesting internal fun reset() { instance = null + deferredInitializer = null + } + + /** + * Register a deferred initializer that will be called lazily when [requireInstance] is accessed. + * This allows processes to register crash reporting setup without immediately initializing + * the CrashReporter and its dependencies. + * + * Note: This will not register the [Thread.UncaughtExceptionHandler] and is primarily for + * cases where we access the crash database or uploader. + * + * @param initializer A function that returns a configured and installed CrashReporter instance. + */ + fun registerDeferredInitializer(initializer: () -> CrashReporter) = synchronized(this) { + deferredInitializer = initializer } internal val requireInstance: CrashReporter - get() = instance ?: throw IllegalStateException( - "You need to call install() on your CrashReporter instance from Application.onCreate().", - ) + get() = synchronized(this) { + instance?.let { return it } + + deferredInitializer?.let { initializer -> + return initializer().also { + it.logger.info("Ran deferred CrashReporter initializer") + instance = it + deferredInitializer = null + } + } + + throw IllegalStateException( + "You need to call install() or registerDeferredInitializer() on your" + + " CrashReporter from Application.onCreate().", + ) + } } } diff --git a/mobile/android/android-components/components/lib/crash/src/test/java/mozilla/components/lib/crash/CrashReporterTest.kt b/mobile/android/android-components/components/lib/crash/src/test/java/mozilla/components/lib/crash/CrashReporterTest.kt @@ -773,6 +773,28 @@ class CrashReporterTest { } @Test + fun `requireInstance can trigger lazy initializer`() { + expectException<IllegalStateException> { + CrashReporter.requireInstance + } + + var initializerCalled: CrashReporter? = null + CrashReporter.registerDeferredInitializer { + CrashReporter( + context = testContext, + services = listOf(mock()), + ).install(testContext).also { + initializerCalled = it + } + } + + assertEquals("Initializer should not be called until requireInstance is accessed", null, initializerCalled) + + val reified = CrashReporter.requireInstance + assertEquals("requireInstance should return our instance", initializerCalled, reified) + } + + @Test fun `CrashReporter invokes PendingIntent if provided for foreground child process crashes`() { val context = Robolectric.buildActivity(Activity::class.java).setup().get() diff --git a/mobile/android/android-components/docs/changelog.md b/mobile/android/android-components/docs/changelog.md @@ -13,6 +13,8 @@ permalink: /changelog/ * 🆕 New: "Copy link text" context menu candidate to allow for the ability to copy link text [Bug 1809303](https://bugzilla.mozilla.org/show_bug.cgi?id=1809303) * **lib-state** * ⚠️ **Breaking change**: Removed `MiddlewareContext`. You can now pass in a `Store` directly when invoking a `Middleware`. [Bug 2005443](https://bugzilla.mozilla.org/show_bug.cgi?id=2005443). +* **lib-crash** + * 🆕 New `CrashReporter.registerDeferredInitializer()` allows registering a lazy initializer for CrashReporter that is evaluated only when `requireInstance` is accessed, avoiding immediate initialization of dependencies [Bug 2005839](https://bugzilla.mozilla.org/show_bug.cgi?id=2005839) # 147.0 * **browser-state**: diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/FenixApplication.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/FenixApplication.kt @@ -167,16 +167,24 @@ open class FenixApplication : LocaleAwareApplication(), Provider { // content providers from Fenix and its libraries have run their initializers already. val start = SystemClock.elapsedRealtimeNanos() - // See Bug 1969818: Crash reporting requires updates to be compatible with - // isolated content process. - if (!android.os.Process.isIsolated()) { - setupCrashReporting() - } - // Capture A-C logs to Android logcat. Note that gecko maybe directly post to logcat // regardless of what we do here. Log.addSink(FenixLogSink(logsDebug = Config.channel.isDebug, AndroidLogSink())) + // Register a deferred initializer for crash reporting that will be called lazily when + // CrashReporter.requireInstance is first accessed. This allows all processes to register + // the initializer without immediately constructing the Components object and its dependencies. + // Non-main processes genera + + // Some of our non-main processes are for CrashReporter business and need to know our + // configuration from Fenix (the Analytics object). To avoid the Gecko processes that don't + // need this from initializing the Components/Objects/CrashReporter we register a lazy + // initializer so only processes that need it setup this Fenix code. + // + // Note: This doesn't setup any [UncaughtExceptionHandler]. + // Note: Gecko processes have their own crash handling mechanisms. + CrashReporter.registerDeferredInitializer(::setupCrashReporting) + // While this [initializeFenixProcess] method is run for _all processes_ in the app, we only // do global initialization for the _main process_ here. This main process initialization // includes setting up native libraries and global 'components' instances. @@ -237,6 +245,9 @@ open class FenixApplication : LocaleAwareApplication(), Provider { // - Glean will queue (most) messages before being started so it is safe for Nimbus to begin // before Glean does and any metrics will be processed once Glean is ready. + // Setup the crash reporter and register the [UncaughtExceptionHandler]. + setupCrashReporting() + // Begin application-services initialization. The megazord contains Nimbus, but not Glean. setupMegazordInitial() @@ -547,8 +558,8 @@ open class FenixApplication : LocaleAwareApplication(), Provider { } } - private fun setupCrashReporting() { - components + private fun setupCrashReporting(): CrashReporter { + return components .analytics .crashReporter .install(this, ::handleCaughtException) @@ -621,9 +632,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider { logger.info("onTrimMemory(), level=$level, main=${isMainProcess()}") - // See Bug 1969818: Crash reporting requires updates to be compatible with - // isolated content process. - if (!android.os.Process.isIsolated()) { + runOnlyInMainProcess { components.analytics.crashReporter.recordCrashBreadcrumb( Breadcrumb( category = "Memory", @@ -635,9 +644,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider { level = Breadcrumb.Level.INFO, ), ) - } - runOnlyInMainProcess { components.core.icons.onTrimMemory(level) components.core.store.dispatch(SystemAction.LowMemoryAction(level)) }