diff options
Diffstat (limited to 'mobile/android/android-components/components/browser/icons')
8 files changed, 343 insertions, 77 deletions
diff --git a/mobile/android/android-components/components/browser/icons/src/main/assets/extensions/browser-icons/icons.js b/mobile/android/android-components/components/browser/icons/src/main/assets/extensions/browser-icons/icons.js index 20eada9a19..bc2c6ee35e 100644 --- a/mobile/android/android-components/components/browser/icons/src/main/assets/extensions/browser-icons/icons.js +++ b/mobile/android/android-components/components/browser/icons/src/main/assets/extensions/browser-icons/icons.js @@ -2,80 +2,81 @@ * 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/. */ - /* - * This web extension looks for known icon tags, collects URLs and available - * meta data (e.g. sizes) and passes that to the app code. - */ +/* + * This web extension looks for known icon tags, collects URLs and available + * meta data (e.g. sizes) and passes that to the app code. + */ /** * Takes a DOMTokenList and returns a String array. */ function sizesToList(sizes) { - if (sizes == null) { - return [] - } + if (sizes == null) { + return []; + } - if (!(sizes instanceof DOMTokenList)) { - return [] - } + if (!(sizes instanceof DOMTokenList)) { + return []; + } - return Array.from(sizes) + return Array.from(sizes); } function collect_link_icons(icons, rel) { - document.querySelectorAll('link[rel="' + rel + '"]').forEach( - function(currentValue, currentIndex, listObj) { - icons.push({ - 'type': rel, - 'href': currentValue.href, - 'sizes': sizesToList(currentValue.sizes), - 'mimeType': currentValue.type - }); - }) + document + .querySelectorAll('link[rel="' + rel + '"]') + .forEach(function (currentValue, currentIndex, listObj) { + icons.push({ + type: rel, + href: currentValue.href, + sizes: sizesToList(currentValue.sizes), + mimeType: currentValue.type, + }); + }); } function collect_meta_property_icons(icons, property) { - document.querySelectorAll('meta[property="' + property + '"]').forEach( - function(currentValue, currentIndex, listObj) { - icons.push({ - 'type': property, - 'href': currentValue.content - }) - } - ) + document + .querySelectorAll('meta[property="' + property + '"]') + .forEach(function (currentValue, currentIndex, listObj) { + icons.push({ + type: property, + href: currentValue.content, + }); + }); } function collect_meta_name_icons(icons, name) { - document.querySelectorAll('meta[name="' + name + '"]').forEach( - function(currentValue, currentIndex, listObj) { - icons.push({ - 'type': name, - 'href': currentValue.content - }) - } - ) + document + .querySelectorAll('meta[name="' + name + '"]') + .forEach(function (currentValue, currentIndex, listObj) { + icons.push({ + type: name, + href: currentValue.content, + }); + }); } let icons = []; -collect_link_icons(icons, 'icon'); -collect_link_icons(icons, 'shortcut icon'); -collect_link_icons(icons, 'fluid-icon') -collect_link_icons(icons, 'apple-touch-icon') -collect_link_icons(icons, 'image_src') -collect_link_icons(icons, 'apple-touch-icon image_src') -collect_link_icons(icons, 'apple-touch-icon-precomposed') +collect_link_icons(icons, "icon"); +collect_link_icons(icons, "shortcut icon"); +collect_link_icons(icons, "fluid-icon"); +collect_link_icons(icons, "apple-touch-icon"); +collect_link_icons(icons, "image_src"); +collect_link_icons(icons, "apple-touch-icon image_src"); +collect_link_icons(icons, "apple-touch-icon-precomposed"); -collect_meta_property_icons(icons, 'og:image') -collect_meta_property_icons(icons, 'og:image:url') -collect_meta_property_icons(icons, 'og:image:secure_url') +collect_meta_property_icons(icons, "og:image"); +collect_meta_property_icons(icons, "og:image:url"); +collect_meta_property_icons(icons, "og:image:secure_url"); -collect_meta_name_icons(icons, 'twitter:image') -collect_meta_name_icons(icons, 'msapplication-TileImage') +collect_meta_name_icons(icons, "twitter:image"); +collect_meta_name_icons(icons, "msapplication-TileImage"); let message = { - 'url': document.location.href, - 'icons': icons -} + url: document.location.href, + icons: icons, +}; browser.runtime.sendNativeMessage("MozacBrowserIcons", message); 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 3bf5c2b2ff..fe44cfc6be 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 @@ -38,10 +38,12 @@ import mozilla.components.browser.icons.extension.IconMessageHandler import mozilla.components.browser.icons.generator.DefaultIconGenerator import mozilla.components.browser.icons.generator.IconGenerator import mozilla.components.browser.icons.loader.DataUriIconLoader +import mozilla.components.browser.icons.loader.DefaultMemoryInfoProvider 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.MemoryInfoProvider import mozilla.components.browser.icons.loader.NonBlockingHttpIconLoader import mozilla.components.browser.icons.pipeline.IconResourceComparator import mozilla.components.browser.icons.preparer.DiskIconPreparer @@ -91,6 +93,7 @@ class BrowserIcons constructor( private val context: Context, httpClient: Client, private val generator: IconGenerator = DefaultIconGenerator(), + private val memoryInfoProvider: MemoryInfoProvider = DefaultMemoryInfoProvider(context), private val preparers: List<IconPreprarer> = listOf( TippyTopIconPreparer(context.assets), MemoryIconPreparer(sharedMemoryCache), @@ -99,7 +102,10 @@ class BrowserIcons constructor( internal var loaders: List<IconLoader> = listOf( MemoryIconLoader(sharedMemoryCache), DiskIconLoader(sharedDiskCache), - HttpIconLoader(httpClient), + HttpIconLoader( + httpClient = httpClient, + memoryInfoProvider = memoryInfoProvider, + ), DataUriIconLoader(), ), private val decoders: List<ImageDecoder> = listOf( @@ -120,7 +126,10 @@ class BrowserIcons 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 -> + private val backgroundHttpIconLoader = NonBlockingHttpIconLoader( + httpClient = httpClient, + memoryInfoProvider = DefaultMemoryInfoProvider(context), + ) { request, resource, result -> val desiredSize = request.getDesiredSize(context, minimumSize, maximumSize) val icon = decodeIconLoaderResult(result, decoders, desiredSize) 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 430f46f3ec..d3217bc4b2 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 @@ -12,26 +12,30 @@ import androidx.core.net.toUri import mozilla.components.browser.icons.Icon import mozilla.components.browser.icons.IconRequest import mozilla.components.concept.fetch.Client +import mozilla.components.concept.fetch.Headers import mozilla.components.concept.fetch.Request import mozilla.components.concept.fetch.Response import mozilla.components.concept.fetch.isSuccess import mozilla.components.support.base.log.logger.Logger import mozilla.components.support.ktx.android.net.isHttpOrHttps import mozilla.components.support.ktx.kotlin.sanitizeURL +import java.io.ByteArrayOutputStream import java.io.IOException import java.util.concurrent.TimeUnit private const val CONNECT_TIMEOUT = 2L // Seconds private const val READ_TIMEOUT = 10L // Seconds +private const val MAX_DOWNLOAD_BYTES = 1048576 // 1MB /** * [IconLoader] implementation that will try to download the icon for resources that point to an http(s) URL. */ open class HttpIconLoader( private val httpClient: Client, + private val memoryInfoProvider: MemoryInfoProvider, ) : IconLoader { - private val logger = Logger("HttpIconLoader") private val failureCache = FailureCache() + private val logger = Logger("HttpIconLoader") override fun load(context: Context, request: IconRequest, resource: IconRequest.Resource): IconLoader.Result { if (!shouldDownload(resource)) { @@ -78,10 +82,45 @@ open class HttpIconLoader( protected fun shouldDownload(resource: IconRequest.Resource): Boolean { return resource.url.sanitizeURL().toUri().isHttpOrHttps && !failureCache.hasFailedRecently(resource.url) } -} -private fun Response.toIconLoaderResult() = body.useStream { - IconLoader.Result.BytesResult(it.readBytes(), Icon.Source.DOWNLOAD) + private fun Response.toIconLoaderResult(): IconLoader.Result { + // Compare the Response Content-Length header with the available memory on device + val contentLengthHeader = headers[Headers.Names.CONTENT_LENGTH] + if (!contentLengthHeader.isNullOrEmpty()) { + val contentLength = contentLengthHeader.toLong() + return if (contentLength > MAX_DOWNLOAD_BYTES || contentLength > memoryInfoProvider.getAvailMem()) { + IconLoader.Result.NoResult + } else { + // Load the icon without reading to buffers since the checks above passed + body.useStream { + IconLoader.Result.BytesResult(it.readBytes(), Icon.Source.DOWNLOAD) + } + } + } else { + // Read the response body in chunks and check with available memory to prevent exceeding it + val buffer = ByteArray(DEFAULT_BUFFER_SIZE) + return ByteArrayOutputStream().use { outStream -> + body.useStream { inputStream -> + var bytesRead = 0 + var bytesInChunk: Int + + while (inputStream.read(buffer).also { bytesInChunk = it } != -1) { + outStream.write(buffer, 0, bytesInChunk) + bytesRead += bytesInChunk + + if (bytesRead > MAX_DOWNLOAD_BYTES || bytesRead > memoryInfoProvider.getAvailMem()) { + return@useStream IconLoader.Result.NoResult + } + + if (bytesInChunk < DEFAULT_BUFFER_SIZE) { + break + } + } + IconLoader.Result.BytesResult(outStream.toByteArray(), Icon.Source.DOWNLOAD) + } + } + } + } } private const val MAX_FAILURE_URLS = 25 diff --git a/mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/loader/MemoryInfoProvider.kt b/mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/loader/MemoryInfoProvider.kt new file mode 100644 index 0000000000..52f8bcbb28 --- /dev/null +++ b/mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/loader/MemoryInfoProvider.kt @@ -0,0 +1,32 @@ +/* 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.app.ActivityManager +import android.content.Context +import androidx.core.content.ContextCompat + +/** + * This class provides information about the device memory info without exposing the android + * framework APIs directly, making it easier to test the code that depends on it. + */ +interface MemoryInfoProvider { + /** + * Returns the device's available memory + */ + fun getAvailMem(): Long +} + +/** + * This class retrieves the available memory on device using activity manager. + */ +class DefaultMemoryInfoProvider(private val context: Context) : MemoryInfoProvider { + override fun getAvailMem(): Long { + val activityManager = ContextCompat.getSystemService(context, ActivityManager::class.java) + val memoryInfo = ActivityManager.MemoryInfo() + activityManager?.getMemoryInfo(memoryInfo) + return memoryInfo.availMem + } +} 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 index c3203dc13f..fcd64662d6 100644 --- 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 @@ -24,9 +24,10 @@ import mozilla.components.concept.fetch.Client */ class NonBlockingHttpIconLoader( httpClient: Client, + memoryInfoProvider: MemoryInfoProvider, private val scope: CoroutineScope = CoroutineScope(Dispatchers.IO), private val loadCallback: (IconRequest, IconRequest.Resource, IconLoader.Result) -> Unit, -) : HttpIconLoader(httpClient) { +) : HttpIconLoader(httpClient, memoryInfoProvider) { override fun load(context: Context, request: IconRequest, resource: IconRequest.Resource): IconLoader.Result { if (!shouldDownload(resource)) { 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 67a392d0a2..b14b5a30b3 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 @@ -13,6 +13,7 @@ import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import mozilla.components.browser.icons.generator.IconGenerator +import mozilla.components.browser.icons.loader.MemoryInfoProvider import mozilla.components.concept.engine.manifest.Size import mozilla.components.lib.fetch.httpurlconnection.HttpURLConnectionClient import mozilla.components.support.test.any @@ -46,6 +47,11 @@ import java.io.OutputStream @ExperimentalCoroutinesApi // for runTestOnMain @RunWith(AndroidJUnit4::class) class BrowserIconsTest { + private val defaultAvailMem: Long = 100000 + + class FakeMemoryInfoProvider(private val availMem: Long) : MemoryInfoProvider { + override fun getAvailMem(): Long = availMem + } @get:Rule val coroutinesTestRule = MainCoroutineRule() @@ -65,7 +71,12 @@ class BrowserIconsTest { `when`(generator.generate(any(), any())).thenReturn(mockedIcon) val request = IconRequest(url = "https://www.mozilla_test.org") - val icon = BrowserIcons(testContext, httpClient = mock(), generator = generator) + val icon = BrowserIcons( + context = testContext, + httpClient = mock(), + generator = generator, + memoryInfoProvider = FakeMemoryInfoProvider(defaultAvailMem), + ) .loadIcon(request) assertEquals(mockedIcon, icon.await()) @@ -114,6 +125,7 @@ class BrowserIconsTest { val icon = BrowserIcons( testContext, httpClient = HttpURLConnectionClient(), + memoryInfoProvider = FakeMemoryInfoProvider(defaultAvailMem), ).loadIcon(request).await() assertNotNull(icon) @@ -139,7 +151,11 @@ class BrowserIconsTest { server.start() try { - val icons = BrowserIcons(testContext, httpClient = HttpURLConnectionClient()) + val icons = BrowserIcons( + context = testContext, + httpClient = HttpURLConnectionClient(), + memoryInfoProvider = FakeMemoryInfoProvider(defaultAvailMem), + ) val request = IconRequest( url = "https://www.mozilla.org", @@ -182,7 +198,11 @@ class BrowserIconsTest { server.start() try { - val icons = BrowserIcons(testContext, httpClient = HttpURLConnectionClient()) + val icons = BrowserIcons( + context = testContext, + httpClient = HttpURLConnectionClient(), + memoryInfoProvider = FakeMemoryInfoProvider(defaultAvailMem), + ) val request = IconRequest( url = "https://www.mozilla.org", diff --git a/mobile/android/android-components/components/browser/icons/src/test/java/mozilla/components/browser/icons/loader/HttpIconLoaderTest.kt b/mobile/android/android-components/components/browser/icons/src/test/java/mozilla/components/browser/icons/loader/HttpIconLoaderTest.kt index 3670066921..872440900a 100644 --- a/mobile/android/android-components/components/browser/icons/src/test/java/mozilla/components/browser/icons/loader/HttpIconLoaderTest.kt +++ b/mobile/android/android-components/components/browser/icons/src/test/java/mozilla/components/browser/icons/loader/HttpIconLoaderTest.kt @@ -34,6 +34,11 @@ import java.io.InputStream @RunWith(AndroidJUnit4::class) class HttpIconLoaderTest { + private val defaultAvailMem: Long = 100000 + + class FakeMemoryInfoProvider(private val availMem: Long) : MemoryInfoProvider { + override fun getAvailMem(): Long = availMem + } @Test fun `Loader downloads data and uses appropriate headers`() { @@ -56,7 +61,7 @@ class HttpIconLoaderTest { server.start() try { - val loader = HttpIconLoader(client) + val loader = HttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem)) val result = loader.load( mock(), mock(), @@ -94,7 +99,7 @@ class HttpIconLoaderTest { fun `Loader will not perform any requests for data uris`() { val client: Client = mock() - val result = HttpIconLoader(client).load( + val result = HttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem)).load( mock(), mock(), IconRequest.Resource( @@ -112,7 +117,7 @@ class HttpIconLoaderTest { fun `Request has timeouts applied`() { val client: Client = mock() - val loader = HttpIconLoader(client) + val loader = HttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem)) doReturn( Response( url = "https://www.example.org", @@ -144,7 +149,7 @@ class HttpIconLoaderTest { fun `NoResult is returned for non-successful requests`() { val client: Client = mock() - val loader = HttpIconLoader(client) + val loader = HttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem)) doReturn( Response( url = "https://www.example.org", @@ -170,7 +175,7 @@ class HttpIconLoaderTest { fun `Loader will not try to load URL again that just recently failed`() { val client: Client = mock() - val loader = HttpIconLoader(client) + val loader = HttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem)) doReturn( Response( url = "https://www.example.org", @@ -203,7 +208,7 @@ class HttpIconLoaderTest { val client: Client = mock() doThrow(IOException("Mock")).`when`(client).fetch(any()) - val loader = HttpIconLoader(client) + val loader = HttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem)) val resource = IconRequest.Resource( url = "https://www.example.org", @@ -223,7 +228,7 @@ class HttpIconLoaderTest { } } - val loader = HttpIconLoader(client) + val loader = HttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem)) doReturn( Response( url = "https://www.example.org", @@ -242,10 +247,164 @@ class HttpIconLoaderTest { } @Test + fun `Loader will return NoResult for response with large Content-Length size`() { + val clients = listOf( + HttpURLConnectionClient(), + OkHttpClient(), + ) + + clients.forEach { client -> + val server = MockWebServer() + + // Create a mock Response object with the Content-Length header set to a large size + server.enqueue( + MockResponse().setBody( + javaClass.getResourceAsStream("/misc/test.txt")!! + .bufferedReader() + .use { it.readText() }, + ).addHeader("Content-Length", "2048576"), + ) + + server.start() + + try { + val loader = HttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem)) + val result = loader.load( + mock(), + mock(), + IconRequest.Resource( + url = server.url("/some/path").toString(), + type = IconRequest.Resource.Type.APPLE_TOUCH_ICON, + ), + ) + + assertTrue(result is IconLoader.Result.NoResult) + } finally { + server.shutdown() + } + } + } + + @Test + fun `Loader will return NoResult for valid Content-Length size and low available memory`() { + val clients = listOf( + HttpURLConnectionClient(), + OkHttpClient(), + ) + + clients.forEach { client -> + val server = MockWebServer() + + server.enqueue( + MockResponse().setBody( + javaClass.getResourceAsStream("/misc/test.txt")!! + .bufferedReader() + .use { it.readText() }, + ).addHeader("Content-Length", "10000"), + ) + + server.start() + + try { + val loader = HttpIconLoader(client, FakeMemoryInfoProvider(availMem = 0)) + val result = loader.load( + mock(), + mock(), + IconRequest.Resource( + url = server.url("/some/path").toString(), + type = IconRequest.Resource.Type.APPLE_TOUCH_ICON, + ), + ) + + assertTrue(result is IconLoader.Result.NoResult) + } finally { + server.shutdown() + } + } + } + + @Test + fun `Loader will return NoResult for null Content-Length header and low available memory`() { + val clients = listOf( + HttpURLConnectionClient(), + OkHttpClient(), + ) + + clients.forEach { client -> + val server = MockWebServer() + + server.enqueue( + MockResponse().setBody( + javaClass.getResourceAsStream("/misc/test.txt")!! + .bufferedReader() + .use { it.readText() }, + ).removeHeader("Content-Length"), + ) + + server.start() + + try { + val loader = HttpIconLoader(client, FakeMemoryInfoProvider(availMem = 0)) + val result = loader.load( + mock(), + mock(), + IconRequest.Resource( + url = server.url("/some/path").toString(), + type = IconRequest.Resource.Type.APPLE_TOUCH_ICON, + ), + ) + + assertTrue(result is IconLoader.Result.NoResult) + } finally { + server.shutdown() + } + } + } + + @Test + fun `Loader downloads data for null Content-Length header and response size within limits`() { + val clients = listOf( + HttpURLConnectionClient(), + OkHttpClient(), + ) + + clients.forEach { client -> + val server = MockWebServer() + + server.enqueue( + MockResponse().setBody( + javaClass.getResourceAsStream("/misc/test.txt")!! + .bufferedReader() + .use { it.readText() }, + ).removeHeader("Content-Length"), + ) + + server.start() + + try { + val loader = HttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem)) + val result = loader.load( + mock(), + mock(), + IconRequest.Resource( + url = server.url("/some/path").toString(), + type = IconRequest.Resource.Type.APPLE_TOUCH_ICON, + ), + ) + assertTrue("Result should match BytesResult", result is IconLoader.Result.BytesResult) + val data = (result as IconLoader.Result.BytesResult).bytes + assertTrue("Data should not be empty", data.isNotEmpty()) + } finally { + server.shutdown() + } + } + } + + @Test fun `Loader will sanitize URL`() { val client: Client = mock() - val loader = HttpIconLoader(client) + val loader = HttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem)) doReturn( Response( url = "https://www.example.org", 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 index 6b2fe8d8ad..2a2da044bf 100644 --- 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 @@ -45,6 +45,11 @@ class NonBlockingHttpIconLoaderTest { @get:Rule val coroutinesTestRule = MainCoroutineRule() private val scope = coroutinesTestRule.scope + private val defaultAvailMem: Long = 100000 + + class FakeMemoryInfoProvider(private val availMem: Long) : MemoryInfoProvider { + override fun getAvailMem(): Long = availMem + } @Test fun `Loader will return IconLoader#Result#NoResult for a load request and respond with the result through a callback`() = runTestOnMain { @@ -71,7 +76,7 @@ class NonBlockingHttpIconLoaderTest { var callbackIconRequest: IconRequest? = null var callbackResource: IconRequest.Resource? = null var callbackIcon: IconLoader.Result? = null - val loader = NonBlockingHttpIconLoader(client, scope) { request, resource, icon -> + val loader = NonBlockingHttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem), scope) { request, resource, icon -> callbackIconRequest = request callbackResource = resource callbackIcon = icon @@ -106,7 +111,7 @@ class NonBlockingHttpIconLoaderTest { var callbackIconRequest: IconRequest? = null var callbackResource: IconRequest.Resource? = null var callbackIcon: IconLoader.Result? = null - val loader = NonBlockingHttpIconLoader(client, scope) { request, resource, icon -> + val loader = NonBlockingHttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem), scope) { request, resource, icon -> callbackIconRequest = request callbackResource = resource callbackIcon = icon @@ -132,7 +137,7 @@ class NonBlockingHttpIconLoaderTest { @Test fun `Request has timeouts applied`() = runTestOnMain { val client: Client = mock() - val loader = NonBlockingHttpIconLoader(client, scope) { _, _, _ -> } + val loader = NonBlockingHttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem), scope) { _, _, _ -> } doReturn( Response( url = "https://www.example.org", @@ -165,7 +170,7 @@ class NonBlockingHttpIconLoaderTest { var callbackIconRequest: IconRequest? = null var callbackResource: IconRequest.Resource? = null var callbackIcon: IconLoader.Result? = null - val loader = NonBlockingHttpIconLoader(client, scope) { request, resource, icon -> + val loader = NonBlockingHttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem), scope) { request, resource, icon -> callbackIconRequest = request callbackResource = resource callbackIcon = icon @@ -198,7 +203,7 @@ class NonBlockingHttpIconLoaderTest { @Test fun `Loader will not try to load URL again that just recently failed`() = runTestOnMain { val client: Client = mock() - val loader = NonBlockingHttpIconLoader(client, scope) { _, _, _ -> } + val loader = NonBlockingHttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem), scope) { _, _, _ -> } doReturn( Response( url = "https://www.example.org", @@ -231,7 +236,7 @@ class NonBlockingHttpIconLoaderTest { var callbackIconRequest: IconRequest? = null var callbackResource: IconRequest.Resource? = null var callbackIcon: IconLoader.Result? = null - val loader = NonBlockingHttpIconLoader(client, scope) { request, resource, icon -> + val loader = NonBlockingHttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem), scope) { request, resource, icon -> callbackIconRequest = request callbackResource = resource callbackIcon = icon @@ -256,7 +261,7 @@ class NonBlockingHttpIconLoaderTest { var callbackIconRequest: IconRequest? = null var callbackResource: IconRequest.Resource? = null var callbackIcon: IconLoader.Result? = null - val loader = NonBlockingHttpIconLoader(client, scope) { request, resource, icon -> + val loader = NonBlockingHttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem), scope) { request, resource, icon -> callbackIconRequest = request callbackResource = resource callbackIcon = icon @@ -292,7 +297,7 @@ class NonBlockingHttpIconLoaderTest { fun `Loader will sanitize URL`() = runTestOnMain { val client: Client = mock() val captor = argumentCaptor<Request>() - val loader = NonBlockingHttpIconLoader(client, scope) { _, _, _ -> } + val loader = NonBlockingHttpIconLoader(client, FakeMemoryInfoProvider(defaultAvailMem), scope) { _, _, _ -> } doReturn( Response( url = "https://www.example.org", |