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

Trigger the Cookie Hidden animation early for filterlist rules #3584

Merged
merged 7 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
82 changes: 57 additions & 25 deletions DuckDuckGo/Autoconsent/AutoconsentUserScript.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ protocol UserScriptWithAutoconsent: UserScript {

final class AutoconsentUserScript: NSObject, WKScriptMessageHandlerWithReply, UserScriptWithAutoconsent {

private struct Constants {
static let filterListCmpName = "filterList" // special CMP name used for reports from the cosmetic filterlist
}

static let newSitePopupHiddenNotification = Notification.Name("newSitePopupHidden")

var injectionTime: WKUserScriptInjectionTime { .atDocumentStart }
Expand Down Expand Up @@ -111,36 +115,36 @@ extension AutoconsentUserScript {

struct PopupFoundMessage: Codable {
let type: String
let cmp: String
let cmp: String // name of the Autoconsent rule that matched
let url: String
}

struct OptOutResultMessage: Codable {
let type: String
let cmp: String
let cmp: String // name of the Autoconsent rule that matched
let result: Bool
let scheduleSelfTest: Bool
let url: String
}

struct OptInResultMessage: Codable {
let type: String
let cmp: String
let cmp: String // name of the Autoconsent rule that matched
let result: Bool
let scheduleSelfTest: Bool
let url: String
}

struct SelfTestResultMessage: Codable {
let type: String
let cmp: String
let cmp: String // name of the Autoconsent rule that matched
let result: Bool
let url: String
}

struct AutoconsentDoneMessage: Codable {
let type: String
let cmp: String
let cmp: String // name of the Autoconsent rule that matched
let url: String
let isCosmetic: Bool
}
Expand Down Expand Up @@ -171,8 +175,7 @@ extension AutoconsentUserScript {
case MessageName.eval:
handleEval(message: message, replyHandler: replyHandler)
case MessageName.popupFound:
Logger.autoconsent.debug("Autoconsent popup found")
replyHandler([ "type": "ok" ], nil) // this is just to prevent a Promise rejection
handlePopupFound(message: message, replyHandler: replyHandler)
case MessageName.optOutResult:
handleOptOutResult(message: message, replyHandler: replyHandler)
case MessageName.optInResult:
Expand All @@ -187,21 +190,19 @@ extension AutoconsentUserScript {
case MessageName.autoconsentDone:
handleAutoconsentDone(message: message, replyHandler: replyHandler)
case MessageName.autoconsentError:
Logger.autoconsent.debug("Autoconsent error: \(String(describing: message.body))")
Logger.autoconsent.error("Autoconsent error: \(String(describing: message.body))")
replyHandler([ "type": "ok" ], nil) // this is just to prevent a Promise rejection
}
}

@MainActor
func handleInit(message: WKScriptMessage, replyHandler: @escaping (Any?, String?) -> Void) {
guard let messageData: InitMessage = decodeMessageBody(from: message.body) else {
guard let messageData: InitMessage = decodeMessageBody(from: message.body),
let url = URL(string: messageData.url) else {
assertionFailure("Received a malformed message from autoconsent")
replyHandler(nil, "cannot decode message")
return
}
guard let url = URL(string: messageData.url) else {
replyHandler(nil, "cannot decode init request")
return
}

guard url.navigationalScheme?.isHypertextScheme == true else {
// ignore special schemes
Expand Down Expand Up @@ -256,6 +257,7 @@ extension AutoconsentUserScript {
@MainActor
func handleEval(message: WKScriptMessage, replyHandler: @escaping (Any?, String?) -> Void) {
guard let messageData: EvalMessage = decodeMessageBody(from: message.body) else {
assertionFailure("Received a malformed message from autoconsent")
replyHandler(nil, "cannot decode message")
return
}
Expand Down Expand Up @@ -291,9 +293,39 @@ extension AutoconsentUserScript {
}
}

@MainActor
func handlePopupFound(message: WKScriptMessage, replyHandler: @escaping (Any?, String?) -> Void) {
guard let messageData: PopupFoundMessage = decodeMessageBody(from: message.body),
let url = URL(string: messageData.url),
let host = url.host else {
assertionFailure("Received a malformed message from autoconsent")
replyHandler(nil, "cannot decode message")
return
}
Logger.autoconsent.debug("Cookie popup found: \(String(describing: messageData))")

// if popupFound is sent with "filterList", it indicates that cosmetic filterlist matched in the prehide stage,
// but a real opt-out may still follow. See https://github.com/duckduckgo/autoconsent/blob/main/api.md#messaging-api
if messageData.cmp == Constants.filterListCmpName {
refreshDashboardState(consentManaged: true, cosmetic: true, optoutFailed: false, selftestFailed: nil)
// trigger animation, but do not cache it because it can still be overridden
if !management.sitesNotifiedCache.contains(host) {
Logger.autoconsent.debug("Starting animation for cosmetic filters")
// post popover notification
NotificationCenter.default.post(name: Self.newSitePopupHiddenNotification, object: self, userInfo: [
"topUrl": self.topUrl ?? url,
"isCosmetic": true
])
}
}

replyHandler([ "type": "ok" ], nil) // this is just to prevent a Promise rejection
}

@MainActor
func handleOptOutResult(message: WKScriptMessage, replyHandler: @escaping (Any?, String?) -> Void) {
guard let messageData: OptOutResultMessage = decodeMessageBody(from: message.body) else {
assertionFailure("Received a malformed message from autoconsent")
replyHandler(nil, "cannot decode message")
return
}
Expand All @@ -313,26 +345,24 @@ extension AutoconsentUserScript {
@MainActor
func handleAutoconsentDone(message: WKScriptMessage, replyHandler: @escaping (Any?, String?) -> Void) {
// report a managed popup
guard let messageData: AutoconsentDoneMessage = decodeMessageBody(from: message.body) else {
replyHandler(nil, "cannot decode message")
return
}
Logger.autoconsent.debug("opt-out successful: \(String(describing: messageData))")

guard let url = URL(string: messageData.url),
guard let messageData: AutoconsentDoneMessage = decodeMessageBody(from: message.body),
let url = URL(string: messageData.url),
let host = url.host else {
assertionFailure("Received a malformed message from autoconsent")
replyHandler(nil, "cannot decode message")
return
}

Logger.autoconsent.debug("opt-out successful: \(String(describing: messageData))")

refreshDashboardState(consentManaged: true, cosmetic: messageData.isCosmetic, optoutFailed: false, selftestFailed: nil)

// trigger popup once per domain
if !management.sitesNotifiedCache.contains(host) {
Logger.autoconsent.debug("bragging that we closed a popup")
management.sitesNotifiedCache.insert(host)
// post popover notification on main thread
DispatchQueue.main.async {
if messageData.cmp != Constants.filterListCmpName { // filterlist animation should have been triggered already (see handlePopupFound)
Logger.autoconsent.debug("Starting animation for the handled cookie popup")
// post popover notification
NotificationCenter.default.post(name: Self.newSitePopupHiddenNotification, object: self, userInfo: [
"topUrl": self.topUrl ?? url,
"isCosmetic": messageData.isCosmetic
Expand All @@ -359,15 +389,17 @@ extension AutoconsentUserScript {
}
)
} else {
Logger.autoconsent.debug("no self-test scheduled in this tab")
Logger.autoconsent.error("no self-test scheduled in this tab")
}
selfTestWebView = nil
selfTestFrameInfo = nil
}

@MainActor
func handleSelfTestResult(message: WKScriptMessage, replyHandler: @escaping (Any?, String?) -> Void) {
guard let messageData: SelfTestResultMessage = decodeMessageBody(from: message.body) else {
guard let messageData: SelfTestResultMessage = decodeMessageBody(from: message.body),
let url = URL(string: messageData.url) else {
assertionFailure("Received a malformed message from autoconsent")
replyHandler(nil, "cannot decode message")
return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ class PhishingDetectionIntegrationTests: XCTestCase {
}

@MainActor
func testPhishingDetectedViaJSRedirectChain_tabIsMarkedPhishing() async throws {
func disabled_testPhishingDetectedViaJSRedirectChain_tabIsMarkedPhishing() async throws {
loadUrl("http://privacy-test-pages.site/security/badware/phishing-js-redirector.html")
try await waitForTabToFinishLoading()
let tabErrorCode = tabViewModel.tab.error?.errorCode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ class AutoconsentMessageProtocolTests: XCTestCase {
let message = MockWKScriptMessage(name: "popupFound", body: [
"type": "popupFound",
"cmp": "some cmp",
"url": "some url"
"url": "https://example.com"
])
userScript.handleMessage(
replyHandler: {(msg: Any?, _: String?) in
Expand Down
Loading