Commit 87fe5495 authored by MozLando's avatar MozLando
Browse files

Merge #7431



7431: Issue #7185: Delete thumbnails when a session is removed r=jonalmeida a=gabrielluong



Co-authored-by: default avatarGabriel Luong <gabriel.luong@gmail.com>
parents f20b1cb2 d93b7622
Loading
Loading
Loading
Loading
+18 −0
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@ package mozilla.components.browser.thumbnails

import mozilla.components.browser.state.action.BrowserAction
import mozilla.components.browser.state.action.ContentAction
import mozilla.components.browser.state.action.TabListAction
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.thumbnails.storage.ThumbnailStorage
import mozilla.components.lib.state.Middleware
@@ -29,6 +30,23 @@ class ThumbnailsMiddleware(
                // thumbnail is updated.
                thumbnailStorage.saveThumbnail(action.sessionId, action.thumbnail)
            }
            is TabListAction.RemoveAllNormalTabsAction -> {
                store.state.tabs.filterNot { it.content.private }.forEach { tab ->
                    thumbnailStorage.deleteThumbnail(tab.id)
                }
            }
            is TabListAction.RemoveAllPrivateTabsAction -> {
                store.state.tabs.filter { it.content.private }.forEach { tab ->
                    thumbnailStorage.deleteThumbnail(tab.id)
                }
            }
            is TabListAction.RemoveAllTabsAction -> {
                thumbnailStorage.clearThumbnails()
            }
            is TabListAction.RemoveTabAction -> {
                // Delete the tab screenshot from the storage when the tab is removed.
                thumbnailStorage.deleteThumbnail(action.tabId)
            }
        }

        next(action)
+19 −0
Original line number Diff line number Diff line
@@ -42,6 +42,25 @@ class ThumbnailStorage(
        context.resources.getDimensionPixelSize(R.dimen.mozac_browser_thumbnails_maximum_size)
    private val scope = CoroutineScope(jobDispatcher)

    /**
     * Clears all the stored thumbnails in the disk cache.
     */
    fun clearThumbnails(): Job =
        scope.launch {
            logger.debug("Cleared all thumbnails from disk")
            sharedDiskCache.clear(context)
        }

    /**
     * Deletes the given thumbnail [Bitmap] from the disk cache with the provided session ID or url
     * as its key.
     */
    fun deleteThumbnail(sessionIdOrUrl: String): Job =
        scope.launch {
            logger.debug("Removed thumbnail from disk (sessionIdOrUrl = $sessionIdOrUrl)")
            sharedDiskCache.removeThumbnailData(context, sessionIdOrUrl)
        }

    /**
     * Asynchronously loads a thumbnail [Bitmap] for the given session ID or url.
     */
+20 −4
Original line number Diff line number Diff line
@@ -6,7 +6,6 @@ package mozilla.components.browser.thumbnails.utils

import android.content.Context
import android.graphics.Bitmap
import androidx.annotation.VisibleForTesting
import com.jakewharton.disklrucache.DiskLruCache
import mozilla.components.support.base.log.logger.Logger
import java.io.File
@@ -24,11 +23,12 @@ class ThumbnailDiskCache {
    private var thumbnailCache: DiskLruCache? = null
    private val thumbnailCacheWriteLock = Any()

    @VisibleForTesting(otherwise = VisibleForTesting.NONE)
    internal fun clear(context: Context) {
        synchronized(thumbnailCacheWriteLock) {
            getThumbnailCache(context).delete()
            thumbnailCache = null
        }
    }

    /**
     * Retrieves the thumbnail data from the disk cache for the given session ID or URL.
@@ -74,6 +74,22 @@ class ThumbnailDiskCache {
        }
    }

    /**
     * Removes the given session ID or URL's thumbnail [Bitmap] from the disk cache.
     *
     * @param context the application [Context].
     * @param sessionIdOrUrl the session ID or URL.
     */
    internal fun removeThumbnailData(context: Context, sessionIdOrUrl: String) {
        try {
            synchronized(thumbnailCacheWriteLock) {
                getThumbnailCache(context).remove(sessionIdOrUrl)
            }
        } catch (e: IOException) {
            logger.info("Failed to remove thumbnail bitmap from disk", e)
        }
    }

    private fun getThumbnailCacheDirectory(context: Context): File {
        val cacheDirectory = File(context.cacheDir, "mozac_browser_thumbnails")
        return File(cacheDirectory, "thumbnails")
+70 −0
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@ package mozilla.components.browser.thumbnails
import android.graphics.Bitmap
import androidx.test.ext.junit.runners.AndroidJUnit4
import mozilla.components.browser.state.action.ContentAction
import mozilla.components.browser.state.action.TabListAction
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.createTab
import mozilla.components.browser.state.store.BrowserStore
@@ -34,4 +35,73 @@ class ThumbnailsMiddlewareTest {
        store.dispatch(ContentAction.UpdateThumbnailAction(sessionIdOrUrl, bitmap)).joinBlocking()
        verify(thumbnailStorage).saveThumbnail(sessionIdOrUrl, bitmap)
    }

    @Test
    fun `thumbnail storage removes the thumbnail on remove all normal tabs action`() {
        val thumbnailStorage: ThumbnailStorage = mock()
        val store = BrowserStore(
            initialState = BrowserState(tabs = listOf(
                createTab("https://www.mozilla.org", id = "test-tab1"),
                createTab("https://www.firefox.com", id = "test-tab2"),
                createTab("https://www.wikipedia.com", id = "test-tab3"),
                createTab("https://www.example.org", private = true, id = "test-ta4")
            )),
            middleware = listOf(ThumbnailsMiddleware(thumbnailStorage))
        )

        store.dispatch(TabListAction.RemoveAllNormalTabsAction).joinBlocking()
        verify(thumbnailStorage).deleteThumbnail("test-tab1")
        verify(thumbnailStorage).deleteThumbnail("test-tab2")
        verify(thumbnailStorage).deleteThumbnail("test-tab3")
    }

    @Test
    fun `thumbnail storage removes the thumbnail on remove all private tabs action`() {
        val thumbnailStorage: ThumbnailStorage = mock()
        val store = BrowserStore(
            initialState = BrowserState(tabs = listOf(
                createTab("https://www.mozilla.org", id = "test-tab1"),
                createTab("https://www.firefox.com", private = true, id = "test-tab2"),
                createTab("https://www.wikipedia.com", private = true, id = "test-tab3"),
                createTab("https://www.example.org", private = true, id = "test-tab4")
            )),
            middleware = listOf(ThumbnailsMiddleware(thumbnailStorage))
        )

        store.dispatch(TabListAction.RemoveAllPrivateTabsAction).joinBlocking()
        verify(thumbnailStorage).deleteThumbnail("test-tab2")
        verify(thumbnailStorage).deleteThumbnail("test-tab3")
        verify(thumbnailStorage).deleteThumbnail("test-tab4")
    }

    @Test
    fun `thumbnail storage removes the thumbnail on remove all tabs action`() {
        val thumbnailStorage: ThumbnailStorage = mock()
        val store = BrowserStore(
            initialState = BrowserState(tabs = listOf(
                createTab("https://www.mozilla.org", id = "test-tab1"),
                createTab("https://www.firefox.com", id = "test-tab2")
            )),
            middleware = listOf(ThumbnailsMiddleware(thumbnailStorage))
        )

        store.dispatch(TabListAction.RemoveAllTabsAction).joinBlocking()
        verify(thumbnailStorage).clearThumbnails()
    }

    @Test
    fun `thumbnail storage removes the thumbnail on remove tab action`() {
        val sessionIdOrUrl = "test-tab1"
        val thumbnailStorage: ThumbnailStorage = mock()
        val store = BrowserStore(
            initialState = BrowserState(tabs = listOf(
                createTab("https://www.mozilla.org", id = "test-tab1"),
                createTab("https://www.firefox.com", id = "test-tab2")
            )),
            middleware = listOf(ThumbnailsMiddleware(thumbnailStorage))
        )

        store.dispatch(TabListAction.RemoveTabAction(sessionIdOrUrl)).joinBlocking()
        verify(thumbnailStorage).deleteThumbnail(sessionIdOrUrl)
    }
}
+34 −0
Original line number Diff line number Diff line
@@ -30,6 +30,40 @@ class ThumbnailStorageTest {
        sharedDiskCache.clear(testContext)
    }

    @Test
    fun `clearThumbnails`() = runBlocking {
        val bitmap: Bitmap = mock()
        val thumbnailStorage = spy(ThumbnailStorage(testContext))

        thumbnailStorage.saveThumbnail("test-tab1", bitmap).joinBlocking()
        thumbnailStorage.saveThumbnail("test-tab2", bitmap).joinBlocking()
        var thumbnail1 = thumbnailStorage.loadThumbnail("test-tab1").await()
        var thumbnail2 = thumbnailStorage.loadThumbnail("test-tab2").await()
        assertNotNull(thumbnail1)
        assertNotNull(thumbnail2)

        thumbnailStorage.clearThumbnails()
        thumbnail1 = thumbnailStorage.loadThumbnail("test-tab1").await()
        thumbnail2 = thumbnailStorage.loadThumbnail("test-tab2").await()
        assertNull(thumbnail1)
        assertNull(thumbnail2)
    }

    @Test
    fun `deleteThumbnail`() = runBlocking {
        val sessionIdOrUrl = "test-tab1"
        val bitmap: Bitmap = mock()
        val thumbnailStorage = spy(ThumbnailStorage(testContext))

        thumbnailStorage.saveThumbnail(sessionIdOrUrl, bitmap).joinBlocking()
        var thumbnail = thumbnailStorage.loadThumbnail(sessionIdOrUrl).await()
        assertNotNull(thumbnail)

        thumbnailStorage.deleteThumbnail(sessionIdOrUrl).joinBlocking()
        thumbnail = thumbnailStorage.loadThumbnail(sessionIdOrUrl).await()
        assertNull(thumbnail)
    }

    @Test
    fun `saveThumbnail`() = runBlocking {
        val sessionIdOrUrl = "test-tab1"
Loading