feat(privacy): Use Content Settings for some Shields Settings on iOS (#30608)

* Add feature flag
- Add an iOS-only feature flag for using content settings for persisting Brave Shields settings.

* Update Shields default/global settings.
- Use content settings when the feature flag is enabled for default settings.

* Use content settings for shields when flag is enabled.
- Update BraveShieldsTabHelper to use content settings when the flag is enabled.

* Use core P3A when feature flag is enabled.
This commit is contained in:
StephenHeaps
2025-08-19 09:27:45 -04:00
committed by GitHub
parent 2e953008dd
commit 64dcc49d3d
15 changed files with 159 additions and 10 deletions
@@ -105,6 +105,10 @@ BASE_FEATURE(kBraveShredCacheData,
#else
base::FEATURE_DISABLED_BY_DEFAULT);
#endif
// When enabled, brave will use content settings for Shields on iOS.
BASE_FEATURE(kBraveShieldsContentSettingsIOS,
"BraveShieldsContentSettingsIOS",
base::FEATURE_DISABLED_BY_DEFAULT);
// When enabled, will display debug menu for adblock features in the Shields
// panel.
BASE_FEATURE(kBraveIOSDebugAdblock,
@@ -34,6 +34,7 @@ BASE_DECLARE_FEATURE(kBraveLocalhostAccessPermission);
BASE_DECLARE_FEATURE(kBraveReduceLanguage);
BASE_DECLARE_FEATURE(kBraveShredFeature);
BASE_DECLARE_FEATURE(kBraveShredCacheData);
BASE_DECLARE_FEATURE(kBraveShieldsContentSettingsIOS);
BASE_DECLARE_FEATURE(kBraveIOSDebugAdblock);
BASE_DECLARE_FEATURE(kBraveIOSEnableFarblingPlugins);
BASE_DECLARE_FEATURE(kBraveShowStrictFingerprintingMode);
@@ -158,6 +158,8 @@ extension BrowserViewController {
}
@MainActor private func recordShieldsLevelUpdateP3A(buckets: [Bucket]) {
// if the flag is enabled, P3A is recorded in core shields functions.
guard FeatureList.kBraveShieldsContentSettings.enabled else { return }
// Q51 On how many domains has the user set the adblock setting to be lower (block less) than the default?
let adsBelowGlobalCount = Domain.totalDomainsWithAdblockShieldsLoweredFromGlobal()
UmaHistogramRecordValueToBucket(
@@ -175,6 +177,8 @@ extension BrowserViewController {
}
func recordFinterprintProtectionP3A(buckets: [Bucket]) {
// if the flag is enabled, P3A is recorded in core shields functions.
guard FeatureList.kBraveShieldsContentSettings.enabled else { return }
// Q53 On how many domains has the user set the FP setting to be lower (block less) than the default?
let fingerprintingBelowGlobalCount =
Domain.totalDomainsWithFingerprintingProtectionLoweredFromGlobal()
@@ -194,6 +198,8 @@ extension BrowserViewController {
}
func recordGlobalAdBlockShieldsP3A() {
// if the flag is enabled, P3A is recorded in core shields functions.
guard FeatureList.kBraveShieldsContentSettings.enabled else { return }
// Q46 What is the global ad blocking shields setting?
enum Answer: Int, CaseIterable {
case disabled = 0
@@ -213,6 +219,8 @@ extension BrowserViewController {
}
func recordGlobalFingerprintingShieldsP3A() {
// if the flag is enabled, P3A is recorded in core shields functions.
guard FeatureList.kBraveShieldsContentSettings.enabled else { return }
// Q47 What is the global fingerprinting shields setting?
enum Answer: Int, CaseIterable {
case disabled = 0
@@ -30,7 +30,12 @@ extension BrowserViewController: TabManagerDelegate {
tab.pageMetadataHelper = .init(tab: tab)
tab.faviconTabHelper = .init(tab: tab)
tab.userActivityHelper = .init(tab: tab)
let braveShieldsHelper: BraveShieldsTabHelper = .init(tab: tab)
let profile =
tab.isPrivate ? profileController.profile.offTheRecordProfile : profileController.profile
let braveShieldsHelper: BraveShieldsTabHelper = .init(
tab: tab,
braveShieldsSettings: BraveShieldsSettingsFactory.create(for: profile)
)
tab.braveShieldsHelper = braveShieldsHelper
// When `BraveShieldsTabHelper+TabPolicyDecider` is moved to `BraveShields` target,
// we should add it as a policy decider at initialization.
@@ -520,6 +520,7 @@ extension BrowserViewController: TopToolbarDelegate {
tabManager: self.tabManager,
feedDataSource: self.feedDataSource,
debounceService: DebounceServiceFactory.get(privateMode: false),
braveShieldsSettings: BraveShieldsSettingsFactory.create(for: profileController.profile),
braveCore: profileController,
p3aUtils: braveCore.p3aUtils,
rewards: rewards,
@@ -2,6 +2,7 @@
// 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/.
import BraveCore
import BraveShields
import Data
import Shared
@@ -42,7 +43,14 @@ class LinkPreviewViewController: UIViewController {
tab.miscDelegate = browserController
tab.createWebView()
tab.addPolicyDecider(browserController)
let braveShieldsTabHelper: BraveShieldsTabHelper = .init(tab: tab)
let profile =
tab.isPrivate
? browserController.profileController.profile.offTheRecordProfile
: browserController.profileController.profile
let braveShieldsTabHelper: BraveShieldsTabHelper = .init(
tab: tab,
braveShieldsSettings: BraveShieldsSettingsFactory.create(for: profile)
)
tab.braveShieldsHelper = braveShieldsTabHelper
tab.addPolicyDecider(braveShieldsTabHelper)
tab.delegate = browserController
@@ -1262,7 +1262,7 @@ class TabManager: NSObject {
} else {
// Don't access `shredLevel` directly, but `TabState` is unavailable
// to access via `BraveShieldsTabHelper`. Temporarily access here until
// we switch to using `BraveShieldsUtilsIOS` with brave-browser#47350
// we switch to using `BraveShieldsSettings` with brave-browser#47350
let siteDomain = Domain.getOrCreate(
forUrl: url,
persistent: !isPrivate
@@ -65,9 +65,35 @@ import os
}
@Published var adBlockAndTrackingPreventionLevel: ShieldLevel {
didSet {
guard oldValue != adBlockAndTrackingPreventionLevel else { return }
if FeatureList.kBraveShieldsContentSettings.enabled {
braveShieldsSettings.defaultAdBlockMode = adBlockAndTrackingPreventionLevel.adBlockMode
}
// Also assign to existing pref until deprecated so reverse migration is not required
ShieldPreferences.blockAdsAndTrackingLevel = adBlockAndTrackingPreventionLevel
}
}
@Published var isBlockScriptsEnabled: Bool {
didSet {
guard oldValue != isBlockScriptsEnabled else { return }
if FeatureList.kBraveShieldsContentSettings.enabled {
braveShieldsSettings.isBlockScriptsEnabledByDefault = isBlockScriptsEnabled
}
// Also assign to existing pref until deprecated so reverse migration is not required
Preferences.Shields.blockScripts.value = isBlockScriptsEnabled
}
}
@Published var isBlockFingerprintingEnabled: Bool {
didSet {
guard oldValue != isBlockFingerprintingEnabled else { return }
if FeatureList.kBraveShieldsContentSettings.enabled {
braveShieldsSettings.defaultFingerprintMode =
isBlockFingerprintingEnabled ? .standardMode : .allowMode
}
// Also assign to existing pref until deprecated so reverse migration is not required
Preferences.Shields.fingerprintingProtection.value = isBlockFingerprintingEnabled
}
}
@Published var httpsUpgradeLevel: HTTPSUpgradeLevel {
didSet {
ShieldPreferences.httpsUpgradeLevel = httpsUpgradeLevel
@@ -79,6 +105,7 @@ import os
}
@Published var shredLevel: SiteShredLevel {
didSet {
// TODO: Support AutoShred via content settings brave-browser#47753
ShieldPreferences.shredLevel = shredLevel
}
}
@@ -107,6 +134,7 @@ import os
private let p3aUtilities: BraveP3AUtils
private let deAmpPrefs: DeAmpPrefs
private let debounceService: (any DebounceService)?
private let braveShieldsSettings: any BraveShieldsSettings
private let rewards: BraveRewards?
private let clearDataCallback: ClearDataCallback
private let webcompatReporterHandler: WebcompatReporterWebcompatReporterHandler?
@@ -117,6 +145,7 @@ import os
tabManager: TabManager,
feedDataSource: FeedDataSource,
debounceService: (any DebounceService)?,
braveShieldsSettings: any BraveShieldsSettings,
braveCore: BraveProfileController,
p3aUtils: BraveP3AUtils,
rewards: BraveRewards?,
@@ -126,14 +155,25 @@ import os
self.p3aUtilities = p3aUtils
self.deAmpPrefs = braveCore.deAmpPrefs
self.debounceService = debounceService
self.braveShieldsSettings = braveShieldsSettings
self.tabManager = tabManager
self.isP3AEnabled = p3aUtilities.isP3AEnabled
self.rewards = rewards
self.clearDataCallback = clearDataCallback
self.adBlockAndTrackingPreventionLevel = ShieldPreferences.blockAdsAndTrackingLevel
if FeatureList.kBraveShieldsContentSettings.enabled {
self.adBlockAndTrackingPreventionLevel = braveShieldsSettings.defaultAdBlockMode.shieldLevel
self.isBlockScriptsEnabled = braveShieldsSettings.isBlockScriptsEnabledByDefault
self.isBlockFingerprintingEnabled =
braveShieldsSettings.defaultFingerprintMode == .standardMode
} else {
self.adBlockAndTrackingPreventionLevel = ShieldPreferences.blockAdsAndTrackingLevel
self.isBlockScriptsEnabled = Preferences.Shields.blockScripts.value
self.isBlockFingerprintingEnabled = Preferences.Shields.fingerprintingProtection.value
}
self.httpsUpgradeLevel = ShieldPreferences.httpsUpgradeLevel
self.isDeAmpEnabled = deAmpPrefs.isDeAmpEnabled
self.isDebounceEnabled = debounceService?.isEnabled ?? false
// TODO: Support AutoShred via content settings brave-browser#47753
self.shredLevel = ShieldPreferences.shredLevel
self.webcompatReporterHandler = webcompatReporterHandler
self.isSurveyPanelistEnabled = rewards?.ads.isSurveyPanelistEnabled ?? false
@@ -72,16 +72,16 @@ struct DefaultShieldsSectionView: View {
subtitle: Strings.autoRedirectTrackingURLsDescription,
toggle: $settings.isDebounceEnabled
)
OptionToggleView(
ToggleView(
title: Strings.blockScripts,
subtitle: Strings.blockScriptsDescription,
option: Preferences.Shields.blockScripts
toggle: $settings.isBlockScriptsEnabled
)
OptionToggleView(
ToggleView(
title: Strings.fingerprintingProtection,
subtitle: Strings.fingerprintingProtectionDescription,
option: Preferences.Shields.fingerprintingProtection
toggle: $settings.isBlockFingerprintingEnabled
)
ToggleView(
@@ -364,6 +364,7 @@ class SettingsViewController: TableViewController {
tabManager: self.tabManager,
feedDataSource: self.feedDataSource,
debounceService: DebounceServiceFactory.get(privateMode: false),
braveShieldsSettings: BraveShieldsSettingsFactory.create(for: braveCore.profile),
braveCore: braveCore,
p3aUtils: p3aUtilities,
rewards: rewards,
@@ -3,6 +3,7 @@
// 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/.
import BraveCore
import Data
import Foundation
import Preferences
@@ -21,25 +22,40 @@ extension TabDataValues {
@MainActor
public class BraveShieldsTabHelper {
private weak var tab: (any TabState)?
private let braveShieldsSettings: any BraveShieldsSettings
public init(tab: some TabState) {
public init(
tab: some TabState,
braveShieldsSettings: any BraveShieldsSettings
) {
self.tab = tab
self.braveShieldsSettings = braveShieldsSettings
}
public func isBraveShieldsEnabled(for url: URL?) -> Bool {
guard let url = url ?? tab?.visibleURL, let isPrivate = tab?.isPrivate else { return false }
if FeatureList.kBraveShieldsContentSettings.enabled {
return braveShieldsSettings.isBraveShieldsEnabled(for: url)
}
let domain = Domain.getOrCreate(forUrl: url, persistent: !isPrivate)
return !domain.areAllShieldsOff
}
public func setBraveShieldsEnabled(_ isEnabled: Bool, for url: URL?) {
guard let url = url ?? tab?.visibleURL, let isPrivate = tab?.isPrivate else { return }
if FeatureList.kBraveShieldsContentSettings.enabled {
braveShieldsSettings.setBraveShieldsEnabled(isEnabled, for: url)
}
// Also assign to Domain until deprecated so reverse migration is required
let domain = Domain.getOrCreate(forUrl: url, persistent: !isPrivate)
domain.shield_allOff = NSNumber(booleanLiteral: !isEnabled)
}
public func shieldLevel(for url: URL?, considerAllShieldsOption: Bool) -> ShieldLevel {
guard let url = url ?? tab?.visibleURL, let isPrivate = tab?.isPrivate else { return .disabled }
if FeatureList.kBraveShieldsContentSettings.enabled {
return braveShieldsSettings.adBlockMode(for: url).shieldLevel
}
let domain = Domain.getOrCreate(forUrl: url, persistent: !isPrivate)
if considerAllShieldsOption {
return domain.globalBlockAdsAndTrackingLevel
@@ -50,18 +66,30 @@ public class BraveShieldsTabHelper {
public func setShieldLevel(_ shieldLevel: ShieldLevel, for url: URL?) {
guard let url = url ?? tab?.visibleURL, let isPrivate = tab?.isPrivate else { return }
if FeatureList.kBraveShieldsContentSettings.enabled {
braveShieldsSettings.setAdBlockMode(shieldLevel.adBlockMode, for: url)
}
// Also assign to Domain until deprecated so reverse migration is required
let domain = Domain.getOrCreate(forUrl: url, persistent: !isPrivate)
domain.domainBlockAdsAndTrackingLevel = shieldLevel
}
public func setBlockScriptsEnabled(_ isEnabled: Bool, for url: URL?) {
guard let url = url ?? tab?.visibleURL, let isPrivate = tab?.isPrivate else { return }
if FeatureList.kBraveShieldsContentSettings.enabled {
braveShieldsSettings.setBlockScriptsEnabled(isEnabled, for: url)
}
// Also assign to Domain until deprecated so reverse migration is required
let domain = Domain.getOrCreate(forUrl: url, persistent: !isPrivate)
domain.shield_noScript = NSNumber(booleanLiteral: isEnabled)
}
public func setBlockFingerprintingEnabled(_ isEnabled: Bool, for url: URL?) {
guard let url = url ?? tab?.visibleURL, let isPrivate = tab?.isPrivate else { return }
if FeatureList.kBraveShieldsContentSettings.enabled {
braveShieldsSettings.setFingerprintMode(isEnabled ? .standardMode : .allowMode, for: url)
}
// Also assign to Domain until deprecated so reverse migration is required
let domain = Domain.getOrCreate(forUrl: url, persistent: !isPrivate)
domain.shield_fpProtection = NSNumber(booleanLiteral: isEnabled)
}
@@ -73,6 +101,20 @@ public class BraveShieldsTabHelper {
considerAllShieldsOption: Bool
) -> Bool {
guard let url = url ?? tab?.visibleURL, let isPrivate = tab?.isPrivate else { return false }
if FeatureList.kBraveShieldsContentSettings.enabled {
if considerAllShieldsOption && !isBraveShieldsEnabled(for: url) {
// Shields is disabled for this url
return false
}
switch shield {
case .allOff:
return braveShieldsSettings.isBraveShieldsEnabled(for: url)
case .fpProtection:
return braveShieldsSettings.fingerprintMode(for: url) == .standardMode
case .noScript:
return braveShieldsSettings.isBlockScriptsEnabled(for: url)
}
}
let domain = Domain.getOrCreate(forUrl: url, persistent: !isPrivate)
return domain.isShieldExpected(shield, considerAllShieldsOption: considerAllShieldsOption)
}
@@ -81,6 +123,7 @@ public class BraveShieldsTabHelper {
for url: URL?
) -> SiteShredLevel {
guard let url = url ?? tab?.visibleURL, let isPrivate = tab?.isPrivate else { return .never }
// TODO: Support AutoShred via content settings brave-browser#47753
let domain = Domain.getOrCreate(forUrl: url, persistent: !isPrivate)
return domain.shredLevel
}
@@ -90,6 +133,7 @@ public class BraveShieldsTabHelper {
for url: URL?
) {
guard let url = url ?? tab?.visibleURL, let isPrivate = tab?.isPrivate else { return }
// TODO: Support AutoShred via content settings brave-browser#47753
let domain = Domain.getOrCreate(forUrl: url, persistent: !isPrivate)
domain.shredLevel = shredLevel
}
@@ -3,6 +3,7 @@
// 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/.
import BraveCore
import Foundation
/// A 3 part option for shield levels varying in strength of blocking content
@@ -37,4 +38,25 @@ public enum ShieldLevel: String, CaseIterable, Hashable {
case .disabled, .standard: return false
}
}
public var adBlockMode: BraveShields.AdBlockMode {
switch self {
case .aggressive: return .aggressive
case .standard: return .standard
case .disabled: return .allow
}
}
}
extension BraveShields.AdBlockMode {
public var shieldLevel: ShieldLevel {
switch self {
case .aggressive: return .aggressive
case .standard: return .standard
case .allow: return .disabled
@unknown default:
assertionFailure("Unexpected AdBlockMode")
return .standard
}
}
}
+1
View File
@@ -53,6 +53,7 @@ OBJC_EXPORT
@property(class, nonatomic, readonly) Feature* kBraveSearchDefaultAPIFeature;
@property(class, nonatomic, readonly) Feature* kBraveShredFeature;
@property(class, nonatomic, readonly) Feature* kBraveShredCacheData;
@property(class, nonatomic, readonly) Feature* kBraveShieldsContentSettings;
@property(class, nonatomic, readonly) Feature* kBraveIOSDebugAdblock;
@property(class, nonatomic, readonly) Feature* kBraveIOSEnableFarblingPlugins;
@property(class, nonatomic, readonly)
+5
View File
@@ -211,6 +211,11 @@
initWithFeature:&brave_shields::features::kBraveShredCacheData];
}
+ (Feature*)kBraveShieldsContentSettings {
return [[Feature alloc] initWithFeature:&brave_shields::features::
kBraveShieldsContentSettingsIOS];
}
+ (Feature*)kBraveIOSDebugAdblock {
return [[Feature alloc]
initWithFeature:&brave_shields::features::kBraveIOSDebugAdblock];
+9
View File
@@ -90,6 +90,15 @@
flags_ui::kOsIos, \
FEATURE_VALUE_TYPE(brave_shields::features::kBraveShredCacheData), \
}, \
{ \
"brave-shields-content-settings", \
"Brave Shields use Content Settings", \
"Brave Shields will use content settings for persisting Shields " \
"preferences", \
flags_ui::kOsIos, \
FEATURE_VALUE_TYPE( \
brave_shields::features::kBraveShieldsContentSettingsIOS), \
}, \
{ \
"https-by-default", \
"Use HTTPS by Default", \