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

Commit

Permalink
Fix #6254: Fix engine crashes by using serial queue for cosmetic filters
Browse files Browse the repository at this point in the history
  • Loading branch information
cuba committed Mar 3, 2023
1 parent 0891c01 commit a34254c
Show file tree
Hide file tree
Showing 12 changed files with 144 additions and 169 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ extension BrowserViewController: WKNavigationDelegate {
// Check if custom user scripts must be added to or removed from the web view.
if let targetFrame = navigationAction.targetFrame {
tab?.currentPageData?.addSubframeURL(forRequestURL: url, isForMainFrame: targetFrame.isMainFrame)
let scriptTypes = tab?.currentPageData?.makeUserScriptTypes(domain: domainForMainFrame) ?? []
let scriptTypes = await tab?.currentPageData?.makeUserScriptTypes(domain: domainForMainFrame) ?? []
tab?.setCustomUserScript(scripts: scriptTypes)
}
}
Expand Down Expand Up @@ -365,9 +365,8 @@ extension BrowserViewController: WKNavigationDelegate {
let domainForShields = Domain.getOrCreate(forUrl: mainDocumentURL, persistent: !isPrivateBrowsing)

// Load rule lists
tab?.contentBlocker.ruleListTypes = ContentBlockerManager.shared.compiledRuleTypes(
for: domainForShields
)
let ruleLists = await ContentBlockerManager.shared.ruleLists(for: domainForShields)
tab?.contentBlocker.set(ruleLists: ruleLists)

let isScriptsEnabled = !domainForShields.isShieldExpected(.NoScript, considerAllShieldsOption: true)
preferences.allowsContentJavaScript = isScriptsEnabled
Expand Down Expand Up @@ -417,7 +416,7 @@ extension BrowserViewController: WKNavigationDelegate {
if let responseURL = responseURL,
let domain = tab?.currentPageData?.domain(persistent: !isPrivateBrowsing),
tab?.currentPageData?.upgradeFrameURL(forResponseURL: responseURL, isForMainFrame: navigationResponse.isForMainFrame) == true {
let scriptTypes = tab?.currentPageData?.makeUserScriptTypes(domain: domain) ?? []
let scriptTypes = await tab?.currentPageData?.makeUserScriptTypes(domain: domain) ?? []
tab?.setCustomUserScript(scripts: scriptTypes)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ class LinkPreviewViewController: UIViewController {
}

let domain = Domain.getOrCreate(forUrl: url, persistent: !PrivateBrowsingManager.shared.isPrivateBrowsing)
let compiledRuleTypes = ContentBlockerManager.shared.compiledRuleTypes(for: domain)

Task(priority: .userInitiated) {
let ruleLists = await ContentBlockerManager.shared.loadRuleLists(for: Set(compiledRuleTypes.map({ $0.ruleType })))
let ruleLists = await ContentBlockerManager.shared.ruleLists(for: domain)

for ruleList in ruleLists {
webView.configuration.userContentController.add(ruleList)
}
Expand Down
22 changes: 15 additions & 7 deletions Sources/Brave/Frontend/Browser/PageData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ struct PageData {
}

/// Return all the user script types for this page. The number of script types grows as more frames are loaded.
@MainActor func makeUserScriptTypes(domain: Domain) -> Set<UserScriptType> {
@MainActor func makeUserScriptTypes(domain: Domain) async -> Set<UserScriptType> {
var userScriptTypes: Set<UserScriptType> = [.siteStateListener]

// Handle dynamic domain level scripts on the main document.
Expand Down Expand Up @@ -98,18 +98,26 @@ struct PageData {
}
}

let allEngineScriptTypes = await makeAllEngineScripts(for: domain)
return userScriptTypes.union(allEngineScriptTypes)
}

func makeMainFrameEngineScriptTypes(domain: Domain) async -> Set<UserScriptType> {
return await adBlockStats.makeEngineScriptTypes(frameURL: mainFrameURL, isMainFrame: true, domain: domain)
}

func makeAllEngineScripts(for domain: Domain) async -> Set<UserScriptType> {
// Add engine scripts for the main frame
userScriptTypes = userScriptTypes.union(
adBlockStats.makeEngineScriptTypes(frameURL: mainFrameURL, isMainFrame: true, domain: domain)
)
async let engineScripts = adBlockStats.makeEngineScriptTypes(frameURL: mainFrameURL, isMainFrame: true, domain: domain)

// Add engine scripts for all of the known sub-frames
let additionalScriptTypes = allSubframeURLs.map({ frameURL in
return self.adBlockStats.makeEngineScriptTypes(frameURL: frameURL, isMainFrame: false, domain: domain)
async let additionalScriptTypes = allSubframeURLs.asyncConcurrentCompactMap({ frameURL in
return await self.adBlockStats.makeEngineScriptTypes(frameURL: frameURL, isMainFrame: false, domain: domain)
}).reduce(Set<UserScriptType>(), { partialResult, scriptTypes in
return partialResult.union(scriptTypes)
})

return userScriptTypes.union(additionalScriptTypes)
let allEngineScripts = await (mainFrame: engineScripts, subFrames: additionalScriptTypes)
return allEngineScripts.mainFrame.union(allEngineScripts.subFrames)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,7 @@ extension PlaylistWebLoader: WKNavigationDelegate {
if let requestURL = navigationAction.request.url,
let targetFrame = navigationAction.targetFrame {
tab.currentPageData?.addSubframeURL(forRequestURL: requestURL, isForMainFrame: targetFrame.isMainFrame)
let scriptTypes = tab.currentPageData?.makeUserScriptTypes(domain: domainForMainFrame) ?? []
let scriptTypes = await tab.currentPageData?.makeUserScriptTypes(domain: domainForMainFrame) ?? []
tab.setCustomUserScript(scripts: scriptTypes)
}

Expand Down Expand Up @@ -633,16 +633,13 @@ extension PlaylistWebLoader: WKNavigationDelegate {
domainForShields.shield_adblockAndTp = true

// Load block lists
let enabledRuleTypes = ContentBlockerManager.shared.compiledRuleTypes(for: domainForShields)
tab.contentBlocker.ruleListTypes = enabledRuleTypes
let ruleLists = await ContentBlockerManager.shared.ruleLists(for: domainForShields)
tab.contentBlocker.set(ruleLists: ruleLists)

let isScriptsEnabled = !domainForShields.isShieldExpected(.NoScript, considerAllShieldsOption: true)
preferences.allowsContentJavaScript = isScriptsEnabled
}

// Cookie Blocking code below
tab.setScript(script: .cookieBlocking, enabled: Preferences.Privacy.blockAllCookies.value)

return (.allow, preferences)
}

Expand All @@ -663,7 +660,7 @@ extension PlaylistWebLoader: WKNavigationDelegate {
if let responseURL = responseURL,
tab.currentPageData?.upgradeFrameURL(forResponseURL: responseURL, isForMainFrame: navigationResponse.isForMainFrame) == true,
let domain = tab.currentPageData?.domain(persistent: false) {
let scriptTypes = tab.currentPageData?.makeUserScriptTypes(domain: domain) ?? []
let scriptTypes = await tab.currentPageData?.makeUserScriptTypes(domain: domain) ?? []
tab.setCustomUserScript(scripts: scriptTypes)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ extension ContentBlockerHelper: TabContentScript {
guard let requestURL = NSURL(idnString: dto.data.resourceURL) as URL? else { return }
guard let sourceURL = NSURL(idnString: dto.data.sourceURL) as URL? else { return }
guard let domainURLString = domain.url else { return }
let loadedRuleTypes = Set(self.loadedRuleTypeWithSourceTypes.map({ $0.ruleType }))
let loadedRuleTypes = ContentBlockerManager.shared.enabledRuleTypes(for: domain)

let blockedType = await TPStatsBlocklistChecker.shared.blockedTypes(
requestURL: requestURL,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class CosmeticFiltersScriptHandler: TabContentScript {

let selectorArrays = await cachedEngines.asyncConcurrentMap { cachedEngine -> [String] in
do {
return try cachedEngine.selectorsForCosmeticRules(
return try await cachedEngine.selectorsForCosmeticRules(
frameURL: frameURL,
ids: dto.data.ids,
classes: dto.data.classes
Expand Down
77 changes: 23 additions & 54 deletions Sources/Brave/WebFilters/ContentBlocker/ContentBlockerHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,8 @@ enum BlockingStrength: String {
class ContentBlockerHelper {
private(set) weak var tab: Tab?

/// The rule types and source types that are currently loaded in this tab
private(set) var loadedRuleTypeWithSourceTypes: Set<ContentBlockerManager.RuleTypeWithSourceType> = []
/// The rule types with their source types that should be loaded in this tab
var ruleListTypes: Set<ContentBlockerManager.RuleTypeWithSourceType> = [] {
didSet { reloadNeededRuleLists() }
}
/// The rule lists that are loaded into the current tab
private var setRuleLists: Set<WKContentRuleList> = []

var stats: TPPageStats = TPPageStats() {
didSet {
Expand All @@ -69,58 +65,31 @@ class ContentBlockerHelper {
self.tab = tab
}

private func reloadNeededRuleLists() {
// Remove old values that were previously added
for loadedRuleTypeWithSourceType in loadedRuleTypeWithSourceTypes {
// Check if should be removed or if the source type doesn't match, otherwise don't do anything
guard !ruleListTypes.contains(loadedRuleTypeWithSourceType) else {
continue
}

guard let ruleList = ContentBlockerManager.shared.cachedRuleList(for: loadedRuleTypeWithSourceType.ruleType) else {
// For some reason the rule is not cached. Shouldn't happen.
// But if it does we have to remove all the rule lists
// We will add back all the necessary ones below
tab?.webView?.configuration.userContentController.removeAllContentRuleLists()
loadedRuleTypeWithSourceTypes = []
assertionFailure("This shouldn't happen!")
break
}

// Since either it shouldn't be included or the source type doesn't match, we remove it
@MainActor func set(ruleLists: Set<WKContentRuleList>) {
guard ruleLists != setRuleLists else { return }
#if DEBUG
ContentBlockerManager.log.debug("Set rule lists:")
#endif

// Remove unwanted rule lists
for ruleList in setRuleLists.subtracting(ruleLists) {
// It's added but we don't want it. So we remove it.
tab?.webView?.configuration.userContentController.remove(ruleList)
loadedRuleTypeWithSourceTypes.remove(loadedRuleTypeWithSourceType)
setRuleLists.remove(ruleList)

#if DEBUG
ContentBlockerManager.log.debug(" - \(ruleList.identifier)")
#endif
}

// Add new values that are not yet added (or were removed above because the source type didn't match)
for ruleTypeWithSourceType in ruleListTypes {
// Only add rule lists that are missing
guard !loadedRuleTypeWithSourceTypes.contains(ruleTypeWithSourceType) else { continue }
guard let ruleList = ContentBlockerManager.shared.cachedRuleList(for: ruleTypeWithSourceType.ruleType) else { continue }
// Add missing rule lists
for ruleList in ruleLists.subtracting(setRuleLists) {
tab?.webView?.configuration.userContentController.add(ruleList)
loadedRuleTypeWithSourceTypes.insert(ruleTypeWithSourceType)
}

#if DEBUG
let rulesString = loadedRuleTypeWithSourceTypes.map { ruleTypeWithSourceType -> String in
let ruleTypeString: String

switch ruleTypeWithSourceType.ruleType {
case .general(let type):
ruleTypeString = type.rawValue
case .filterList(let uuid):
ruleTypeString = "filterList(\(uuid))"
}
setRuleLists.insert(ruleList)

let rulesDebugString = [
"ruleType: \(ruleTypeString)",
"sourceType: \(ruleTypeWithSourceType.sourceType)"
].joined(separator: ", ")

return ["{", rulesDebugString, "}"].joined()
}.joined(separator: ", ")

log.debug("Loaded \(self.loadedRuleTypeWithSourceTypes.count, privacy: .public) tab rules: \(rulesString, privacy: .public)")
#endif
#if DEBUG
ContentBlockerManager.log.debug(" + \(ruleList.identifier)")
#endif
}
}
}
69 changes: 16 additions & 53 deletions Sources/Brave/WebFilters/ContentBlocker/ContentBlockerManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -313,53 +313,44 @@ final public class ContentBlockerManager: Sendable {
return ruleList
}

/// Return the enabled rule types for this domain and the enabled settings
@MainActor public func compiledRuleTypes(for domain: Domain) -> Set<RuleTypeWithSourceType> {
@MainActor public func enabledRuleTypes(for domain: Domain) -> Set<ContentBlockerManager.BlocklistRuleType> {
let filterLists = FilterListResourceDownloader.shared.filterLists

if domain.shield_allOff == 1 {
return []
}

var results = Set<RuleTypeWithSourceType>()
var results = Set<ContentBlockerManager.BlocklistRuleType>()

// Get domain specific rule types
if domain.isShieldExpected(.AdblockAndTp, considerAllShieldsOption: true) {
if let sourceType = self.sourceType(for: .general(.blockAds)) {
results.insert(RuleTypeWithSourceType(ruleType: .general(.blockAds), sourceType: sourceType))
}

if let sourceType = self.sourceType(for: .general(.blockTrackers)) {
results.insert(RuleTypeWithSourceType(ruleType: .general(.blockTrackers), sourceType: sourceType))
}
results.insert(.general(.blockAds))
results.insert(.general(.blockTrackers))
}

// Get filter list specific rule types
filterLists.forEach { filterList in
guard filterList.isEnabled else { return }
let ruleType = filterList.makeRuleType()

guard let sourceType = self.sourceType(for: ruleType) else {
return
}

guard cachedRuleList(for: ruleType) != nil else {
return
}

results.insert(RuleTypeWithSourceType(ruleType: ruleType, sourceType: sourceType))
results.insert(filterList.makeRuleType())
}

// Get global rule types
if Preferences.Privacy.blockAllCookies.value {
if let sourceType = sourceType(for: .general(.blockCookies)) {
results.insert(RuleTypeWithSourceType(ruleType: .general(.blockCookies), sourceType: sourceType))
}
results.insert(.general(.blockCookies))
}

return results
}

/// Return the enabled rule types for this domain and the enabled settings
@MainActor public func ruleLists(for domain: Domain) async -> Set<WKContentRuleList> {
let ruleTypes = enabledRuleTypes(for: domain)

return await Set(ruleTypes.asyncConcurrentCompactMap { ruleType in
return self.cachedRuleList(for: ruleType)
})
}

/// Return a source type for the given rule type
public func sourceType(for ruleType: BlocklistRuleType) -> BlocklistSourceType? {
guard let compileResult = cachedCompileResults[ruleType.identifier] else { return nil }
Expand All @@ -373,42 +364,14 @@ final public class ContentBlockerManager: Sendable {
}

/// Get the cached rule list for this rule type. Returns nil if it's either not compiled or there were errors during compilation
public func cachedRuleList(for ruleType: BlocklistRuleType) -> WKContentRuleList? {
private func cachedRuleList(for ruleType: BlocklistRuleType) -> WKContentRuleList? {
guard let compileResult = cachedCompileResults[ruleType.identifier] else { return nil }

switch compileResult.result {
case .success(let ruleList): return ruleList
case .failure: return nil
}
}

/// Load the rule lists for the given rule types
public func loadRuleLists(for ruleTypes: Set<BlocklistRuleType>) async -> [WKContentRuleList] {
return await withTaskGroup(of: WKContentRuleList?.self) { group in
for ruleType in ruleTypes {
group.addTask {
do {
return try await self.loadRuleList(for: ruleType)
} catch {
Self.log.error("\(error.localizedDescription)")
return nil
}
}
}

var results: [WKContentRuleList] = []
for await ruleList in group {
guard let ruleList = ruleList else { continue }
results.append(ruleList)
}
return results
}
}

/// Load the rule list for the given rule type
public func loadRuleList(for ruleType: BlocklistRuleType) async throws -> WKContentRuleList? {
return try await ruleStore.contentRuleList(forIdentifier: ruleType.identifier)
}

/// Compile the data and set it for the given general type.
private func getBundledResource(for type: GeneralBlocklistTypes) async -> Resource? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ public class AdBlockStats {
}

/// This returns all the user script types for the given frame
@MainActor func makeEngineScriptTypes(frameURL: URL, isMainFrame: Bool, domain: Domain) -> Set<UserScriptType> {
@MainActor func makeEngineScriptTypes(frameURL: URL, isMainFrame: Bool, domain: Domain) async -> Set<UserScriptType> {
// Add any engine scripts for this frame
return cachedEngines.enumerated().map({ (index, cachedEngine) -> Set<UserScriptType> in
return await cachedEngines.enumerated().asyncMap({ (index, cachedEngine) -> Set<UserScriptType> in
guard cachedEngine.isEnabled(for: domain) else { return [] }

do {
return try cachedEngine.makeEngineScriptTypes(
return try await cachedEngine.makeEngineScriptTypes(
frameURL: frameURL, isMainFrame: isMainFrame, domain: domain, index: index
)
} catch {
Expand Down
Loading

0 comments on commit a34254c

Please sign in to comment.