[Shred] Privacy Bypass – Private Tab Domain Disclosure via “Shred Data” Popup on Brave Android (#36920)

Added an ability to hide the manual shred button when the tab switch
mode is active in incognito mode and private tabs are locked.

Resolves: https://github.com/brave/brave-browser/issues/55944
---------

Signed-off-by: Vadym Struts <vstruts@brave.com>
This commit is contained in:
vadims
2026-06-05 09:43:44 +02:00
committed by GitHub
parent f9ff51f087
commit cec58890a6
8 changed files with 256 additions and 9 deletions
@@ -145,6 +145,8 @@ import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.fullscreen.BrowserControlsManager;
import org.chromium.chrome.browser.fullscreen.FullscreenManager;
import org.chromium.chrome.browser.incognito.reauth.IncognitoReauthController;
import org.chromium.chrome.browser.incognito.reauth.IncognitoReauthManager;
import org.chromium.chrome.browser.informers.BraveSyncAccountDeletedInformer;
import org.chromium.chrome.browser.lifetime.ApplicationLifetime;
import org.chromium.chrome.browser.misc_metrics.MiscAndroidMetricsConnectionErrorHandler;
@@ -357,6 +359,7 @@ public abstract class BraveActivity extends ChromeActivity
private SearchWidgetPromoPanel mSearchWidgetPromoPanel;
private ApplicationStateListener mApplicationStateListener;
private IncognitoReauthController mIncognitoReauthController;
/** Serves as a general exception for failed attempts to get BraveActivity. */
public static class BraveActivityNotFoundException extends Exception {
@@ -2461,6 +2464,9 @@ public abstract class BraveActivity extends ChromeActivity
dbUtil.cleanUpDbOperationRequest();
}
super.performPreInflationStartup();
mRootUiCoordinator
.getIncognitoReauthControllerSupplier()
.onAvailable(irc -> mIncognitoReauthController = irc);
}
@Override
@@ -2824,6 +2830,30 @@ public abstract class BraveActivity extends ChromeActivity
}
}
@Override
public boolean isShredButtonVisible() {
return mIncognitoReauthController == null
|| !mIncognitoReauthController.isIncognitoReauthPending();
}
@Override
public void setShredButtonVisibilityObserver(
IncognitoReauthManager.IncognitoReauthCallback callback) {
if (mIncognitoReauthController == null) {
return;
}
mIncognitoReauthController.addIncognitoReauthCallback(callback);
}
@Override
public void removeShredButtonVisibilityObserver(
IncognitoReauthManager.IncognitoReauthCallback callback) {
if (mIncognitoReauthController == null) {
return;
}
mIncognitoReauthController.removeIncognitoReauthCallback(callback);
}
private void shredData(Tab currentTab) {
LayoutInflater inflater =
(LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+13
View File
@@ -218,4 +218,17 @@ if (is_android) {
deps = [ "//chrome/android/junit:chrome_junit_tests_helper" ]
}
robolectric_library("brave_junit_tests_org.chromium.chrome.browser.hub") {
sources = [
"src/org/chromium/chrome/browser/hub/BraveHubToolbarViewUnitTest.java",
]
deps = [
"//brave/browser/brave_shields/android:java",
"//chrome/android:chrome_java",
"//chrome/android/junit:chrome_junit_tests_helper",
"//chrome/browser/hub/internal:java",
]
}
}
@@ -0,0 +1,136 @@
/* Copyright (c) 2026 The Brave Authors. All rights reserved.
* 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 https://mozilla.org/MPL/2.0/. */
package org.chromium.chrome.browser.hub;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import static org.chromium.chrome.browser.hub.HubColorMixer.COLOR_MIXER;
import static org.chromium.chrome.browser.hub.HubToolbarProperties.IS_INCOGNITO;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.FrameLayout;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.annotation.Config;
import org.chromium.base.BraveFeatureList;
import org.chromium.base.supplier.ObservableSuppliers;
import org.chromium.base.supplier.SettableMonotonicObservableSupplier;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Features.DisableFeatures;
import org.chromium.base.test.util.Features.EnableFeatures;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.app.BraveActivity;
import org.chromium.chrome.browser.brave_shields.FirstPartyStorageCleanerInterface;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
/** Unit tests for {@link BraveHubToolbarView}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
@CommandLineFlags.Add({
ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
ChromeSwitches.DISABLE_NATIVE_INITIALIZATION
})
public class BraveHubToolbarViewUnitTest {
@Rule
public ActivityScenarioRule<ChromeTabbedActivity> mActivityScenarioRule =
new ActivityScenarioRule<>(ChromeTabbedActivity.class);
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
private Pane mPane;
private SettableMonotonicObservableSupplier<Pane> mFocusedPaneSupplier;
private FrameLayout mToolbarContainer;
private Button mShredButton;
private PropertyModel mPropertyModel;
private BraveActivity mActivity;
private HubColorMixer mColorMixer;
private BraveHubToolbarView mHubToolbarView;
@Before
public void setUp() throws Exception {
mActivityScenarioRule.getScenario().onActivity(this::onActivity);
}
private void onActivity(ChromeTabbedActivity activity) {
mPane = mock();
BraveActivity braveActivity = (BraveActivity) (Object) activity;
mActivity = spy(braveActivity);
mActivity.setTheme(R.style.Theme_BrowserUI_DayNight);
LayoutInflater inflater = LayoutInflater.from(mActivity);
mToolbarContainer =
(FrameLayout) inflater.inflate(R.layout.hub_toolbar_layout, null, false);
mHubToolbarView = (BraveHubToolbarView) mToolbarContainer.findViewById(R.id.hub_toolbar);
mShredButton =
mToolbarContainer.findViewById(
org.chromium.chrome.browser.brave_shields.R.id.shred_data_button);
mActivity.setContentView(mToolbarContainer);
mHubToolbarView.setFirstPartyStorageCleanerForTesting(
(FirstPartyStorageCleanerInterface) mActivity);
mFocusedPaneSupplier = ObservableSuppliers.createMonotonic();
mColorMixer =
spy(
new HubColorMixerImpl(
mActivity, ObservableSuppliers.alwaysTrue(), mFocusedPaneSupplier));
mPropertyModel =
new PropertyModel.Builder(HubToolbarProperties.ALL_KEYS)
.with(COLOR_MIXER, mColorMixer)
.build();
PropertyModelChangeProcessor.create(
mPropertyModel, mHubToolbarView, HubToolbarViewBinder::bind);
when(mPane.getColorScheme()).thenReturn(HubColorScheme.DEFAULT);
mFocusedPaneSupplier.set(mPane);
}
@Test
@EnableFeatures({BraveFeatureList.BRAVE_SHRED})
public void testHideManualShredButtonInIncognitoMode() {
when(mActivity.isShredButtonVisible()).thenReturn(true);
assertEquals(View.VISIBLE, mShredButton.getVisibility());
when(mActivity.isShredButtonVisible()).thenReturn(false);
mPropertyModel.set(IS_INCOGNITO, true);
assertEquals(View.INVISIBLE, mShredButton.getVisibility());
when(mActivity.isShredButtonVisible()).thenReturn(true);
mPropertyModel.set(IS_INCOGNITO, false);
assertEquals(View.VISIBLE, mShredButton.getVisibility());
}
@Test
@DisableFeatures({BraveFeatureList.BRAVE_SHRED})
public void testHideManualShredButtonIfFeatureFlagDisabled() {
when(mActivity.isShredButtonVisible()).thenReturn(true);
assertEquals(View.VISIBLE, mShredButton.getVisibility());
when(mActivity.isShredButtonVisible()).thenReturn(false);
mPropertyModel.set(IS_INCOGNITO, true);
assertEquals(View.INVISIBLE, mShredButton.getVisibility());
when(mActivity.isShredButtonVisible()).thenReturn(true);
mPropertyModel.set(IS_INCOGNITO, false);
assertEquals(View.INVISIBLE, mShredButton.getVisibility());
}
}
+1
View File
@@ -19,6 +19,7 @@ android_library("java") {
deps = [
":java_resources",
"//base:base_java",
"//chrome/browser/incognito:java",
"//chrome/browser/profiles/android:java",
"//chrome/browser/tab:java",
"//third_party/android_deps:com_airbnb_android_lottie_java",
@@ -6,8 +6,16 @@
package org.chromium.chrome.browser.brave_shields;
import org.chromium.build.annotations.NullMarked;
import org.chromium.chrome.browser.incognito.reauth.IncognitoReauthManager;
@NullMarked
public interface FirstPartyStorageCleanerInterface {
void shredSiteData();
boolean isShredButtonVisible();
void setShredButtonVisibilityObserver(IncognitoReauthManager.IncognitoReauthCallback callback);
void removeShredButtonVisibilityObserver(
IncognitoReauthManager.IncognitoReauthCallback callback);
}
@@ -13,5 +13,6 @@ brave_browser_hub_internal_java_sources = [
brave_browser_hub_internal_java_deps = [
"//base:base_shared_preferences_java",
"//brave/browser/brave_shields/android:java",
"//chrome/browser/incognito:java",
"//chrome/browser/preferences:java",
]
@@ -14,10 +14,15 @@ import android.view.ViewGroup;
import android.widget.Button;
import android.widget.FrameLayout;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.BraveFeatureList;
import org.chromium.base.BravePreferenceKeys;
import org.chromium.build.annotations.Nullable;
import org.chromium.chrome.browser.brave_shields.FirstPartyStorageCleanerInterface;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.incognito.reauth.IncognitoReauthManager;
import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;
import org.chromium.chrome.browser.toolbar.settings.AddressBarPreference;
@@ -25,10 +30,14 @@ import org.chromium.chrome.browser.toolbar.settings.AddressBarPreference;
* Brave's extension for {@link HubToolbarView}. Here we control what elements should be visible in
* tab switcher mode when bottom toolbar is visible.
*/
public class BraveHubToolbarView extends HubToolbarView {
public class BraveHubToolbarView extends HubToolbarView
implements ApplicationStatus.TaskVisibilityListener,
IncognitoReauthManager.IncognitoReauthCallback {
private Button mActionButton;
private Button mShredButton;
private FrameLayout mMenuButton;
private boolean mIsIncognitoSelected = true;
private @Nullable FirstPartyStorageCleanerInterface mFpCleaner;
public BraveHubToolbarView(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
@@ -37,7 +46,6 @@ public class BraveHubToolbarView extends HubToolbarView {
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mActionButton = findViewById(R.id.toolbar_action_button);
mShredButton =
findViewById(org.chromium.chrome.browser.brave_shields.R.id.shred_data_button);
@@ -45,15 +53,34 @@ public class BraveHubToolbarView extends HubToolbarView {
mShredButton.setOnClickListener(
v -> {
Context context = getContext();
if (context instanceof FirstPartyStorageCleanerInterface) {
FirstPartyStorageCleanerInterface fpCleaner =
(FirstPartyStorageCleanerInterface) context;
fpCleaner.shredSiteData();
if (mFpCleaner != null) {
mFpCleaner.shredSiteData();
}
});
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
Context context = getContext();
if (context instanceof FirstPartyStorageCleanerInterface) {
mFpCleaner = (FirstPartyStorageCleanerInterface) context;
mFpCleaner.setShredButtonVisibilityObserver(this);
}
ApplicationStatus.registerTaskVisibilityListener(this);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
ApplicationStatus.unregisterTaskVisibilityListener(this);
if (mFpCleaner != null) {
mFpCleaner.removeShredButtonVisibilityObserver(this);
}
}
@Override
void setPaneSwitcherIndex(int index) {
super.setPaneSwitcherIndex(index);
@@ -62,6 +89,13 @@ public class BraveHubToolbarView extends HubToolbarView {
updateButtonsVisibility();
}
@Override
void updateIncognitoElements(boolean isIncognito) {
super.updateIncognitoElements(isIncognito);
mIsIncognitoSelected = isIncognito;
updateButtonsVisibility();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
@@ -76,10 +110,31 @@ public class BraveHubToolbarView extends HubToolbarView {
@Override
void setMenuButtonVisible(boolean visible) {
super.setMenuButtonVisible(visible);
updateButtonsVisibility();
}
@Override
public void onIncognitoReauthSuccess() {
updateButtonsVisibility();
}
@Override
public void onIncognitoReauthNotPossible() {}
@Override
public void onIncognitoReauthFailure() {}
@Override
public void onTaskVisibilityChanged(int taskId, boolean isVisible) {
updateButtonsVisibility();
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
public void setFirstPartyStorageCleanerForTesting(
FirstPartyStorageCleanerInterface firstPartyStorageCleaner) {
mFpCleaner = firstPartyStorageCleaner;
}
private void updateButtonsVisibility() {
boolean shouldHideButtons =
AddressBarPreference.isToolbarConfiguredToShowOnTop()
@@ -104,8 +159,10 @@ public class BraveHubToolbarView extends HubToolbarView {
}
}
final boolean isShredButtonVisible =
!mIsIncognitoSelected || mFpCleaner == null || mFpCleaner.isShredButtonVisible();
final boolean shouldShowShredButton =
ChromeFeatureList.isEnabled(BraveFeatureList.BRAVE_SHRED);
ChromeFeatureList.isEnabled(BraveFeatureList.BRAVE_SHRED) && isShredButtonVisible;
mShredButton.setVisibility(shouldShowShredButton ? View.VISIBLE : View.INVISIBLE);
}
}
+1
View File
@@ -1690,6 +1690,7 @@ if (is_android) {
"//brave/android/junit:brave_junit_tests_org.chromium.chrome.browser.compositor",
"//brave/android/junit:brave_junit_tests_org.chromium.chrome.browser.custom_search_engines",
"//brave/android/junit:brave_junit_tests_org.chromium.chrome.browser.homepage",
"//brave/android/junit:brave_junit_tests_org.chromium.chrome.browser.hub",
"//brave/android/junit:brave_junit_tests_org.chromium.chrome.browser.ntp",
"//brave/android/junit:brave_junit_tests_org.chromium.chrome.browser.preferences.website",
"//brave/android/junit:brave_junit_tests_org.chromium.chrome.browser.quick_search_engines",