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:
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