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

Add rollout for cookie popup management enabled by default #2373

Merged
merged 14 commits into from
Feb 5, 2024
Merged
3 changes: 3 additions & 0 deletions Core/FeatureFlag.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public enum FeatureFlag: String {
case networkProtectionWaitlistAccess
case networkProtectionWaitlistActive
case subscription
case autoconsentOnByDefault
}

extension FeatureFlag: FeatureFlagSourceProviding {
Expand Down Expand Up @@ -64,6 +65,8 @@ extension FeatureFlag: FeatureFlagSourceProviding {
return .remoteReleasable(.subfeature(AutofillSubfeature.onByDefault))
case .incontextSignup:
return .remoteReleasable(.feature(.incontextSignup))
case .autoconsentOnByDefault:
return .remoteReleasable(.subfeature(AutoconsentSubfeature.onByDefault))
}
}
}
Expand Down
6 changes: 0 additions & 6 deletions Core/PixelEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,6 @@ extension Pixel {
case daxDialogsFireEducationShown
case daxDialogsFireEducationConfirmed
case daxDialogsFireEducationCancelled
case daxDialogsAutoconsentShown
case daxDialogsAutoconsentConfirmed
case daxDialogsAutoconsentCancelled

case defaultBrowserButtonPressedSettings

Expand Down Expand Up @@ -624,9 +621,6 @@ extension Pixel.Event {
case .daxDialogsFireEducationShown: return "m_dx_fe_s"
case .daxDialogsFireEducationConfirmed: return "m_dx_fe_co"
case .daxDialogsFireEducationCancelled: return "m_dx_fe_ca"
case .daxDialogsAutoconsentShown: return "m_dax_dialog_autoconsent_shown"
case .daxDialogsAutoconsentConfirmed: return "m_dax_dialog_autoconsent_confirmed"
case .daxDialogsAutoconsentCancelled: return "m_dax_dialog_autoconsent_cancelled"

case .defaultBrowserButtonPressedSettings: return "m_db_s"

Expand Down
1 change: 0 additions & 1 deletion Core/UserDefaultsPropertyWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ public struct UserDefaultsWrapper<T> {

case voiceSearchEnabled = "com.duckduckgo.app.voiceSearchEnabled"

case autoconsentPromptSeen = "com.duckduckgo.ios.autoconsentPromptSeen"
case autoconsentEnabled = "com.duckduckgo.ios.autoconsentEnabled"

case shouldScheduleRulesCompilationOnAppLaunch = "com.duckduckgo.ios.shouldScheduleRulesCompilationOnAppLaunch"
Expand Down
44 changes: 0 additions & 44 deletions DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -105,17 +105,12 @@
1CB7B82123CEA1F800AA24EA /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CB7B82023CEA1F800AA24EA /* DateExtension.swift */; };
1CB7B82323CEA28300AA24EA /* DateExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CB7B82223CEA28300AA24EA /* DateExtensionTests.swift */; };
1E016AB42949FEB500F21625 /* OmniBarNotificationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E016AB32949FEB500F21625 /* OmniBarNotificationViewModel.swift */; };
1E016AB6294A5EB100F21625 /* CustomDaxDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E016AB5294A5EB100F21625 /* CustomDaxDialog.swift */; };
1E05D1D629C46EBB00BF9A1F /* DailyPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E05D1D529C46EBB00BF9A1F /* DailyPixel.swift */; };
1E05D1D829C46EDA00BF9A1F /* TimedPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E05D1D729C46EDA00BF9A1F /* TimedPixel.swift */; };
1E05D1DB29C47B3300BF9A1F /* DailyPixelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E05D1D929C47B2B00BF9A1F /* DailyPixelTests.swift */; };
1E0A75EA27A2FBD000A2BFB6 /* Downloads.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1E0A75E927A2FBD000A2BFB6 /* Downloads.storyboard */; };
1E162605296840D80004127F /* Triangle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E162604296840D80004127F /* Triangle.swift */; };
1E1626072968413B0004127F /* ViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E1626062968413B0004127F /* ViewExtension.swift */; };
1E16260B296845120004127F /* cookie-banner-illustration-animated.json in Resources */ = {isa = PBXBuildFile; fileRef = 1E162609296845120004127F /* cookie-banner-illustration-animated.json */; };
1E16260C296845120004127F /* cookie-banner-illustration-animated-dark.json in Resources */ = {isa = PBXBuildFile; fileRef = 1E16260A296845120004127F /* cookie-banner-illustration-animated-dark.json */; };
1E162610296C5C630004127F /* CustomDaxDialogViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E16260F296C5C630004127F /* CustomDaxDialogViewModel.swift */; };
1E162613296C62820004127F /* CookieConsentDaxDialogViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E162612296C62820004127F /* CookieConsentDaxDialogViewModel.swift */; };
1E162615296D910F0004127F /* cookie-icon-animated-40-dark.json in Resources */ = {isa = PBXBuildFile; fileRef = 1E162614296D910F0004127F /* cookie-icon-animated-40-dark.json */; };
1E1D8B5D2994FFE100C96994 /* AutoconsentMessageProtocolTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E1D8B5C2994FFE100C96994 /* AutoconsentMessageProtocolTests.swift */; };
1E1D8B6129950FD200C96994 /* AutoconsentBackgroundTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E1D8B6029950FD200C96994 /* AutoconsentBackgroundTests.swift */; };
Expand Down Expand Up @@ -1210,17 +1205,12 @@
1CB7B82023CEA1F800AA24EA /* DateExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateExtension.swift; sourceTree = "<group>"; };
1CB7B82223CEA28300AA24EA /* DateExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateExtensionTests.swift; sourceTree = "<group>"; };
1E016AB32949FEB500F21625 /* OmniBarNotificationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OmniBarNotificationViewModel.swift; sourceTree = "<group>"; };
1E016AB5294A5EB100F21625 /* CustomDaxDialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDaxDialog.swift; sourceTree = "<group>"; };
1E05D1D529C46EBB00BF9A1F /* DailyPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyPixel.swift; sourceTree = "<group>"; };
1E05D1D729C46EDA00BF9A1F /* TimedPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimedPixel.swift; sourceTree = "<group>"; };
1E05D1D929C47B2B00BF9A1F /* DailyPixelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyPixelTests.swift; sourceTree = "<group>"; };
1E0A75E927A2FBD000A2BFB6 /* Downloads.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Downloads.storyboard; sourceTree = "<group>"; };
1E162604296840D80004127F /* Triangle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Triangle.swift; sourceTree = "<group>"; };
1E1626062968413B0004127F /* ViewExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewExtension.swift; sourceTree = "<group>"; };
1E162609296845120004127F /* cookie-banner-illustration-animated.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "cookie-banner-illustration-animated.json"; sourceTree = "<group>"; };
1E16260A296845120004127F /* cookie-banner-illustration-animated-dark.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "cookie-banner-illustration-animated-dark.json"; sourceTree = "<group>"; };
1E16260F296C5C630004127F /* CustomDaxDialogViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDaxDialogViewModel.swift; sourceTree = "<group>"; };
1E162612296C62820004127F /* CookieConsentDaxDialogViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CookieConsentDaxDialogViewModel.swift; sourceTree = "<group>"; };
1E162614296D910F0004127F /* cookie-icon-animated-40-dark.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "cookie-icon-animated-40-dark.json"; sourceTree = "<group>"; };
1E1D8B5C2994FFE100C96994 /* AutoconsentMessageProtocolTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoconsentMessageProtocolTests.swift; sourceTree = "<group>"; };
1E1D8B6029950FD200C96994 /* AutoconsentBackgroundTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoconsentBackgroundTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3095,16 +3085,6 @@
name = AppTPBreakageForm;
sourceTree = "<group>";
};
1E16260029683B4D0004127F /* CustomDaxDialog */ = {
isa = PBXGroup;
children = (
1E1626082968418F0004127F /* Animations */,
1E162611296C62350004127F /* Model */,
1E016AB5294A5EB100F21625 /* CustomDaxDialog.swift */,
);
name = CustomDaxDialog;
sourceTree = "<group>";
};
1E162603296840790004127F /* SwiftUI */ = {
isa = PBXGroup;
children = (
Expand All @@ -3116,24 +3096,6 @@
name = SwiftUI;
sourceTree = "<group>";
};
1E1626082968418F0004127F /* Animations */ = {
isa = PBXGroup;
children = (
1E16260A296845120004127F /* cookie-banner-illustration-animated-dark.json */,
1E162609296845120004127F /* cookie-banner-illustration-animated.json */,
);
name = Animations;
sourceTree = "<group>";
};
1E162611296C62350004127F /* Model */ = {
isa = PBXGroup;
children = (
1E16260F296C5C630004127F /* CustomDaxDialogViewModel.swift */,
1E162612296C62820004127F /* CookieConsentDaxDialogViewModel.swift */,
);
name = Model;
sourceTree = "<group>";
};
1E162616296D962A0004127F /* Model */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -4941,7 +4903,6 @@
F11CEF581EBB66C80088E4D7 /* Tutorials */ = {
isa = PBXGroup;
children = (
1E16260029683B4D0004127F /* CustomDaxDialog */,
858650CF2469BCC100C36F8A /* DaxOnboarding */,
85EE7F53224667C3000FE757 /* WebContainer */,
85C11E4A209084DE00BFFEB4 /* HomeRow */,
Expand Down Expand Up @@ -6085,7 +6046,6 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
1E16260C296845120004127F /* cookie-banner-illustration-animated-dark.json in Resources */,
AA4D6A8D23DE49A5007E8790 /* [email protected] in Resources */,
F47E53DB250A9A1C0037C686 /* Onboarding.xcassets in Resources */,
AA4D6ACC23DE4D27007E8790 /* [email protected] in Resources */,
Expand Down Expand Up @@ -6148,7 +6108,6 @@
85A313972028E78A00327D00 /* release_notes.txt in Resources */,
9865DFFD22A84CF300D27829 /* FavoriteHomeCell.xib in Resources */,
1EE411FE2858B9300003FE64 /* dark-shield.json in Resources */,
1E16260B296845120004127F /* cookie-banner-illustration-animated.json in Resources */,
AA4D6AD323DE4D27007E8790 /* [email protected] in Resources */,
AA4D6AA123DE4CC4007E8790 /* [email protected] in Resources */,
984147A824F0259000362052 /* Onboarding.storyboard in Resources */,
Expand Down Expand Up @@ -6531,7 +6490,6 @@
D664C7C72B289AA200CBFA76 /* PurchaseInProgressView.swift in Sources */,
D6E83C122B1E6AB3006C8AFB /* SettingsView.swift in Sources */,
F1668BCE1E798081008CBA04 /* BookmarksViewController.swift in Sources */,
1E162610296C5C630004127F /* CustomDaxDialogViewModel.swift in Sources */,
8590CB69268A4E190089F6BF /* DebugEtagStorage.swift in Sources */,
D6D12CA62B291CAA0054390C /* AppStoreRestoreFlow.swift in Sources */,
C1CDA3162AFB9C7F006D1476 /* AutofillNeverPromptWebsitesManager.swift in Sources */,
Expand Down Expand Up @@ -6769,7 +6727,6 @@
98DA6ECA2181E41F00E65433 /* ThemeManager.swift in Sources */,
C159DF072A430B60007834BB /* EmailSignupViewController.swift in Sources */,
37A6A8FE2AFD0208008580A3 /* FaviconsFetcherOnboarding.swift in Sources */,
1E016AB6294A5EB100F21625 /* CustomDaxDialog.swift in Sources */,
02341FA42A437999008A1531 /* OnboardingStepView.swift in Sources */,
F1CA3C3B1F045B65005FADB3 /* Authenticator.swift in Sources */,
CBD4F13D279EBFA000B20FD7 /* HomeMessageCollectionViewCell.swift in Sources */,
Expand Down Expand Up @@ -6958,7 +6915,6 @@
3132FA2827A0788400DD7A12 /* PassKitPreviewHelper.swift in Sources */,
8505836C219F424500ED4EDB /* TextFieldWithInsets.swift in Sources */,
CBD4F13F279EBFAF00B20FD7 /* HomeMessageViewModel.swift in Sources */,
1E162613296C62820004127F /* CookieConsentDaxDialogViewModel.swift in Sources */,
1E4DCF4A27B6A38000961E25 /* DownloadListRepresentable.swift in Sources */,
2DC3FC65C6D9DA634426672D /* AutofillNoAuthAvailableView.swift in Sources */,
);
Expand Down
1 change: 0 additions & 1 deletion DuckDuckGo/AppSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ protocol AppSettings: AnyObject {

func isWidgetInstalled() async -> Bool

var autoconsentPromptSeen: Bool { get set }
var autoconsentEnabled: Bool { get set }

var isSyncBookmarksPaused: Bool { get }
Expand Down
28 changes: 23 additions & 5 deletions DuckDuckGo/AppUserDefaults.swift
Original file line number Diff line number Diff line change
Expand Up @@ -287,12 +287,30 @@ public class AppUserDefaults: AppSettings {
}
}
}

@UserDefaultsWrapper(key: .autoconsentPromptSeen, defaultValue: false)
var autoconsentPromptSeen: Bool


var autoconsentEnabled: Bool {
get {
// Use settings value if present
if let isEnabled = autoconsentEnabledSetting {
return isEnabled
}

// Use onByDefault rollout otherwise
return featureFlagger.isFeatureOn(.autoconsentOnByDefault)
}

set {
autoconsentEnabledSetting = newValue
}
}

// Only for testing and `DebugViewController` purposes
func clearAutoconsentUserSetting() {
autoconsentEnabledSetting = nil
}

@UserDefaultsWrapper(key: .autoconsentEnabled, defaultValue: false)
var autoconsentEnabled: Bool
private var autoconsentEnabledSetting: Bool?

var inspectableWebViewEnabled: Bool {
get {
Expand Down
1 change: 0 additions & 1 deletion DuckDuckGo/Autoconsent/AutoconsentManagement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ final class AutoconsentManagement {
private init() {}

var sitesNotifiedCache = Set<String>()
var promptLastShown: Date?

func clearCache() {
dispatchPrecondition(condition: .onQueue(.main))
Expand Down
58 changes: 9 additions & 49 deletions DuckDuckGo/Autoconsent/AutoconsentUserScript.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,13 @@ import PrivacyDashboard
// swiftlint:disable file_length

protocol AutoconsentPreferences {
var autoconsentPromptSeen: Bool { get set }
var autoconsentEnabled: Bool { get set }
}

extension AppUserDefaults: AutoconsentPreferences { }

protocol AutoconsentUserScriptDelegate: AnyObject {
func autoconsentUserScript(_ script: AutoconsentUserScript, didUpdateCookieConsentStatus cookieConsentStatus: CookieConsentInfo)
func autoconsentUserScript(_ script: AutoconsentUserScript, didRequestAskingUserForConsent completion: @escaping (Bool) -> Void)
}

protocol UserScriptWithAutoconsent: UserScript {
Expand Down Expand Up @@ -211,6 +209,12 @@ extension AutoconsentUserScript {
}
}

@MainActor
func handlePopupFound(message: WKScriptMessage, replyHandler: @escaping (Any?, String?) -> Void) {
os_log("Autoconsent popup found", log: .autoconsentLog)
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 {
Expand All @@ -229,7 +233,7 @@ extension AutoconsentUserScript {
return
}

if preferences.autoconsentPromptSeen == true && preferences.autoconsentEnabled == false {
Copy link
Contributor Author

@dus7 dus7 Jan 24, 2024

Choose a reason for hiding this comment

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

To make reasoning about this and following changes easier, note autoconsentPromptSeen was set to true after user enabled the feature (https://github.com/duckduckgo/iOS/pull/2373/files#diff-d04b75c0b651987027e4a487112e5facbd38ff7c45b585c5e202b118eaf68b3dL71).

if preferences.autoconsentEnabled == false {
// this will only happen if the user has just declined a prompt in this tab
replyHandler([ "type": "ok" ], nil) // this is just to prevent a Promise rejection
return
Expand Down Expand Up @@ -260,11 +264,9 @@ extension AutoconsentUserScript {
"rules": nil, // rules are bundled with the content script atm
"config": [
"enabled": true,
// if it's the first time, disable autoAction
"autoAction": preferences.autoconsentPromptSeen ? "optOut" : nil,
"autoAction": "optOut",
"disabledCmps": disabledCMPs,
// the very first time (autoconsentEnabled = nil), make sure the popup is visible
"enablePrehide": preferences.autoconsentPromptSeen,
"enablePrehide": true,
"enableCosmeticRules": true,
"detectRetries": 20,
"isMainWorld": false
Expand Down Expand Up @@ -309,31 +311,7 @@ extension AutoconsentUserScript {
replyHandler(nil, "missing frame target")
}
}

@MainActor
func handlePopupFound(message: WKScriptMessage, replyHandler: @escaping (Any?, String?) -> Void) {
guard preferences.autoconsentPromptSeen == false else {
// if feature is already enabled, opt-out will happen automatically
replyHandler([ "type": "ok" ], nil) // this is just to prevent a Promise rejection
return
}

os_log("Prompting user about autoconsent", log: .autoconsentLog, type: .debug)

// if it's the first time, prompt the user and trigger opt-out
if message.webView?.window != nil {
ensurePrompt(callback: { shouldProceed in
if shouldProceed {
Task {
replyHandler([ "type": "optOut" ], nil)
}
}
})
} else {
replyHandler(nil, "missing frame target")
}
}

@MainActor
func handleOptOutResult(message: WKScriptMessage, replyHandler: @escaping (Any?, String?) -> Void) {
guard let messageData: OptOutResultMessage = decodeMessageBody(from: message.body) else {
Expand Down Expand Up @@ -419,24 +397,6 @@ extension AutoconsentUserScript {
refreshDashboardState(consentManaged: true, cosmetic: nil, optoutFailed: false, selftestFailed: messageData.result)
replyHandler([ "type": "ok" ], nil) // this is just to prevent a Promise rejection
}

@MainActor
func ensurePrompt(callback: @escaping (Bool) -> Void) {
let now = Date.init()
guard management.promptLastShown == nil || now > management.promptLastShown!.addingTimeInterval(1) else {
// user said "not now" recently, don't bother asking
os_log("Have a recent user response, canceling prompt", log: .autoconsentLog, type: .debug)
callback(preferences.autoconsentEnabled) // if two prompts were scheduled from the same tab, result could be true
return
}

management.promptLastShown = now
self.delegate?.autoconsentUserScript(self, didRequestAskingUserForConsent: { result in
self.preferences.autoconsentEnabled = result
self.preferences.autoconsentPromptSeen = true
callback(result)
})
}
}

extension NSNotification.Name {
Expand Down
1 change: 0 additions & 1 deletion DuckDuckGo/AutoconsentSettingsViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ final class AutoconsentSettingsViewController: UITableViewController {

@IBAction private func onAutoconsentValueChanged(_ sender: Any) {
appSettings.autoconsentEnabled = autoconsentToggle.isOn
appSettings.autoconsentPromptSeen = true
Pixel.fire(pixel: autoconsentToggle.isOn ? .settingsAutoconsentOn : .settingsAutoconsentOff)
}

Expand Down
Loading
Loading