From 3d7ad9803aef43509d428512e8ce69e6be2b794e Mon Sep 17 00:00:00 2001 From: Mugurell Date: Thu, 28 Oct 2021 10:49:15 +0300 Subject: [PATCH] [components] For https://github.com/mozilla-mobile/android-components/issues/10749 - New NonBlockingHttpIconLoader This will download and persist the network resources in the background without blocking actually returning a response. This means the first request for a network resource will result in a generated icon but future requests of the same resources should be able to load the locally persisted icon. --- .../components/browser/icons/build.gradle | 1 + .../components/browser/icons/BrowserIcons.kt | 42 ++- .../components/browser/icons/IconRequest.kt | 6 +- .../browser/icons/loader/HttpIconLoader.kt | 8 +- .../icons/loader/NonBlockingHttpIconLoader.kt | 43 +++ .../components/browser/icons/BrowserIconsTest.kt | 15 + .../icons/loader/NonBlockingHttpIconLoaderTest.kt | 310 +++++++++++++++++++++ .../provider/BookmarksStorageSuggestionProvider.kt | 2 +- .../provider/HistoryMetadataSuggestionProvider.kt | 2 +- .../provider/HistoryStorageSuggestionProvider.kt | 2 +- .../provider/SessionSuggestionProvider.kt | 4 +- .../SyncedTabsStorageSuggestionProvider.kt | 6 +- 12 files changed, 427 insertions(+), 14 deletions(-) create mode 100644 mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/loader/NonBlockingHttpIconLoader.kt create mode 100644 mobile/android/android-components/components/browser/icons/src/test/java/mozilla/components/browser/icons/loader/NonBlockingHttpIconLoaderTest.kt diff --git a/mobile/android/android-components/components/browser/icons/build.gradle b/mobile/android/android-components/components/browser/icons/build.gradle index 5a5c0e47807a..3777dfe554ef 100644 --- a/mobile/android/android-components/components/browser/icons/build.gradle +++ b/mobile/android/android-components/components/browser/icons/build.gradle @@ -72,6 +72,7 @@ dependencies { testImplementation Dependencies.testing_mockito testImplementation Dependencies.testing_mockwebserver testImplementation Dependencies.testing_robolectric + testImplementation Dependencies.testing_coroutines androidTestImplementation Dependencies.androidx_test_core androidTestImplementation Dependencies.androidx_test_runner diff --git a/mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/BrowserIcons.kt b/mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/BrowserIcons.kt index 7ab29aef5d51..394a8ed09d78 100644 --- a/mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/BrowserIcons.kt +++ b/mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/BrowserIcons.kt @@ -10,6 +10,7 @@ import android.graphics.Bitmap import android.graphics.drawable.Drawable import android.widget.ImageView import androidx.annotation.MainThread +import androidx.annotation.VisibleForTesting import androidx.annotation.WorkerThread import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineDispatcher @@ -32,6 +33,7 @@ import mozilla.components.browser.icons.loader.DiskIconLoader import mozilla.components.browser.icons.loader.HttpIconLoader import mozilla.components.browser.icons.loader.IconLoader import mozilla.components.browser.icons.loader.MemoryIconLoader +import mozilla.components.browser.icons.loader.NonBlockingHttpIconLoader import mozilla.components.browser.icons.pipeline.IconResourceComparator import mozilla.components.browser.icons.preparer.DiskIconPreparer import mozilla.components.browser.icons.preparer.IconPreprarer @@ -59,7 +61,8 @@ import mozilla.components.support.ktx.kotlinx.coroutines.flow.filterChanged import java.lang.ref.WeakReference import java.util.concurrent.Executors -private const val MAXIMUM_SCALE_FACTOR = 2.0f +@VisibleForTesting +internal const val MAXIMUM_SCALE_FACTOR = 2.0f private const val EXTENSION_MESSAGING_NAME = "MozacBrowserIcons" @@ -77,14 +80,15 @@ internal val sharedDiskCache = IconDiskCache() */ class BrowserIcons @Suppress("LongParameterList") constructor( private val context: Context, - private val httpClient: Client, + httpClient: Client, private val generator: IconGenerator = DefaultIconGenerator(), private val preparers: List = listOf( TippyTopIconPreparer(context.assets), MemoryIconPreparer(sharedMemoryCache), DiskIconPreparer(sharedDiskCache) ), - private val loaders: List = listOf( + @VisibleForTesting + internal var loaders: List = listOf( MemoryIconLoader(sharedMemoryCache), DiskIconLoader(sharedDiskCache), HttpIconLoader(httpClient), @@ -107,6 +111,14 @@ class BrowserIcons @Suppress("LongParameterList") constructor( private val maximumSize = context.resources.getDimensionPixelSize(R.dimen.mozac_browser_icons_maximum_size) private val minimumSize = context.resources.getDimensionPixelSize(R.dimen.mozac_browser_icons_minimum_size) private val scope = CoroutineScope(jobDispatcher) + private val backgroundHttpIconLoader = NonBlockingHttpIconLoader(httpClient) { request, resource, result -> + val desiredSize = request.getDesiredSize(context, minimumSize, maximumSize) + + val icon = decodeIconLoaderResult(result, decoders, desiredSize) + ?: generator.generate(context, request) + + process(context, processors, request, resource, icon, desiredSize) + } /** * Asynchronously loads an [Icon] for the given [IconRequest]. @@ -129,11 +141,20 @@ class BrowserIcons @Suppress("LongParameterList") constructor( // (1) First prepare the request. val request = prepare(context, preparers, initialRequest) - // (2) Then try to load an icon. - val (icon, resource) = load(context, request, loaders, decoders, desiredSize) + // (2) Check whether icons should be downloaded in background. + val updatedLoaders = loaders.map { + if (it is HttpIconLoader && !initialRequest.waitOnNetworkLoad) { + backgroundHttpIconLoader + } else { + it + } + } + + // (3) Then try to load an icon. + val (icon, resource) = load(context, request, updatedLoaders, decoders, desiredSize) ?: generator.generate(context, request) to null - // (3) Finally process the icon. + // (4) Finally process the icon. return process(context, processors, request, resource, icon, desiredSize) ?: generator.generate(context, request) } @@ -317,6 +338,15 @@ private fun decodeIconLoaderResult( decodeBytes(result.bytes, decoders, desiredSize)?.let { Icon(it, source = result.source) } } +@VisibleForTesting +internal fun IconRequest.getDesiredSize(context: Context, minimumSize: Int, maximumSize: Int) = + DesiredSize( + targetSize = context.resources.getDimensionPixelSize(size.dimen), + minSize = minimumSize, + maxSize = maximumSize, + maxScaleFactor = MAXIMUM_SCALE_FACTOR + ) + private fun decodeBytes( data: ByteArray, decoders: List, diff --git a/mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/IconRequest.kt b/mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/IconRequest.kt index 7b1c96cc7443..d9faae366a4d 100644 --- a/mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/IconRequest.kt +++ b/mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/IconRequest.kt @@ -15,13 +15,17 @@ import mozilla.components.concept.engine.manifest.Size as HtmlSize * @property size The preferred size of the icon that should be loaded. * @property resources An optional list of icon resources to load the icon from. * @property color The suggested dominant color of the icon. + * @property isPrivate Whether this request for this icon came from a private session. + * @property waitOnNetworkLoad Whether client code should wait on the resource being loaded or + * loading can continue in background. */ data class IconRequest( val url: String, val size: Size = Size.DEFAULT, val resources: List = emptyList(), @ColorInt val color: Int? = null, - val isPrivate: Boolean = false + val isPrivate: Boolean = false, + val waitOnNetworkLoad: Boolean = true ) { /** diff --git a/mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/loader/HttpIconLoader.kt b/mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/loader/HttpIconLoader.kt index 398184a08262..c00d28193954 100644 --- a/mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/loader/HttpIconLoader.kt +++ b/mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/loader/HttpIconLoader.kt @@ -27,7 +27,7 @@ private const val READ_TIMEOUT = 10L // Seconds /** * [IconLoader] implementation that will try to download the icon for resources that point to an http(s) URL. */ -class HttpIconLoader( +open class HttpIconLoader( private val httpClient: Client ) : IconLoader { private val logger = Logger("HttpIconLoader") @@ -41,6 +41,10 @@ class HttpIconLoader( // Right now we always perform a download. We shouldn't retry to download from URLs that have failed just // recently: https://github.com/mozilla-mobile/android-components/issues/2591 + return internalLoad(request, resource) + } + + protected fun internalLoad(request: IconRequest, resource: IconRequest.Resource): IconLoader.Result { val downloadRequest = Request( url = resource.url.sanitizeURL(), method = Request.Method.GET, @@ -70,7 +74,7 @@ class HttpIconLoader( } } - private fun shouldDownload(resource: IconRequest.Resource): Boolean { + protected fun shouldDownload(resource: IconRequest.Resource): Boolean { return resource.url.sanitizeURL().toUri().isHttpOrHttps && !failureCache.hasFailedRecently(resource.url) } } diff --git a/mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/loader/NonBlockingHttpIconLoader.kt b/mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/loader/NonBlockingHttpIconLoader.kt new file mode 100644 index 000000000000..4ac66eed1f0c --- /dev/null +++ b/mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/loader/NonBlockingHttpIconLoader.kt @@ -0,0 +1,43 @@ +/* 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.browser.icons.loader + +import android.content.Context +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import mozilla.components.browser.icons.IconRequest +import mozilla.components.concept.fetch.Client + +/** + * [HttpIconLoader] variation that will immediately resolve a [load] request with [IconLoader.Result.NoResult] + * and then continue to actually download the icon in the background finally calling [loadCallback] + * with the actual result and details about the request. + * + * @property httpClient [Client] used for downloading the icon. + * @property scope [CoroutineScope] used for downloading the icon in the background. + * Defaults to a new scope using [Dispatchers.IO] for allowing multiple requests to block their threads + * while waiting for the download to complete. + * @property loadCallback Callback for when the network icon finished downloading or an error or timeout occurred. + */ +class NonBlockingHttpIconLoader( + httpClient: Client, + private val scope: CoroutineScope = CoroutineScope(Dispatchers.IO), + private val loadCallback: (IconRequest, IconRequest.Resource, IconLoader.Result) -> Unit +) : HttpIconLoader(httpClient) { + override fun load(context: Context, request: IconRequest, resource: IconRequest.Resource): IconLoader.Result { + if (!shouldDownload(resource)) { + return IconLoader.Result.NoResult + } + + scope.launch { + val icon = internalLoad(request, resource) + + loadCallback(request, resource, icon) + } + + return IconLoader.Result.NoResult + } +} diff --git a/mobile/android/android-components/components/browser/icons/src/test/java/mozilla/components/browser/icons/BrowserIconsTest.kt b/mobile/android/android-components/components/browser/icons/src/test/java/mozilla/components/browser/icons/BrowserIconsTest.kt index ad4f51098a4e..263405dc5d92 100644 --- a/mobile/android/android-components/components/browser/icons/src/test/java/mozilla/components/browser/icons/BrowserIconsTest.kt +++ b/mobile/android/android-components/components/browser/icons/src/test/java/mozilla/components/browser/icons/BrowserIconsTest.kt @@ -309,4 +309,19 @@ class BrowserIconsTest { assertEquals(0, sharedDiskCache.getResources(testContext, request).size) assertEquals(0, sharedMemoryCache.getResources(request).size) } + + @Test + fun `GIVEN an IconRequest WHEN getDesiredSize is called THEN set min and max bounds to the request target size`() { + val request = IconRequest("https://mozilla.org", IconRequest.Size.LAUNCHER_ADAPTIVE) + + val result = request.getDesiredSize(testContext, 11, 101) + + assertEquals( + testContext.resources.getDimensionPixelSize(IconRequest.Size.LAUNCHER_ADAPTIVE.dimen), + result.targetSize + ) + assertEquals(11, result.minSize) + assertEquals(101, result.maxSize) + assertEquals(MAXIMUM_SCALE_FACTOR, result.maxScaleFactor) + } } diff --git a/mobile/android/android-components/components/browser/icons/src/test/java/mozilla/components/browser/icons/loader/NonBlockingHttpIconLoaderTest.kt b/mobile/android/android-components/components/browser/icons/src/test/java/mozilla/components/browser/icons/loader/NonBlockingHttpIconLoaderTest.kt new file mode 100644 index 000000000000..61e3de9872a8 --- /dev/null +++ b/mobile/android/android-components/components/browser/icons/src/test/java/mozilla/components/browser/icons/loader/NonBlockingHttpIconLoaderTest.kt @@ -0,0 +1,310 @@ +/* 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.browser.icons.loader + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestCoroutineScope +import kotlinx.coroutines.test.runBlockingTest +import mozilla.components.browser.icons.Icon +import mozilla.components.browser.icons.IconRequest +import mozilla.components.concept.fetch.Client +import mozilla.components.concept.fetch.MutableHeaders +import mozilla.components.concept.fetch.Request +import mozilla.components.concept.fetch.Response +import mozilla.components.lib.fetch.httpurlconnection.HttpURLConnectionClient +import mozilla.components.lib.fetch.okhttp.OkHttpClient +import mozilla.components.support.test.any +import mozilla.components.support.test.argumentCaptor +import mozilla.components.support.test.mock +import mozilla.components.support.test.robolectric.testContext +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Assert.assertSame +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.doThrow +import org.mockito.Mockito.never +import org.mockito.Mockito.reset +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions +import java.io.IOException +import java.io.InputStream + +@ExperimentalCoroutinesApi +@RunWith(AndroidJUnit4::class) +class NonBlockingHttpIconLoaderTest { + val scope = TestCoroutineScope() + + @Test + fun `Loader will return IconLoader#Result#NoResult for a load request and respond with the result through a callback`() = runBlockingTest { + val clients = listOf( + HttpURLConnectionClient(), + OkHttpClient() + ) + + clients.forEach { client -> + + val server = MockWebServer() + + server.enqueue( + MockResponse().setBody( + javaClass.getResourceAsStream("/misc/test.txt")!! + .bufferedReader() + .use { it.readText() } + ) + ) + + server.start() + + try { + var callbackIconRequest: IconRequest? = null + var callbackResource: IconRequest.Resource? = null + var callbackIcon: IconLoader.Result? = null + val loader = NonBlockingHttpIconLoader(client, scope) { request, resource, icon -> + callbackIconRequest = request + callbackResource = resource + callbackIcon = icon + } + val iconRequest: IconRequest = mock() + + val result = loader.load( + mock(), iconRequest, + IconRequest.Resource( + url = server.url("/some/path").toString(), + type = IconRequest.Resource.Type.APPLE_TOUCH_ICON + ) + ) + + assertTrue(result is IconLoader.Result.NoResult) + val downloadedResource = String(((callbackIcon as IconLoader.Result.BytesResult).bytes), Charsets.UTF_8) + assertEquals("Hello World!", downloadedResource) + assertSame(Icon.Source.DOWNLOAD, ((callbackIcon as IconLoader.Result.BytesResult).source)) + assertTrue(callbackResource!!.url.endsWith("/some/path")) + assertSame(IconRequest.Resource.Type.APPLE_TOUCH_ICON, callbackResource?.type) + assertSame(iconRequest, callbackIconRequest) + } finally { + server.shutdown() + } + } + } + + @Test + fun `Loader will not perform any requests for data uris`() = runBlockingTest { + val client: Client = mock() + var callbackIconRequest: IconRequest? = null + var callbackResource: IconRequest.Resource? = null + var callbackIcon: IconLoader.Result? = null + val loader = NonBlockingHttpIconLoader(client, scope) { request, resource, icon -> + callbackIconRequest = request + callbackResource = resource + callbackIcon = icon + } + + val result = loader.load( + mock(), mock(), + IconRequest.Resource( + url = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAA" + + "AAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", + type = IconRequest.Resource.Type.FAVICON + ) + ) + + assertEquals(IconLoader.Result.NoResult, result) + assertNull(callbackIconRequest) + assertNull(callbackResource) + assertNull(callbackIcon) + verify(client, never()).fetch(any()) + } + + @Test + fun `Request has timeouts applied`() = runBlockingTest { + val client: Client = mock() + val loader = NonBlockingHttpIconLoader(client, scope) { _, _, _ -> } + doReturn( + Response( + url = "https://www.example.org", + headers = MutableHeaders(), + status = 404, + body = Response.Body.empty() + ) + ).`when`(client).fetch(any()) + + loader.load( + mock(), mock(), + IconRequest.Resource( + url = "https://www.example.org", + type = IconRequest.Resource.Type.APPLE_TOUCH_ICON + ) + ) + + val captor = argumentCaptor() + verify(client).fetch(captor.capture()) + val request = captor.value + assertNotNull(request) + assertNotNull(request.connectTimeout) + assertNotNull(request.readTimeout) + } + + @Test + fun `NoResult is returned for non-successful requests`() = runBlockingTest { + val client: Client = mock() + var callbackIconRequest: IconRequest? = null + var callbackResource: IconRequest.Resource? = null + var callbackIcon: IconLoader.Result? = null + val loader = NonBlockingHttpIconLoader(client, scope) { request, resource, icon -> + callbackIconRequest = request + callbackResource = resource + callbackIcon = icon + } + doReturn( + Response( + url = "https://www.example.org", + headers = MutableHeaders(), + status = 404, + body = Response.Body.empty() + ) + ).`when`(client).fetch(any()) + + val result = loader.load( + mock(), mock(), + IconRequest.Resource( + url = "https://www.example.org", + type = IconRequest.Resource.Type.APPLE_TOUCH_ICON + ) + ) + + assertEquals(IconLoader.Result.NoResult, result) + assertEquals(IconLoader.Result.NoResult, callbackIcon) + assertNotNull(callbackIconRequest) + assertEquals("https://www.example.org", callbackResource!!.url) + assertSame(IconRequest.Resource.Type.APPLE_TOUCH_ICON, callbackResource?.type) + } + + @Test + fun `Loader will not try to load URL again that just recently failed`() = runBlockingTest { + val client: Client = mock() + val loader = NonBlockingHttpIconLoader(client, scope) { _, _, _ -> } + doReturn( + Response( + url = "https://www.example.org", + headers = MutableHeaders(), + status = 404, + body = Response.Body.empty() + ) + ).`when`(client).fetch(any()) + val resource = IconRequest.Resource( + url = "https://www.example.org", + type = IconRequest.Resource.Type.APPLE_TOUCH_ICON + ) + + val result = loader.load(mock(), mock(), resource) + + assertEquals(IconLoader.Result.NoResult, result) + // First load tries to fetch, but load fails (404) + verify(client).fetch(any()) + verifyNoMoreInteractions(client) + reset(client) + assertEquals(IconLoader.Result.NoResult, loader.load(mock(), mock(), resource)) + // Second load does not try to fetch again. + verify(client, never()).fetch(any()) + } + + @Test + fun `Loader will return NoResult for IOExceptions happening during fetch`() = runBlockingTest { + val client: Client = mock() + doThrow(IOException("Mock")).`when`(client).fetch(any()) + var callbackIconRequest: IconRequest? = null + var callbackResource: IconRequest.Resource? = null + var callbackIcon: IconLoader.Result? = null + val loader = NonBlockingHttpIconLoader(client, scope) { request, resource, icon -> + callbackIconRequest = request + callbackResource = resource + callbackIcon = icon + } + + val resource = IconRequest.Resource( + url = "https://www.example.org", + type = IconRequest.Resource.Type.APPLE_TOUCH_ICON + ) + + val result = loader.load(testContext, mock(), resource) + assertEquals(IconLoader.Result.NoResult, result) + assertEquals(IconLoader.Result.NoResult, callbackIcon) + assertNotNull(callbackIconRequest) + assertEquals("https://www.example.org", callbackResource!!.url) + assertSame(IconRequest.Resource.Type.APPLE_TOUCH_ICON, callbackResource?.type) + } + + @Test + fun `Loader will return NoResult for IOExceptions happening during toIconLoaderResult`() = runBlockingTest { + val client: Client = mock() + var callbackIconRequest: IconRequest? = null + var callbackResource: IconRequest.Resource? = null + var callbackIcon: IconLoader.Result? = null + val loader = NonBlockingHttpIconLoader(client, scope) { request, resource, icon -> + callbackIconRequest = request + callbackResource = resource + callbackIcon = icon + } + val failingStream: InputStream = object : InputStream() { + override fun read(): Int { + throw IOException("Kaboom") + } + } + doReturn( + Response( + url = "https://www.example.org", + headers = MutableHeaders(), + status = 200, + body = Response.Body(failingStream) + ) + ).`when`(client).fetch(any()) + val resource = IconRequest.Resource( + url = "https://www.example.org", + type = IconRequest.Resource.Type.APPLE_TOUCH_ICON + ) + + val result = loader.load(testContext, mock(), resource) + + assertEquals(IconLoader.Result.NoResult, result) + assertEquals(IconLoader.Result.NoResult, callbackIcon) + assertNotNull(callbackIconRequest) + assertEquals("https://www.example.org", callbackResource!!.url) + assertSame(IconRequest.Resource.Type.APPLE_TOUCH_ICON, callbackResource?.type) + } + + @Test + fun `Loader will sanitize URL`() = runBlockingTest { + val client: Client = mock() + val captor = argumentCaptor() + val loader = NonBlockingHttpIconLoader(client, scope) { _, _, _ -> } + doReturn( + Response( + url = "https://www.example.org", + headers = MutableHeaders(), + status = 404, + body = Response.Body.empty() + ) + ).`when`(client).fetch(any()) + + loader.load( + mock(), mock(), + IconRequest.Resource( + url = " \n\n https://www.example.org \n\n ", + type = IconRequest.Resource.Type.APPLE_TOUCH_ICON + ) + ) + + verify(client).fetch(captor.capture()) + val request = captor.value + assertEquals("https://www.example.org", request.url) + } +} diff --git a/mobile/android/android-components/components/feature/awesomebar/src/main/java/mozilla/components/feature/awesomebar/provider/BookmarksStorageSuggestionProvider.kt b/mobile/android/android-components/components/feature/awesomebar/src/main/java/mozilla/components/feature/awesomebar/provider/BookmarksStorageSuggestionProvider.kt index 2b601350a8a5..0c41fa9cf7c4 100644 --- a/mobile/android/android-components/components/feature/awesomebar/src/main/java/mozilla/components/feature/awesomebar/provider/BookmarksStorageSuggestionProvider.kt +++ b/mobile/android/android-components/components/feature/awesomebar/src/main/java/mozilla/components/feature/awesomebar/provider/BookmarksStorageSuggestionProvider.kt @@ -59,7 +59,7 @@ class BookmarksStorageSuggestionProvider( * Expects list of BookmarkNode to be specifically of bookmarks (e.g. nodes with a url). */ private suspend fun List.into(): List { - val iconRequests = this.map { icons?.loadIcon(IconRequest(it.url!!)) } + val iconRequests = this.map { icons?.loadIcon(IconRequest(url = it.url!!, waitOnNetworkLoad = false)) } return this.zip(iconRequests) { result, icon -> AwesomeBar.Suggestion( diff --git a/mobile/android/android-components/components/feature/awesomebar/src/main/java/mozilla/components/feature/awesomebar/provider/HistoryMetadataSuggestionProvider.kt b/mobile/android/android-components/components/feature/awesomebar/src/main/java/mozilla/components/feature/awesomebar/provider/HistoryMetadataSuggestionProvider.kt index e3c28db26ad4..5263c016473f 100644 --- a/mobile/android/android-components/components/feature/awesomebar/src/main/java/mozilla/components/feature/awesomebar/provider/HistoryMetadataSuggestionProvider.kt +++ b/mobile/android/android-components/components/feature/awesomebar/src/main/java/mozilla/components/feature/awesomebar/provider/HistoryMetadataSuggestionProvider.kt @@ -63,7 +63,7 @@ internal suspend fun Iterable.into( icons: BrowserIcons?, loadUrlUseCase: SessionUseCases.LoadUrlUseCase ): List { - val iconRequests = this.map { icons?.loadIcon(IconRequest(it.key.url)) } + val iconRequests = this.map { icons?.loadIcon(IconRequest(url = it.key.url, waitOnNetworkLoad = false)) } return this.zip(iconRequests) { result, icon -> AwesomeBar.Suggestion( provider = provider, diff --git a/mobile/android/android-components/components/feature/awesomebar/src/main/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProvider.kt b/mobile/android/android-components/components/feature/awesomebar/src/main/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProvider.kt index d34a55b2fa3f..454e96cf4b42 100644 --- a/mobile/android/android-components/components/feature/awesomebar/src/main/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProvider.kt +++ b/mobile/android/android-components/components/feature/awesomebar/src/main/java/mozilla/components/feature/awesomebar/provider/HistoryStorageSuggestionProvider.kt @@ -68,7 +68,7 @@ internal suspend fun Iterable.into( icons: BrowserIcons?, loadUrlUseCase: SessionUseCases.LoadUrlUseCase ): List { - val iconRequests = this.map { icons?.loadIcon(IconRequest(it.url)) } + val iconRequests = this.map { icons?.loadIcon(IconRequest(url = it.url, waitOnNetworkLoad = false)) } return this.zip(iconRequests) { result, icon -> AwesomeBar.Suggestion( provider = provider, diff --git a/mobile/android/android-components/components/feature/awesomebar/src/main/java/mozilla/components/feature/awesomebar/provider/SessionSuggestionProvider.kt b/mobile/android/android-components/components/feature/awesomebar/src/main/java/mozilla/components/feature/awesomebar/provider/SessionSuggestionProvider.kt index f92dd03b2ba9..1d87b2221abd 100644 --- a/mobile/android/android-components/components/feature/awesomebar/src/main/java/mozilla/components/feature/awesomebar/provider/SessionSuggestionProvider.kt +++ b/mobile/android/android-components/components/feature/awesomebar/src/main/java/mozilla/components/feature/awesomebar/provider/SessionSuggestionProvider.kt @@ -43,7 +43,9 @@ class SessionSuggestionProvider( val tabs = state.tabs val suggestions = mutableListOf() - val iconRequests: List?> = tabs.map { icons?.loadIcon(IconRequest(it.content.url)) } + val iconRequests: List?> = tabs.map { + icons?.loadIcon(IconRequest(url = it.content.url, waitOnNetworkLoad = false)) + } tabs.zip(iconRequests) { result, icon -> if ( diff --git a/mobile/android/android-components/components/feature/syncedtabs/src/main/java/mozilla/components/feature/syncedtabs/SyncedTabsStorageSuggestionProvider.kt b/mobile/android/android-components/components/feature/syncedtabs/src/main/java/mozilla/components/feature/syncedtabs/SyncedTabsStorageSuggestionProvider.kt index 7f3d70d1d76f..f4085637762c 100644 --- a/mobile/android/android-components/components/feature/syncedtabs/src/main/java/mozilla/components/feature/syncedtabs/SyncedTabsStorageSuggestionProvider.kt +++ b/mobile/android/android-components/components/feature/syncedtabs/src/main/java/mozilla/components/feature/syncedtabs/SyncedTabsStorageSuggestionProvider.kt @@ -60,7 +60,11 @@ class SyncedTabsStorageSuggestionProvider( */ private suspend fun List.into(): List { val iconRequests = this.map { client -> - client.tab.iconUrl?.let { iconUrl -> icons?.loadIcon(IconRequest(iconUrl)) } + client.tab.iconUrl?.let { iconUrl -> + icons?.loadIcon( + IconRequest(url = iconUrl, waitOnNetworkLoad = false) + ) + } } return this.zip(iconRequests) { result, icon -> -- 2.11.4.GIT