tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 5cf5d7e9dd3e3668767f2335f9097121a3ac275a
parent 6b34e973008b6ee2bd7136e6e5087cfa5cd962cd
Author: Andrey Zinovyev <azinovyev@mozilla.com>
Date:   Mon,  3 Nov 2025 18:50:22 +0000

Bug 1985955 - Site exception indicator (blue dot) not displayed on the shield icon r=android-reviewers,petru

Differential Revision: https://phabricator.services.mozilla.com/D265089

Diffstat:
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/TabPreview.kt | 9+++++++++
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMiddleware.kt | 26++++++++++++++++++++++++++
Mmobile/android/fenix/app/src/test/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMiddlewareTest.kt | 135+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 170 insertions(+), 0 deletions(-)

diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/TabPreview.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/TabPreview.kt @@ -182,10 +182,17 @@ class TabPreview @JvmOverloads constructor( ) ToolbarAction.SiteInfo -> { + val highlight = ( + tab?.content?.permissionHighlights?.permissionsChanged == true + ) || ( + tab?.trackingProtection?.ignoredOnTrackingProtection == true + ) + if (tab?.content?.url?.isContentUrl() == true) { ActionButtonRes( drawableResId = iconsR.drawable.mozac_ic_page_portrait_24, contentDescription = toolbarR.string.mozac_browser_toolbar_content_description_site_info, + highlighted = highlight, onClick = object : BrowserToolbarEvent {}, ) } else if ( @@ -196,12 +203,14 @@ class TabPreview @JvmOverloads constructor( ActionButtonRes( drawableResId = iconsR.drawable.mozac_ic_shield_checkmark_24, contentDescription = toolbarR.string.mozac_browser_toolbar_content_description_site_info, + highlighted = highlight, onClick = object : BrowserToolbarEvent {}, ) } else { ActionButtonRes( drawableResId = iconsR.drawable.mozac_ic_shield_slash_24, contentDescription = toolbarR.string.mozac_browser_toolbar_content_description_site_info, + highlighted = highlight, onClick = object : BrowserToolbarEvent {}, ) } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMiddleware.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMiddleware.kt @@ -239,6 +239,7 @@ class BrowserToolbarMiddleware( environment = action.environment as? BrowserToolbarEnvironment updateStartBrowserActions(context) + updateStartPageActions(context) updateCurrentPageOrigin(context) environment?.fragment?.viewLifecycleOwner?.lifecycleScope?.launch { updateEndBrowserActions(context) @@ -261,6 +262,7 @@ class BrowserToolbarMiddleware( observePageRefreshUpdates(context) observePageTrackingProtectionUpdates(context) observePageSecurityUpdates(context) + observePermissionHighlightsUpdates(context) } is EnvironmentCleared -> { @@ -1071,6 +1073,17 @@ class BrowserToolbarMiddleware( } } + private fun observePermissionHighlightsUpdates( + context: MiddlewareContext<BrowserToolbarState, BrowserToolbarAction>, + ) { + browserStore.observeWhileActive { + distinctUntilChangedBy { it.selectedTab?.content?.permissionHighlights } + .collect { + updateStartPageActions(context) + } + } + } + private inline fun <S : State, A : MVIAction> Store<S, A>.observeWhileActive( crossinline observe: suspend (Flow<S>.() -> Unit), ): Job? = environment?.fragment?.viewLifecycleOwner?.run { @@ -1226,11 +1239,22 @@ class BrowserToolbarMiddleware( } ToolbarAction.SiteInfo -> { + val highlight = ( + browserStore.state.selectedTab + ?.content + ?.permissionHighlights + ?.permissionsChanged == true + ) || ( + browserStore.state.selectedTab + ?.trackingProtection + ?.ignoredOnTrackingProtection == true + ) val selectedTab = browserStore.state.selectedTab if (selectedTab?.content?.url?.isContentUrl() == true) { ActionButtonRes( drawableResId = iconsR.drawable.mozac_ic_page_portrait_24, contentDescription = toolbarR.string.mozac_browser_toolbar_content_description_site_info, + highlighted = highlight, onClick = StartPageActions.SiteInfoClicked, ) } else if ( @@ -1241,12 +1265,14 @@ class BrowserToolbarMiddleware( ActionButtonRes( drawableResId = iconsR.drawable.mozac_ic_shield_checkmark_24, contentDescription = toolbarR.string.mozac_browser_toolbar_content_description_site_info, + highlighted = highlight, onClick = StartPageActions.SiteInfoClicked, ) } else { ActionButtonRes( drawableResId = iconsR.drawable.mozac_ic_shield_slash_24, contentDescription = toolbarR.string.mozac_browser_toolbar_content_description_site_info, + highlighted = highlight, onClick = StartPageActions.SiteInfoClicked, ) } diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMiddlewareTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMiddlewareTest.kt @@ -26,6 +26,7 @@ import io.mockk.verify import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.test.runTest import mozilla.components.browser.state.action.BrowserAction +import mozilla.components.browser.state.action.ContentAction import mozilla.components.browser.state.action.ContentAction.UpdateLoadingStateAction import mozilla.components.browser.state.action.ContentAction.UpdateProgressAction import mozilla.components.browser.state.action.ContentAction.UpdateSecurityInfoAction @@ -2750,6 +2751,140 @@ class BrowserToolbarMiddlewareTest { } @Test + fun `GIVEN site permissions different than default WHEN observing THEN SiteInfo button is highlighted`() = runTest { + val currentTab = createTab("example.com", private = false) + val browserStore = BrowserStore( + BrowserState(tabs = listOf(currentTab), selectedTabId = currentTab.id), + ) + val middleware = buildMiddleware(browserStore = browserStore) + val toolbarStore = buildStore(middleware).also { + it.dispatch(BrowserToolbarAction.Init()) + } + mainLooperRule.idle() + + var siteInfo = toolbarStore.state.displayState.pageActionsStart.first() as ActionButtonRes + assertTrue(!siteInfo.highlighted) + + browserStore.dispatch( + ContentAction.UpdatePermissionHighlightsStateAction.NotificationChangedAction( + tabId = currentTab.id, + value = true, + ), + ).joinBlocking() + mainLooperRule.idle() + + siteInfo = toolbarStore.state.displayState.pageActionsStart.first() as ActionButtonRes + assertTrue(siteInfo.highlighted) + } + + @Test + fun `GIVEN no custom site permissions WHEN observing THEN SiteInfo button is NOT highlighted`() = runTest { + val currentTab = createTab("example.com", private = false) + val browserStore = BrowserStore( + BrowserState(tabs = listOf(currentTab), selectedTabId = currentTab.id), + ) + val middleware = buildMiddleware(browserStore = browserStore) + val toolbarStore = buildStore(middleware).also { + it.dispatch(BrowserToolbarAction.Init()) + } + mainLooperRule.idle() + + val siteInfo = toolbarStore.state.displayState.pageActionsStart.first() as ActionButtonRes + assertTrue(!siteInfo.highlighted) + } + + @Test + fun `GIVEN tracking protection ignored WHEN observing THEN SiteInfo button is highlighted`() = runTest { + val currentTab = createTab( + url = "https://example.com", + private = false, + trackingProtection = TrackingProtectionState( + enabled = true, + ignoredOnTrackingProtection = true, + ), + ) + val browserStore = BrowserStore( + BrowserState(tabs = listOf(currentTab), selectedTabId = currentTab.id), + ) + val middleware = buildMiddleware(browserStore = browserStore) + val toolbarStore = buildStore(middleware).also { + it.dispatch(BrowserToolbarAction.Init()) + } + mainLooperRule.idle() + + val siteInfo = toolbarStore.state.displayState.pageActionsStart.first() as ActionButtonRes + assertTrue(siteInfo.highlighted) + } + + @Test + fun `GIVEN tracking protection not ignored WHEN it becomes ignored THEN SiteInfo button becomes highlighted`() = runTest { + val currentTab = createTab( + url = "https://example.com", + private = false, + trackingProtection = TrackingProtectionState( + enabled = true, + ignoredOnTrackingProtection = false, + ), + ) + val browserStore = BrowserStore( + BrowserState(tabs = listOf(currentTab), selectedTabId = currentTab.id), + ) + val middleware = buildMiddleware(browserStore = browserStore) + val toolbarStore = buildStore(middleware).also { + it.dispatch(BrowserToolbarAction.Init()) + } + mainLooperRule.idle() + + var siteInfo = toolbarStore.state.displayState.pageActionsStart.first() as ActionButtonRes + assertTrue(!siteInfo.highlighted) + + browserStore.dispatch( + TrackingProtectionAction.ToggleExclusionListAction( + tabId = currentTab.id, + excluded = true, + ), + ).joinBlocking() + mainLooperRule.idle() + + siteInfo = toolbarStore.state.displayState.pageActionsStart.first() as ActionButtonRes + assertTrue(siteInfo.highlighted) + } + + @Test + fun `GIVEN tracking protection ignored WHEN it is no longer ignored THEN SiteInfo button is NOT highlighted`() = runTest { + val currentTab = createTab( + url = "https://example.com", + private = false, + trackingProtection = TrackingProtectionState( + enabled = true, + ignoredOnTrackingProtection = true, + ), + ) + val browserStore = BrowserStore( + BrowserState(tabs = listOf(currentTab), selectedTabId = currentTab.id), + ) + val middleware = buildMiddleware(browserStore = browserStore) + val toolbarStore = buildStore(middleware).also { + it.dispatch(BrowserToolbarAction.Init()) + } + mainLooperRule.idle() + + var siteInfo = toolbarStore.state.displayState.pageActionsStart.first() as ActionButtonRes + assertTrue(siteInfo.highlighted) + + browserStore.dispatch( + TrackingProtectionAction.ToggleExclusionListAction( + tabId = currentTab.id, + excluded = false, + ), + ).joinBlocking() + mainLooperRule.idle() + + siteInfo = toolbarStore.state.displayState.pageActionsStart.first() as ActionButtonRes + assertTrue(!siteInfo.highlighted) + } + + @Test fun `GIVEN share shortcut is selected THEN update end page actions without share action`() = runTest { configuration = Configuration().apply { screenHeightDp = 400