Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@ import Shared
import Storage

protocol SearchEnginesManagerProvider: AnyObject {
@MainActor
var defaultEngine: OpenSearchEngine? { get }
@MainActor
var orderedEngines: [OpenSearchEngine] { get }
@MainActor
var delegate: SearchEngineDelegate? { get set }
@MainActor
func getOrderedEngines(completion: @escaping SearchEngineCompletion)
}

Expand Down Expand Up @@ -46,6 +50,7 @@ struct SearchEngineProviderFactory {
/// enabled quick search engines, and it is possible to disable every non-default quick search engine).
///
/// This class is not thread-safe -- you should only access it on a single thread (usually, the main thread)!
@MainActor
class SearchEnginesManager: SearchEnginesManagerProvider {
private let prefs: Prefs
private let fileAccessor: FileAccessor
Expand Down Expand Up @@ -291,7 +296,7 @@ class SearchEnginesManager: SearchEnginesManagerProvider {
}
}

func deleteCustomEngine(_ engine: OpenSearchEngine, completion: @escaping @Sendable () -> Void) {
func deleteCustomEngine(_ engine: OpenSearchEngine, completion: @MainActor @escaping @Sendable () -> Void) {
// We can't delete a preinstalled engine or an engine that is currently the default.
guard engine.isCustomEngine && !isEngineDefault(engine) else { return }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ final class AddressToolbarContainerModel: Equatable {
)
}

@MainActor
init(
state: ToolbarState,
profile: Profile,
Expand Down Expand Up @@ -242,6 +243,7 @@ final class AddressToolbarContainerModel: Equatable {
self.toolbarHelper = toolbarHelper
}

@MainActor
func searchTermFromURL(_ url: URL?) -> String? {
var searchURL: URL? = url

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ protocol TopSitesManagerInterface {
/// - Parameters:
/// - otherSites: Contains the user's pinned sites, history, and default suggested sites.
/// - sponsoredSites: Contains the sponsored sites.
@MainActor
func recalculateTopSites(otherSites: [TopSiteConfiguration], sponsoredSites: [Site]) -> [TopSiteConfiguration]

/// Removes the site out of the top sites.
Expand Down Expand Up @@ -78,6 +79,7 @@ final class TopSitesManager: TopSitesManagerInterface, FeatureFlaggable {
self.maxTopSites = maxTopSites
}

@MainActor
func recalculateTopSites(otherSites: [TopSiteConfiguration], sponsoredSites: [Site]) -> [TopSiteConfiguration] {
let availableSpaceCount = getAvailableSpaceCount(with: otherSites)
let googleTopSite = addGoogleTopSite(with: availableSpaceCount)
Expand Down Expand Up @@ -142,6 +144,7 @@ final class TopSitesManager: TopSitesManagerInterface, FeatureFlaggable {
return featureFlags.isFeatureEnabled(.hntSponsoredShortcuts, checking: .userOnly)
}

@MainActor
private func filterSponsoredSites(
contiles: [Site],
with availableSpaceCount: Int,
Expand All @@ -161,6 +164,7 @@ final class TopSitesManager: TopSitesManagerInterface, FeatureFlaggable {

/// Show the sponsored site only if site is not already present in the pinned sites
/// and it's not the default search engine
@MainActor
private func shouldShowSponsoredSite(with sponsoredSite: Site, and otherSites: [TopSiteConfiguration]) -> Bool {
let siteDomain = sponsoredSite.url.asURL?.shortDomain
let sponsoredSiteIsAlreadyPresent = otherSites.contains { (topSite: TopSiteConfiguration) in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class TopSiteHistoryManager: TopSiteHistoryManagerProvider {
private let topSiteCacheSize: Int32 = 32
private let topSitesProvider: TopSitesProvider

@MainActor
init(profile: Profile) {
self.profile = profile
self.topSitesProvider = TopSitesProviderImplementation(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ protocol TopSitesDataAdaptor {
var numberOfRows: Int { get }

/// Get top sites data
@MainActor
func getTopSitesData() -> [TopSite]
}

Expand Down Expand Up @@ -78,6 +79,7 @@ class TopSitesDataAdaptorImplementation: TopSitesDataAdaptor, FeatureFlaggable {
loadTopSitesData()
}

@MainActor
func getTopSitesData() -> [TopSite] {
recalculateTopSiteData()
return topSites
Expand All @@ -88,6 +90,7 @@ class TopSitesDataAdaptorImplementation: TopSitesDataAdaptor, FeatureFlaggable {
/// Top sites are composed of pinned sites, history, Contiles and Google top site.
/// Google top site is always first, then comes the contiles, pinned sites and history top sites.
/// We only add Google top site or Contiles if number of pins doesn't exceeds the available number shown of tiles.
@MainActor
private func recalculateTopSiteData() {
var sites = historySites
let availableSpaceCount = getAvailableSpaceCount(maxTopSites: maxTopSites)
Expand Down Expand Up @@ -120,9 +123,11 @@ class TopSitesDataAdaptorImplementation: TopSitesDataAdaptor, FeatureFlaggable {
// FXIOS-13954 - This code will disappear soon since it's part of the legacy homepage only. If it hasn't
// disappeared then we should is to be revisited once we are on Swift 6.2 with default main actor isolation.
// Note: `didLoadNewData` calls `ensureMainThread`
dispatchGroup.notify(queue: dataQueue) { [weak self] in
self?.recalculateTopSiteData()
self?.delegate?.didLoadNewData()
dispatchGroup.notify(queue: .main) { [weak self] in
MainActor.assumeIsolated {
self?.recalculateTopSiteData()
self?.delegate?.didLoadNewData()
}
}
}

Expand Down Expand Up @@ -188,6 +193,7 @@ class TopSitesDataAdaptorImplementation: TopSitesDataAdaptor, FeatureFlaggable {
return Int(preferredNumberOfRows ?? defaultNumberOfRows)
}

@MainActor
func addSponsoredTiles(sites: inout [Site],
shouldAddGoogle: Bool,
availableSpaceCount: Int) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class TopSitesViewModel {
private let unifiedAdsTelemetry: UnifiedAdsCallbackTelemetry
private let sponsoredTileTelemetry: SponsoredTileTelemetry

@MainActor
init(profile: Profile,
isZeroSearch: Bool = false,
theme: Theme,
Expand Down
54 changes: 36 additions & 18 deletions firefox-ios/Client/Telemetry/TelemetryWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ enum SearchLocation: String {
}

// FIXME: FXIOS-13987 Make truly thread safe
class TelemetryWrapper: TelemetryWrapperProtocol, FeatureFlaggable, @unchecked Sendable {
class TelemetryWrapper: TelemetryWrapperProtocol,
FeatureFlaggable,
Notifiable,
@unchecked Sendable {
typealias ExtraKey = TelemetryWrapper.EventExtraKey

static let shared = TelemetryWrapper()
Expand Down Expand Up @@ -83,6 +86,7 @@ class TelemetryWrapper: TelemetryWrapperProtocol, FeatureFlaggable, @unchecked S
} catch {}
}

@MainActor
func setup(profile: Profile) {
migratePathComponentInDocumentsDirectory("MozTelemetry-Default-core", to: .cachesDirectory)
migratePathComponentInDocumentsDirectory("MozTelemetry-Default-mobile-event", to: .cachesDirectory)
Expand All @@ -94,6 +98,7 @@ class TelemetryWrapper: TelemetryWrapperProtocol, FeatureFlaggable, @unchecked S
initGlean(profile, sendUsageData: sendUsageData)
}

@MainActor
func initGlean(
_ profile: Profile,
searchEnginesManager: SearchEnginesManager = AppContainer.shared.resolve(),
Expand Down Expand Up @@ -176,17 +181,13 @@ class TelemetryWrapper: TelemetryWrapperProtocol, FeatureFlaggable, @unchecked S

// Register an observer to record settings and other metrics that are more appropriate to
// record on going to background rather than during initialization.
NotificationCenter.default.addObserver(
self,
selector: #selector(recordEnteredBackgroundPreferenceMetrics(notification:)),
name: UIApplication.didEnterBackgroundNotification,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(recordFinishedLaunchingPreferenceMetrics(notification:)),
name: UIApplication.didFinishLaunchingNotification,
object: nil
startObservingNotifications(
withNotificationCenter: NotificationCenter.default,
forObserver: self,
observing: [
UIApplication.didEnterBackgroundNotification,
UIApplication.didFinishLaunchingNotification
]
)
}

Expand All @@ -206,9 +207,28 @@ class TelemetryWrapper: TelemetryWrapperProtocol, FeatureFlaggable, @unchecked S
}
}

@objc
func recordFinishedLaunchingPreferenceMetrics(notification: NSNotification) {
// MARK: Notifiable
func handleNotifications(_ notification: Notification) {
switch notification.name {
case UIApplication.didEnterBackgroundNotification:
// For recording metrics that are better recorded when going to background due
// to the particular measurement, or availability of the information.
ensureMainThread {
self.recordEnteredBackgroundPreferenceMetrics()
}
case UIApplication.didFinishLaunchingNotification:
ensureMainThread {
self.recordFinishedLaunchingPreferenceMetrics()
}
default:
break
}
}

@MainActor
func recordFinishedLaunchingPreferenceMetrics() {
guard let profile = self.profile else { return }

// Pocket stories visible
if let pocketStoriesVisible = profile.prefs.boolForKey(PrefsKeys.UserFeatureFlagPrefs.ASPocketStories) {
GleanMetrics.FirefoxHomePage.pocketStoriesVisible.set(pocketStoriesVisible)
Expand All @@ -217,10 +237,8 @@ class TelemetryWrapper: TelemetryWrapperProtocol, FeatureFlaggable, @unchecked S
}
}

// Function for recording metrics that are better recorded when going to background due
// to the particular measurement, or availability of the information.
@objc
func recordEnteredBackgroundPreferenceMetrics(notification: NSNotification) {
@MainActor
func recordEnteredBackgroundPreferenceMetrics() {
guard let profile = self.profile else {
assertionFailure("Error unwrapping profile")
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,6 @@ class DependencyHelperMock {
)
AppContainer.shared.register(service: profile as Profile)

let searchEnginesManager = SearchEnginesManager(
prefs: profile.prefs,
files: profile.files,
engineProvider: MockSearchEngineProvider()
)
AppContainer.shared.register(service: searchEnginesManager)

let diskImageStore: DiskImageStore = DefaultDiskImageStore(
files: profile.files,
namespace: TabManagerConstants.tabScreenshotNamespace,
Expand All @@ -52,6 +45,13 @@ class DependencyHelperMock {
windowManager: windowManager
)
AppContainer.shared.register(service: MockThemeManager() as ThemeManager)

let searchEnginesManager = SearchEnginesManager(
prefs: profile.prefs,
files: profile.files,
engineProvider: MockSearchEngineProvider()
)
AppContainer.shared.register(service: searchEnginesManager)
}
} else {
DispatchQueue.main.sync {
Expand All @@ -61,6 +61,13 @@ class DependencyHelperMock {
windowManager: windowManager
)
AppContainer.shared.register(service: MockThemeManager() as ThemeManager)

let searchEnginesManager = SearchEnginesManager(
prefs: profile.prefs,
files: profile.files,
engineProvider: MockSearchEngineProvider()
)
AppContainer.shared.register(service: searchEnginesManager)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
@testable import Client
import Foundation
import XCTest
import Common
import Shared

@MainActor
class SearchEnginesManagerTests: XCTestCase {
private let defaultSearchEngineName = "ATester"
private let expectedEngineNames = ["ATester", "BTester", "CTester", "DTester", "ETester", "FTester"]
Expand Down Expand Up @@ -73,9 +75,11 @@ class SearchEnginesManagerTests: XCTestCase {

let exp = expectation(description: "Engine was deleted")
searchEnginesManager.deleteCustomEngine(testEngine) { [self] in
XCTAssertFalse(searchEnginesManager.orderedEngines.contains(where: { $0 == testEngine }))
ensureMainThread {
XCTAssertFalse(self.searchEnginesManager.orderedEngines.contains(where: { $0 == testEngine }))

exp.fulfill()
exp.fulfill()
}
}

waitForExpectations(timeout: 2)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Shared
import Storage
import XCTest

@MainActor
class TopSitesDataAdaptorTests: XCTestCase, FeatureFlaggable {
private var profile: MockProfile!
private var searchEnginesManager: SearchEnginesManager!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class TopSitesViewModelTests: XCTestCase {
super.tearDown()
}

@MainActor
func testDeletionOfSingleSuggestedSite() {
let viewModel = TopSitesViewModel(profile: profile,
isZeroSearch: false,
Expand All @@ -47,6 +48,7 @@ class TopSitesViewModelTests: XCTestCase {
}))
}

@MainActor
func testDeletionOfAllDefaultSites() {
let viewModel = TopSitesViewModel(profile: self.profile,
isZeroSearch: false,
Expand All @@ -68,6 +70,7 @@ class TopSitesViewModelTests: XCTestCase {

// MARK: Helper methods
extension TopSitesViewModelTests {
@MainActor
func createViewModel(overridenSiteCount: Int = 40, overridenNumberOfRows: Int = 2) -> TopSitesViewModel {
let viewModel = TopSitesViewModel(profile: self.profile,
isZeroSearch: false,
Expand Down
Loading