Skip to content
This repository was archived by the owner on Feb 24, 2025. It is now read-only.

Commit 22a19c9

Browse files
committed
Merge branch 'main' into dominik/xcode-16
# By Daniel Bernal (7) and others # Via Daniel Bernal (3) and others * main: (29 commits) Send pixel on sync secure storage failure (#3542) Onboarding Add to Dock Refactor for Intro scenario (#3538) Update C-S-S to 6.29.0 (#3541) Change save password Never for Site button to Not Now (#3471) Release 7.144.0-1 (#3540) UserDefaults misbehavior monitoring (#3510) Send pixel on sync secure storage read failure (#3530) Remove NewTabPage retain cycles (#3532) Update release notes (#3529) Release 7.144.0-0 (#3528) Add Privacy Config feature to control ad attribution reporting (#3506) Validate VPN errors before re-throwing them (#3513) Allowing users to delete suggestions on macOS (#3465) Update build number Update build number Bump rexml from 3.3.8 to 3.3.9 (#3495) Release 7.142.1-1 (#3525) Add a debouncer to NavBars animator (#3519) Release 7.143.0-1 (#3516) Update to subscription cookie (#3512) ... # Conflicts: # DuckDuckGo.xcodeproj/project.pbxproj # DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
2 parents aa55e99 + 1a978c2 commit 22a19c9

File tree

117 files changed

+15662
-2033
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

117 files changed

+15662
-2033
lines changed

Configuration/Version.xcconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
MARKETING_VERSION = 7.143.0
1+
MARKETING_VERSION = 7.144.0

Core/AppPrivacyConfigurationDataProvider.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ import BrowserServicesKit
2323
final public class AppPrivacyConfigurationDataProvider: EmbeddedDataProvider {
2424

2525
public struct Constants {
26-
public static let embeddedDataETag = "\"f8b9cfd5f1eb7b77c21d4476f85bd177\""
27-
public static let embeddedDataSHA = "c26c97714d73a9e1e99dbd341d5890da42b49d34a296672be3d3cea00bdd37a0"
26+
public static let embeddedDataETag = "\"516f95a16f7a556c58e14ee6f193cc30\""
27+
public static let embeddedDataSHA = "87314e1ac02784472a722844a27b443b0387a164ac72afaac00d9a70731fc572"
2828
}
2929

3030
public var embeddedDataEtag: String {

Core/BoolFileMarker.swift

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//
2+
// BoolFileMarker.swift
3+
// DuckDuckGo
4+
//
5+
// Copyright © 2024 DuckDuckGo. All rights reserved.
6+
//
7+
// Licensed under the Apache License, Version 2.0 (the "License");
8+
// you may not use this file except in compliance with the License.
9+
// You may obtain a copy of the License at
10+
//
11+
// http://www.apache.org/licenses/LICENSE-2.0
12+
//
13+
// Unless required by applicable law or agreed to in writing, software
14+
// distributed under the License is distributed on an "AS IS" BASIS,
15+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
// See the License for the specific language governing permissions and
17+
// limitations under the License.
18+
//
19+
20+
public struct BoolFileMarker {
21+
let fileManager = FileManager.default
22+
private let url: URL
23+
24+
public var isPresent: Bool {
25+
fileManager.fileExists(atPath: url.path)
26+
}
27+
28+
public func mark() {
29+
if !isPresent {
30+
fileManager.createFile(atPath: url.path, contents: nil, attributes: [.protectionKey: FileProtectionType.none])
31+
}
32+
}
33+
34+
public func unmark() {
35+
if isPresent {
36+
try? fileManager.removeItem(at: url)
37+
}
38+
}
39+
40+
public init?(name: Name) {
41+
guard let applicationSupportDirectory = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else {
42+
return nil
43+
}
44+
45+
self.url = applicationSupportDirectory.appendingPathComponent(name.rawValue)
46+
}
47+
48+
public struct Name: RawRepresentable {
49+
public let rawValue: String
50+
51+
public init(rawValue: String) {
52+
self.rawValue = "\(rawValue).marker"
53+
}
54+
}
55+
}

Core/BoolFileMarkerTests.swift

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
//
2+
// BoolFileMarkerTests.swift
3+
// DuckDuckGo
4+
//
5+
// Copyright © 2024 DuckDuckGo. All rights reserved.
6+
//
7+
// Licensed under the Apache License, Version 2.0 (the "License");
8+
// you may not use this file except in compliance with the License.
9+
// You may obtain a copy of the License at
10+
//
11+
// http://www.apache.org/licenses/LICENSE-2.0
12+
//
13+
// Unless required by applicable law or agreed to in writing, software
14+
// distributed under the License is distributed on an "AS IS" BASIS,
15+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
// See the License for the specific language governing permissions and
17+
// limitations under the License.
18+
//
19+
20+
import XCTest
21+
@testable import Core
22+
23+
final class BoolFileMarkerTests: XCTestCase {
24+
25+
private let marker = BoolFileMarker(name: .init(rawValue: "test"))!
26+
27+
override func tearDown() {
28+
super.tearDown()
29+
30+
marker.unmark()
31+
}
32+
33+
private var testFileURL: URL? {
34+
FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first?.appendingPathComponent("test.marker")
35+
}
36+
37+
func testMarkCreatesCorrectFile() throws {
38+
39+
marker.mark()
40+
41+
let fileURL = try XCTUnwrap(testFileURL)
42+
43+
let attributes = try FileManager.default.attributesOfItem(atPath: fileURL.path)
44+
XCTAssertNil(attributes[.protectionKey])
45+
XCTAssertTrue(FileManager.default.fileExists(atPath: fileURL.path))
46+
XCTAssertEqual(marker.isPresent, true)
47+
}
48+
49+
func testUnmarkRemovesFile() throws {
50+
marker.mark()
51+
marker.unmark()
52+
53+
let fileURL = try XCTUnwrap(testFileURL)
54+
55+
XCTAssertFalse(marker.isPresent)
56+
XCTAssertFalse(FileManager.default.fileExists(atPath: fileURL.path))
57+
}
58+
}

Core/FeatureFlag.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public enum FeatureFlag: String {
4545
case onboardingAddToDock
4646
case autofillSurveys
4747
case autcompleteTabs
48+
case adAttributionReporting
4849

4950
/// https://app.asana.com/0/72649045549333/1208231259093710/f
5051
case networkProtectionUserTips
@@ -103,6 +104,8 @@ extension FeatureFlag: FeatureFlagSourceProviding {
103104
return .remoteReleasable(.feature(.autocompleteTabs))
104105
case .networkProtectionUserTips:
105106
return .remoteReleasable(.subfeature(NetworkProtectionSubfeature.userTips))
107+
case .adAttributionReporting:
108+
return .remoteReleasable(.feature(.adAttributionReporting))
106109
}
107110
}
108111
}

Core/HistoryManager.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ class NullHistoryCoordinator: HistoryCoordinating {
145145
completion()
146146
}
147147

148+
func removeUrlEntry(_ url: URL, completion: (((any Error)?) -> Void)?) {
149+
completion?(nil)
150+
}
151+
148152
}
149153

150154
public class HistoryDatabase {

Core/PixelEvent.swift

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,8 @@ extension Pixel {
460460
case networkProtectionConfigurationInvalidPayload(configuration: Configuration)
461461
case networkProtectionConfigurationPixelTest
462462

463+
case networkProtectionMalformedErrorDetected
464+
463465
// MARK: remote messaging pixels
464466

465467
case remoteMessageShown
@@ -622,6 +624,7 @@ extension Pixel {
622624
case syncRemoveDeviceError
623625
case syncDeleteAccountError
624626
case syncLoginExistingAccountError
627+
case syncSecureStorageReadError
625628

626629
case syncGetOtherDevices
627630
case syncGetOtherDevicesCopy
@@ -710,8 +713,8 @@ extension Pixel {
710713
case privacyProKeychainAccessError
711714
case privacyProSubscriptionCookieMissingTokenOnSignIn
712715
case privacyProSubscriptionCookieMissingCookieOnSignOut
713-
case privacyProSubscriptionCookieRefreshedWithUpdate
714-
case privacyProSubscriptionCookieRefreshedWithDelete
716+
case privacyProSubscriptionCookieRefreshedWithAccessToken
717+
case privacyProSubscriptionCookieRefreshedWithEmptyValue
715718
case privacyProSubscriptionCookieFailedToSetSubscriptionCookie
716719

717720
// MARK: Pixel Experiment
@@ -799,6 +802,10 @@ extension Pixel {
799802
case duckPlayerSettingAlwaysSettings
800803
case duckPlayerSettingNeverSettings
801804
case duckPlayerSettingBackToDefault
805+
case duckPlayerSettingsAlwaysOverlaySERP
806+
case duckPlayerSettingsAlwaysOverlayYoutube
807+
case duckPlayerSettingsNeverOverlaySERP
808+
case duckPlayerSettingsNeverOverlayYoutube
802809
case duckPlayerWatchOnYoutube
803810
case duckPlayerSettingAlwaysOverlayYoutube
804811
case duckPlayerSettingNeverOverlayYoutube
@@ -826,16 +833,14 @@ extension Pixel {
826833
case pproFeedbackSubcategoryScreenShow(source: String, reportType: String, category: String)
827834
case pproFeedbackSubmitScreenShow(source: String, reportType: String, category: String, subcategory: String)
828835
case pproFeedbackSubmitScreenFAQClick(source: String, reportType: String, category: String, subcategory: String)
829-
830-
// MARK: DuckPlayer Pixel Experiment
831-
case duckplayerExperimentCohortAssign
832-
case duckplayerExperimentSearch
833-
case duckplayerExperimentDailySearch
834-
case duckplayerExperimentWeeklySearch
835-
case duckplayerExperimentYoutubePageView
836836

837837
// MARK: WebView Error Page Shown
838838
case webViewErrorPageShown
839+
840+
// MARK: UserDefaults incositency monitoring
841+
case protectedDataUnavailableWhenBecomeActive
842+
case statisticsLoaderATBStateMismatch
843+
case adAttributionReportStateMismatch
839844
}
840845

841846
}
@@ -1266,6 +1271,8 @@ extension Pixel.Event {
12661271
case .networkProtectionConfigurationInvalidPayload(let config): return "m_netp_vpn_configuration_\(config.rawValue)_invalid_payload"
12671272
case .networkProtectionConfigurationPixelTest: return "m_netp_vpn_configuration_pixel_test"
12681273

1274+
case .networkProtectionMalformedErrorDetected: return "m_netp_vpn_malformed_error_detected"
1275+
12691276
// MARK: remote messaging pixels
12701277

12711278
case .remoteMessageShown: return "m_remote_message_shown"
@@ -1431,6 +1438,7 @@ extension Pixel.Event {
14311438
case .syncRemoveDeviceError: return "m_d_sync_remove_device_error"
14321439
case .syncDeleteAccountError: return "m_d_sync_delete_account_error"
14331440
case .syncLoginExistingAccountError: return "m_d_sync_login_existing_account_error"
1441+
case .syncSecureStorageReadError: return "m_d_sync_secure_storage_error"
14341442

14351443
case .syncGetOtherDevices: return "sync_get_other_devices"
14361444
case .syncGetOtherDevicesCopy: return "sync_get_other_devices_copy"
@@ -1528,8 +1536,8 @@ extension Pixel.Event {
15281536
case .privacyProKeychainAccessError: return "m_privacy-pro_keychain_access_error"
15291537
case .privacyProSubscriptionCookieMissingTokenOnSignIn: return "m_privacy-pro_subscription-cookie-missing_token_on_sign_in"
15301538
case .privacyProSubscriptionCookieMissingCookieOnSignOut: return "m_privacy-pro_subscription-cookie-missing_cookie_on_sign_out"
1531-
case .privacyProSubscriptionCookieRefreshedWithUpdate: return "m_privacy-pro_subscription-cookie-refreshed_with_update"
1532-
case .privacyProSubscriptionCookieRefreshedWithDelete: return "m_privacy-pro_subscription-cookie-refreshed_with_delete"
1539+
case .privacyProSubscriptionCookieRefreshedWithAccessToken: return "m_privacy-pro_subscription-cookie-refreshed_with_access_token"
1540+
case .privacyProSubscriptionCookieRefreshedWithEmptyValue: return "m_privacy-pro_subscription-cookie-refreshed_with_empty_value"
15331541
case .privacyProSubscriptionCookieFailedToSetSubscriptionCookie: return "m_privacy-pro_subscription-cookie-failed_to_set_subscription_cookie"
15341542

15351543
// MARK: Pixel Experiment
@@ -1623,6 +1631,10 @@ extension Pixel.Event {
16231631
case .duckPlayerViewFromOther: return "duckplayer_view-from_other"
16241632
case .duckPlayerSettingAlwaysSettings: return "duckplayer_setting_always_settings"
16251633
case .duckPlayerSettingAlwaysDuckPlayer: return "duckplayer_setting_always_duck-player"
1634+
case .duckPlayerSettingsAlwaysOverlaySERP: return "duckplayer_setting_always_overlay_serp"
1635+
case .duckPlayerSettingsAlwaysOverlayYoutube: return "duckplayer_setting_always_overlay_youtube"
1636+
case .duckPlayerSettingsNeverOverlaySERP: return "duckplayer_setting_never_overlay_serp"
1637+
case .duckPlayerSettingsNeverOverlayYoutube: return "duckplayer_setting_never_overlay_youtube"
16261638
case .duckPlayerOverlayYoutubeImpressions: return "duckplayer_overlay_youtube_impressions"
16271639
case .duckPlayerOverlayYoutubeWatchHere: return "duckplayer_overlay_youtube_watch_here"
16281640
case .duckPlayerSettingNeverSettings: return "duckplayer_setting_never_settings"
@@ -1656,18 +1668,16 @@ extension Pixel.Event {
16561668
case .pproFeedbackSubmitScreenShow: return "m_ppro_feedback_submit-screen_show"
16571669
case .pproFeedbackSubmitScreenFAQClick: return "m_ppro_feedback_submit-screen-faq_click"
16581670

1659-
// MARK: Duckplayer experiment
1660-
case .duckplayerExperimentCohortAssign: return "duckplayer_experiment_cohort_assign_v2"
1661-
case .duckplayerExperimentSearch: return "duckplayer_experiment_search_v2"
1662-
case .duckplayerExperimentDailySearch: return "duckplayer_experiment_daily_search_v2"
1663-
case .duckplayerExperimentWeeklySearch: return "duckplayer_experiment_weekly_search_v2"
1664-
case .duckplayerExperimentYoutubePageView: return "duckplayer_experiment_youtube_page_view_v2"
1665-
16661671
// MARK: - WebView Error Page shown
16671672
case .webViewErrorPageShown: return "m_errorpageshown"
16681673

16691674
// MARK: - DuckPlayer FE Application Telemetry
16701675
case .duckPlayerLandscapeLayoutImpressions: return "duckplayer_landscape_layout_impressions"
1676+
1677+
// MARK: UserDefaults incositency monitoring
1678+
case .protectedDataUnavailableWhenBecomeActive: return "m_protected_data_unavailable_when_become_active"
1679+
case .statisticsLoaderATBStateMismatch: return "m_statistics_loader_atb_state_mismatch"
1680+
case .adAttributionReportStateMismatch: return "m_ad_attribution_report_state_mismatch"
16711681
}
16721682
}
16731683
}

Core/StatisticsLoader.swift

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,29 @@ public class StatisticsLoader {
3333
private let returnUserMeasurement: ReturnUserMeasurement
3434
private let usageSegmentation: UsageSegmenting
3535
private let parser = AtbParser()
36+
private let atbPresenceFileMarker = BoolFileMarker(name: .isATBPresent)
37+
private let inconsistencyMonitoring: StatisticsStoreInconsistencyMonitoring
3638

3739
init(statisticsStore: StatisticsStore = StatisticsUserDefaults(),
3840
returnUserMeasurement: ReturnUserMeasurement = KeychainReturnUserMeasurement(),
39-
usageSegmentation: UsageSegmenting = UsageSegmentation()) {
41+
usageSegmentation: UsageSegmenting = UsageSegmentation(),
42+
inconsistencyMonitoring: StatisticsStoreInconsistencyMonitoring = StorageInconsistencyMonitor()) {
4043
self.statisticsStore = statisticsStore
4144
self.returnUserMeasurement = returnUserMeasurement
4245
self.usageSegmentation = usageSegmentation
46+
self.inconsistencyMonitoring = inconsistencyMonitoring
4347
}
4448

4549
public func load(completion: @escaping Completion = {}) {
46-
if statisticsStore.hasInstallStatistics {
50+
let hasFileMarker = atbPresenceFileMarker?.isPresent ?? false
51+
let hasInstallStatistics = statisticsStore.hasInstallStatistics
52+
53+
inconsistencyMonitoring.statisticsDidLoad(hasFileMarker: hasFileMarker, hasInstallStatistics: hasInstallStatistics)
54+
55+
if hasInstallStatistics {
56+
// Synchronize file marker with current state
57+
createATBFileMarker()
58+
4759
completion()
4860
return
4961
}
@@ -85,10 +97,15 @@ public class StatisticsLoader {
8597
self.statisticsStore.installDate = Date()
8698
self.statisticsStore.atb = atb.version
8799
self.returnUserMeasurement.installCompletedWithATB(atb)
100+
self.createATBFileMarker()
88101
completion()
89102
}
90103
}
91104

105+
private func createATBFileMarker() {
106+
atbPresenceFileMarker?.mark()
107+
}
108+
92109
public func refreshSearchRetentionAtb(completion: @escaping Completion = {}) {
93110
guard let url = StatisticsDependentURLFactory(statisticsStore: statisticsStore).makeSearchAtbURL() else {
94111
requestInstallStatistics {
@@ -169,3 +186,7 @@ public class StatisticsLoader {
169186
processUsageSegmentation(atb: nil, activityType: activityType)
170187
}
171188
}
189+
190+
private extension BoolFileMarker.Name {
191+
static let isATBPresent = BoolFileMarker.Name(rawValue: "atb-present")
192+
}

0 commit comments

Comments
 (0)