Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Content Blocking to use BSK stack #354

Merged
merged 24 commits into from
Nov 30, 2021
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b017c3a
Initial setup for Privacy Config
bwaresiak Nov 19, 2021
7d17294
Migrating Content Blocker Manager
bwaresiak Nov 19, 2021
888fbf3
Update content blocker classes
bwaresiak Nov 23, 2021
f178f43
Fix message processing after toggling privacy protection
bwaresiak Nov 24, 2021
da4005a
Simplify APIs
bwaresiak Nov 24, 2021
823af8b
More updates to used APIs
bwaresiak Nov 24, 2021
72cceae
Update user scripts
bwaresiak Nov 26, 2021
c3208cc
Updates to user scripts event flow and tests
bwaresiak Nov 26, 2021
dba8534
Fix unit tests and add temporary ones based on reference tests
bwaresiak Nov 26, 2021
1e9c984
Merge branch 'develop' into bartek/update-content-blocking
bwaresiak Nov 26, 2021
763a944
Update embedded Config, add tests
bwaresiak Nov 26, 2021
5bd2073
Logging for Content Blocking
bwaresiak Nov 26, 2021
86226e3
Pixels and updates to test after recent BSK changes
bwaresiak Nov 27, 2021
ef70870
Cleanup
bwaresiak Nov 27, 2021
8e63a81
Better support for Pixels in BSK
bwaresiak Nov 28, 2021
3f04dfe
Update BSK dependency
bwaresiak Nov 28, 2021
69f9dfe
Merge branch 'develop' into bartek/update-content-blocking
bwaresiak Nov 28, 2021
110d2b9
Hide Domains Protection Store from regular usage
bwaresiak Nov 28, 2021
ba505e8
Remove old comment
bwaresiak Nov 28, 2021
9102a99
Cleanup code
bwaresiak Nov 28, 2021
4a8ebe2
Remove unused file
bwaresiak Nov 30, 2021
a00b5d3
Merge branch 'develop' into bartek/update-content-blocking
bwaresiak Nov 30, 2021
826e6ab
Restore removed file
bwaresiak Nov 30, 2021
238ddab
Update BSK reference
bwaresiak Nov 30, 2021
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
90 changes: 13 additions & 77 deletions DuckDuckGo.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"package": "BrowserServicesKit",
"repositoryURL": "https://github.com/duckduckgo/BrowserServicesKit.git",
"state": {
"branch": "release/friends-and-family",
"revision": "aeb2dc2fc19c75dcca1b0727a2b078742ebac280",
"branch": "bartek/content-blocking",
"revision": "62bece25ef650c2fca539310c4ea9d7e85b3884f",
"version": null
}
},
Expand Down Expand Up @@ -51,8 +51,8 @@
"repositoryURL": "https://github.com/sparkle-project/Sparkle.git",
"state": {
"branch": null,
"revision": "c0933a516b420806e9216e71bd13ba76c54f0de6",
"version": "1.26.0"
"revision": "7918c1c8fc68baa37917eeaa67286b077ad5e393",
"version": "1.27.1"
}
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import WebKit
public final class NavigatorCredentialsUserScript: NSObject, UserScript {
public let messageNames: [String] = []

init(scriptSource: ScriptSourceProviding = DefaultScriptSourceProvider.shared) {
init(scriptSource: ScriptSourceProviding) {
source = scriptSource.navigatorCredentialsSource
}

Expand Down
46 changes: 27 additions & 19 deletions DuckDuckGo/BrowserTab/Model/Tab.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ final class Tab: NSObject {
webCacheManager: WebCacheManager = WebCacheManager.shared,
webViewConfiguration: WebViewConfiguration? = nil,
historyCoordinating: HistoryCoordinating = HistoryCoordinator.shared,
scriptsSource: ScriptSourceProviding = DefaultScriptSourceProvider.shared,
visitedDomains: Set<String> = Set<String>(),
title: String? = nil,
error: Error? = nil,
Expand All @@ -104,6 +105,7 @@ final class Tab: NSObject {
self.content = content
self.faviconService = faviconService
self.historyCoordinating = historyCoordinating
self.scriptsSource = scriptsSource
self.visitedDomains = visitedDomains
self.title = title
self.error = error
Expand Down Expand Up @@ -343,7 +345,7 @@ final class Tab: NSObject {
webView.allowsBackForwardNavigationGestures = true
webView.allowsMagnification = true

subscribeToUserScripts()
subscribeToUserScriptChanges()
subscribeToOpenExternalUrlEvents()

superviewObserver = webView.observe(\.superview, options: .old) { [weak self] _, change in
Expand Down Expand Up @@ -403,7 +405,10 @@ final class Tab: NSObject {
}

// MARK: - User Scripts


let scriptsSource: ScriptSourceProviding
private var userScriptsUpdatedCancellable: AnyCancellable?

lazy var emailManager: EmailManager = {
let emailManager = EmailManager()
emailManager.requestDelegate = self
Expand All @@ -416,8 +421,6 @@ final class Tab: NSObject {
return manager
}()

private var userScriptsUpdatedCancellable: AnyCancellable?

private var userScripts: UserScripts! {
willSet {
if let userScripts = userScripts {
Expand All @@ -428,7 +431,7 @@ final class Tab: NSObject {
userScripts.debugScript.instrumentation = instrumentation
userScripts.faviconScript.delegate = self
userScripts.contextMenuScript.delegate = self
userScripts.contentBlockerScript.delegate = self
userScripts.surrogatesScript.delegate = self
userScripts.contentBlockerRulesScript.delegate = self
userScripts.autofillScript.emailDelegate = emailManager
userScripts.autofillScript.vaultDelegate = vaultManager
Expand All @@ -442,11 +445,12 @@ final class Tab: NSObject {
}
}

private func subscribeToUserScripts() {
userScriptsUpdatedCancellable = UserScriptsManager.shared
.$userScripts
.map(UserScripts.init(copy:))
.weakAssign(to: \.userScripts, on: self)
private func subscribeToUserScriptChanges() {
userScriptsUpdatedCancellable = scriptsSource.sourceUpdatedPublisher.receive(on: RunLoop.main).sink { [weak self] _ in
guard let self = self, self.delegate != nil else { return }

self.userScripts = UserScripts(with: self.scriptsSource)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could check the diff of the rules here (e.g. for the presence of unprotected sites changes) and reload the tab here, instead of doing that from Dashboard (but it would require figuring out if current tab is the one for which change has been triggered, or if it matches the unprotected sites change).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1
Not sure if it helps, but active tab state can be easily determined with webview.superview != nil

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still need to know if that tab is relevant to the change - hard to do that now, probably should be tackled elsewhere.

}
}

// MARK: - Find in Page
Expand Down Expand Up @@ -596,24 +600,28 @@ extension Tab: FaviconUserScriptDelegate {

}

extension Tab: ContentBlockerUserScriptDelegate {
extension Tab: ContentBlockerRulesUserScriptDelegate {

func contentBlockerUserScriptShouldProcessTrackers(_ script: UserScript) -> Bool {
func contentBlockerRulesUserScriptShouldProcessTrackers(_ script: ContentBlockerRulesUserScript) -> Bool {
return true
}

func contentBlockerUserScript(_ script: ContentBlockerUserScript,
detectedTracker tracker: DetectedTracker,
withSurrogate host: String) {
trackerInfo?.add(installedSurrogateHost: host)
func contentBlockerRulesUserScript(_ script: ContentBlockerRulesUserScript, detectedTracker tracker: DetectedTracker) {
trackerInfo?.add(detectedTracker: tracker)
}
}

extension Tab: SurrogatesUserScriptDelegate {

contentBlockerUserScript(script, detectedTracker: tracker)
func surrogatesUserScriptShouldProcessTrackers(_ script: SurrogatesUserScript) -> Bool {
return true
}

func contentBlockerUserScript(_ script: UserScript, detectedTracker tracker: DetectedTracker) {
func surrogatesUserScript(_ script: SurrogatesUserScript, detectedTracker tracker: DetectedTracker, withSurrogate host: String) {
trackerInfo?.add(installedSurrogateHost: host)

trackerInfo?.add(detectedTracker: tracker)
}

}

extension Tab: EmailManagerRequestDelegate { }
Expand Down
19 changes: 10 additions & 9 deletions DuckDuckGo/BrowserTab/Model/UserContentController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,16 @@

import WebKit
import Combine
import BrowserServicesKit

final class UserContentController: WKUserContentController {
private var blockingRulesUpdatedCancellable: AnyCancellable?

let privacyConfiguration: PrivacyConfigurationManagment
let privacyConfigurationManager: PrivacyConfigurationManager

public init(rulesPublisher: AnyPublisher<WKContentRuleList?, Never> = ContentBlockerRulesManager.shared.blockingRules,
privacyConfiguration: PrivacyConfigurationManagment = PrivacyConfigurationManager.shared) {
self.privacyConfiguration = privacyConfiguration
public init(rulesPublisher: ContentBlockingUpdating.NewRulesPublisher = ContentBlocking.contentBlockingUpdating.contentBlockingRules,
privacyConfigurationManager: PrivacyConfigurationManager = ContentBlocking.privacyConfigurationManager) {
self.privacyConfigurationManager = privacyConfigurationManager
super.init()

installContentBlockingRules(publisher: rulesPublisher)
Expand All @@ -36,17 +37,17 @@ final class UserContentController: WKUserContentController {
fatalError("init(coder:) has not been implemented")
}

func installContentBlockingRules(publisher: AnyPublisher<WKContentRuleList?, Never>) {
blockingRulesUpdatedCancellable = publisher.receive(on: RunLoop.main).sink { [weak self] rules in
func installContentBlockingRules(publisher: ContentBlockingUpdating.NewRulesPublisher) {
blockingRulesUpdatedCancellable = publisher.receive(on: RunLoop.main).sink { [weak self] newRules in
dispatchPrecondition(condition: .onQueue(.main))

guard let self = self,
let rules = rules
let newRules = newRules
else { return }

self.removeAllContentRuleLists()
if self.privacyConfiguration.isEnabled(featureKey: .contentBlocking) {
self.add(rules)
if self.privacyConfigurationManager.privacyConfig.isEnabled(featureKey: .contentBlocking) {
self.add(newRules.rules.rulesList)
}
}
}
Expand Down
47 changes: 24 additions & 23 deletions DuckDuckGo/BrowserTab/Model/UserScripts.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,44 +18,45 @@

import Foundation
import BrowserServicesKit
import TrackerRadarKit

final class UserScripts {

let pageObserverScript = PageObserverUserScript()
let faviconScript = FaviconUserScript()
let contextMenuScript = ContextMenuUserScript()
let findInPageScript = FindInPageUserScript()
let contentBlockerScript = ContentBlockerUserScript()
let contentBlockerRulesScript = ContentBlockerRulesUserScript()
let autofillScript = AutofillUserScript()
let printingUserScript = PrintingUserScript()
let hoverUserScript = HoverUserScript()
let navigatorCredentialsUserScript = NavigatorCredentialsUserScript()
let debugScript = DebugUserScript()
let gpcScript = GPCUserScript()
let autofillScript = AutofillUserScript()

init() {
}
let contentBlockerRulesScript: ContentBlockerRulesUserScript
let surrogatesScript: SurrogatesUserScript
let navigatorCredentialsUserScript: NavigatorCredentialsUserScript
let gpcScript: GPCUserScript

init(with sourceProvider: ScriptSourceProviding) {

init(copy other: UserScripts) {
scripts = other.scripts
scripts.removeLast()
scripts.append(autofillScript.makeWKUserScript())
contentBlockerRulesScript = ContentBlockerRulesUserScript(configuration: sourceProvider.contentBlockerRulesConfig!)
surrogatesScript = SurrogatesUserScript(configuration: sourceProvider.surrogatesConfig!)
navigatorCredentialsUserScript = NavigatorCredentialsUserScript(scriptSource: sourceProvider)
gpcScript = GPCUserScript(scriptSource: sourceProvider)
}

lazy var userScripts: [UserScript] = [
self.debugScript,
self.faviconScript,
self.contextMenuScript,
self.findInPageScript,
self.contentBlockerScript,
self.contentBlockerRulesScript,
self.pageObserverScript,
self.printingUserScript,
self.hoverUserScript,
self.gpcScript,
self.navigatorCredentialsUserScript,
self.autofillScript
debugScript,
faviconScript,
contextMenuScript,
findInPageScript,
surrogatesScript,
contentBlockerRulesScript,
pageObserverScript,
printingUserScript,
hoverUserScript,
gpcScript,
navigatorCredentialsUserScript,
autofillScript
]

lazy var scripts = userScripts.map { $0.makeWKUserScript() }
Expand Down
49 changes: 0 additions & 49 deletions DuckDuckGo/Common/Extensions/HashExtension.swift

This file was deleted.

29 changes: 14 additions & 15 deletions DuckDuckGo/Common/Extensions/URLExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import Foundation
import os.log
import BrowserServicesKit

extension URL {

Expand Down Expand Up @@ -429,29 +430,27 @@ extension URL {
}

// MARK: - GPC

static func gpcHeadersEnabled(config: PrivacyConfiguration) -> [String] {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest, these two methods are weird. They are static on URL, but the main point is to look into the instance of PrivacyConfiguration. I would define them there. Definitely not scope of this PR, I added a task into my tech debt list.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely good idea, I didn't want to touch "too much" to not add to the scope creep.

let settings = config.settings(for: .gpc)

guard let enabledSites = settings["gpcHeaderEnabledSites"] as? [String] else {
return []
}

return enabledSites
}

static func isGPCEnabled(url: URL,
config: PrivacyConfigurationManager = PrivacyConfigurationManager.shared) -> Bool {
let enabledSites = config.gpcHeadersEnabled()
config: PrivacyConfiguration = ContentBlocking.privacyConfigurationManager.privacyConfig) -> Bool {
let enabledSites = gpcHeadersEnabled(config: config)

for gpcHost in enabledSites {
if url.isPart(ofDomain: gpcHost) {

// Check if url is on exception list
// Since headers are only enabled for a small numbers of sites
// perfrom this check here for efficency
let exceptions = config.tempUnprotectedDomains + config.exceptionsList(forFeature: .gpc)
let protectionStore = DomainsProtectionUserDefaultsStore()
if protectionStore.unprotectedDomains.contains(url.host ?? "") {
return false
}
for exception in exceptions {
if url.isPart(ofDomain: exception) {
return false
}
}

return true
return config.isFeature(.gpc, enabledForDomain: url.host)
}
}

Expand Down
7 changes: 7 additions & 0 deletions DuckDuckGo/Common/Utilities/Logging.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ extension OSLog {
Logging.pixelLoggingEnabled ? Logging.pixelLog : .disabled
}

static var contentBlocking: OSLog {
Logging.contentBlockingLoggingEnabled ? Logging.contentBlockingLog : .disabled
}

}

struct Logging {
Expand All @@ -66,4 +70,7 @@ struct Logging {
fileprivate static let pixelLoggingEnabled = false
fileprivate static let pixelLog: OSLog = OSLog(subsystem: Bundle.main.bundleIdentifier ?? "DuckDuckGo", category: "Pixel")

fileprivate static let contentBlockingLoggingEnabled = false
fileprivate static let contentBlockingLog: OSLog = OSLog(subsystem: Bundle.main.bundleIdentifier ?? "DuckDuckGo", category: "Content Blocking")

}
5 changes: 4 additions & 1 deletion DuckDuckGo/Configuration/ConfigurationDownloading.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
// limitations under the License.
//

import Foundation
import Combine
import BrowserServicesKit

protocol ConfigurationDownloading {

Expand Down Expand Up @@ -138,7 +140,8 @@ final class DefaultConfigurationDownloader: ConfigurationDownloading {

func embeddedEtag(for config: ConfigurationLocation) -> String? {
switch config {
case .trackerRadar: return TrackerRadarManager.Constants.embeddedDataSetETag
case .trackerRadar: return TrackerDataManager.Constants.embeddedDataSetETag
case .privacyConfiguration: return AppPrivacyConfigurationDataProvider.Constants.embeddedConfigETag
bwaresiak marked this conversation as resolved.
Show resolved Hide resolved
default: return nil
}
}
Expand Down
Loading