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

initial FB Click to Load (WIP) #329

Merged
merged 51 commits into from
Jan 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
d48c4b2
initial FB Click to Load (WIP)
Nov 16, 2021
28e9733
refactor icon loading into model
Nov 22, 2021
b6d659b
cleanup fb-sdk.js
Nov 22, 2021
cead07f
cleanup clickToLoad.js
Nov 24, 2021
77a83e2
rebase clickToLoad.js
Nov 24, 2021
ca33bd1
Update fb-tds.json
Dec 3, 2021
d368d55
Update Package.resolved
Dec 3, 2021
8272d5e
restore ClickToLoadModel.swift
Dec 6, 2021
91188ec
Update to use multiple rules list API
bwaresiak Dec 17, 2021
b3f603d
Merge remote-tracking branch 'origin/feature/bartek/multiple-blocking…
Dec 17, 2021
e05fa22
multi list support WIP
Dec 21, 2021
eae78b0
Update to Content Blocking API
bwaresiak Dec 21, 2021
beeb3f6
support multiple rule lists
Dec 22, 2021
4b12665
cleanup debug
Dec 22, 2021
607aedc
fix CTL userscript race condition
Dec 28, 2021
0324967
support exceptions for FB CTL
Dec 30, 2021
cbc2806
handle empty hostname, cleanup console
Dec 30, 2021
11de181
remove duplicate FB SDK loads
Jan 4, 2022
55c7791
Update project.pbxproj
Jan 6, 2022
d0bd44e
generate etag for embedded FB CTL config
Jan 7, 2022
725d11d
nits
Jan 7, 2022
28bce63
refactor FB CTL in Tabs
Jan 7, 2022
ccb30b5
Update project.pbxproj
Jan 7, 2022
8a41820
privacy dashboard wip
Jan 10, 2022
c20c278
fix webfonts in userscript (wip)
Jan 10, 2022
fb760ef
Merge branch 'develop' into la/fb-click-to-load
bwaresiak Jan 10, 2022
979e07e
Update API after recent BSK changes
bwaresiak Jan 10, 2022
1a40be4
Update BSK reference
bwaresiak Jan 10, 2022
63f2fab
Update fb-tds.json
Jan 10, 2022
92df654
fix fonts in CTL dialogs
Jan 11, 2022
fbeefc8
Update to latest BSK API
bwaresiak Jan 11, 2022
e57ed48
Error pixels for click to load list
bwaresiak Jan 12, 2022
98561b8
Add workaround code
bwaresiak Jan 12, 2022
ae90572
Update Package.resolved
Jan 12, 2022
87c2bb2
ctl trackers at startup
Jan 12, 2022
b0dfde7
update FB CTL copy
Jan 18, 2022
d242a5f
eslint nits
Jan 18, 2022
eeda581
eslint nits
Jan 18, 2022
9d5e7f3
Merge branch 'develop' into la/fb-click-to-load
ladamski Jan 18, 2022
294c21a
fix lottie branch
Jan 18, 2022
46ae73c
fix config download test failure
Jan 18, 2022
3f9d820
Update ConfigurationDownloading.swift
Jan 18, 2022
5178d2b
Merge branch 'develop' into la/fb-click-to-load
ladamski Jan 19, 2022
b1de248
refactor ClickToLoadModel with tests
Jan 19, 2022
683ac56
fix dupe surrogate injection on slow page loads
Jan 19, 2022
1b5223e
FBblocked -> fbBlockingEnabled
Jan 19, 2022
4a08a95
fix ClickToLoadModelTests method name
Jan 19, 2022
91c7af5
explicit failure on corrupt FB TDS
Jan 25, 2022
2498324
add CTL TDS parsing test
Jan 25, 2022
0de1110
nit
Jan 25, 2022
1cf6f05
update BSK rev
Jan 25, 2022
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: 73 additions & 17 deletions DuckDuckGo.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
"pins": [
{
"package": "BrowserServicesKit",
"repositoryURL": "https://github.com/duckduckgo/BrowserServicesKit.git",
"repositoryURL": "https://github.com/duckduckgo/BrowserServicesKit",
"state": {
"branch": null,
"revision": "31f7e314e484bc4aa17f9dd87142a4fee346e158",
"revision": "da3f840edb7d9adf681c3fed0b4da6ad841fd288",
"version": null
}
},
Expand Down
61 changes: 58 additions & 3 deletions DuckDuckGo/BrowserTab/Model/Tab.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import WebKit
import os
import Combine
import BrowserServicesKit
import TrackerRadarKit

protocol TabDelegate: FileDownloadManagerDelegate {
func tabWillStartNavigation(_ tab: Tab, isUserInitiated: Bool)
Expand Down Expand Up @@ -95,6 +96,7 @@ final class Tab: NSObject {
webViewConfiguration: WebViewConfiguration? = nil,
historyCoordinating: HistoryCoordinating = HistoryCoordinator.shared,
scriptsSource: ScriptSourceProviding = DefaultScriptSourceProvider.shared,
contentBlockingManager: ContentBlockerRulesManager = ContentBlocking.contentBlockingManager,
visitedDomains: Set<String> = Set<String>(),
title: String? = nil,
error: Error? = nil,
Expand All @@ -108,6 +110,7 @@ final class Tab: NSObject {
self.faviconManagement = faviconManagement
self.historyCoordinating = historyCoordinating
self.scriptsSource = scriptsSource
self.contentBlockingManager = contentBlockingManager
self.visitedDomains = visitedDomains
self.title = title
self.error = error
Expand Down Expand Up @@ -142,7 +145,9 @@ final class Tab: NSObject {
var userEnteredUrl = true

var contentChangeEnabled = true


var fbBlockingEnabled = true

@Published private(set) var content: TabContent {
didSet {
handleFavicon(oldContent: oldValue)
Expand Down Expand Up @@ -296,6 +301,33 @@ final class Tab: NSObject {
}
}

@discardableResult
private func setFBProtection(enabled: Bool) -> Bool {
guard self.fbBlockingEnabled != enabled else {
return false
}

if let fbRules = contentBlockingManager.currentRules.first(where: {
$0.name == ContentBlockerRulesLists.Constants.clickToLoadRulesListName
}) {
if self.fbBlockingEnabled {
self.fbBlockingEnabled = false
webView.configuration.userContentController.remove(fbRules.rulesList)
} else {
self.fbBlockingEnabled = true
webView.configuration.userContentController.add(fbRules.rulesList)
}
return true
} else {
assertionFailure("Missing FB List")
}
return false
}

private func clickToLoadBlockFB() {
setFBProtection(enabled: true)
}

private func reloadIfNeeded(shouldLoadInBackground: Bool = false) {
let url: URL
switch self.content {
Expand Down Expand Up @@ -399,6 +431,8 @@ final class Tab: NSObject {

let scriptsSource: ScriptSourceProviding
private var userScriptsUpdatedCancellable: AnyCancellable?

let contentBlockingManager: ContentBlockerRulesManager

lazy var emailManager: EmailManager = {
let emailManager = EmailManager()
Expand All @@ -424,6 +458,7 @@ final class Tab: NSObject {
userScripts.contextMenuScript.delegate = self
userScripts.surrogatesScript.delegate = self
userScripts.contentBlockerRulesScript.delegate = self
userScripts.clickToLoadScript.delegate = self
userScripts.autofillScript.emailDelegate = emailManager
userScripts.autofillScript.vaultDelegate = vaultManager
userScripts.pageObserverScript.delegate = self
Expand Down Expand Up @@ -591,16 +626,35 @@ extension Tab: ContentBlockerRulesUserScriptDelegate {
return true
}

func contentBlockerRulesUserScriptShouldProcessCTLTrackers(_ script: ContentBlockerRulesUserScript) -> Bool {
return fbBlockingEnabled
}

func contentBlockerRulesUserScript(_ script: ContentBlockerRulesUserScript, detectedTracker tracker: DetectedTracker) {
trackerInfo?.add(detectedTracker: tracker)
}

}

extension Tab: SurrogatesUserScriptDelegate {
extension Tab: ClickToLoadUserScriptDelegate {

func clickToLoadUserScriptAllowFB(_ script: UserScript, replyHandler: @escaping (Bool) -> Void) {
guard self.fbBlockingEnabled else {
replyHandler(true)
return
}

if setFBProtection(enabled: false) {
replyHandler(true)
} else {
replyHandler(false)
tomasstrba marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

extension Tab: SurrogatesUserScriptDelegate {
func surrogatesUserScriptShouldProcessTrackers(_ script: SurrogatesUserScript) -> Bool {
return true
return true
}

func surrogatesUserScript(_ script: SurrogatesUserScript, detectedTracker tracker: DetectedTracker, withSurrogate host: String) {
Expand Down Expand Up @@ -796,6 +850,7 @@ extension Tab: WKNavigationDelegate {
}

func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
self.clickToLoadBlockFB()
ladamski marked this conversation as resolved.
Show resolved Hide resolved
delegate?.tabDidStartNavigation(self)

// Unnecessary assignment triggers publishing
Expand Down
4 changes: 3 additions & 1 deletion DuckDuckGo/BrowserTab/Model/UserContentController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ final class UserContentController: WKUserContentController {

self.removeAllContentRuleLists()
if self.privacyConfigurationManager.privacyConfig.isEnabled(featureKey: .contentBlocking) {
self.add(newRules.rules.rulesList)
for rules in newRules.rules {
self.add(rules.rulesList)
}
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions DuckDuckGo/BrowserTab/Model/UserScripts.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ final class UserScripts {
let hoverUserScript = HoverUserScript()
let debugScript = DebugUserScript()
let autofillScript = AutofillUserScript()
let clickToLoadScript = ClickToLoadUserScript()

let contentBlockerRulesScript: ContentBlockerRulesUserScript
let surrogatesScript: SurrogatesUserScript
Expand All @@ -55,6 +56,7 @@ final class UserScripts {
pageObserverScript,
printingUserScript,
hoverUserScript,
clickToLoadScript,
contentScopeUserScript,
autofillScript
]
Expand Down
3 changes: 2 additions & 1 deletion DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ public struct UserDefaultsWrapper<T> {
case configStorageBloomFilterExclusionsEtag = "config.storage.bloomfilter.exclusions.etag"
case configStorageSurrogatesEtag = "config.storage.surrogates.etag"
case configStoragePrivacyConfigurationEtag = "config.storage.privacyconfiguration.etag"

case configFBConfigEtag = "config.storage.fbconfig.etag"

case fireproofDomains = "com.duckduckgo.fireproofing.allowedDomains"
case unprotectedDomains = "com.duckduckgo.contentblocker.unprotectedDomains"

Expand Down
4 changes: 3 additions & 1 deletion DuckDuckGo/Configuration/ConfigurationDownloading.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ enum ConfigurationLocation: String, CaseIterable {
case surrogates = "https://duckduckgo.com/contentblocking.js?l=surrogates"
case trackerRadar = "https://staticcdn.duckduckgo.com/trackerblocking/v2.1/tds.json"
case privacyConfiguration = "https://staticcdn.duckduckgo.com/trackerblocking/config/v1/macos-config.json"

// In archived repo, to be refactored shortly (https://staticcdn.duckduckgo.com/useragents/social_ctp_configuration.json)
case FBConfig = "https://staticcdn.duckduckgo.com/useragents/"

}

final class DefaultConfigurationDownloader: ConfigurationDownloading {
Expand Down
21 changes: 16 additions & 5 deletions DuckDuckGo/Configuration/ConfigurationStoring.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ final class DefaultConfigurationStorage: ConfigurationStoring {
.bloomFilterSpec: "smarterEncryptionSpec.json",
.surrogates: "surrogates.txt",
.privacyConfiguration: "macos-config.json",
.trackerRadar: "tracker-radar.json"
.trackerRadar: "tracker-radar.json",
.FBConfig: "social_ctp_configuration.json"
]

static let shared = DefaultConfigurationStorage()
Expand All @@ -58,7 +59,10 @@ final class DefaultConfigurationStorage: ConfigurationStoring {
private var surrogatesEtag: String?

@UserDefaultsWrapper(key: .configStoragePrivacyConfigurationEtag, defaultValue: nil)
private var privacyConfigruationEtag: String?
private var privacyConfigurationEtag: String?

@UserDefaultsWrapper(key: .configFBConfigEtag, defaultValue: nil)
private var FBConfigEtag: String?

private init() { }

Expand All @@ -80,7 +84,10 @@ final class DefaultConfigurationStorage: ConfigurationStoring {
return trackerRadarEtag

case .privacyConfiguration:
return privacyConfigruationEtag
return privacyConfigurationEtag

case .FBConfig:
return FBConfigEtag
}
}

Expand All @@ -102,7 +109,10 @@ final class DefaultConfigurationStorage: ConfigurationStoring {
trackerRadarEtag = etag

case .privacyConfiguration:
privacyConfigruationEtag = etag
privacyConfigurationEtag = etag

case .FBConfig:
return FBConfigEtag = etag
}
}

Expand All @@ -127,7 +137,8 @@ final class DefaultConfigurationStorage: ConfigurationStoring {
os_log("bloomFilterExcludedDomainsEtag %{public}s", log: .config, type: .default, bloomFilterExcludedDomainsEtag ?? "")
os_log("surrogatesEtag %{public}s", log: .config, type: .default, surrogatesEtag ?? "")
os_log("trackerRadarEtag %{public}s", log: .config, type: .default, trackerRadarEtag ?? "")
os_log("privacyConfigurationEtag %{public}s", log: .config, type: .default, privacyConfigruationEtag ?? "")
os_log("privacyConfigurationEtag %{public}s", log: .config, type: .default, privacyConfigurationEtag ?? "")
os_log("FBConfigEtag %{public}s", log: .config, type: .default, FBConfigEtag ?? "")
}

func fileUrl(for config: ConfigurationLocation) -> URL {
Expand Down
55 changes: 55 additions & 0 deletions DuckDuckGo/ContentBlocker/ClickToLoadModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//
// ClickToLoadModel.swift
//
// Copyright © 2021 DuckDuckGo. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Foundation

struct ClickToLoadModel {

private static func loadFile(name: String) -> String? {
let pathPrefix = "social_images/"
let fileArgs = name.split(separator: ".")
let fileName = String(fileArgs[0])
let fileExt = String(fileArgs[1])

let filePath = pathPrefix + fileName

let imgURL = Bundle.main.url(
forResource: filePath,
withExtension: fileExt
)
if imgURL == nil {
return nil
}
guard let base64String = try? Data(contentsOf: imgURL!).base64EncodedString() else { return nil }
let image = "data:image/" + (fileExt == "svg" ? "svg+xml" : fileExt) + ";base64," + base64String
return image
}

static let getImage: [String: String] = {
return [
"dax.png": Self.loadFile(name: "dax.png")!,
"loading_light.svg": Self.loadFile(name: "loading_light.svg")!,
"loading_dark.svg": Self.loadFile(name: "loading_dark.svg")!,
"blocked_facebook_logo.svg": Self.loadFile(name: "blocked_facebook_logo.svg")!,
"blocked_group.svg": Self.loadFile(name: "blocked_group.svg")!,
"blocked_page.svg": Self.loadFile(name: "blocked_page.svg")!,
"blocked_post.svg": Self.loadFile(name: "blocked_post.svg")!,
"blocked_video.svg": Self.loadFile(name: "blocked_video.svg")!
]
}()
}
88 changes: 88 additions & 0 deletions DuckDuckGo/ContentBlocker/ClickToLoadUserScript.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//
// ClickToLoadUserScript.swift
//
// Copyright © 2021 DuckDuckGo. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import WebKit
import BrowserServicesKit

protocol ClickToLoadUserScriptDelegate: AnyObject {

func clickToLoadUserScriptAllowFB(_ script: UserScript, replyHandler: @escaping (Bool) -> Void)
}

final class ClickToLoadUserScript: NSObject, UserScript, WKScriptMessageHandlerWithReply {

var injectionTime: WKUserScriptInjectionTime { .atDocumentStart }
var forMainFrameOnly: Bool { false }
var messageNames: [String] { ["getImage", "enableFacebook", "initClickToLoad" ] }
let source: String

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

weak var delegate: ClickToLoadUserScriptDelegate?

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage,
replyHandler: @escaping (Any?, String?) -> Void) {
if message.name == "initClickToLoad" {
let controller = userContentController as? UserContentController
let privacyConfigurationManager = controller!.privacyConfigurationManager
let privacyConfiguration = privacyConfigurationManager.privacyConfig

let protected = privacyConfiguration.isProtected(domain: message.body as? String)
if protected {
replyHandler(true, nil)
} else {
replyHandler(false, nil)
}
return
}
if message.name == "enableFacebook" {
guard let delegate = delegate else { return }
delegate.clickToLoadUserScriptAllowFB(self) { (_) -> Void in
guard let isLogin = message.body as? Bool else {
replyHandler(nil, nil)
return
}

replyHandler(isLogin, nil)
}

return
}

var image: String

guard let arg = message.body as? String else {
replyHandler(nil, nil)
return
}
if message.name == "getImage" {
image = ClickToLoadModel.getImage[arg]!
} else {
assertionFailure("Uknown message type")
ladamski marked this conversation as resolved.
Show resolved Hide resolved
replyHandler(nil, nil)
return
}
replyHandler(image, nil)
}

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
assertionFailure("SHOULDN'T BE HERE!")
ladamski marked this conversation as resolved.
Show resolved Hide resolved
}
}
Loading