summaryrefslogtreecommitdiffstats
path: root/mobile/android/android-components/components/browser/icons
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/android-components/components/browser/icons')
-rw-r--r--mobile/android/android-components/components/browser/icons/src/main/assets/extensions/browser-icons/icons.js103
-rw-r--r--mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/BrowserIcons.kt13
-rw-r--r--mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/loader/HttpIconLoader.kt47
-rw-r--r--mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/loader/MemoryInfoProvider.kt32
-rw-r--r--mobile/android/android-components/components/browser/icons/src/main/java/mozilla/components/browser/icons/loader/NonBlockingHttpIconLoader.kt3
-rw-r--r--mobile/android/android-components/components/browser/icons/src/test/java/mozilla/components/browser/icons/BrowserIconsTest.kt26
-rw-r--r--mobile/android/android-components/components/browser/icons/src/test/java/mozilla/components/browser/icons/loader/HttpIconLoaderTest.kt175
-rw-r--r--mobile/android/android-components/components/browser/icons/src/test/java/mozilla/components/browser/icons/loader/NonBlockingHttpIconLoaderTest.kt21
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",