diff --git a/components/browser/engine-system/src/main/java/mozilla/components/browser/engine/system/SystemEngineView.kt b/components/browser/engine-system/src/main/java/mozilla/components/browser/engine/system/SystemEngineView.kt index 8c589c2182d..200ac32162b 100644 --- a/components/browser/engine-system/src/main/java/mozilla/components/browser/engine/system/SystemEngineView.kt +++ b/components/browser/engine-system/src/main/java/mozilla/components/browser/engine/system/SystemEngineView.kt @@ -269,6 +269,8 @@ class SystemEngineView @JvmOverloads constructor( super.shouldInterceptRequest(view, request) } + + is InterceptionResponse.Deny -> super.shouldInterceptRequest(view, request) } } } diff --git a/components/concept/engine/src/main/java/mozilla/components/concept/engine/request/RequestInterceptor.kt b/components/concept/engine/src/main/java/mozilla/components/concept/engine/request/RequestInterceptor.kt index 50cb838704b..f73cfa4e14f 100644 --- a/components/concept/engine/src/main/java/mozilla/components/concept/engine/request/RequestInterceptor.kt +++ b/components/concept/engine/src/main/java/mozilla/components/concept/engine/request/RequestInterceptor.kt @@ -26,6 +26,11 @@ interface RequestInterceptor { data class Url(val url: String) : InterceptionResponse() data class AppIntent(val appIntent: Intent, val url: String) : InterceptionResponse() + + /** + * Deny request without further action. + */ + object Deny : InterceptionResponse() } /** diff --git a/components/feature/pwa/src/main/java/mozilla/components/feature/pwa/ManifestStorage.kt b/components/feature/pwa/src/main/java/mozilla/components/feature/pwa/ManifestStorage.kt index 88a53a10876..39a4922d0a0 100644 --- a/components/feature/pwa/src/main/java/mozilla/components/feature/pwa/ManifestStorage.kt +++ b/components/feature/pwa/src/main/java/mozilla/components/feature/pwa/ManifestStorage.kt @@ -19,10 +19,12 @@ import mozilla.components.feature.pwa.db.ManifestEntity * @param activeThresholdMs a timeout in milliseconds after which the storage will consider a manifest * as unused. By default this is [ACTIVE_THRESHOLD_MS]. */ +@Suppress("TooManyFunctions") class ManifestStorage(context: Context, private val activeThresholdMs: Long = ACTIVE_THRESHOLD_MS) { @VisibleForTesting internal var manifestDao = lazy { ManifestDatabase.get(context).manifestDao() } + internal var installedScopes: MutableMap? = null /** * Load a Web App Manifest for the given URL from disk. @@ -70,6 +72,34 @@ class ManifestStorage(context: Context, private val activeThresholdMs: Long = AC manifestDao.value.recentManifestsCount(thresholdMs = currentTimeMs - activeThresholdMs) } + /** + * Returns the cached scope for an url if the url falls into a web app scope that has been installed by the user. + * + * @param url the url to match against installed web app scopes. + */ + fun getInstalledScope(url: String) = installedScopes?.keys?.sortedDescending()?.find { url.startsWith(it) } + + /** + * Returns a cached start url for an installed web app scope. + * + * @param scope the scope url to look up. + */ + fun getStartUrlForInstalledScope(scope: String) = installedScopes?.get(scope) + + /** + * Populates a cache of currently installed web app scopes and their start urls. + * + * @param currentTime the current time is used to determine which web apps are still installed. + */ + suspend fun warmUpScopes(currentTime: Long) = withContext(IO) { + installedScopes = manifestDao.value + .getInstalledScopes(currentTime - activeThresholdMs) + .map { manifest -> manifest.scope?.let { scope -> Pair(scope, manifest.startUrl) } } + .filterNotNull() + .toMap() + .toMutableMap() + } + /** * Save a Web App Manifest to disk. */ @@ -101,6 +131,12 @@ class ManifestStorage(context: Context, private val activeThresholdMs: Long = AC manifestDao.value.getManifest(manifest.startUrl)?.let { existing -> val update = existing.copy(usedAt = System.currentTimeMillis()) manifestDao.value.updateManifest(update) + + existing.scope?.let { scope -> + installedScopes?.put(scope, existing.startUrl) + } + + return@let } } diff --git a/components/feature/pwa/src/main/java/mozilla/components/feature/pwa/WebAppInterceptor.kt b/components/feature/pwa/src/main/java/mozilla/components/feature/pwa/WebAppInterceptor.kt new file mode 100644 index 00000000000..f58cd0b9f96 --- /dev/null +++ b/components/feature/pwa/src/main/java/mozilla/components/feature/pwa/WebAppInterceptor.kt @@ -0,0 +1,60 @@ +/* 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 mozilla.components.feature.pwa + +import android.content.Context +import android.content.Intent +import android.net.Uri +import mozilla.components.concept.engine.EngineSession +import mozilla.components.concept.engine.request.RequestInterceptor +import mozilla.components.feature.pwa.ext.putUrlOverride +import mozilla.components.feature.pwa.intent.WebAppIntentProcessor + +/** + * This feature will intercept requests and reopen them in the corresponding installed PWA, if any. + * + * @param shortcutManager current shortcut manager instance to lookup web app install states + */ +class WebAppInterceptor( + private val context: Context, + private val manifestStorage: ManifestStorage, + private val launchFromInterceptor: Boolean = true +) : RequestInterceptor { + + @Suppress("ReturnCount") + override fun onLoadRequest( + engineSession: EngineSession, + uri: String, + hasUserGesture: Boolean, + isSameDomain: Boolean, + isRedirect: Boolean, + isDirectNavigation: Boolean + ): RequestInterceptor.InterceptionResponse? { + val scope = manifestStorage.getInstalledScope(uri) ?: return null + val startUrl = manifestStorage.getStartUrlForInstalledScope(scope) ?: return null + val intent = createIntentFromUri(startUrl, uri) + + if (!launchFromInterceptor) { + return RequestInterceptor.InterceptionResponse.AppIntent(intent, uri) + } + + intent.flags = intent.flags or Intent.FLAG_ACTIVITY_NEW_TASK + context.startActivity(intent) + + return RequestInterceptor.InterceptionResponse.Deny + } + + /** + * Creates a new VIEW_PWA intent for a URL. + * + * @param uri target URL for the new intent + */ + private fun createIntentFromUri(startUrl: String, urlOverride: String = startUrl): Intent { + return Intent(WebAppIntentProcessor.ACTION_VIEW_PWA, Uri.parse(startUrl)).apply { + this.addCategory(Intent.CATEGORY_DEFAULT) + this.putUrlOverride(urlOverride) + } + } +} diff --git a/components/feature/pwa/src/main/java/mozilla/components/feature/pwa/db/ManifestDao.kt b/components/feature/pwa/src/main/java/mozilla/components/feature/pwa/db/ManifestDao.kt index 39b029bda7e..3055d089826 100644 --- a/components/feature/pwa/src/main/java/mozilla/components/feature/pwa/db/ManifestDao.kt +++ b/components/feature/pwa/src/main/java/mozilla/components/feature/pwa/db/ManifestDao.kt @@ -43,4 +43,8 @@ internal interface ManifestDao { @WorkerThread @Query("DELETE FROM manifests WHERE start_url IN (:startUrls)") fun deleteManifests(startUrls: List) + + @WorkerThread + @Query("SELECT * from manifests WHERE used_at > :expiresAt ORDER BY LENGTH(scope)") + fun getInstalledScopes(expiresAt: Long): List } diff --git a/components/feature/pwa/src/main/java/mozilla/components/feature/pwa/ext/Intent.kt b/components/feature/pwa/src/main/java/mozilla/components/feature/pwa/ext/Intent.kt index 0432596d863..7258a7341c2 100644 --- a/components/feature/pwa/src/main/java/mozilla/components/feature/pwa/ext/Intent.kt +++ b/components/feature/pwa/src/main/java/mozilla/components/feature/pwa/ext/Intent.kt @@ -8,6 +8,8 @@ import android.content.Intent import mozilla.components.concept.engine.manifest.WebAppManifest import mozilla.components.concept.engine.manifest.WebAppManifestParser +internal const val EXTRA_URL_OVERRIDE = "mozilla.components.feature.pwa.EXTRA_URL_OVERRIDE" + /** * Add extended [WebAppManifest] data to the intent. */ @@ -22,3 +24,25 @@ fun Intent.putWebAppManifest(webAppManifest: WebAppManifest) { fun Intent.getWebAppManifest(): WebAppManifest? { return extras?.getWebAppManifest() } + +/** + * Add [String] URL override to the intent. + * + * @param url The URL override value. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see [getUrlOverride] + */ +fun Intent.putUrlOverride(url: String?): Intent { + return putExtra(EXTRA_URL_OVERRIDE, url) +} + +/** + * Retrieves [String] Url override from the intent. + * + * @return The URL override previously added with [putUrlOverride], + * or null if no URL was found. + */ +fun Intent.getUrlOverride(): String? = getStringExtra(EXTRA_URL_OVERRIDE) diff --git a/components/feature/pwa/src/main/java/mozilla/components/feature/pwa/intent/WebAppIntentProcessor.kt b/components/feature/pwa/src/main/java/mozilla/components/feature/pwa/intent/WebAppIntentProcessor.kt index 4f67be658b1..a6692ff60aa 100644 --- a/components/feature/pwa/src/main/java/mozilla/components/feature/pwa/intent/WebAppIntentProcessor.kt +++ b/components/feature/pwa/src/main/java/mozilla/components/feature/pwa/intent/WebAppIntentProcessor.kt @@ -10,7 +10,10 @@ import kotlinx.coroutines.runBlocking import mozilla.components.browser.session.Session import mozilla.components.browser.session.Session.Source import mozilla.components.browser.session.SessionManager +import mozilla.components.browser.state.state.ExternalAppType import mozilla.components.concept.engine.EngineSession +import mozilla.components.concept.engine.manifest.WebAppManifest +import mozilla.components.feature.pwa.ext.getUrlOverride import mozilla.components.feature.intent.ext.putSessionId import mozilla.components.feature.intent.processing.IntentProcessor import mozilla.components.feature.pwa.ManifestStorage @@ -45,13 +48,14 @@ class WebAppIntentProcessor( return if (!url.isNullOrEmpty() && matches(intent)) { val webAppManifest = runBlocking { storage.loadManifest(url) } ?: return false + val targetUrl = intent.getUrlOverride() ?: url - val session = Session(url, private = false, source = Source.HOME_SCREEN) - session.webAppManifest = webAppManifest - session.customTabConfig = webAppManifest.toCustomTabConfig() + val session = findExistingSession(webAppManifest) ?: createSession(webAppManifest, url) + + if (targetUrl !== url) { + loadUrlUseCase(targetUrl, session, EngineSession.LoadUrlFlags.external()) + } - sessionManager.add(session) - loadUrlUseCase(url, session, EngineSession.LoadUrlFlags.external()) intent.flags = FLAG_ACTIVITY_NEW_DOCUMENT intent.putSessionId(session.id) intent.putWebAppManifest(webAppManifest) @@ -62,6 +66,30 @@ class WebAppIntentProcessor( } } + /** + * Returns an existing web app session that matches the manifest. + */ + private fun findExistingSession(webAppManifest: WebAppManifest): Session? { + return sessionManager.all.find { + it.customTabConfig?.externalAppType == ExternalAppType.PROGRESSIVE_WEB_APP && + it.webAppManifest?.startUrl == webAppManifest.startUrl + } + } + + /** + * Returns a new web app session. + */ + private fun createSession(webAppManifest: WebAppManifest, url: String): Session { + return Session(url, private = false, source = Source.HOME_SCREEN) + .apply { + this.webAppManifest = webAppManifest + this.customTabConfig = webAppManifest.toCustomTabConfig() + }.also { + sessionManager.add(it) + loadUrlUseCase(url, it, EngineSession.LoadUrlFlags.external()) + } + } + companion object { const val ACTION_VIEW_PWA = "mozilla.components.feature.pwa.VIEW_PWA" } diff --git a/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/ManifestStorageTest.kt b/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/ManifestStorageTest.kt index c7c6e61ff55..97988beccc0 100644 --- a/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/ManifestStorageTest.kt +++ b/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/ManifestStorageTest.kt @@ -37,6 +37,18 @@ class ManifestStorageTest { scope = "/" ) + private val googleMapsManifest = WebAppManifest( + name = "Google Maps", + startUrl = "https://google.com/maps", + scope = "https://google.com/maps/" + ) + + private val exampleWebAppManifest = WebAppManifest( + name = "Example Web App", + startUrl = "https://pwa.example.com/dashboard", + scope = "https://pwa.example.com/" + ) + @Test fun `load returns null if entry does not exist`() = runBlocking { val storage = spy(ManifestStorage(testContext)) @@ -202,6 +214,65 @@ class ManifestStorageTest { )) } + @Test + fun `warmUpScopes populates cache of already installed web app scopes`() = runBlocking { + val storage = spy(ManifestStorage(testContext)) + val dao = mockDatabase(storage) + + val manifest1 = ManifestEntity(manifest = firefoxManifest, createdAt = 0, updatedAt = 0) + val manifest2 = ManifestEntity(manifest = googleMapsManifest, createdAt = 0, updatedAt = 0) + val manifest3 = ManifestEntity(manifest = exampleWebAppManifest, createdAt = 0, updatedAt = 0) + + whenever(dao.getInstalledScopes(0)).thenReturn(listOf(manifest1, manifest2, manifest3)) + + storage.warmUpScopes(ManifestStorage.ACTIVE_THRESHOLD_MS) + + assertEquals( + mapOf( + Pair("/", "https://firefox.com"), + Pair("https://google.com/maps/", "https://google.com/maps"), + Pair("https://pwa.example.com/", "https://pwa.example.com/dashboard") + ), + storage.installedScopes + ) + } + + @Test + fun `getInstalledScope returns cached scope for an url`() = runBlocking { + val storage = spy(ManifestStorage(testContext)) + val dao = mockDatabase(storage) + + val manifest1 = ManifestEntity(manifest = firefoxManifest, createdAt = 0, updatedAt = 0) + val manifest2 = ManifestEntity(manifest = googleMapsManifest, createdAt = 0, updatedAt = 0) + val manifest3 = ManifestEntity(manifest = exampleWebAppManifest, createdAt = 0, updatedAt = 0) + + whenever(dao.getInstalledScopes(0)).thenReturn(listOf(manifest1, manifest2, manifest3)) + + storage.warmUpScopes(ManifestStorage.ACTIVE_THRESHOLD_MS) + + val result = storage.getInstalledScope("https://pwa.example.com/profile/me") + + assertEquals("https://pwa.example.com/", result) + } + + @Test + fun `getStartUrlForInstalledScope returns cached start url for a currently installed scope`() = runBlocking { + val storage = spy(ManifestStorage(testContext)) + val dao = mockDatabase(storage) + + val manifest1 = ManifestEntity(manifest = firefoxManifest, createdAt = 0, updatedAt = 0) + val manifest2 = ManifestEntity(manifest = googleMapsManifest, createdAt = 0, updatedAt = 0) + val manifest3 = ManifestEntity(manifest = exampleWebAppManifest, createdAt = 0, updatedAt = 0) + + whenever(dao.getInstalledScopes(0)).thenReturn(listOf(manifest1, manifest2, manifest3)) + + storage.warmUpScopes(ManifestStorage.ACTIVE_THRESHOLD_MS) + + val result = storage.getStartUrlForInstalledScope("https://pwa.example.com/") + + assertEquals("https://pwa.example.com/dashboard", result) + } + private fun mockDatabase(storage: ManifestStorage): ManifestDao = mock().also { storage.manifestDao = lazy { it } } diff --git a/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/WebAppInterceptorTest.kt b/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/WebAppInterceptorTest.kt new file mode 100644 index 00000000000..45da0b4a8e8 --- /dev/null +++ b/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/WebAppInterceptorTest.kt @@ -0,0 +1,102 @@ +/* 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 mozilla.components.feature.pwa + +import android.content.Context +import androidx.test.ext.junit.runners.AndroidJUnit4 +import mozilla.components.concept.engine.EngineSession +import mozilla.components.concept.engine.request.RequestInterceptor +import mozilla.components.support.test.mock +import mozilla.components.support.test.whenever +import org.junit.Assert.assertNull +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class WebAppInterceptorTest { + private lateinit var mockContext: Context + private lateinit var mockEngineSession: EngineSession + private lateinit var mockManifestStorage: ManifestStorage + private lateinit var webAppInterceptor: WebAppInterceptor + + private val webUrl = "https://example.com" + private val webUrlWithWebApp = "https://google.com/maps/" + private val webUrlOutOfScope = "https://google.com/search/" + + @Before + fun setup() { + mockContext = mock() + mockEngineSession = mock() + mockManifestStorage = mock() + + webAppInterceptor = WebAppInterceptor( + context = mockContext, + manifestStorage = mockManifestStorage, + launchFromInterceptor = true + ) + } + + @Test + fun `request is intercepted when navigating to an installed web app`() { + whenever(mockManifestStorage.getInstalledScope(webUrlWithWebApp)).thenReturn(webUrlWithWebApp) + whenever(mockManifestStorage.getStartUrlForInstalledScope(webUrlWithWebApp)).thenReturn(webUrlWithWebApp) + + val response = webAppInterceptor.onLoadRequest(mockEngineSession, webUrlWithWebApp, true, false, false, false) + + assert(response is RequestInterceptor.InterceptionResponse.Deny) + } + + @Test + fun `request is not intercepted when url is out of scope`() { + whenever(mockManifestStorage.getInstalledScope(webUrlOutOfScope)).thenReturn(null) + whenever(mockManifestStorage.getStartUrlForInstalledScope(webUrlOutOfScope)).thenReturn(null) + + val response = webAppInterceptor.onLoadRequest(mockEngineSession, webUrlOutOfScope, true, false, false, false) + + assertNull(response) + } + + @Test + fun `request is not intercepted when url is not part of a web app`() { + whenever(mockManifestStorage.getInstalledScope(webUrl)).thenReturn(null) + whenever(mockManifestStorage.getStartUrlForInstalledScope(webUrl)).thenReturn(null) + + val response = webAppInterceptor.onLoadRequest(mockEngineSession, webUrl, true, false, false, false) + + assertNull(response) + } + + @Test + fun `request is intercepted with app intent if not launchFromInterceptor`() { + webAppInterceptor = WebAppInterceptor( + context = mockContext, + manifestStorage = mockManifestStorage, + launchFromInterceptor = false + ) + + whenever(mockManifestStorage.getInstalledScope(webUrlWithWebApp)).thenReturn(webUrlWithWebApp) + whenever(mockManifestStorage.getStartUrlForInstalledScope(webUrlWithWebApp)).thenReturn(webUrlWithWebApp) + + val response = webAppInterceptor.onLoadRequest(mockEngineSession, webUrlWithWebApp, true, false, false, false) + + assert(response is RequestInterceptor.InterceptionResponse.AppIntent) + } + + @Test + fun `launchFromInterceptor is enabled by default`() { + webAppInterceptor = WebAppInterceptor( + context = mockContext, + manifestStorage = mockManifestStorage + ) + + whenever(mockManifestStorage.getInstalledScope(webUrlWithWebApp)).thenReturn(webUrlWithWebApp) + whenever(mockManifestStorage.getStartUrlForInstalledScope(webUrlWithWebApp)).thenReturn(webUrlWithWebApp) + + val response = webAppInterceptor.onLoadRequest(mockEngineSession, webUrlWithWebApp, true, false, false, false) + + assert(response is RequestInterceptor.InterceptionResponse.Deny) + } +} diff --git a/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/intent/WebAppIntentProcessorTest.kt b/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/intent/WebAppIntentProcessorTest.kt index 6514edfcb47..4cc0b63aef6 100644 --- a/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/intent/WebAppIntentProcessorTest.kt +++ b/components/feature/pwa/src/test/java/mozilla/components/feature/pwa/intent/WebAppIntentProcessorTest.kt @@ -17,7 +17,11 @@ import mozilla.components.concept.engine.manifest.WebAppManifest import mozilla.components.feature.intent.ext.getSessionId import mozilla.components.feature.pwa.ManifestStorage import mozilla.components.feature.pwa.ext.getWebAppManifest +import mozilla.components.feature.pwa.ext.putUrlOverride import mozilla.components.feature.pwa.intent.WebAppIntentProcessor.Companion.ACTION_VIEW_PWA +import mozilla.components.feature.session.SessionUseCases +import mozilla.components.support.test.any +import mozilla.components.support.test.eq import mozilla.components.support.test.mock import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse @@ -26,6 +30,7 @@ import org.junit.Assert.assertTrue import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.`when` +import org.mockito.Mockito.verify @RunWith(AndroidJUnit4::class) @ExperimentalCoroutinesApi @@ -87,4 +92,26 @@ class WebAppIntentProcessorTest { assertNotNull(sessionState.config) assertEquals(ExternalAppType.PROGRESSIVE_WEB_APP, sessionState.config.externalAppType) } + + @Test + fun `url override is applied to session if present`() = runBlockingTest { + val storage: ManifestStorage = mock() + val loadUrlUseCase: SessionUseCases.DefaultLoadUrlUseCase = mock() + val processor = WebAppIntentProcessor(mock(), loadUrlUseCase, storage) + val urlOverride = "https://mozilla.com/deep/link/index.html" + + val manifest = WebAppManifest( + name = "Test Manifest", + startUrl = "https://mozilla.com" + ) + + `when`(storage.loadManifest("https://mozilla.com")).thenReturn(manifest) + + val intent = Intent(ACTION_VIEW_PWA, "https://mozilla.com".toUri()) + + intent.putUrlOverride(urlOverride) + + assertTrue(processor.process(intent)) + verify(loadUrlUseCase).invoke(eq(urlOverride), any(), any(), any()) + } } diff --git a/samples/browser/src/main/java/org/mozilla/samples/browser/DefaultComponents.kt b/samples/browser/src/main/java/org/mozilla/samples/browser/DefaultComponents.kt index 5261299ffe6..ec373936e50 100644 --- a/samples/browser/src/main/java/org/mozilla/samples/browser/DefaultComponents.kt +++ b/samples/browser/src/main/java/org/mozilla/samples/browser/DefaultComponents.kt @@ -53,6 +53,7 @@ import mozilla.components.feature.intent.processing.TabIntentProcessor import mozilla.components.feature.media.RecordingDevicesNotificationFeature import mozilla.components.feature.media.middleware.MediaMiddleware import mozilla.components.feature.pwa.ManifestStorage +import mozilla.components.feature.pwa.WebAppInterceptor import mozilla.components.feature.pwa.WebAppShortcutManager import mozilla.components.feature.pwa.WebAppUseCases import mozilla.components.feature.pwa.intent.TrustedWebActivityIntentProcessor @@ -209,6 +210,13 @@ open class DefaultComponents(private val applicationContext: Context) { ) } + val webAppInterceptor by lazy { + WebAppInterceptor( + applicationContext, + webAppManifestStorage + ) + } + val webAppManifestStorage by lazy { ManifestStorage(applicationContext) } val webAppShortcutManager by lazy { WebAppShortcutManager(applicationContext, client, webAppManifestStorage) } val webAppUseCases by lazy { WebAppUseCases(applicationContext, sessionManager, webAppShortcutManager) } diff --git a/samples/browser/src/main/java/org/mozilla/samples/browser/SampleApplication.kt b/samples/browser/src/main/java/org/mozilla/samples/browser/SampleApplication.kt index 5af72515bc4..a585158e7cf 100644 --- a/samples/browser/src/main/java/org/mozilla/samples/browser/SampleApplication.kt +++ b/samples/browser/src/main/java/org/mozilla/samples/browser/SampleApplication.kt @@ -5,6 +5,9 @@ package org.mozilla.samples.browser import android.app.Application +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import mozilla.appservices.Megazord import mozilla.components.browser.session.Session import mozilla.components.concept.fetch.Client @@ -51,6 +54,10 @@ class SampleApplication : Application() { components.engine.warmUp() + GlobalScope.launch(Dispatchers.IO) { + components.webAppManifestStorage.warmUpScopes(System.currentTimeMillis()) + } + try { GlobalAddonDependencyProvider.initialize( components.addonManager, diff --git a/samples/browser/src/main/java/org/mozilla/samples/browser/request/SampleRequestInterceptor.kt b/samples/browser/src/main/java/org/mozilla/samples/browser/request/SampleRequestInterceptor.kt index 654fd47cbcf..eb32873cebc 100644 --- a/samples/browser/src/main/java/org/mozilla/samples/browser/request/SampleRequestInterceptor.kt +++ b/samples/browser/src/main/java/org/mozilla/samples/browser/request/SampleRequestInterceptor.kt @@ -27,8 +27,17 @@ class SampleRequestInterceptor(val context: Context) : RequestInterceptor { ): InterceptionResponse? { return when (uri) { "sample:about" -> InterceptionResponse.Content("

I am the sample browser

") - else -> context.components.appLinksInterceptor.onLoadRequest( - engineSession, uri, hasUserGesture, isSameDomain, isRedirect, isDirectNavigation) + else -> { + var response = context.components.appLinksInterceptor.onLoadRequest( + engineSession, uri, hasUserGesture, isSameDomain, isRedirect, isDirectNavigation) + + if (response == null && !isDirectNavigation) { + response = context.components.webAppInterceptor.onLoadRequest( + engineSession, uri, hasUserGesture, isSameDomain, isRedirect, isDirectNavigation) + } + + response + } } }