From 91f1f9a5698e515428447b55682450648f7983fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20=C5=81yp?= Date: Mon, 11 Mar 2024 17:41:28 +0100 Subject: [PATCH] Reports on toggle protections off (#2312) Task/Issue URL: https://app.asana.com/0/72649045549333/1205734628204384/f **Description**: Send a simplified breakage report through Pixel when user disables protections on iOS and macOS as such volume information from the toggle can help us identify breakage beyond what we can learn from regular breakage reports. We aim to: - Explicitly request permission for such report without directing the user toward a specific response. - Be transparent by openly listing the exact information we intend to send. **Steps to test this PR**: See: https://app.asana.com/0/0/1206747477156794/f --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 8 +- DuckDuckGo/Application/AppDelegate.swift | 1 + .../ContentBlocker/ContentBlocking.swift | 10 ++ .../Mocks/MockPrivacyConfiguration.swift | 3 + DuckDuckGo/Menus/MainMenuActions.swift | 2 +- .../View/PrivacyDashboardViewController.swift | 145 ++++++++++++------ DuckDuckGo/Statistics/PixelEvent.swift | 9 ++ DuckDuckGo/Statistics/PixelParameters.swift | 6 +- DuckDuckGoDBPBackgroundAgent/DBPMocks.swift | 14 +- .../DataBrokerProtection/Package.swift | 2 +- .../CCF/DataBrokerProtectionUtils.swift | 2 + .../DataBrokerRunCustomJSONViewModel.swift | 14 +- .../DataBrokerProtectionTests/Mocks.swift | 2 + .../NetworkProtectionMac/Package.swift | 2 +- LocalPackages/SubscriptionUI/Package.swift | 2 +- .../AppPrivacyConfigurationTests.swift | 3 +- .../BrokenSiteReportingReferenceTests.swift | 36 +++-- .../PrivacyReferenceTestHelper.swift | 3 +- .../WebsiteBreakageReportTests.swift | 14 +- 20 files changed, 192 insertions(+), 88 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index c0cf06df29..e005658d55 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -13770,7 +13770,7 @@ repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 120.0.0; + version = 121.0.0; }; }; AA06B6B52672AF8100F541C5 /* XCRemoteSwiftPackageReference "Sparkle" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 5787b3eda5..8749a59390 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { - "revision" : "cea7c43e5ab1d7484ab29abeda429350ed8a7dc1", - "version" : "120.0.0" + "revision" : "4555c3dbf265f1dca0304c69e7013b9d46a758b3", + "version" : "121.0.0" } }, { @@ -95,8 +95,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/privacy-dashboard", "state" : { - "revision" : "c67d268bf234760f49034a0fe7a6137a1b216b05", - "version" : "3.2.0" + "revision" : "43a6e1c1864846679a254e60c91332c3fbd922ee", + "version" : "3.3.0" } }, { diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index db71e63d65..7d4b4b7711 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -332,6 +332,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel #if DBP DataBrokerProtectionAppEvents().applicationDidBecomeActive() #endif + AppPrivacyFeatures.shared.contentBlocking.privacyConfigurationManager.toggleProtectionsCounter.sendEventsIfNeeded() } func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { diff --git a/DuckDuckGo/ContentBlocker/ContentBlocking.swift b/DuckDuckGo/ContentBlocker/ContentBlocking.swift index 637834c2b3..dee72a7514 100644 --- a/DuckDuckGo/ContentBlocker/ContentBlocking.swift +++ b/DuckDuckGo/ContentBlocker/ContentBlocking.swift @@ -64,6 +64,7 @@ final class AppContentBlocking { embeddedDataProvider: AppPrivacyConfigurationDataProvider(), localProtection: LocalUnprotectedDomains.shared, errorReporting: Self.debugEvents, + toggleProtectionsCounterEventReporting: toggleProtectionsEvents, internalUserDecider: internalUserDecider) trackerDataManager = TrackerDataManager(etag: ConfigurationStore.shared.loadEtag(for: .trackerDataSet), @@ -96,6 +97,15 @@ final class AppContentBlocking { log: .attribution) } + private let toggleProtectionsEvents = EventMapping { event, _, parameters, _ in + let domainEvent: Pixel.Event + switch event { + case .toggleProtectionsCounterDaily: + domainEvent = .toggleProtectionsDailyCount + } + Pixel.fire(domainEvent, withAdditionalParameters: parameters ?? [:]) + } + private static let debugEvents = EventMapping { event, error, parameters, onComplete in guard NSApp.runType.requiresEnvironment else { return } diff --git a/DuckDuckGo/ContentBlocker/Mocks/MockPrivacyConfiguration.swift b/DuckDuckGo/ContentBlocker/Mocks/MockPrivacyConfiguration.swift index 1078823ed8..60dc35a8e1 100644 --- a/DuckDuckGo/ContentBlocker/Mocks/MockPrivacyConfiguration.swift +++ b/DuckDuckGo/ContentBlocker/Mocks/MockPrivacyConfiguration.swift @@ -18,6 +18,7 @@ import BrowserServicesKit import Combine +import Common #if DEBUG @@ -95,6 +96,8 @@ final class MockPrivacyConfigurationManager: NSObject, PrivacyConfigurationManag var updatesPublisher: AnyPublisher = Just(()).eraseToAnyPublisher() var privacyConfig: PrivacyConfiguration = MockPrivacyConfiguration() var internalUserDecider: InternalUserDecider = DefaultInternalUserDecider() + var toggleProtectionsCounter: ToggleProtectionsCounter = ToggleProtectionsCounter(eventReporting: EventMapping { _, _, _, _ in + }) } #endif diff --git a/DuckDuckGo/Menus/MainMenuActions.swift b/DuckDuckGo/Menus/MainMenuActions.swift index 80bb0ee02a..2f4c286472 100644 --- a/DuckDuckGo/Menus/MainMenuActions.swift +++ b/DuckDuckGo/Menus/MainMenuActions.swift @@ -122,7 +122,7 @@ extension AppDelegate { @objc func openReportBrokenSite(_ sender: Any?) { let storyboard = NSStoryboard(name: "PrivacyDashboard", bundle: nil) let privacyDashboardViewController = storyboard.instantiateController(identifier: "PrivacyDashboardViewController") { coder in - PrivacyDashboardViewController(coder: coder, initMode: .reportBrokenSite) + PrivacyDashboardViewController(coder: coder, privacyInfo: nil, dashboardMode: .report) } privacyDashboardViewController.sizeDelegate = self diff --git a/DuckDuckGo/PrivacyDashboard/View/PrivacyDashboardViewController.swift b/DuckDuckGo/PrivacyDashboard/View/PrivacyDashboardViewController.swift index f6ea4e16b9..d30d1700d7 100644 --- a/DuckDuckGo/PrivacyDashboard/View/PrivacyDashboardViewController.swift +++ b/DuckDuckGo/PrivacyDashboard/View/PrivacyDashboardViewController.swift @@ -36,49 +36,65 @@ final class PrivacyDashboardViewController: NSViewController { static let initialContentWidth: CGFloat = 360.0 } - /// Type of web page displayed - enum Mode { - case privacyDashboard - case reportBrokenSite - } - private var webView: WKWebView! - private let initMode: Mode - - var source: WebsiteBreakage.Source { - initMode == .reportBrokenSite ? .appMenu : .dashboard - } + private let privacyDashboardController: PrivacyDashboardController + private var privacyDashboardDidTriggerDismiss: Bool = false - private let privacyDashboardController = PrivacyDashboardController(privacyInfo: nil) public let rulesUpdateObserver = ContentBlockingRulesUpdateObserver() - private let websiteBreakageReporter: WebsiteBreakageReporter = { - WebsiteBreakageReporter(pixelHandler: { parameters in + private let brokenSiteReporter: BrokenSiteReporter = { + BrokenSiteReporter(pixelHandler: { parameters in Pixel.fire( .brokenSiteReport, withAdditionalParameters: parameters, - allowedQueryReservedCharacters: WebsiteBreakage.allowedQueryReservedCharacters + allowedQueryReservedCharacters: BrokenSiteReport.allowedQueryReservedCharacters ) }, keyValueStoring: UserDefaults.standard) }() + private let toggleProtectionsOffReporter: BrokenSiteReporter = { + BrokenSiteReporter(pixelHandler: { parameters in + Pixel.fire( + .protectionToggledOffBreakageReport, + withAdditionalParameters: parameters, + allowedQueryReservedCharacters: BrokenSiteReport.allowedQueryReservedCharacters) + }, keyValueStoring: UserDefaults.standard) + }() + + private let toggleReportEvents = EventMapping { event, _, parameters, _ in + let domainEvent: Pixel.Event + switch event { + case .toggleReportDismiss: domainEvent = .toggleReportDismiss + case .toggleReportDoNotSend: domainEvent = .toggleReportDoNotSend + } + Pixel.fire(domainEvent, withAdditionalParameters: parameters) + } + private let permissionHandler = PrivacyDashboardPermissionHandler() private var preferredMaxHeight: CGFloat = Constants.initialContentHeight func setPreferredMaxHeight(_ height: CGFloat) { guard height > Constants.initialContentHeight else { return } - preferredMaxHeight = height } var sizeDelegate: PrivacyDashboardViewControllerSizeDelegate? private weak var tabViewModel: TabViewModel? - required init?(coder: NSCoder, initMode: Mode) { - self.initMode = initMode + required init?(coder: NSCoder, + privacyInfo: PrivacyInfo?, + dashboardMode: PrivacyDashboardMode, + privacyConfigurationManager: PrivacyConfigurationManaging = ContentBlocking.shared.privacyConfigurationManager) { + self.privacyDashboardController = PrivacyDashboardController(privacyInfo: privacyInfo, + dashboardMode: dashboardMode, + privacyConfigurationManager: privacyConfigurationManager, + eventMapping: toggleReportEvents) super.init(coder: coder) } required init?(coder: NSCoder) { - self.initMode = .privacyDashboard + self.privacyDashboardController = PrivacyDashboardController(privacyInfo: nil, + dashboardMode: .dashboard, + privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager, + eventMapping: toggleReportEvents) super.init(coder: coder) } @@ -94,16 +110,23 @@ final class PrivacyDashboardViewController: NSViewController { } public override func viewDidLoad() { - super.viewDidLoad() initWebView() - privacyDashboardController.setup(for: webView, reportBrokenSiteOnly: initMode == .reportBrokenSite ? true : false) + privacyDashboardController.setup(for: webView) privacyDashboardController.privacyDashboardNavigationDelegate = self privacyDashboardController.privacyDashboardDelegate = self privacyDashboardController.privacyDashboardReportBrokenSiteDelegate = self + privacyDashboardController.privacyDashboardToggleReportDelegate = self privacyDashboardController.preferredLocale = Bundle.main.preferredLocalizations.first } + override func viewWillDisappear() { + super.viewWillDisappear() + if !privacyDashboardDidTriggerDismiss { + privacyDashboardController.handleViewWillDisappear() + } + } + private func initWebView() { let configuration = WKWebViewConfiguration() #if DEBUG @@ -146,9 +169,7 @@ final class PrivacyDashboardViewController: NSViewController { } private func privacyDashboardProtectionSwitchChangeHandler(state: ProtectionState) { - - dismiss() - + privacyDashboardDidTriggerDismiss = true guard let domain = privacyDashboardController.privacyInfo?.url.host else { return } @@ -175,7 +196,9 @@ extension PrivacyDashboardViewController: PrivacyDashboardControllerDelegate { // Not used in macOS: Pixel.fire(.privacyDashboardReportBrokenSite) } - func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, didChangeProtectionSwitch protectionState: ProtectionState) { + func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, + didChangeProtectionSwitch protectionState: ProtectionState, + didSendReport: Bool) { privacyDashboardProtectionSwitchChangeHandler(state: protectionState) } @@ -206,7 +229,6 @@ extension PrivacyDashboardViewController: PrivacyDashboardControllerDelegate { func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, didSetPermission permissionName: String, to state: PermissionAuthorizationState) { guard let domain = self.privacyDashboardController.privacyInfo?.url.host else { return } - permissionHandler.setPermissionAuthorization(authorizationState: state, domain: domain, permissionName: permissionName) } @@ -235,11 +257,12 @@ extension PrivacyDashboardViewController: PrivacyDashboardReportBrokenSiteDelega func privacyDashboardController(_ privacyDashboardController: PrivacyDashboard.PrivacyDashboardController, didRequestSubmitBrokenSiteReportWithCategory category: String, description: String) { + let source: BrokenSiteReport.Source = privacyDashboardController.initDashboardMode == .report ? .appMenu : .dashboard do { - let websiteBreakage = try makeWebsiteBreakage(category: category, description: description) - try websiteBreakageReporter.report(breakage: websiteBreakage) + let report = try makeBrokenSiteReport(category: category, description: description, source: source) + try brokenSiteReporter.report(report, reportMode: .regular) } catch { - os_log("Failed to generate or send the website breakage report: \(error.localizedDescription)", type: .error) + os_log("Failed to generate or send the broken site report: \(error.localizedDescription)", type: .error) } } @@ -250,20 +273,44 @@ extension PrivacyDashboardViewController: PrivacyDashboardReportBrokenSiteDelega } } +// MARK: - PrivacyDashboardToggleReportDelegate + +extension PrivacyDashboardViewController: PrivacyDashboardToggleReportDelegate { + + func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, + didRequestSubmitToggleReportWithSource source: BrokenSiteReport.Source, + didOpenReportInfo: Bool, + toggleReportCounter: Int?) { + do { + let report = try makeBrokenSiteReport(source: source, + didOpenReportInfo: didOpenReportInfo, + toggleReportCounter: toggleReportCounter) + try toggleProtectionsOffReporter.report(report, reportMode: .toggle) + } catch { + os_log("Failed to generate or send the broken site report: %@", type: .error, error.localizedDescription) + } + } + +} + // MARK: - Breakage extension PrivacyDashboardViewController { - enum WebsiteBreakageError: Error { + enum BrokenSiteReportError: Error { case failedToFetchTheCurrentURL } - private func makeWebsiteBreakage(category: String, description: String) throws -> WebsiteBreakage { + private func makeBrokenSiteReport(category: String = "", + description: String = "", + source: BrokenSiteReport.Source, + didOpenReportInfo: Bool = false, + toggleReportCounter: Int? = nil) throws -> BrokenSiteReport { // ⚠️ To limit privacy risk, site URL is trimmed to not include query and fragment guard let currentTab = tabViewModel?.tab, let currentURL = currentTab.content.url?.trimmingQueryItemsAndFragment() else { - throw WebsiteBreakageError.failedToFetchTheCurrentURL + throw BrokenSiteReportError.failedToFetchTheCurrentURL } let blockedTrackerDomains = currentTab.privacyInfo?.trackerInfo.trackersBlocked.compactMap { $0.domain } ?? [] let installedSurrogates = currentTab.privacyInfo?.trackerInfo.installedSurrogates.map {$0} ?? [] @@ -283,22 +330,24 @@ extension PrivacyDashboardViewController { statusCodes = [httpStatusCode] } - let websiteBreakage = WebsiteBreakage(siteUrl: currentURL, - category: category.lowercased(), - description: description, - osVersion: "\(ProcessInfo.processInfo.operatingSystemVersion)", - manufacturer: "Apple", - upgradedHttps: currentTab.privacyInfo?.connectionUpgradedTo != nil, - tdsETag: ContentBlocking.shared.contentBlockingManager.currentRules.first?.etag, - blockedTrackerDomains: blockedTrackerDomains, - installedSurrogates: installedSurrogates, - isGPCEnabled: PrivacySecurityPreferences.shared.gpcEnabled, - ampURL: ampURL, - urlParametersRemoved: urlParametersRemoved, - protectionsState: protectionsState, - reportFlow: source, - errors: errors, - httpStatusCodes: statusCodes) + let websiteBreakage = BrokenSiteReport(siteUrl: currentURL, + category: category.lowercased(), + description: description, + osVersion: "\(ProcessInfo.processInfo.operatingSystemVersion)", + manufacturer: "Apple", + upgradedHttps: currentTab.privacyInfo?.connectionUpgradedTo != nil, + tdsETag: ContentBlocking.shared.contentBlockingManager.currentRules.first?.etag, + blockedTrackerDomains: blockedTrackerDomains, + installedSurrogates: installedSurrogates, + isGPCEnabled: PrivacySecurityPreferences.shared.gpcEnabled, + ampURL: ampURL, + urlParametersRemoved: urlParametersRemoved, + protectionsState: protectionsState, + reportFlow: source, + errors: errors, + httpStatusCodes: statusCodes, + didOpenReportInfo: didOpenReportInfo, + toggleReportCounter: toggleReportCounter) return websiteBreakage } } diff --git a/DuckDuckGo/Statistics/PixelEvent.swift b/DuckDuckGo/Statistics/PixelEvent.swift index d8cb04dbae..6e6db6ffeb 100644 --- a/DuckDuckGo/Statistics/PixelEvent.swift +++ b/DuckDuckGo/Statistics/PixelEvent.swift @@ -219,6 +219,11 @@ extension Pixel { case dailyPixel(Event, isFirst: Bool) + case protectionToggledOffBreakageReport + case toggleProtectionsDailyCount + case toggleReportDoNotSend + case toggleReportDismiss + enum Debug { /// This is a convenience pixel that allows us to fire `PixelKitEvents` using our /// regular `Pixel.fire()` calls. This is a convenience intermediate step to help ensure @@ -595,6 +600,10 @@ extension Pixel.Event { return "m_mac_netp_ev_geoswitching_set_custom" case .networkProtectionGeoswitchingNoLocations: return "m_mac_netp_ev_geoswitching_no_locations" + case .protectionToggledOffBreakageReport: return "m_mac_protection-toggled-off-breakage-report" + case .toggleProtectionsDailyCount: return "m_mac_toggle-protections-daily-count" + case .toggleReportDoNotSend: return "m_mac_toggle-report-do-not-send" + case .toggleReportDismiss: return "m_mac_toggle-report-dismiss" } } } diff --git a/DuckDuckGo/Statistics/PixelParameters.swift b/DuckDuckGo/Statistics/PixelParameters.swift index 692f6e9234..56203884e6 100644 --- a/DuckDuckGo/Statistics/PixelParameters.swift +++ b/DuckDuckGo/Statistics/PixelParameters.swift @@ -165,7 +165,11 @@ extension Pixel.Event { .dataBrokerDisableAndDeleteDaily, .dataBrokerEnableLoginItemDaily, .dataBrokerDisableLoginItemDaily, - .dataBrokerResetLoginItemDaily: + .dataBrokerResetLoginItemDaily, + .protectionToggledOffBreakageReport, + .toggleProtectionsDailyCount, + .toggleReportDoNotSend, + .toggleReportDismiss: return nil } } diff --git a/DuckDuckGoDBPBackgroundAgent/DBPMocks.swift b/DuckDuckGoDBPBackgroundAgent/DBPMocks.swift index b184a883df..5e4e36ef2f 100644 --- a/DuckDuckGoDBPBackgroundAgent/DBPMocks.swift +++ b/DuckDuckGoDBPBackgroundAgent/DBPMocks.swift @@ -19,6 +19,7 @@ import Foundation import BrowserServicesKit import Combine +import Common /* This mock is a hack for now @@ -56,23 +57,30 @@ final class PrivacyConfigurationManagingMock: PrivacyConfigurationManaging { guard let privacyConfigurationData = try? PrivacyConfigurationData(data: data) else { fatalError("Could not retrieve privacy configuration data") } - let privacyConfig = privacyConfiguration(withData: privacyConfigurationData, internalUserDecider: internalUserDecider) + let privacyConfig = privacyConfiguration(withData: privacyConfigurationData, + internalUserDecider: internalUserDecider, + toggleProtectionsCounter: toggleProtectionsCounter) return privacyConfig } var internalUserDecider: InternalUserDecider = DefaultInternalUserDecider(store: InternalUserDeciderStoreMock()) + var toggleProtectionsCounter: ToggleProtectionsCounter = ToggleProtectionsCounter(eventReporting: EventMapping { _, _, _, _ in + }) func reload(etag: String?, data: Data?) -> PrivacyConfigurationManager.ReloadResult { .downloaded } } -func privacyConfiguration(withData data: PrivacyConfigurationData, internalUserDecider: InternalUserDecider) -> PrivacyConfiguration { +func privacyConfiguration(withData data: PrivacyConfigurationData, + internalUserDecider: InternalUserDecider, + toggleProtectionsCounter: ToggleProtectionsCounter) -> PrivacyConfiguration { let domain = MockDomainsProtectionStore() return AppPrivacyConfiguration(data: data, identifier: UUID().uuidString, localProtection: domain, - internalUserDecider: internalUserDecider) + internalUserDecider: internalUserDecider, + toggleProtectionsCounter: toggleProtectionsCounter) } final class MockDomainsProtectionStore: DomainsProtectionStore { diff --git a/LocalPackages/DataBrokerProtection/Package.swift b/LocalPackages/DataBrokerProtection/Package.swift index af9dd89079..87c5b62cc9 100644 --- a/LocalPackages/DataBrokerProtection/Package.swift +++ b/LocalPackages/DataBrokerProtection/Package.swift @@ -29,7 +29,7 @@ let package = Package( targets: ["DataBrokerProtection"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "120.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "121.0.0"), .package(path: "../PixelKit"), .package(path: "../SwiftUIExtensions"), .package(path: "../XPCHelper"), diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionUtils.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionUtils.swift index d65b1653b2..8c2d9a03bc 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionUtils.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionUtils.swift @@ -164,6 +164,7 @@ private class PrivacyConfigurationDataBrokerProtectionConfigOverride: PrivacyCon var updatesPublisher: AnyPublisher<(), Never> var privacyConfig: PrivacyConfiguration var internalUserDecider: InternalUserDecider + var toggleProtectionsCounter: ToggleProtectionsCounter var currentConfig: Data { return updateConfigWithBrokerProtection() @@ -178,6 +179,7 @@ private class PrivacyConfigurationDataBrokerProtectionConfigOverride: PrivacyCon updatesPublisher = manager.updatesPublisher privacyConfig = manager.privacyConfig internalUserDecider = manager.internalUserDecider + toggleProtectionsCounter = manager.toggleProtectionsCounter } private func updateConfigWithBrokerProtection() -> Data { diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DataBrokerRunCustomJSONViewModel.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DataBrokerRunCustomJSONViewModel.swift index 523770aed0..50208b2899 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DataBrokerRunCustomJSONViewModel.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DataBrokerRunCustomJSONViewModel.swift @@ -275,23 +275,31 @@ private final class PrivacyConfigurationManagingMock: PrivacyConfigurationManagi guard let privacyConfigurationData = try? PrivacyConfigurationData(data: data) else { fatalError("Could not retrieve privacy configuration data") } - let privacyConfig = privacyConfiguration(withData: privacyConfigurationData, internalUserDecider: internalUserDecider) + let privacyConfig = privacyConfiguration(withData: privacyConfigurationData, + internalUserDecider: internalUserDecider, + toggleProtectionsCounter: toggleProtectionsCounter) return privacyConfig } var internalUserDecider: InternalUserDecider = DefaultInternalUserDecider(store: InternalUserDeciderStoreMock()) + var toggleProtectionsCounter: ToggleProtectionsCounter = ToggleProtectionsCounter(eventReporting: EventMapping { _, _, _, _ in + }) + func reload(etag: String?, data: Data?) -> PrivacyConfigurationManager.ReloadResult { .downloaded } } -func privacyConfiguration(withData data: PrivacyConfigurationData, internalUserDecider: InternalUserDecider) -> PrivacyConfiguration { +func privacyConfiguration(withData data: PrivacyConfigurationData, + internalUserDecider: InternalUserDecider, + toggleProtectionsCounter: ToggleProtectionsCounter) -> PrivacyConfiguration { let domain = MockDomainsProtectionStore() return AppPrivacyConfiguration(data: data, identifier: UUID().uuidString, localProtection: domain, - internalUserDecider: internalUserDecider) + internalUserDecider: internalUserDecider, + toggleProtectionsCounter: toggleProtectionsCounter) } final class MockDomainsProtectionStore: DomainsProtectionStore { diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift index 6cecf5a81d..71aa241a45 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift @@ -67,6 +67,8 @@ final class InternalUserDeciderStoreMock: InternalUserStoring { } final class PrivacyConfigurationManagingMock: PrivacyConfigurationManaging { + var toggleProtectionsCounter: ToggleProtectionsCounter = ToggleProtectionsCounter(eventReporting: nil) + var currentConfig: Data = Data() var updatesPublisher: AnyPublisher = .init(Just(())) diff --git a/LocalPackages/NetworkProtectionMac/Package.swift b/LocalPackages/NetworkProtectionMac/Package.swift index bd45f1e253..36497d486c 100644 --- a/LocalPackages/NetworkProtectionMac/Package.swift +++ b/LocalPackages/NetworkProtectionMac/Package.swift @@ -31,7 +31,7 @@ let package = Package( .library(name: "NetworkProtectionUI", targets: ["NetworkProtectionUI"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "120.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "121.0.0"), .package(path: "../XPCHelper"), .package(path: "../SwiftUIExtensions"), .package(path: "../LoginItems"), diff --git a/LocalPackages/SubscriptionUI/Package.swift b/LocalPackages/SubscriptionUI/Package.swift index 6239fb6917..a450104d34 100644 --- a/LocalPackages/SubscriptionUI/Package.swift +++ b/LocalPackages/SubscriptionUI/Package.swift @@ -12,7 +12,7 @@ let package = Package( targets: ["SubscriptionUI"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "120.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "121.0.0"), .package(path: "../SwiftUIExtensions") ], targets: [ diff --git a/UnitTests/ContentBlocker/AppPrivacyConfigurationTests.swift b/UnitTests/ContentBlocker/AppPrivacyConfigurationTests.swift index 279979e840..0226be7ecc 100644 --- a/UnitTests/ContentBlocker/AppPrivacyConfigurationTests.swift +++ b/UnitTests/ContentBlocker/AppPrivacyConfigurationTests.swift @@ -54,7 +54,8 @@ class AppPrivacyConfigurationTests: XCTestCase { let config = AppPrivacyConfiguration(data: configData, identifier: "", localProtection: MockDomainsProtectionStore(), - internalUserDecider: DefaultInternalUserDecider(store: InternalUserDeciderStoreMock())) + internalUserDecider: DefaultInternalUserDecider(store: InternalUserDeciderStoreMock()), + toggleProtectionsCounter: ToggleProtectionsCounter(eventReporting: nil)) XCTAssert(config.isEnabled(featureKey: .contentBlocking)) diff --git a/UnitTests/PrivacyReferenceTests/BrokenSiteReportingReferenceTests.swift b/UnitTests/PrivacyReferenceTests/BrokenSiteReportingReferenceTests.swift index 73e7cba06c..7adfb26758 100644 --- a/UnitTests/PrivacyReferenceTests/BrokenSiteReportingReferenceTests.swift +++ b/UnitTests/PrivacyReferenceTests/BrokenSiteReportingReferenceTests.swift @@ -53,7 +53,7 @@ final class BrokenSiteReportingReferenceTests: XCTestCase { params["test"] = "1" let configuration = APIRequest.Configuration(url: URL.pixelUrl(forPixelNamed: Pixel.Event.brokenSiteReport.name), queryParameters: params, - allowedQueryReservedCharacters: WebsiteBreakage.allowedQueryReservedCharacters) + allowedQueryReservedCharacters: BrokenSiteReport.allowedQueryReservedCharacters) return configuration.request } @@ -74,22 +74,24 @@ final class BrokenSiteReportingReferenceTests: XCTestCase { errors = errs.map { MockError($0) } } - let breakage = WebsiteBreakage(siteUrl: test.siteURL, - category: test.category, - description: test.providedDescription, - osVersion: test.os ?? "", - manufacturer: "Apple", - upgradedHttps: test.wasUpgraded, - tdsETag: test.blocklistVersion, - blockedTrackerDomains: test.blockedTrackers, - installedSurrogates: test.surrogates, - isGPCEnabled: test.gpcEnabled ?? false, - ampURL: "", - urlParametersRemoved: false, - protectionsState: test.protectionsEnabled, - reportFlow: .appMenu, - errors: errors, - httpStatusCodes: test.httpErrorCodes ?? []) + let breakage = BrokenSiteReport(siteUrl: test.siteURL, + category: test.category, + description: test.providedDescription, + osVersion: test.os ?? "", + manufacturer: "Apple", + upgradedHttps: test.wasUpgraded, + tdsETag: test.blocklistVersion, + blockedTrackerDomains: test.blockedTrackers, + installedSurrogates: test.surrogates, + isGPCEnabled: test.gpcEnabled ?? false, + ampURL: "", + urlParametersRemoved: false, + protectionsState: test.protectionsEnabled, + reportFlow: .appMenu, + errors: errors, + httpStatusCodes: test.httpErrorCodes ?? [], + didOpenReportInfo: false, + toggleReportCounter: nil) let request = makeURLRequest(with: breakage.requestParameters) diff --git a/UnitTests/PrivacyReferenceTests/PrivacyReferenceTestHelper.swift b/UnitTests/PrivacyReferenceTests/PrivacyReferenceTestHelper.swift index 65480c0240..cdef96e4d0 100644 --- a/UnitTests/PrivacyReferenceTests/PrivacyReferenceTestHelper.swift +++ b/UnitTests/PrivacyReferenceTests/PrivacyReferenceTestHelper.swift @@ -56,6 +56,7 @@ struct PrivacyReferenceTestHelper { return AppPrivacyConfiguration(data: data, identifier: UUID().uuidString, localProtection: domain, - internalUserDecider: DefaultInternalUserDecider(store: InternalUserDeciderStoreMock())) + internalUserDecider: DefaultInternalUserDecider(store: InternalUserDeciderStoreMock()), + toggleProtectionsCounter: ToggleProtectionsCounter(eventReporting: nil)) } } diff --git a/UnitTests/WebsiteBreakageReport/WebsiteBreakageReportTests.swift b/UnitTests/WebsiteBreakageReport/WebsiteBreakageReportTests.swift index 2493402b40..341e6c9c69 100644 --- a/UnitTests/WebsiteBreakageReport/WebsiteBreakageReportTests.swift +++ b/UnitTests/WebsiteBreakageReport/WebsiteBreakageReportTests.swift @@ -26,7 +26,7 @@ import XCTest class WebsiteBreakageReportTests: XCTestCase { func testCommonSetOfFields() throws { - let breakage = WebsiteBreakage( + let breakage = BrokenSiteReport( siteUrl: #URL("https://example.test/"), category: "contentIsMissing", description: nil, @@ -47,7 +47,9 @@ class WebsiteBreakageReportTests: XCTestCase { protectionsState: true, reportFlow: .appMenu, errors: nil, - httpStatusCodes: nil + httpStatusCodes: nil, + didOpenReportInfo: false, + toggleReportCounter: nil ) let urlRequest = makeURLRequest(with: breakage.requestParameters) @@ -69,7 +71,7 @@ class WebsiteBreakageReportTests: XCTestCase { } func testThatNativeAppSpecificFieldsAreReported() throws { - let breakage = WebsiteBreakage( + let breakage = BrokenSiteReport( siteUrl: #URL("http://unsafe.example.test/path/to/thing.html"), category: "videoOrImagesDidntLoad", description: nil, @@ -90,7 +92,9 @@ class WebsiteBreakageReportTests: XCTestCase { protectionsState: true, reportFlow: .appMenu, errors: nil, - httpStatusCodes: nil + httpStatusCodes: nil, + didOpenReportInfo: false, + toggleReportCounter: nil ) let urlRequest = makeURLRequest(with: breakage.requestParameters) @@ -120,7 +124,7 @@ class WebsiteBreakageReportTests: XCTestCase { params["test"] = "1" let configuration = APIRequest.Configuration(url: URL.pixelUrl(forPixelNamed: Pixel.Event.brokenSiteReport.name), queryParameters: params, - allowedQueryReservedCharacters: WebsiteBreakage.allowedQueryReservedCharacters) + allowedQueryReservedCharacters: BrokenSiteReport.allowedQueryReservedCharacters) return configuration.request } }