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

Commit facee90

Browse files
committed
Merge branch 'main' into sam/fix-memory-pressure-monitor
* main: 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)
2 parents 3298d35 + 76b1f76 commit facee90

File tree

75 files changed

+1471
-175
lines changed

Some content is hidden

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

75 files changed

+1471
-175
lines changed

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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.0.4
1+
3.3.4

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

Lines changed: 29 additions & 0 deletions
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
@@ -624,6 +635,7 @@ extension Pixel {
624635
case syncRemoveDeviceError
625636
case syncDeleteAccountError
626637
case syncLoginExistingAccountError
638+
case syncSecureStorageReadError
627639

628640
case syncGetOtherDevices
629641
case syncGetOtherDevicesCopy
@@ -835,6 +847,11 @@ extension Pixel {
835847

836848
// MARK: WebView Error Page Shown
837849
case webViewErrorPageShown
850+
851+
// MARK: UserDefaults incositency monitoring
852+
case protectedDataUnavailableWhenBecomeActive
853+
case statisticsLoaderATBStateMismatch
854+
case adAttributionReportStateMismatch
838855
}
839856

840857
}
@@ -998,6 +1015,11 @@ extension Pixel.Event {
9981015
case .daxDialogsEndOfJourneyNewTabUnique: return "m_dx_end_new_tab_unique"
9991016
case .daxDialogsEndOfJourneyDismissed: return "m_dx_end_dialog_dismissed"
10001017

1018+
case .onboardingAddToDockPromoImpressionsUnique: return "m_onboarding_add_to_dock_promo_impressions_unique"
1019+
case .onboardingAddToDockPromoShowTutorialCTATapped: return "m_onboarding_add_to_dock_promo_show_tutorial_button_tapped"
1020+
case .onboardingAddToDockPromoDismissCTATapped: return "m_onboarding_add_to_dock_promo_dismiss_button_tapped"
1021+
case .onboardingAddToDockTutorialDismissCTATapped: return "m_onboarding_add_to_dock_tutorial_dismiss_button_tapped"
1022+
10011023
case .widgetsOnboardingCTAPressed: return "m_o_w_a"
10021024
case .widgetsOnboardingDeclineOptionPressed: return "m_o_w_d"
10031025
case .widgetsOnboardingMovedToBackground: return "m_o_w_b"
@@ -1432,6 +1454,7 @@ extension Pixel.Event {
14321454
case .syncRemoveDeviceError: return "m_d_sync_remove_device_error"
14331455
case .syncDeleteAccountError: return "m_d_sync_delete_account_error"
14341456
case .syncLoginExistingAccountError: return "m_d_sync_login_existing_account_error"
1457+
case .syncSecureStorageReadError: return "m_d_sync_secure_storage_error"
14351458

14361459
case .syncGetOtherDevices: return "sync_get_other_devices"
14371460
case .syncGetOtherDevicesCopy: return "sync_get_other_devices_copy"
@@ -1666,6 +1689,11 @@ extension Pixel.Event {
16661689

16671690
// MARK: - DuckPlayer FE Application Telemetry
16681691
case .duckPlayerLandscapeLayoutImpressions: return "duckplayer_landscape_layout_impressions"
1692+
1693+
// MARK: UserDefaults incositency monitoring
1694+
case .protectedDataUnavailableWhenBecomeActive: return "m_protected_data_unavailable_when_become_active"
1695+
case .statisticsLoaderATBStateMismatch: return "m_statistics_loader_atb_state_mismatch"
1696+
case .adAttributionReportStateMismatch: return "m_ad_attribution_report_state_mismatch"
16691697
}
16701698
}
16711699
}
@@ -1717,6 +1745,7 @@ extension Pixel.Event {
17171745

17181746
case tabClosed = "tab_closed"
17191747
case appQuit = "app_quit"
1748+
case appBackgrounded = "app_backgrounded"
17201749
case success
17211750

17221751
}

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+
}
Lines changed: 66 additions & 0 deletions
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

Lines changed: 2 additions & 0 deletions
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)