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

Commit

Permalink
Compile max content blockers
Browse files Browse the repository at this point in the history
  • Loading branch information
cuba committed Oct 27, 2023
1 parent b14b83e commit 619fd3e
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 123 deletions.
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
18 changes: 6 additions & 12 deletions Sources/Brave/WebFilters/AdblockResourceDownloader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,18 +121,12 @@ 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
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 @@ -169,9 +170,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 @@ -257,6 +260,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
37 changes: 15 additions & 22 deletions Sources/Brave/WebFilters/FilterListCustomURLDownloader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,50 +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
let blocklistType = ContentBlockerManager.BlocklistType.customFilterList(uuid: 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)

try await ContentBlockerManager.shared.compile(
encodedContentRuleList: result.rulesJSON,
for: blocklistType,
options: .all
)
} catch {
ContentBlockerManager.log.error(
"Failed to compile rule lists for `\(blocklistType.debugDescription)`: \(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
75 changes: 22 additions & 53 deletions Sources/Brave/WebFilters/FilterListResourceDownloader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,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 @@ -196,22 +197,22 @@ public actor FilterListResourceDownloader {
localFileURL: localFileURL,
version: version, fileType: isLegacy ? .dat : .text
)

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 @@ -220,22 +221,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 @@ -290,44 +295,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 `\(blocklistType.debugDescription)` v\(version): \(error)"
)
}
}
}
}

Expand Down
Loading

0 comments on commit 619fd3e

Please sign in to comment.