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

Commit

Permalink
Fix #7360: Add non-aggressive cosmetic filters (#7333)
Browse files Browse the repository at this point in the history
* Add non-aggressive cosmetic filters

Undo

* Make default ad-block level standard

* Fix issue with extracting origin on selectors poller script
  • Loading branch information
cuba authored and iccub committed May 16, 2023
1 parent cd08836 commit 736898a
Show file tree
Hide file tree
Showing 9 changed files with 971 additions and 439 deletions.
1 change: 1 addition & 0 deletions Sources/Brave/Frontend/Browser/BrowserViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2514,6 +2514,7 @@ extension BrowserViewController: TabDelegate {
DeAmpScriptHandler(tab: tab),
SiteStateListenerScriptHandler(tab: tab),
CosmeticFiltersScriptHandler(tab: tab),
URLPartinessScriptHandler(tab: tab),
FaviconScriptHandler(tab: tab),
Web3NameServiceScriptHandler(tab: tab),
Web3IPFSScriptHandler(tab: tab),
Expand Down
29 changes: 15 additions & 14 deletions Sources/Brave/Frontend/Browser/User Scripts/UserScriptType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,24 @@ enum UserScriptType: Hashable {
/// We need this to control which script gets executed on which frame
/// on the JS side since we cannot control this on the iOS side
let frameURL: URL
/// Determines if we hide first party content or not
///
/// - Note: For now this is always true as to be more aggressive. Later we may make this more configurable
let hideFirstPartyContent = true
/// Determines if we hide first party content or not. This is controlled via agressive or standard mode
/// Standard mode may unhide 1p content for certain filter lists.
let hideFirstPartyContent: Bool
/// This value come from the engine. In most cases this is false.
let genericHide: Bool
/// The delay on which to start polling on.
///
/// For the most part, this is 0 (or undefined) on main frames but fixed on sub-frames.
let firstSelectorsPollingDelayMs: Int? = nil

/// Some setting the script requires. Not used for now so hard-coed to nil
let switchToSelectorsPollingThreshold: Int? = nil
/// Some setting the script requires. Not used for now so hard-coed to nil
let fetchNewClassIdRulesThrottlingMs: Int? = nil
/// These are hide selectors that will get automatically processed when the script loads.
let hideSelectors: Set<String>
let firstSelectorsPollingDelayMs: Int?
/// After a while of using the mutation observer we switch to selectors polling.
/// This is purely an optimizaiton
let switchToSelectorsPollingThreshold: Int?
/// We can add a delay when sending new classes and ids
let fetchNewClassIdRulesThrottlingMs: Int?
/// These are agressive hide selectors that will get automatically processed when the script loads.
/// Agressive selectors may never be unhidden even on standard mode
let agressiveSelectors: Set<String>
/// These are standard hide selectors that will get automatically processed when the script loads.
/// Standard selectors may be unhidden on standard mode if they contain 1p content
let standardSelectors: Set<String>
/// These are hide selectors that will get automatically processed when the script loads.
let styleSelectors: Set<StyleSelectorEntry>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ class CosmeticFiltersScriptHandler: TabContentScript {
}

Task { @MainActor in
let domain = Domain.getOrCreate(forUrl: frameURL, persistent: tab?.isPrivate == true ? false : true)
let domain = Domain.getOrCreate(forUrl: frameURL, persistent: self.tab?.isPrivate == true ? false : true)
let cachedEngines = AdBlockStats.shared.cachedEngines(for: domain)

let selectorArrays = await cachedEngines.asyncConcurrentMap { cachedEngine -> [String] in
let selectorArrays = await cachedEngines.asyncConcurrentCompactMap { cachedEngine -> CachedAdBlockEngine.SelectorsTuple? in
do {
return try await cachedEngine.selectorsForCosmeticRules(
frameURL: frameURL,
Expand All @@ -65,11 +65,28 @@ class CosmeticFiltersScriptHandler: TabContentScript {
)
} catch {
Logger.module.error("\(error.localizedDescription)")
return []
return nil
}
}

replyHandler(selectorArrays.flatMap({ $0 }), nil)
var standardSelectors: Set<String> = []
var agressiveSelectors: Set<String> = []
for tuple in selectorArrays {
let isAgressive = tuple.source.isAlwaysAgressive(
given: FilterListStorage.shared.filterLists
)

if isAgressive {
agressiveSelectors = agressiveSelectors.union(tuple.selectors)
} else {
standardSelectors = standardSelectors.union(tuple.selectors)
}
}

replyHandler([
"agressiveSelectors": Array(agressiveSelectors),
"standardSelectors": Array(standardSelectors)
], nil)
}
} catch {
assertionFailure("Invalid type of message. Fix the `RequestBlocking.js` script")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright 2023 The Brave Authors. All rights reserved.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

import Foundation
import WebKit
import Shared
import Data
import os.log

/// This handler receives a list of urls for a given frame that need to determine its partiness (i.e. 1st part vs 3rd party)
///
/// The urls are collected in the `SelectorsPollerScript.js` file.
class URLPartinessScriptHandler: TabContentScript {
private struct PartinessDTO: Decodable {
struct PartinessDTOData: Decodable, Hashable {
let sourceURL: String
let urls: [String]
}

let securityToken: String
let data: PartinessDTOData
}

static let scriptName = "URLPartinessScript"
static let scriptId = CosmeticFiltersScriptHandler.scriptId
static let messageHandlerName = "\(scriptName)_\(messageUUID)"
static let scriptSandbox: WKContentWorld = .defaultClient
static let userScript: WKUserScript? = nil

private weak var tab: Tab?

init(tab: Tab) {
self.tab = tab
}

func userContentController(_ userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage, replyHandler: @escaping (Any?, String?) -> Void) {
if !verifyMessage(message: message) {
assertionFailure("Invalid security token. Fix the `SelectorsPollerScript.js` script")
replyHandler(nil, nil)
return
}

do {
let data = try JSONSerialization.data(withJSONObject: message.body)
let dto = try JSONDecoder().decode(PartinessDTO.self, from: data)
var results: [String: Bool] = [:]

guard let frameURL = NSURL(idnString: dto.data.sourceURL) as URL? else {
// Since we can't create a url from the source,
// we will assume they are all 3rd party
for urlString in dto.data.urls {
results[urlString] = false
}

replyHandler(results, nil)
return
}

let frameETLD1 = frameURL.baseDomain

for urlString in dto.data.urls {
guard let etld1 = (NSURL(idnString: urlString) as? URL)?.baseDomain else {
// We can't determine a url.
// Let's assume it's 3rd party
results[urlString] = false
continue
}

results[urlString] = frameETLD1 == etld1
}

replyHandler(results, nil)
} catch {
assertionFailure("Invalid type of message. Fix the `RequestBlocking.js` script")
replyHandler(nil, nil)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,14 @@ class SiteStateListenerScriptHandler: TabContentScript {
if domain.areAllShieldsOff { return }

let models = await AdBlockStats.shared.cosmeticFilterModels(forFrameURL: frameURL, domain: domain)
let args = try await self.makeArgs(from: models, frameURL: frameURL)
let args = try self.makeArgs(from: models, frameURL: frameURL)
let source = try ScriptFactory.shared.makeScriptSource(of: .selectorsPoller).replacingOccurrences(of: "$<args>", with: args)

let secureSource = CosmeticFiltersScriptHandler.secureScript(
handlerName: CosmeticFiltersScriptHandler.messageHandlerName,
handlerNamesMap: [
"$<message_handler>": CosmeticFiltersScriptHandler.messageHandlerName,
"$<partiness_message_handler>": URLPartinessScriptHandler.messageHandlerName
],
securityToken: CosmeticFiltersScriptHandler.scriptId,
script: source
)
Expand All @@ -94,17 +97,21 @@ class SiteStateListenerScriptHandler: TabContentScript {
}
}

private func makeArgs(from models: [CosmeticFilterModel], frameURL: URL) async throws -> String {
let hideSelectors = models.reduce(Set<String>(), { partialResult, model in
return partialResult.union(model.hideSelectors)
})

@MainActor private func makeArgs(from modelTuples: [CachedAdBlockEngine.CosmeticFilterModelTuple], frameURL: URL) throws -> String {
var standardSelectors: Set<String> = []
var agressiveSelectors: Set<String> = []
var styleSelectors: [String: Set<String>] = [:]

for model in models {
for (key, values) in model.styleSelectors {
for modelTuple in modelTuples {
for (key, values) in modelTuple.model.styleSelectors {
styleSelectors[key] = styleSelectors[key]?.union(Set(values)) ?? Set(values)
}

if modelTuple.source.isAlwaysAgressive(given: FilterListStorage.shared.filterLists) {
agressiveSelectors = agressiveSelectors.union(modelTuple.model.hideSelectors)
} else {
standardSelectors = standardSelectors.union(modelTuple.model.hideSelectors)
}
}

let styleSelectorObjects = styleSelectors.map { selector, rules -> UserScriptType.SelectorsPollerSetup.StyleSelectorEntry in
Expand All @@ -113,10 +120,19 @@ class SiteStateListenerScriptHandler: TabContentScript {
)
}

// TODO: @JS #7352 Add UI and enable standard mode
// Standard mode is controlled by the `hideFirstPartyContent` boolean
// True means standard, false means aggresive
// (i.e. we don't hide first party content on standard mode)
let setup = UserScriptType.SelectorsPollerSetup(
frameURL: frameURL,
genericHide: models.contains { $0.genericHide },
hideSelectors: hideSelectors,
hideFirstPartyContent: false,
genericHide: modelTuples.contains { $0.model.genericHide },
firstSelectorsPollingDelayMs: nil,
switchToSelectorsPollingThreshold: 1000,
fetchNewClassIdRulesThrottlingMs: 100,
agressiveSelectors: agressiveSelectors,
standardSelectors: standardSelectors,
styleSelectors: Set(styleSelectorObjects)
)

Expand Down
Loading

0 comments on commit 736898a

Please sign in to comment.