Loading components/feature/downloads/src/main/java/mozilla/components/feature/downloads/DownloadsFeature.kt +37 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import mozilla.components.support.base.feature.OnNeedToRequestPermissions import mozilla.components.support.base.feature.PermissionsFeature import mozilla.components.support.ktx.android.content.appName import mozilla.components.support.ktx.android.content.isPermissionGranted import mozilla.components.support.ktx.kotlin.isSameOriginAs import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged import mozilla.components.support.utils.Browsers Loading Loading @@ -85,17 +86,46 @@ class DownloadsFeature( private var scope: CoroutineScope? = null @VisibleForTesting internal var dismissPromptScope: CoroutineScope? = null @VisibleForTesting internal var previousTab: SessionState? = null /** * Starts observing downloads on the selected session and sends them to the [DownloadManager] * to be processed. */ @Suppress("Deprecation") override fun start() { // Dismiss the previous prompts when the user navigates to another site. // This prevents prompts from the previous page from covering content. dismissPromptScope = store.flowScoped { flow -> flow.mapNotNull { state -> state.findTabOrCustomTabOrSelectedTab(tabId) } .ifChanged { it.content.url } .collect { val currentHost = previousTab?.content?.url val newHost = it.content.url // The user is navigating to another site if (currentHost?.isSameOriginAs(newHost) == false) { previousTab?.let { tab -> // We have an old download request. tab.content.download?.let { download -> dismissAllDownloadDialogs() useCases.consumeDownload(tab.id, download.id) previousTab = null } } } } } scope = store.flowScoped { flow -> flow.mapNotNull { state -> state.findTabOrCustomTabOrSelectedTab(tabId) } .ifChanged { it.content.download } .collect { state -> state.content.download?.let { downloadState -> previousTab = state processDownload(state, downloadState) } } Loading @@ -115,6 +145,7 @@ class DownloadsFeature( */ override fun stop() { scope?.cancel() dismissPromptScope?.cancel() downloadManager.unregisterListeners() } Loading Loading @@ -309,6 +340,12 @@ class DownloadsFeature( .map { it.toDownloaderApp(context, download) } + listOfNotNull(thisApp) } @VisibleForTesting internal fun dismissAllDownloadDialogs() { findPreviousDownloadDialogFragment()?.dismiss() findPreviousAppDownloaderDialogFragment()?.dismiss() } private val ActivityInfo.identifier: String get() = packageName + name private fun DownloaderApp.toIntent(): Intent { Loading components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadsFeatureTest.kt +71 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ import kotlinx.coroutines.test.TestCoroutineDispatcher import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.setMain import mozilla.components.browser.state.action.ContentAction import mozilla.components.browser.state.action.TabListAction import mozilla.components.browser.state.selector.findTab import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.state.content.DownloadState Loading Loading @@ -729,6 +730,76 @@ class DownloadsFeatureTest { val app = resolveInfo.toDownloaderApp(spyContext, download) assertEquals(expectedApp, app) } @Test fun `previous dialogs MUST be dismissed when navigating to another website`() { val downloadsUseCases = spy(DownloadsUseCases(store)) val consumeDownloadUseCase = mock<ConsumeDownloadUseCase>() val download = DownloadState(url = "https://www.mozilla.org", sessionId = "test-tab") store.dispatch(ContentAction.UpdateDownloadAction("test-tab", download = download)) .joinBlocking() doReturn(consumeDownloadUseCase).`when`(downloadsUseCases).consumeDownload val feature = spy(DownloadsFeature( testContext, store, useCases = downloadsUseCases, downloadManager = mock() )) doNothing().`when`(feature).dismissAllDownloadDialogs() doReturn(true).`when`(feature).processDownload(any(), any()) feature.start() store.dispatch(ContentAction.UpdateDownloadAction("test-tab", download = download)) .joinBlocking() grantPermissions() val tab = createTab("https://www.firefox.com") store.dispatch(TabListAction.AddTabAction(tab, select = true)).joinBlocking() verify(feature).dismissAllDownloadDialogs() verify(downloadsUseCases).consumeDownload assertNull(feature.previousTab) } @Test fun `previous dialogs must NOT be dismissed when navigating on the same website`() { val downloadsUseCases = spy(DownloadsUseCases(store)) val consumeDownloadUseCase = mock<ConsumeDownloadUseCase>() val download = DownloadState(url = "https://www.mozilla.org", sessionId = "test-tab") store.dispatch(ContentAction.UpdateDownloadAction("test-tab", download = download)) .joinBlocking() doReturn(consumeDownloadUseCase).`when`(downloadsUseCases).consumeDownload val feature = spy(DownloadsFeature( testContext, store, useCases = downloadsUseCases, downloadManager = mock() )) doNothing().`when`(feature).dismissAllDownloadDialogs() doReturn(true).`when`(feature).processDownload(any(), any()) feature.start() store.dispatch(ContentAction.UpdateDownloadAction("test-tab", download = download)) .joinBlocking() grantPermissions() val tab = createTab("https://www.mozilla.org/example") store.dispatch(TabListAction.AddTabAction(tab, select = true)).joinBlocking() verify(feature, never()).dismissAllDownloadDialogs() verify(downloadsUseCases, never()).consumeDownload assertNotNull(feature.previousTab) } } private fun grantPermissions() { Loading Loading
components/feature/downloads/src/main/java/mozilla/components/feature/downloads/DownloadsFeature.kt +37 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import mozilla.components.support.base.feature.OnNeedToRequestPermissions import mozilla.components.support.base.feature.PermissionsFeature import mozilla.components.support.ktx.android.content.appName import mozilla.components.support.ktx.android.content.isPermissionGranted import mozilla.components.support.ktx.kotlin.isSameOriginAs import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged import mozilla.components.support.utils.Browsers Loading Loading @@ -85,17 +86,46 @@ class DownloadsFeature( private var scope: CoroutineScope? = null @VisibleForTesting internal var dismissPromptScope: CoroutineScope? = null @VisibleForTesting internal var previousTab: SessionState? = null /** * Starts observing downloads on the selected session and sends them to the [DownloadManager] * to be processed. */ @Suppress("Deprecation") override fun start() { // Dismiss the previous prompts when the user navigates to another site. // This prevents prompts from the previous page from covering content. dismissPromptScope = store.flowScoped { flow -> flow.mapNotNull { state -> state.findTabOrCustomTabOrSelectedTab(tabId) } .ifChanged { it.content.url } .collect { val currentHost = previousTab?.content?.url val newHost = it.content.url // The user is navigating to another site if (currentHost?.isSameOriginAs(newHost) == false) { previousTab?.let { tab -> // We have an old download request. tab.content.download?.let { download -> dismissAllDownloadDialogs() useCases.consumeDownload(tab.id, download.id) previousTab = null } } } } } scope = store.flowScoped { flow -> flow.mapNotNull { state -> state.findTabOrCustomTabOrSelectedTab(tabId) } .ifChanged { it.content.download } .collect { state -> state.content.download?.let { downloadState -> previousTab = state processDownload(state, downloadState) } } Loading @@ -115,6 +145,7 @@ class DownloadsFeature( */ override fun stop() { scope?.cancel() dismissPromptScope?.cancel() downloadManager.unregisterListeners() } Loading Loading @@ -309,6 +340,12 @@ class DownloadsFeature( .map { it.toDownloaderApp(context, download) } + listOfNotNull(thisApp) } @VisibleForTesting internal fun dismissAllDownloadDialogs() { findPreviousDownloadDialogFragment()?.dismiss() findPreviousAppDownloaderDialogFragment()?.dismiss() } private val ActivityInfo.identifier: String get() = packageName + name private fun DownloaderApp.toIntent(): Intent { Loading
components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadsFeatureTest.kt +71 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ import kotlinx.coroutines.test.TestCoroutineDispatcher import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.setMain import mozilla.components.browser.state.action.ContentAction import mozilla.components.browser.state.action.TabListAction import mozilla.components.browser.state.selector.findTab import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.state.content.DownloadState Loading Loading @@ -729,6 +730,76 @@ class DownloadsFeatureTest { val app = resolveInfo.toDownloaderApp(spyContext, download) assertEquals(expectedApp, app) } @Test fun `previous dialogs MUST be dismissed when navigating to another website`() { val downloadsUseCases = spy(DownloadsUseCases(store)) val consumeDownloadUseCase = mock<ConsumeDownloadUseCase>() val download = DownloadState(url = "https://www.mozilla.org", sessionId = "test-tab") store.dispatch(ContentAction.UpdateDownloadAction("test-tab", download = download)) .joinBlocking() doReturn(consumeDownloadUseCase).`when`(downloadsUseCases).consumeDownload val feature = spy(DownloadsFeature( testContext, store, useCases = downloadsUseCases, downloadManager = mock() )) doNothing().`when`(feature).dismissAllDownloadDialogs() doReturn(true).`when`(feature).processDownload(any(), any()) feature.start() store.dispatch(ContentAction.UpdateDownloadAction("test-tab", download = download)) .joinBlocking() grantPermissions() val tab = createTab("https://www.firefox.com") store.dispatch(TabListAction.AddTabAction(tab, select = true)).joinBlocking() verify(feature).dismissAllDownloadDialogs() verify(downloadsUseCases).consumeDownload assertNull(feature.previousTab) } @Test fun `previous dialogs must NOT be dismissed when navigating on the same website`() { val downloadsUseCases = spy(DownloadsUseCases(store)) val consumeDownloadUseCase = mock<ConsumeDownloadUseCase>() val download = DownloadState(url = "https://www.mozilla.org", sessionId = "test-tab") store.dispatch(ContentAction.UpdateDownloadAction("test-tab", download = download)) .joinBlocking() doReturn(consumeDownloadUseCase).`when`(downloadsUseCases).consumeDownload val feature = spy(DownloadsFeature( testContext, store, useCases = downloadsUseCases, downloadManager = mock() )) doNothing().`when`(feature).dismissAllDownloadDialogs() doReturn(true).`when`(feature).processDownload(any(), any()) feature.start() store.dispatch(ContentAction.UpdateDownloadAction("test-tab", download = download)) .joinBlocking() grantPermissions() val tab = createTab("https://www.mozilla.org/example") store.dispatch(TabListAction.AddTabAction(tab, select = true)).joinBlocking() verify(feature, never()).dismissAllDownloadDialogs() verify(downloadsUseCases, never()).consumeDownload assertNotNull(feature.previousTab) } } private fun grantPermissions() { Loading