diff --git a/Sources/Brave/Frontend/Browser/Helpers/LaunchHelper.swift b/Sources/Brave/Frontend/Browser/Helpers/LaunchHelper.swift index d1deaa96a21..a8a843ca664 100644 --- a/Sources/Brave/Frontend/Browser/Helpers/LaunchHelper.swift +++ b/Sources/Brave/Frontend/Browser/Helpers/LaunchHelper.swift @@ -87,6 +87,26 @@ public actor LaunchHelper { let remainingModes = ContentBlockerManager.BlockingMode.allCases.filter({ !loadedBlockModes.contains($0) }) Task.detached(priority: .low) { + // Let's disable filter lists if we have reached a maxumum amount + let enabledSources = await AdBlockStats.shared.enabledSources + if enabledSources.count > AdBlockStats.maxNumberOfAllowedFilterLists { + let toDisableSources = enabledSources[AdBlockStats.maxNumberOfAllowedFilterLists...] + + for source in toDisableSources { + switch source { + case .adBlock: + // This should never be in the list because the order of enabledSources places this as the first item + continue + case .filterList(let componentId): + ContentBlockerManager.log.debug("Disabling filter list \(source.debugDescription)") + await FilterListStorage.shared.ensureFilterList(for: componentId, isEnabled: false) + case .filterListURL(let uuid): + ContentBlockerManager.log.debug("Disabling custom filter list \(source.debugDescription)") + await CustomFilterListStorage.shared.ensureFilterList(for: uuid, isEnabled: false) + } + } + } + let signpostID = Self.signpost.makeSignpostID() let state = Self.signpost.beginInterval("nonBlockingLaunchTask", id: signpostID) await FilterListResourceDownloader.shared.start(with: adBlockService) diff --git a/Sources/Brave/Frontend/Settings/Features/ShieldsPrivacy/FilterLists/FilterListsView.swift b/Sources/Brave/Frontend/Settings/Features/ShieldsPrivacy/FilterLists/FilterListsView.swift index 9583c513a25..91d3bdbdf28 100644 --- a/Sources/Brave/Frontend/Settings/Features/ShieldsPrivacy/FilterLists/FilterListsView.swift +++ b/Sources/Brave/Frontend/Settings/Features/ShieldsPrivacy/FilterLists/FilterListsView.swift @@ -41,6 +41,7 @@ struct FilterListsView: View { } header: { Text(Strings.customFilterLists) } + .listRowBackground(Color(.secondaryBraveGroupedBackground)) .toggleStyle(SwitchToggleStyle(tint: .accentColor)) Section { @@ -52,10 +53,9 @@ struct FilterListsView: View { Text(Strings.filterListsDescription) .textCase(.none) } - } + }.listRowBackground(Color(.secondaryBraveGroupedBackground)) } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - .listRowBackground(Color(.secondaryBraveGroupedBackground)) .animation(.default, value: customFilterListStorage.filterListsURLs) .listBackgroundColor(Color(UIColor.braveGroupedBackground)) .listStyle(.insetGrouped) diff --git a/Sources/Brave/WebFilters/CustomFilterListStorage.swift b/Sources/Brave/WebFilters/CustomFilterListStorage.swift index 412bdd5f5f1..70c8842f3cf 100644 --- a/Sources/Brave/WebFilters/CustomFilterListStorage.swift +++ b/Sources/Brave/WebFilters/CustomFilterListStorage.swift @@ -32,6 +32,17 @@ import Data } } } + + /// Ensures that the settings for a filter list are stored + /// - Parameters: + /// - uuid: The uuid of the filter list to update + /// - isEnabled: A boolean indicating if the filter list is enabled or not + public func ensureFilterList(for uuid: String, isEnabled: Bool) { + // Enable the setting + if let index = filterListsURLs.firstIndex(where: { $0.setting.uuid == uuid }) { + filterListsURLs[index].setting.isEnabled = isEnabled + } + } func update(filterListId id: ObjectIdentifier, with result: Result) { guard let index = filterListsURLs.firstIndex(where: { $0.id == id }) else { diff --git a/Sources/Brave/WebFilters/FilterListCustomURLDownloader.swift b/Sources/Brave/WebFilters/FilterListCustomURLDownloader.swift index b5014d14b7a..4caf134bfa9 100644 --- a/Sources/Brave/WebFilters/FilterListCustomURLDownloader.swift +++ b/Sources/Brave/WebFilters/FilterListCustomURLDownloader.swift @@ -128,14 +128,10 @@ actor FilterListCustomURLDownloader: ObservableObject { return } - do { - try await AdBlockStats.shared.compileDelayed( - filterListInfo: filterListInfo, resourcesInfo: resourcesInfo, - isAlwaysAggressive: true, delayed: true - ) - } catch { - // Don't handle cancellation errors - } + await AdBlockStats.shared.compile( + filterListInfo: filterListInfo, resourcesInfo: resourcesInfo, + isAlwaysAggressive: true + ) } /// Start fetching the resource for the given filter list. Once a new version is downloaded, the file will be processed using the `handle` method diff --git a/Sources/Brave/WebFilters/FilterListResourceDownloader.swift b/Sources/Brave/WebFilters/FilterListResourceDownloader.swift index a667ad5b1f2..3ec37276406 100644 --- a/Sources/Brave/WebFilters/FilterListResourceDownloader.swift +++ b/Sources/Brave/WebFilters/FilterListResourceDownloader.swift @@ -194,16 +194,10 @@ public actor FilterListResourceDownloader { return } - let isImportant = await AdBlockStats.shared.criticalSources.contains(source) - - do { - try await AdBlockStats.shared.compileDelayed( - filterListInfo: filterListInfo, resourcesInfo: resourcesInfo, - isAlwaysAggressive: isAlwaysAggressive, delayed: !isImportant - ) - } catch { - // Don't handle cancellation errors - } + await AdBlockStats.shared.compile( + filterListInfo: filterListInfo, resourcesInfo: resourcesInfo, + isAlwaysAggressive: isAlwaysAggressive + ) } /// Register all enabled filter lists with the `AdBlockService` diff --git a/Sources/Brave/WebFilters/ShieldStats/Adblock/AdBlockStats.swift b/Sources/Brave/WebFilters/ShieldStats/Adblock/AdBlockStats.swift index 648af310393..1952e851e1b 100644 --- a/Sources/Brave/WebFilters/ShieldStats/Adblock/AdBlockStats.swift +++ b/Sources/Brave/WebFilters/ShieldStats/Adblock/AdBlockStats.swift @@ -11,7 +11,13 @@ import os.log /// This object holds on to our adblock engines and returns information needed for stats tracking as well as some conveniences /// for injected scripts needed during web navigation and cosmetic filters models needed by the `SelectorsPollerScript.js` script. public actor AdBlockStats { - static let maxNumberOfAllowedFilterLists = 30 + /// The max number of enabled filter lists depending on the amount memory available to the device + static var maxNumberOfAllowedFilterLists: Int = { + let memory = Int(ProcessInfo.processInfo.physicalMemory / 1073741824) + ContentBlockerManager.log.debug("Memory: \(memory)") + return min(5 * memory, 40) + }() + typealias CosmeticFilterModelTuple = (isAlwaysAggressive: Bool, model: CosmeticFilterModel) public static let shared = AdBlockStats() @@ -63,33 +69,13 @@ public actor AdBlockStats { await removeDisabledEngines() } - public func compileDelayed( - filterListInfo: CachedAdBlockEngine.FilterListInfo, resourcesInfo: CachedAdBlockEngine.ResourcesInfo, - isAlwaysAggressive: Bool, delayed: Bool - ) async throws { - if delayed { - Task.detached(priority: .background) { - ContentBlockerManager.log.debug("Delaying \(filterListInfo.source.debugDescription)") - await self.compile( - filterListInfo: filterListInfo, resourcesInfo: resourcesInfo, - isAlwaysAggressive: isAlwaysAggressive - ) - } - } else { - await self.compile( - filterListInfo: filterListInfo, resourcesInfo: resourcesInfo, - isAlwaysAggressive: isAlwaysAggressive - ) - } - } - /// Create and add an engine from the given resources. /// If an engine already exists for the given source, it will be replaced. /// /// - Note: This method will ensure syncronous compilation public func compile( filterListInfo: CachedAdBlockEngine.FilterListInfo, resourcesInfo: CachedAdBlockEngine.ResourcesInfo, - isAlwaysAggressive: Bool + isAlwaysAggressive: Bool, ignoreMaximum: Bool = false ) async { await currentCompileTask?.value @@ -99,7 +85,7 @@ public actor AdBlockStats { } currentCompileTask = Task { - if reachedMaxLimit && cachedEngines[filterListInfo.source] == nil { + if !ignoreMaximum && reachedMaxLimit && cachedEngines[filterListInfo.source] == nil { ContentBlockerManager.log.error("Failed to compile engine for \(filterListInfo.source.debugDescription): Reached maximum!") return } @@ -171,22 +157,18 @@ public actor AdBlockStats { /// Remove all engines that have disabled sources func ensureEnabledEngines() async { do { - var count = 0 - for source in await enabledSources { guard cachedEngines[source] == nil else { continue } guard let availableFilterList = availableFilterLists[source] else { continue } guard let resourcesInfo = self.resourcesInfo else { continue } - try await compileDelayed( + await compile( filterListInfo: availableFilterList.filterListInfo, resourcesInfo: resourcesInfo, - isAlwaysAggressive: availableFilterList.isAlwaysAggressive, - delayed: count > 5 + isAlwaysAggressive: availableFilterList.isAlwaysAggressive ) // Sleep for 1ms. This drastically reduces memory usage without much impact to usability - count += 1 try await Task.sleep(nanoseconds: 1000000) } } catch {