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

Commit 9b9be64

Browse files
committed
Merge branch 'main' into sam/vpn-clean-up
# By Graeme Arthur (4) and others # Via GitHub (1) and Graeme Arthur (1) * main: Fix VPN memory pressure monitor (#3535) Update Ruby to 3.3.4 (#3547) Onboarding Add To Dock Pixels (#3543) Switch to free runners for tests that run on Maestro (#3546) Fix email protection test (#3539) Update BSK for PixelKit suffix change (#3534) Adding app backgrounded result to rule compilation (#3533) 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) # Conflicts: # DuckDuckGo.xcodeproj/project.pbxproj # DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
2 parents 99aa718 + f8b9fa5 commit 9b9be64

File tree

82 files changed

+1691
-551
lines changed

Some content is hidden

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

82 files changed

+1691
-551
lines changed

.github/workflows/end-to-end.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ jobs:
6868
end-to-end-tests:
6969
name: End to end Tests
7070
needs: build-end-to-end-tests
71-
runs-on: macos-14-xlarge
71+
runs-on: macos-14
7272
timeout-minutes: 90
7373
strategy:
7474
matrix:

.github/workflows/sync-end-to-end.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ jobs:
6868
sync-end-to-end-tests:
6969
name: Sync End To End Tests
7070
needs: build-for-sync-end-to-end-tests
71-
runs-on: macos-14-xlarge
71+
runs-on: macos-14
7272
timeout-minutes: 90
7373
strategy:
7474
matrix:

.maestro/release_tests/emailprotection.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ tags:
2727
- scroll
2828
- assertVisible: Email Protection
2929
- tapOn: Email Protection
30-
- assertVisible: Email privacy, simplified.
30+
- assertVisible: Email privacy, protected.
3131
- assertVisible:
3232
id: searchEntry
3333
- tapOn:

.ruby-version

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.0.4
1+
3.3.4

Configuration/Version.xcconfig

+1-1
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

+2-2
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

+55
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

+58
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

+3
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/PixelEvent.swift

+29
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ extension Pixel {
143143

144144
case brokenSiteReport
145145

146+
// MARK: - Onboarding
147+
146148
case onboardingIntroShownUnique
147149
case onboardingIntroComparisonChartShownUnique
148150
case onboardingIntroChooseBrowserCTAPressed
@@ -173,6 +175,15 @@ extension Pixel {
173175
case daxDialogsEndOfJourneyNewTabUnique
174176
case daxDialogsEndOfJourneyDismissed
175177

178+
// MARK: - Onboarding Add To Dock
179+
180+
case onboardingAddToDockPromoImpressionsUnique
181+
case onboardingAddToDockPromoShowTutorialCTATapped
182+
case onboardingAddToDockPromoDismissCTATapped
183+
case onboardingAddToDockTutorialDismissCTATapped
184+
185+
// MARK: - Onboarding Add To Dock
186+
176187
case widgetsOnboardingCTAPressed
177188
case widgetsOnboardingDeclineOptionPressed
178189
case widgetsOnboardingMovedToBackground
@@ -620,6 +631,7 @@ extension Pixel {
620631
case syncRemoveDeviceError
621632
case syncDeleteAccountError
622633
case syncLoginExistingAccountError
634+
case syncSecureStorageReadError
623635

624636
case syncGetOtherDevices
625637
case syncGetOtherDevicesCopy
@@ -831,6 +843,11 @@ extension Pixel {
831843

832844
// MARK: WebView Error Page Shown
833845
case webViewErrorPageShown
846+
847+
// MARK: UserDefaults incositency monitoring
848+
case protectedDataUnavailableWhenBecomeActive
849+
case statisticsLoaderATBStateMismatch
850+
case adAttributionReportStateMismatch
834851
}
835852

836853
}
@@ -994,6 +1011,11 @@ extension Pixel.Event {
9941011
case .daxDialogsEndOfJourneyNewTabUnique: return "m_dx_end_new_tab_unique"
9951012
case .daxDialogsEndOfJourneyDismissed: return "m_dx_end_dialog_dismissed"
9961013

1014+
case .onboardingAddToDockPromoImpressionsUnique: return "m_onboarding_add_to_dock_promo_impressions_unique"
1015+
case .onboardingAddToDockPromoShowTutorialCTATapped: return "m_onboarding_add_to_dock_promo_show_tutorial_button_tapped"
1016+
case .onboardingAddToDockPromoDismissCTATapped: return "m_onboarding_add_to_dock_promo_dismiss_button_tapped"
1017+
case .onboardingAddToDockTutorialDismissCTATapped: return "m_onboarding_add_to_dock_tutorial_dismiss_button_tapped"
1018+
9971019
case .widgetsOnboardingCTAPressed: return "m_o_w_a"
9981020
case .widgetsOnboardingDeclineOptionPressed: return "m_o_w_d"
9991021
case .widgetsOnboardingMovedToBackground: return "m_o_w_b"
@@ -1424,6 +1446,7 @@ extension Pixel.Event {
14241446
case .syncRemoveDeviceError: return "m_d_sync_remove_device_error"
14251447
case .syncDeleteAccountError: return "m_d_sync_delete_account_error"
14261448
case .syncLoginExistingAccountError: return "m_d_sync_login_existing_account_error"
1449+
case .syncSecureStorageReadError: return "m_d_sync_secure_storage_error"
14271450

14281451
case .syncGetOtherDevices: return "sync_get_other_devices"
14291452
case .syncGetOtherDevicesCopy: return "sync_get_other_devices_copy"
@@ -1658,6 +1681,11 @@ extension Pixel.Event {
16581681

16591682
// MARK: - DuckPlayer FE Application Telemetry
16601683
case .duckPlayerLandscapeLayoutImpressions: return "duckplayer_landscape_layout_impressions"
1684+
1685+
// MARK: UserDefaults incositency monitoring
1686+
case .protectedDataUnavailableWhenBecomeActive: return "m_protected_data_unavailable_when_become_active"
1687+
case .statisticsLoaderATBStateMismatch: return "m_statistics_loader_atb_state_mismatch"
1688+
case .adAttributionReportStateMismatch: return "m_ad_attribution_report_state_mismatch"
16611689
}
16621690
}
16631691
}
@@ -1709,6 +1737,7 @@ extension Pixel.Event {
17091737

17101738
case tabClosed = "tab_closed"
17111739
case appQuit = "app_quit"
1740+
case appBackgrounded = "app_backgrounded"
17121741
case success
17131742

17141743
}

Core/StatisticsLoader.swift

+23-2
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+
}
+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//
2+
// StorageInconsistencyMonitor.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 UIKit
21+
22+
public protocol AppActivationInconsistencyMonitoring {
23+
/// See `StorageInconsistencyMonitor` for details
24+
func didBecomeActive(isProtectedDataAvailable: Bool)
25+
}
26+
27+
public protocol StatisticsStoreInconsistencyMonitoring {
28+
/// See `StorageInconsistencyMonitor` for details
29+
func statisticsDidLoad(hasFileMarker: Bool, hasInstallStatistics: Bool)
30+
}
31+
32+
public protocol AdAttributionReporterInconsistencyMonitoring {
33+
/// See `StorageInconsistencyMonitor` for details
34+
func addAttributionReporter(hasFileMarker: Bool, hasCompletedFlag: Bool)
35+
}
36+
37+
/// Takes care of reporting inconsistency in storage availability and/or state.
38+
/// See https://app.asana.com/0/481882893211075/1208618515043198/f for details.
39+
public struct StorageInconsistencyMonitor: AppActivationInconsistencyMonitoring & StatisticsStoreInconsistencyMonitoring & AdAttributionReporterInconsistencyMonitoring {
40+
41+
public init() { }
42+
43+
/// Reports a pixel if data is not available while app is active
44+
public func didBecomeActive(isProtectedDataAvailable: Bool) {
45+
if !isProtectedDataAvailable {
46+
Pixel.fire(pixel: .protectedDataUnavailableWhenBecomeActive)
47+
assertionFailure("This is unexpected state, debug if possible")
48+
}
49+
}
50+
51+
/// Reports a pixel if file marker exists but installStatistics are missing
52+
public func statisticsDidLoad(hasFileMarker: Bool, hasInstallStatistics: Bool) {
53+
if hasFileMarker == true && hasInstallStatistics == false {
54+
Pixel.fire(pixel: .statisticsLoaderATBStateMismatch)
55+
assertionFailure("This is unexpected state, debug if possible")
56+
}
57+
}
58+
59+
/// Reports a pixel if file marker exists but completion flag is false
60+
public func addAttributionReporter(hasFileMarker: Bool, hasCompletedFlag: Bool) {
61+
if hasFileMarker == true && hasCompletedFlag == false {
62+
Pixel.fire(pixel: .adAttributionReportStateMismatch)
63+
assertionFailure("This is unexpected state, debug if possible")
64+
}
65+
}
66+
}

Core/SyncErrorHandler.swift

+2
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ public class SyncErrorHandler: EventMapping<SyncError> {
100100
Pixel.fire(pixel: .syncFailedToLoadAccount, error: error)
101101
case .failedToSetupEngine:
102102
Pixel.fire(pixel: .syncFailedToSetupEngine, error: error)
103+
case .failedToReadSecureStore:
104+
Pixel.fire(pixel: .syncSecureStorageReadError, error: error)
103105
default:
104106
// Should this be so generic?
105107
let domainEvent = Pixel.Event.syncSentUnauthenticatedRequest

0 commit comments

Comments
 (0)