diff --git a/components/brave_shields/core/common/features.cc b/components/brave_shields/core/common/features.cc index 7968ea6b086..9d92545263f 100644 --- a/components/brave_shields/core/common/features.cc +++ b/components/brave_shields/core/common/features.cc @@ -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, diff --git a/components/brave_shields/core/common/features.h b/components/brave_shields/core/common/features.h index 0bdf0a27a38..e8d7419e252 100644 --- a/components/brave_shields/core/common/features.h +++ b/components/brave_shields/core/common/features.h @@ -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); diff --git a/ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+P3A.swift b/ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+P3A.swift index 6bc0626a86e..a4f53733a22 100644 --- a/ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+P3A.swift +++ b/ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+P3A.swift @@ -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 diff --git a/ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+TabManagerDelegate.swift b/ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+TabManagerDelegate.swift index b6093005d94..1e0b7fefe2d 100644 --- a/ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+TabManagerDelegate.swift +++ b/ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+TabManagerDelegate.swift @@ -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. diff --git a/ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+ToolbarDelegate.swift b/ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+ToolbarDelegate.swift index c40108c60bd..6ce37d1c718 100644 --- a/ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+ToolbarDelegate.swift +++ b/ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+ToolbarDelegate.swift @@ -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, diff --git a/ios/brave-ios/Sources/Brave/Frontend/Browser/LinkPreviewViewController.swift b/ios/brave-ios/Sources/Brave/Frontend/Browser/LinkPreviewViewController.swift index 4e8a2258177..ea35e99179d 100644 --- a/ios/brave-ios/Sources/Brave/Frontend/Browser/LinkPreviewViewController.swift +++ b/ios/brave-ios/Sources/Brave/Frontend/Browser/LinkPreviewViewController.swift @@ -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 diff --git a/ios/brave-ios/Sources/Brave/Frontend/Browser/TabManager.swift b/ios/brave-ios/Sources/Brave/Frontend/Browser/TabManager.swift index 9ba9c68e484..f61c9653de9 100644 --- a/ios/brave-ios/Sources/Brave/Frontend/Browser/TabManager.swift +++ b/ios/brave-ios/Sources/Brave/Frontend/Browser/TabManager.swift @@ -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 diff --git a/ios/brave-ios/Sources/Brave/Frontend/Settings/Features/ShieldsPrivacy/AdvancedShieldSettings.swift b/ios/brave-ios/Sources/Brave/Frontend/Settings/Features/ShieldsPrivacy/AdvancedShieldSettings.swift index 851a07e5d2e..e3e74076476 100644 --- a/ios/brave-ios/Sources/Brave/Frontend/Settings/Features/ShieldsPrivacy/AdvancedShieldSettings.swift +++ b/ios/brave-ios/Sources/Brave/Frontend/Settings/Features/ShieldsPrivacy/AdvancedShieldSettings.swift @@ -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 diff --git a/ios/brave-ios/Sources/Brave/Frontend/Settings/Features/ShieldsPrivacy/DefaultShieldsSectionView.swift b/ios/brave-ios/Sources/Brave/Frontend/Settings/Features/ShieldsPrivacy/DefaultShieldsSectionView.swift index 01e9c610c6a..13e23353b88 100644 --- a/ios/brave-ios/Sources/Brave/Frontend/Settings/Features/ShieldsPrivacy/DefaultShieldsSectionView.swift +++ b/ios/brave-ios/Sources/Brave/Frontend/Settings/Features/ShieldsPrivacy/DefaultShieldsSectionView.swift @@ -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( diff --git a/ios/brave-ios/Sources/Brave/Frontend/Settings/SettingsViewController.swift b/ios/brave-ios/Sources/Brave/Frontend/Settings/SettingsViewController.swift index d44e078b2e2..ecaf250875d 100644 --- a/ios/brave-ios/Sources/Brave/Frontend/Settings/SettingsViewController.swift +++ b/ios/brave-ios/Sources/Brave/Frontend/Settings/SettingsViewController.swift @@ -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, diff --git a/ios/brave-ios/Sources/BraveShields/BraveShieldsTabHelper.swift b/ios/brave-ios/Sources/BraveShields/BraveShieldsTabHelper.swift index dbd9381b661..42a0c0c3dbf 100644 --- a/ios/brave-ios/Sources/BraveShields/BraveShieldsTabHelper.swift +++ b/ios/brave-ios/Sources/BraveShields/BraveShieldsTabHelper.swift @@ -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 } diff --git a/ios/brave-ios/Sources/BraveShields/ShieldLevel.swift b/ios/brave-ios/Sources/BraveShields/ShieldLevel.swift index c0badbb332b..129c761fcbd 100644 --- a/ios/brave-ios/Sources/BraveShields/ShieldLevel.swift +++ b/ios/brave-ios/Sources/BraveShields/ShieldLevel.swift @@ -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 + } + } } diff --git a/ios/browser/api/features/features.h b/ios/browser/api/features/features.h index eac4fe83bf2..b4fbbad519c 100644 --- a/ios/browser/api/features/features.h +++ b/ios/browser/api/features/features.h @@ -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) diff --git a/ios/browser/api/features/features.mm b/ios/browser/api/features/features.mm index ada5a6faa0a..a8992699f87 100644 --- a/ios/browser/api/features/features.mm +++ b/ios/browser/api/features/features.mm @@ -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]; diff --git a/ios/browser/flags/about_flags.mm b/ios/browser/flags/about_flags.mm index 3dd300d1b6e..38cbfc4dc95 100644 --- a/ios/browser/flags/about_flags.mm +++ b/ios/browser/flags/about_flags.mm @@ -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", \