Core.kt 17.5 KB
Newer Older
1
2
3
4
5
6
/* 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.components

7
import GeckoProvider
8
import android.content.Context
9
import android.content.res.Configuration
10
import android.os.Build
11
import android.os.StrictMode
12
import androidx.core.content.ContextCompat
13
import io.sentry.Sentry
14
import mozilla.components.browser.engine.gecko.GeckoEngine
15
import mozilla.components.browser.engine.gecko.fetch.GeckoViewFetchClient
16
import mozilla.components.browser.icons.BrowserIcons
17
import mozilla.components.browser.session.Session
18
import mozilla.components.browser.session.SessionManager
19
import mozilla.components.browser.session.engine.EngineMiddleware
20
import mozilla.components.browser.session.storage.SessionStorage
21
22
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.store.BrowserStore
23
import mozilla.components.browser.storage.sync.PlacesBookmarksStorage
24
import mozilla.components.browser.storage.sync.PlacesHistoryStorage
25
import mozilla.components.browser.storage.sync.RemoteTabsStorage
26
import mozilla.components.browser.thumbnails.ThumbnailsMiddleware
27
import mozilla.components.browser.thumbnails.storage.ThumbnailStorage
28
import mozilla.components.concept.base.crash.CrashReporting
29
30
import mozilla.components.concept.engine.DefaultSettings
import mozilla.components.concept.engine.Engine
31
import mozilla.components.concept.engine.mediaquery.PreferredColorScheme
32
import mozilla.components.concept.fetch.Client
33
import mozilla.components.feature.customtabs.store.CustomTabsServiceStore
34
import mozilla.components.feature.downloads.DownloadMiddleware
35
import mozilla.components.feature.logins.exceptions.LoginExceptionStorage
36
import mozilla.components.feature.media.MediaSessionFeature
37
import mozilla.components.feature.media.middleware.RecordingDevicesMiddleware
Tiger Oakes's avatar
Tiger Oakes committed
38
39
import mozilla.components.feature.pwa.ManifestStorage
import mozilla.components.feature.pwa.WebAppShortcutManager
40
import mozilla.components.feature.readerview.ReaderViewMiddleware
ekager's avatar
ekager committed
41
import mozilla.components.feature.recentlyclosed.RecentlyClosedMiddleware
42
43
import mozilla.components.feature.search.middleware.SearchMiddleware
import mozilla.components.feature.search.region.RegionMiddleware
44
import mozilla.components.feature.session.HistoryDelegate
45
46
import mozilla.components.feature.session.middleware.LastAccessMiddleware
import mozilla.components.feature.session.middleware.undo.UndoMiddleware
47
48
import mozilla.components.feature.top.sites.DefaultTopSitesStorage
import mozilla.components.feature.top.sites.PinnedSiteStorage
49
import mozilla.components.feature.webcompat.WebCompatFeature
50
import mozilla.components.feature.webcompat.reporter.WebCompatReporterFeature
51
import mozilla.components.feature.webnotifications.WebNotificationFeature
52
53
import mozilla.components.lib.dataprotect.SecureAbove22Preferences
import mozilla.components.lib.dataprotect.generateEncryptionKey
Tiger Oakes's avatar
Tiger Oakes committed
54
import mozilla.components.service.digitalassetlinks.RelationChecker
55
56
import mozilla.components.service.digitalassetlinks.local.StatementApi
import mozilla.components.service.digitalassetlinks.local.StatementRelationChecker
57
58
import mozilla.components.service.location.LocationService
import mozilla.components.service.location.MozillaLocationService
59
import mozilla.components.service.sync.autofill.AutofillCreditCardsAddressesStorage
60
import mozilla.components.service.sync.logins.SyncableLoginsStorage
61
import mozilla.components.support.locale.LocaleManager
62
import org.mozilla.fenix.AppRequestInterceptor
63
import org.mozilla.fenix.BuildConfig
64
import org.mozilla.fenix.Config
65
66
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
67
import org.mozilla.fenix.TelemetryMiddleware
68
import org.mozilla.fenix.components.search.SearchMigration
69
import org.mozilla.fenix.downloads.DownloadService
70
import org.mozilla.fenix.ext.components
71
import org.mozilla.fenix.ext.settings
72
73
import org.mozilla.fenix.media.MediaSessionService
import org.mozilla.fenix.perf.StrictModeManager
74
import org.mozilla.fenix.perf.lazyMonitored
75
76
import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry
import org.mozilla.fenix.search.telemetry.incontent.InContentTelemetry
77
78
import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.settings.advanced.getSelectedLocale
79
import org.mozilla.fenix.utils.Mockable
80
import org.mozilla.fenix.utils.getUndoDelay
81
82
83
84

/**
 * Component group for all core browser functionality.
 */
85
@Mockable
86
@Suppress("LargeClass")
87
88
89
90
91
class Core(
    private val context: Context,
    private val crashReporter: CrashReporting,
    strictMode: StrictModeManager
) {
92
93
94
95
    /**
     * The browser engine component initialized based on the build
     * configuration (see build variants).
     */
96
    val engine: Engine by lazyMonitored {
97
        val defaultSettings = DefaultSettings(
98
            requestInterceptor = requestInterceptor,
99
100
            remoteDebuggingEnabled = context.settings().isRemoteDebuggingEnabled &&
                    Build.VERSION.SDK_INT >= Build.VERSION_CODES.M,
101
            testingModeEnabled = false,
102
            trackingProtectionPolicy = trackingProtectionPolicyFactory.createTrackingProtectionPolicy(),
103
            historyTrackingDelegate = HistoryDelegate(lazyHistoryStorage),
104
            preferredColorScheme = getPreferredColorScheme(),
105
106
            automaticFontSizeAdjustment = context.settings().shouldUseAutoSize,
            fontInflationEnabled = context.settings().shouldUseAutoSize,
107
            suspendMediaWhenInactive = false,
108
            forceUserScalableContent = context.settings().forceEnableZoom,
109
110
111
112
            loginAutofillEnabled = context.settings().shouldAutofillLogins,
            clearColor = ContextCompat.getColor(
                context,
                R.color.foundation_normal_theme
Alex Catarineu's avatar
Alex Catarineu committed
113
114
            ),
            spoofEnglish = context.settings().spoofEnglish
115
        )
116

117
118
119
        GeckoEngine(
            context,
            defaultSettings,
120
121
122
123
124
            GeckoProvider.getOrCreateRuntime(
                context,
                lazyPasswordsStorage,
                trackingProtectionPolicyFactory.createTrackingProtectionPolicy()
            )
125
        ).also {
126
127
128
129
130
131
132

            /**
             * There are some issues around localization to be resolved, as well as questions around
             * the capacity of the WebCompat team, so the "Report site issue" feature should stay
             * disabled in Fenix Release builds for now.
             * This is consistent with both Fennec and Firefox Desktop.
             */
Alex Catarineu's avatar
Alex Catarineu committed
133
134
            if (false && (Config.channel.isNightlyOrDebug || Config.channel.isBeta)) {
                WebCompatFeature.install(it)
135
                WebCompatReporterFeature.install(it, "fenix")
136
            }
Alex Catarineu's avatar
Alex Catarineu committed
137
138

            TorBrowserFeatures.install(context, it)
139
        }
140
141
    }

142
143
144
145
146
147
148
149
150
    /**
     * Passed to [engine] to intercept requests for app links,
     * and various features triggered by page load requests.
     *
     * NB: This does not need to be lazy as it is initialized
     * with the engine on startup.
     */
    val requestInterceptor = AppRequestInterceptor(context)

151
152
153
    /**
     * [Client] implementation to be used for code depending on `concept-fetch``
     */
154
    val client: Client by lazyMonitored {
155
156
        GeckoViewFetchClient(
            context,
157
158
159
160
161
            GeckoProvider.getOrCreateRuntime(
                context,
                lazyPasswordsStorage,
                trackingProtectionPolicyFactory.createTrackingProtectionPolicy()
            )
162
        )
163
164
    }

165
    val sessionStorage: SessionStorage by lazyMonitored {
166
        SessionStorage(context, engine = engine)
167
168
    }

169
170
171
172
173
174
175
176
    private val locationService: LocationService by lazyMonitored {
        if (Config.channel.isDebug || BuildConfig.MLS_TOKEN.isEmpty()) {
            LocationService.default()
        } else {
            MozillaLocationService(context, client, BuildConfig.MLS_TOKEN)
        }
    }

177
178
179
    /**
     * The [BrowserStore] holds the global [BrowserState].
     */
180
    val store by lazyMonitored {
181
182
        val middlewareList =
            mutableListOf(
183
                LastAccessMiddleware(),
ekager's avatar
ekager committed
184
                RecentlyClosedMiddleware(context, RECENTLY_CLOSED_MAX, engine),
185
                DownloadMiddleware(context, DownloadService::class.java),
186
                ReaderViewMiddleware(),
187
188
189
190
191
                TelemetryMiddleware(
                    context.settings(),
                    adsTelemetry,
                    metrics
                ),
192
                ThumbnailsMiddleware(thumbnailStorage),
193
194
195
196
197
198
                UndoMiddleware(::lookupSessionManager, context.getUndoDelay()),
                RegionMiddleware(context, locationService),
                SearchMiddleware(
                    context,
                    additionalBundledSearchEngineIds = listOf("reddit", "youtube"),
                    migration = SearchMigration(context)
199
200
                ),
                RecordingDevicesMiddleware(context)
201
202
203
204
            )

        BrowserStore(
            middleware = middlewareList + EngineMiddleware.create(engine, ::findSessionById)
205
        )
206
207
    }

208
    @Suppress("Deprecation")
209
210
211
212
    private fun lookupSessionManager(): SessionManager {
        return sessionManager
    }

213
    @Suppress("Deprecation")
214
215
216
217
    private fun findSessionById(tabId: String): Session? {
        return sessionManager.findSessionById(tabId)
    }

218
219
220
    /**
     * The [CustomTabsServiceStore] holds global custom tabs related data.
     */
221
    val customTabsStore by lazyMonitored { CustomTabsServiceStore() }
222

Tiger Oakes's avatar
Tiger Oakes committed
223
224
225
    /**
     * The [RelationChecker] checks Digital Asset Links relationships for Trusted Web Activities.
     */
226
    val relationChecker: RelationChecker by lazyMonitored {
227
        StatementRelationChecker(StatementApi(client))
Tiger Oakes's avatar
Tiger Oakes committed
228
229
    }

230
231
232
233
234
235
    /**
     * The session manager component provides access to a centralized registry of
     * all browser sessions (i.e. tabs). It is initialized here to persist and restore
     * sessions from the [SessionStorage], and with a default session (about:blank) in
     * case all sessions/tabs are closed.
     */
236
    @Deprecated("Use browser store (for reading) and use cases (for writing) instead")
237
    val sessionManager by lazyMonitored {
238
        SessionManager(engine, store).also {
239
            // Install the "icons" WebExtension to automatically load icons for every visited website.
240
            icons.install(engine, store)
241

242
            // Install the "ads" WebExtension to get the links in an partner page.
243
244
245
246
            adsTelemetry.install(engine, store)

            // Install the "cookies" WebExtension and tracks user interaction with SERPs.
            searchTelemetry.install(engine, store)
247

248
249
            WebNotificationFeature(
                context, engine, icons, R.drawable.ic_status_logo,
250
                permissionStorage.permissionsStorage, HomeActivity::class.java
251
            )
252

253
            MediaSessionFeature(context, MediaSessionService::class.java, store).start()
254
        }
255
256
    }

257
258
259
    /**
     * Icons component for loading, caching and processing website icons.
     */
260
    val icons by lazyMonitored {
Tiger Oakes's avatar
Tiger Oakes committed
261
262
263
        BrowserIcons(context, client)
    }

264
    val metrics by lazyMonitored {
265
266
267
        context.components.analytics.metrics
    }

268
    val adsTelemetry by lazyMonitored {
269
        AdsTelemetry(metrics)
270
271
    }

272
    val searchTelemetry by lazyMonitored {
273
        InContentTelemetry(metrics)
274
275
    }

Tiger Oakes's avatar
Tiger Oakes committed
276
277
278
    /**
     * Shortcut component for managing shortcuts on the device home screen.
     */
279
    val webAppShortcutManager by lazyMonitored {
Tiger Oakes's avatar
Tiger Oakes committed
280
281
282
        WebAppShortcutManager(
            context,
            client,
ekager's avatar
ekager committed
283
            webAppManifestStorage
Tiger Oakes's avatar
Tiger Oakes committed
284
        )
285
286
    }

287
288
289
290
291
    // Lazy wrappers around storage components are used to pass references to these components without
    // initializing them until they're accessed.
    // Use these for startup-path code, where we don't want to do any work that's not strictly necessary.
    // For example, this is how the GeckoEngine delegates (history, logins) are configured.
    // We can fully initialize GeckoEngine without initialized our storage.
292
293
294
    val lazyHistoryStorage = lazyMonitored { PlacesHistoryStorage(context, crashReporter) }
    val lazyBookmarksStorage = lazyMonitored { PlacesBookmarksStorage(context) }
    val lazyPasswordsStorage = lazyMonitored { SyncableLoginsStorage(context, passwordsEncryptionKey) }
295
    val lazyAutofillStorage = lazyMonitored { AutofillCreditCardsAddressesStorage(context) }
296

297
298
299
    /**
     * The storage component to sync and persist tabs in a Firefox Sync account.
     */
300
    val lazyRemoteTabsStorage = lazyMonitored { RemoteTabsStorage() }
301

302
    // For most other application code (non-startup), these wrappers are perfectly fine and more ergonomic.
303
304
305
    val historyStorage: PlacesHistoryStorage get() = lazyHistoryStorage.value
    val bookmarksStorage: PlacesBookmarksStorage get() = lazyBookmarksStorage.value
    val passwordsStorage: SyncableLoginsStorage get() = lazyPasswordsStorage.value
306
    val autofillStorage: AutofillCreditCardsAddressesStorage get() = lazyAutofillStorage.value
307

308
    val tabCollectionStorage by lazyMonitored {
309
310
311
312
313
        TabCollectionStorage(
            context,
            strictMode
        )
    }
314

315
316
317
    /**
     * A storage component for persisting thumbnail images of tabs.
     */
318
    val thumbnailStorage by lazyMonitored { ThumbnailStorage(context) }
319

320
    val pinnedSiteStorage by lazyMonitored { PinnedSiteStorage(context) }
321

322
    val topSitesStorage by lazyMonitored {
323
324
        val defaultTopSites = mutableListOf<Pair<String, String>>()

325
        strictMode.resetAfter(StrictMode.allowThreadDiskReads()) {
326
            if (!context.settings().defaultTopSitesAdded) {
327
328
329
330
331
332
                if (Config.channel.isMozillaOnline) {
                    defaultTopSites.add(
                        Pair(
                            context.getString(R.string.default_top_site_baidu),
                            SupportUtils.BAIDU_URL
                        )
333
334
                    )

335
336
                    defaultTopSites.add(
                        Pair(
337
338
339
340
341
342
343
344
345
                            context.getString(R.string.default_top_site_jd),
                            SupportUtils.JD_URL
                        )
                    )
                } else {
                    defaultTopSites.add(
                        Pair(
                            context.getString(R.string.default_top_site_google),
                            SupportUtils.GOOGLE_URL
346
347
348
                        )
                    )

349
350
351
352
353
354
355
356
357
358
359
360
361
362
                    if (LocaleManager.getSelectedLocale(context).language == "en") {
                        defaultTopSites.add(
                            Pair(
                                context.getString(R.string.pocket_pinned_top_articles),
                                SupportUtils.POCKET_TRENDING_URL
                            )
                        )
                    }

                    defaultTopSites.add(
                        Pair(
                            context.getString(R.string.default_top_site_wikipedia),
                            SupportUtils.WIKIPEDIA_URL
                        )
363
                    )
364
                }
365

366
367
                context.settings().defaultTopSitesAdded = true
            }
368
369
370
        }

        DefaultTopSitesStorage(
371
            pinnedSiteStorage,
372
373
374
375
            historyStorage,
            defaultTopSites
        )
    }
376

377
    val permissionStorage by lazyMonitored { PermissionStorage(context) }
378

379
    val webAppManifestStorage by lazyMonitored { ManifestStorage(context) }
Tiger Oakes's avatar
Tiger Oakes committed
380

381
    val loginExceptionStorage by lazyMonitored { LoginExceptionStorage(context) }
382

383
384
    /**
     * Shared Preferences that encrypt/decrypt using Android KeyStore and lib-dataprotect for 23+
385
386
     * only on Nightly/Debug for now, otherwise simply stored.
     * See https://github.com/mozilla-mobile/fenix/issues/8324
387
     */
388
    private fun getSecureAbove22Preferences() =
389
390
391
392
393
        SecureAbove22Preferences(
            context = context,
            name = KEY_STORAGE_NAME,
            forceInsecure = !Config.channel.isNightlyOrDebug
        )
394

395
    private val passwordsEncryptionKey by lazyMonitored {
ekager's avatar
ekager committed
396
        getSecureAbove22Preferences().getString(PASSWORDS_KEY)
397
            ?: generateEncryptionKey(KEY_STRENGTH).also {
398
                if (context.settings().passwordsEncryptionKeyGenerated &&
399
400
                    isSentryEnabled()
                ) {
401
402
403
404
                    // We already had previously generated an encryption key, but we have lost it
                    Sentry.capture("Passwords encryption key for passwords storage was lost and we generated a new one")
                }
                context.settings().recordPasswordsEncryptionKeyGenerated()
ekager's avatar
ekager committed
405
                getSecureAbove22Preferences().putString(PASSWORDS_KEY, it)
406
            }
407
    }
408

409
    val trackingProtectionPolicyFactory = TrackingProtectionPolicyFactory(context.settings())
mcarare's avatar
mcarare committed
410

411
412
413
    /**
     * Sets Preferred Color scheme based on Dark/Light Theme Settings or Current Configuration
     */
414
    fun getPreferredColorScheme(): PreferredColorScheme {
415
416
        val inDark =
            (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) ==
ekager's avatar
ekager committed
417
                    Configuration.UI_MODE_NIGHT_YES
418
        return when {
419
420
            context.settings().shouldUseDarkTheme -> PreferredColorScheme.Dark
            context.settings().shouldUseLightTheme -> PreferredColorScheme.Light
421
422
423
424
            inDark -> PreferredColorScheme.Dark
            else -> PreferredColorScheme.Light
        }
    }
425
426
427
428
429

    companion object {
        private const val KEY_STRENGTH = 256
        private const val KEY_STORAGE_NAME = "core_prefs"
        private const val PASSWORDS_KEY = "passwords"
430
        private const val RECENTLY_CLOSED_MAX = 10
431
    }
432
}