Skip to content
This repository has been archived by the owner on May 10, 2024. It is now read-only.

[Uplift] Impose the max engines limitation on content blockers (uplift to 1.58.1) #8336

Merged
merged 2 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Sources/Brave/Frontend/Browser/Helpers/LaunchHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ public actor LaunchHelper {

Task.detached(priority: .low) {
// Let's disable filter lists if we have reached a maxumum amount
let enabledSources = await AdBlockStats.shared.enabledSources
let enabledSources = await AdBlockStats.shared.enabledPrioritizedSources

if enabledSources.count > AdBlockStats.maxNumberOfAllowedFilterLists {
let toDisableSources = enabledSources[AdBlockStats.maxNumberOfAllowedFilterLists...]

Expand Down
27 changes: 9 additions & 18 deletions Sources/Brave/WebFilters/AdblockResourceDownloader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,35 +122,26 @@ public actor AdblockResourceDownloader: Sendable {
switch resource {
case .adBlockRules:
let blocklistType = ContentBlockerManager.BlocklistType.generic(.blockAds)
let modes = await blocklistType.allowedModes.asyncFilter { mode in
guard allowedModes.contains(mode) else { return false }
if downloadResult.isModified { return true }

// If the file wasn't modified, make sure we have something compiled.
// We should, but this can be false during upgrades if the identifier changed for some reason.
if await ContentBlockerManager.shared.hasRuleList(for: blocklistType, mode: mode) {
ContentBlockerManager.log.debug("Rule list already compiled for `\(blocklistType.makeIdentifier(for: mode))`")
return false
} else {
return true
}
var modes = blocklistType.allowedModes

if !downloadResult.isModified && !allowedModes.isEmpty {
// If the download is not modified, only compile the missing modes for performance reasons
let missingModes = await ContentBlockerManager.shared.missingModes(for: blocklistType)
modes = missingModes.filter({ allowedModes.contains($0) })
}

// No modes are needed to be compiled
guard !modes.isEmpty else { return }

do {
guard let filterSet = try resource.downloadedString() else {
guard let fileURL = resource.downloadedFileURL else {
assertionFailure("This file was downloaded successfully so it should not be nil")
return
}

let result = try AdblockEngine.contentBlockerRules(fromFilterSet: filterSet)

// try to compile
try await ContentBlockerManager.shared.compile(
encodedContentRuleList: result.rulesJSON, for: blocklistType,
modes: modes
try await ContentBlockerManager.shared.compileRuleList(
at: fileURL, for: blocklistType, modes: modes
)
} catch {
ContentBlockerManager.log.error(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Data
import Shared
import Preferences
import BraveShields
import BraveCore
import os.log

/// A class that aids in the managment of rule lists on the rule store.
Expand Down Expand Up @@ -165,9 +166,11 @@ actor ContentBlockerManager {
}
}

/// Compile the given resource and store it in cache for the given blocklist type using all allowed modes
func compile(encodedContentRuleList: String, for type: BlocklistType, options: CompileOptions = []) async throws {
try await self.compile(encodedContentRuleList: encodedContentRuleList, for: type, modes: type.allowedModes)
/// Compile the rule list found in the given local URL using the specified modes
func compileRuleList(at localFileURL: URL, for type: BlocklistType, options: CompileOptions = [], modes: [BlockingMode]) async throws {
let filterSet = try String(contentsOf: localFileURL)
let result = try AdblockEngine.contentBlockerRules(fromFilterSet: filterSet)
try await compile(encodedContentRuleList: result.rulesJSON, for: type, options: options, modes: modes)
}

/// Compile the given resource and store it in cache for the given blocklist type and specified modes
Expand Down Expand Up @@ -253,6 +256,20 @@ actor ContentBlockerManager {
return ruleList
}

/// Return all the modes that need to be compiled for the given type
func missingModes(for type: BlocklistType) async -> [BlockingMode] {
return await type.allowedModes.asyncFilter { mode in
// If the file wasn't modified, make sure we have something compiled.
// We should, but this can be false during upgrades if the identifier changed for some reason.
if await hasRuleList(for: type, mode: mode) {
ContentBlockerManager.log.debug("Rule list already compiled for `\(type.makeIdentifier(for: mode))`")
return false
} else {
return true
}
}
}

/// Check if a rule list is compiled for this type
func hasRuleList(for type: BlocklistType, mode: BlockingMode) async -> Bool {
do {
Expand Down
41 changes: 15 additions & 26 deletions Sources/Brave/WebFilters/FilterListCustomURLDownloader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,54 +87,43 @@ actor FilterListCustomURLDownloader: ObservableObject {
/// Handle the download results of a custom filter list. This will process the download by compiling iOS rule lists and adding the rule list to the `AdblockEngineManager`.
private func handle(downloadResult: ResourceDownloader<DownloadResource>.DownloadResult, for filterListCustomURL: FilterListCustomURL) async {
let uuid = await filterListCustomURL.setting.uuid

// Compile this rule list if we haven't already or if the file has been modified
if downloadResult.isModified {
do {
let filterSet = try String(contentsOf: downloadResult.fileURL, encoding: .utf8)
let result = try AdblockEngine.contentBlockerRules(fromFilterSet: filterSet)
let type = ContentBlockerManager.BlocklistType.customFilterList(uuid: uuid)

try await ContentBlockerManager.shared.compile(
encodedContentRuleList: result.rulesJSON,
for: type,
options: .all
)

ContentBlockerManager.log.debug(
"Loaded custom filter list content blockers: \(String(describing: type))"
)
} catch {
ContentBlockerManager.log.error(
"Failed to convert custom filter list to content blockers: \(error.localizedDescription)"
)
}
}

// Add/remove the resource depending on if it is enabled/disabled
let source = CachedAdBlockEngine.Source.filterListURL(uuid: uuid)
guard let resourcesInfo = await FilterListResourceDownloader.shared.resourcesInfo else {
assertionFailure("This should not have been called if the resources are not ready")
return
}

let source = await filterListCustomURL.setting.engineSource
let version = fileVersionDateFormatter.string(from: downloadResult.date)
let filterListInfo = CachedAdBlockEngine.FilterListInfo(
source: .filterListURL(uuid: uuid),
localFileURL: downloadResult.fileURL,
version: version, fileType: .text
)
let lazyInfo = AdBlockStats.LazyFilterListInfo(
filterListInfo: filterListInfo, isAlwaysAggressive: true
)

guard await AdBlockStats.shared.isEagerlyLoaded(source: source) else {
// Don't compile unless eager
await AdBlockStats.shared.updateIfNeeded(resourcesInfo: resourcesInfo)
await AdBlockStats.shared.updateIfNeeded(filterListInfo: filterListInfo, isAlwaysAggressive: true)

// To free some space, remove any rule lists that are not needed
if let blocklistType = lazyInfo.blocklistType {
do {
try await ContentBlockerManager.shared.removeRuleLists(for: blocklistType)
} catch {
ContentBlockerManager.log.error("Failed to remove rule lists for \(filterListInfo.debugDescription)")
}
}
return
}

await AdBlockStats.shared.compile(
filterListInfo: filterListInfo, resourcesInfo: resourcesInfo,
isAlwaysAggressive: true
lazyInfo: lazyInfo, resourcesInfo: resourcesInfo,
compileContentBlockers: downloadResult.isModified
)
}

Expand Down
80 changes: 22 additions & 58 deletions Sources/Brave/WebFilters/FilterListResourceDownloader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ public actor FilterListResourceDownloader {
await self.compileFilterListEngineIfNeeded(
fromComponentId: componentId, folderURL: folderURL,
isAlwaysAggressive: setting.isAlwaysAggressive,
resourcesInfo: resourcesInfo
resourcesInfo: resourcesInfo,
compileContentBlockers: false
)

// Sleep for 1ms. This drastically reduces memory usage without much impact to usability
Expand Down Expand Up @@ -157,22 +158,22 @@ public actor FilterListResourceDownloader {
localFileURL: folderURL.appendingPathComponent("rs-ABPFilterParserData.dat", conformingTo: .data),
version: version, fileType: .dat
)

guard await AdBlockStats.shared.needsCompilation(for: filterListInfo, resourcesInfo: resourcesInfo) else {
return
}
let lazyInfo = AdBlockStats.LazyFilterListInfo(
filterListInfo: filterListInfo, isAlwaysAggressive: false
)

await AdBlockStats.shared.compile(
filterListInfo: filterListInfo, resourcesInfo: resourcesInfo,
isAlwaysAggressive: false
lazyInfo: lazyInfo, resourcesInfo: resourcesInfo,
compileContentBlockers: false
)
}

/// Load general filter lists (shields) from the given `AdblockService` `shieldsInstallPath` `URL`.
private func compileFilterListEngineIfNeeded(
fromComponentId componentId: String, folderURL: URL,
isAlwaysAggressive: Bool,
resourcesInfo: CachedAdBlockEngine.ResourcesInfo
resourcesInfo: CachedAdBlockEngine.ResourcesInfo,
compileContentBlockers: Bool
) async {
let version = folderURL.lastPathComponent
let source = CachedAdBlockEngine.Source.filterList(componentId: componentId)
Expand All @@ -181,22 +182,26 @@ public actor FilterListResourceDownloader {
localFileURL: folderURL.appendingPathComponent("list.txt", conformingTo: .text),
version: version, fileType: .text
)

let lazyInfo = AdBlockStats.LazyFilterListInfo(filterListInfo: filterListInfo, isAlwaysAggressive: isAlwaysAggressive)
guard await AdBlockStats.shared.isEagerlyLoaded(source: source) else {
// Don't compile unless eager
await AdBlockStats.shared.updateIfNeeded(resourcesInfo: resourcesInfo)
await AdBlockStats.shared.updateIfNeeded(filterListInfo: filterListInfo, isAlwaysAggressive: isAlwaysAggressive)
return
}

guard await AdBlockStats.shared.needsCompilation(for: filterListInfo, resourcesInfo: resourcesInfo) else {
// Don't compile unless needed

// To free some space, remove any rule lists that are not needed
if let blocklistType = lazyInfo.blocklistType {
do {
try await ContentBlockerManager.shared.removeRuleLists(for: blocklistType)
} catch {
ContentBlockerManager.log.error("Failed to remove rule lists for \(filterListInfo.debugDescription)")
}
}
return
}

await AdBlockStats.shared.compile(
filterListInfo: filterListInfo, resourcesInfo: resourcesInfo,
isAlwaysAggressive: isAlwaysAggressive
lazyInfo: lazyInfo, resourcesInfo: resourcesInfo,
compileContentBlockers: compileContentBlockers
)
}

Expand Down Expand Up @@ -258,49 +263,8 @@ public actor FilterListResourceDownloader {
// Add or remove the filter list from the engine depending if it's been enabled or not
await self.compileFilterListEngineIfNeeded(
fromComponentId: componentId, folderURL: folderURL, isAlwaysAggressive: isAlwaysAggressive,
resourcesInfo: resourcesInfo
resourcesInfo: resourcesInfo, compileContentBlockers: loadContentBlockers
)

// Compile this rule list if we haven't already or if the file has been modified
// We also don't load them if they are loading from cache because this will cost too much during launch
if loadContentBlockers {
let version = folderURL.lastPathComponent
let blocklistType = ContentBlockerManager.BlocklistType.filterList(componentId: componentId, isAlwaysAggressive: isAlwaysAggressive)
let modes = await blocklistType.allowedModes.asyncFilter { mode in
if let loadedVersion = await FilterListStorage.shared.loadedRuleListVersions.value[componentId] {
// if we know the loaded version we can just check it (optimization)
return loadedVersion != version
} else {
return true
}
}

// No modes need to be compiled
guard !modes.isEmpty else { return }

do {
let filterSet = try String(contentsOf: filterListURL, encoding: .utf8)
let result = try AdblockEngine.contentBlockerRules(fromFilterSet: filterSet)

try await ContentBlockerManager.shared.compile(
encodedContentRuleList: result.rulesJSON, for: blocklistType,
options: .all, modes: modes
)

await MainActor.run {
FilterListStorage.shared.loadedRuleListVersions.value[componentId] = version
}
} catch {
ContentBlockerManager.log.error(
"Failed to create content blockers for `\(componentId)` v\(version): \(error)"
)
#if DEBUG
ContentBlockerManager.log.debug(
"`\(componentId)`: \(filterListURL.absoluteString)"
)
#endif
}
}
}
}

Expand Down
Loading
Loading