From 51ae84f48a323d23935ed3f26f9e3c263aef2ecc Mon Sep 17 00:00:00 2001 From: Andrei Ermoshin Date: Mon, 8 Apr 2024 13:43:17 +0300 Subject: [PATCH 01/32] Save repository enhacements workl for now --- .../CoreBrowser/Tabs/TabsDataService.swift | 24 +++++++++---------- ...sStoragable.swift => TabsRepository.swift} | 2 +- .../catowser.xcodeproj/project.pbxproj | 20 ++++++++-------- .../Environment/TabsEnvironment.swift | 8 +++---- ...bsCache.swift => TabsRepositoryImpl.swift} | 13 +++++----- 5 files changed, 33 insertions(+), 34 deletions(-) rename catowseriOS/CoreBrowser/Tabs/{TabsStoragable.swift => TabsRepository.swift} (96%) rename catowseriOS/catowser/WebTabs/{TabsCache.swift => TabsRepositoryImpl.swift} (82%) diff --git a/catowseriOS/CoreBrowser/Tabs/TabsDataService.swift b/catowseriOS/CoreBrowser/Tabs/TabsDataService.swift index 4d6afad1..788c5be5 100644 --- a/catowseriOS/CoreBrowser/Tabs/TabsDataService.swift +++ b/catowseriOS/CoreBrowser/Tabs/TabsDataService.swift @@ -30,17 +30,17 @@ public actor TabsDataService { /// Tabs count input for the async stream private var tabsCountInput: IntStream.Continuation! /// Database interface - private let storage: TabsStoragable + private let tabsRepository: TabsRepository /// Default positioning settings private let positioning: TabsStates /// A list of observers, usually some views which need to observer tabs count or changes to the tabs list private var tabObservers: [TabsObserver] - public init(_ storage: TabsStoragable, + public init(_ storage: TabsRepository, _ positioning: TabsStates, _ selectionStrategy: TabSelectionStrategy) async { self.selectionStrategy = selectionStrategy - self.storage = storage + self.tabsRepository = storage self.positioning = positioning self.tabObservers = [] self.selectedTabIdentifier = positioning.defaultSelectedTabId @@ -123,7 +123,7 @@ private extension TabsDataService { tabsCountInput.yield(tabs.count) let needSelect = selectionStrategy.makeTabActiveAfterAdding do { - let addedTab = try await storage.add(tab, select: needSelect) + let addedTab = try await tabsRepository.add(tab, select: needSelect) await handleTabAdded(addedTab, index: newIndex, select: needSelect) } catch { // It doesn't matter, on view level it must be added right away @@ -134,7 +134,7 @@ private extension TabsDataService { func handleCloseTabCommand(_ tab: Tab) async -> TabsServiceDataOutput { do { - let removedTabs = try await storage.remove(tabs: [tab]) + let removedTabs = try await tabsRepository.remove(tabs: [tab]) // swiftlint:disable:next force_unwrapping await handleCachedTabRemove(removedTabs.first!) } catch { @@ -154,11 +154,11 @@ private extension TabsDataService { func handleCloseAllCommand() async -> TabsServiceDataOutput { let contentState = await positioning.contentState do { - _ = try await storage.remove(tabs: tabs) + _ = try await tabsRepository.remove(tabs: tabs) tabs.removeAll() tabsCountInput.yield(0) let tab: Tab = .init(contentType: contentState) - _ = try await storage.add(tab, select: true) + _ = try await tabsRepository.add(tab, select: true) } catch { // tab view should be removed immediately on view level anyway print("Failure to remove tab and reset to one tab: \(error)") @@ -168,7 +168,7 @@ private extension TabsDataService { func handleSelectTabCommand(_ tab: Tab) async -> TabsServiceDataOutput { do { - let identifier = try await storage.select(tab: tab) + let identifier = try await tabsRepository.select(tab: tab) guard identifier != selectedTabIdentifier else { return .tabSelected } @@ -193,7 +193,7 @@ private extension TabsDataService { newTab.previewData = nil do { - _ = try storage.update(tab: newTab) + _ = try tabsRepository.update(tab: newTab) tabs[tabIndex] = newTab // Need to notify observers to allow them to update title for tab view for observer in tabObservers { @@ -344,13 +344,13 @@ private extension TabsDataService { } func fetchTabs() async throws { - var cachedTabs = try await storage.fetchAllTabs() + var cachedTabs = try await tabsRepository.fetchAllTabs() if cachedTabs.isEmpty { let tab = Tab(contentType: await positioning.contentState) - let savedTab = try await storage.add(tab, select: true) + let savedTab = try await tabsRepository.add(tab, select: true) cachedTabs = [savedTab] } - let id = try await storage.fetchSelectedTabId() + let id = try await tabsRepository.fetchSelectedTabId() guard !cachedTabs.isEmpty else { return } diff --git a/catowseriOS/CoreBrowser/Tabs/TabsStoragable.swift b/catowseriOS/CoreBrowser/Tabs/TabsRepository.swift similarity index 96% rename from catowseriOS/CoreBrowser/Tabs/TabsStoragable.swift rename to catowseriOS/CoreBrowser/Tabs/TabsRepository.swift index c4aaad19..7884b8bb 100644 --- a/catowseriOS/CoreBrowser/Tabs/TabsStoragable.swift +++ b/catowseriOS/CoreBrowser/Tabs/TabsRepository.swift @@ -9,7 +9,7 @@ import Foundation import AutoMockable -public protocol TabsStoragable: AutoMockable { +public protocol TabsRepository: AutoMockable { /// Defines human redable name for Int if it is describes index. /// e.g. implementation could use Index type instead. typealias TabIndex = Int diff --git a/catowseriOS/catowser.xcodeproj/project.pbxproj b/catowseriOS/catowser.xcodeproj/project.pbxproj index 586bdd6c..e2fa0009 100644 --- a/catowseriOS/catowser.xcodeproj/project.pbxproj +++ b/catowseriOS/catowser.xcodeproj/project.pbxproj @@ -280,9 +280,9 @@ 8A6CD6D524585D8C0068ACB5 /* Combine.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A6CD6D324585D830068ACB5 /* Combine.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 8A745C0A247FE93F00A379AD /* DefaultTabProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60724ED92204332B0071BECF /* DefaultTabProvider.swift */; }; 8A745C0B247FE94000A379AD /* DefaultTabProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60724ED92204332B0071BECF /* DefaultTabProvider.swift */; }; - 8A745C0C248027C600A379AD /* TabsCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60724EA621FF17C90071BECF /* TabsCache.swift */; }; - 8A745C0D248027C600A379AD /* TabsCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60724EA621FF17C90071BECF /* TabsCache.swift */; }; - 8A745C0F2480287700A379AD /* TabsStoragable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A745C0E2480287700A379AD /* TabsStoragable.swift */; }; + 8A745C0C248027C600A379AD /* TabsRepositoryImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60724EA621FF17C90071BECF /* TabsRepositoryImpl.swift */; }; + 8A745C0D248027C600A379AD /* TabsRepositoryImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60724EA621FF17C90071BECF /* TabsRepositoryImpl.swift */; }; + 8A745C0F2480287700A379AD /* TabsRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A745C0E2480287700A379AD /* TabsRepository.swift */; }; 8A745C112480295B00A379AD /* TabsEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A745C102480295B00A379AD /* TabsEnvironment.swift */; }; 8A745C122480295B00A379AD /* TabsEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A745C102480295B00A379AD /* TabsEnvironment.swift */; }; 8A745C1424802E4F00A379AD /* TabsObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A745C1324802E4F00A379AD /* TabsObserver.swift */; }; @@ -1015,7 +1015,7 @@ 605C54F421F8A8A100B3DC73 /* TabsPreviewsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabsPreviewsViewController.swift; sourceTree = ""; }; 605C54F621F8A9E900B3DC73 /* TabPreviewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabPreviewCell.swift; sourceTree = ""; }; 605C550621F8AA5900B3DC73 /* ReusableItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReusableItem.swift; sourceTree = ""; }; - 60724EA621FF17C90071BECF /* TabsCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabsCache.swift; sourceTree = ""; }; + 60724EA621FF17C90071BECF /* TabsRepositoryImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabsRepositoryImpl.swift; sourceTree = ""; }; 60724EA821FF1BD80071BECF /* CounterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CounterView.swift; sourceTree = ""; }; 60724ED92204332B0071BECF /* DefaultTabProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultTabProvider.swift; sourceTree = ""; }; 60724EE82209C9A20071BECF /* AnyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyViewController.swift; sourceTree = ""; }; @@ -1201,7 +1201,7 @@ 8A68E41124597E880070F338 /* DnsRR.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DnsRR.swift; sourceTree = ""; }; 8A6CD6D324585D830068ACB5 /* Combine.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Combine.framework; path = System/Library/Frameworks/Combine.framework; sourceTree = SDKROOT; }; 8A74188B245086330019695E /* Downloadable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Downloadable.swift; sourceTree = ""; }; - 8A745C0E2480287700A379AD /* TabsStoragable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabsStoragable.swift; sourceTree = ""; }; + 8A745C0E2480287700A379AD /* TabsRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabsRepository.swift; sourceTree = ""; }; 8A745C102480295B00A379AD /* TabsEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabsEnvironment.swift; sourceTree = ""; }; 8A745C1324802E4F00A379AD /* TabsObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabsObserver.swift; sourceTree = ""; }; 8A7A968C24462E8700E035F8 /* SearchEngine+OpenSearchParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SearchEngine+OpenSearchParser.swift"; sourceTree = ""; }; @@ -1636,7 +1636,7 @@ 607D02AE21FEF87F009FCD0B /* TabsDataService.swift */, 750B6DE62B5C348E00063E75 /* TabsServiceCommand.swift */, 750B6DE82B5C34D000063E75 /* TabsServiceDataOutput.swift */, - 8A745C0E2480287700A379AD /* TabsStoragable.swift */, + 8A745C0E2480287700A379AD /* TabsRepository.swift */, 8AA60B2125A33568002EF8AF /* TabCacheErrors.swift */, 752A8C072A7058B100804319 /* TabsListError.swift */, 608DA708222E6F5E009F75BD /* TabSelectionStrategy.swift */, @@ -1863,7 +1863,7 @@ 841A2D7A203ACB970050A996 /* WebTabs */ = { isa = PBXGroup; children = ( - 60724EA621FF17C90071BECF /* TabsCache.swift */, + 60724EA621FF17C90071BECF /* TabsRepositoryImpl.swift */, 847D7C881EF6C30F00291B87 /* TabsViewController.swift */, 84704FC11EEE769800F89F6B /* TabView.swift */, 8A9495BD2A6C0023001D275D /* TabViewModel.swift */, @@ -3472,7 +3472,7 @@ 8A9980642845409100E18762 /* TabsSubject.swift in Sources */, 752A8C082A7058B100804319 /* TabsListError.swift in Sources */, 8A99806028453FFD00E18762 /* TabAddStates.swift in Sources */, - 8A745C0F2480287700A379AD /* TabsStoragable.swift in Sources */, + 8A745C0F2480287700A379AD /* TabsRepository.swift in Sources */, 8AD18E102447580A00224702 /* ResourceReader.swift in Sources */, 8A9980622845403600E18762 /* TabContentState.swift in Sources */, 6094E741221FE706004269A2 /* Tab.swift in Sources */, @@ -3599,7 +3599,7 @@ 6010ECC621F74E2900972E28 /* Theme.swift in Sources */, 841022B9224781A20021516F /* LinkTagsViewController.swift in Sources */, 75196D102944AFD800B3A45B /* MainBrowserV2ViewController.swift in Sources */, - 8A745C0D248027C600A379AD /* TabsCache.swift in Sources */, + 8A745C0D248027C600A379AD /* TabsRepositoryImpl.swift in Sources */, 753392A7291FEBFD00570461 /* TabletViewControllerFactory.swift in Sources */, 750EDEBB2949215900212A4E /* MainBrowserViewModel.swift in Sources */, 8AFDBB0329F93980006C0E47 /* TabletSearchBarViewV2.swift in Sources */, @@ -3867,7 +3867,7 @@ 8A8035C024442F0F000E035F /* Theme.swift in Sources */, 8A8035C124442F0F000E035F /* LinkTagsViewController.swift in Sources */, 75196D112944AFD800B3A45B /* MainBrowserV2ViewController.swift in Sources */, - 8A745C0C248027C600A379AD /* TabsCache.swift in Sources */, + 8A745C0C248027C600A379AD /* TabsRepositoryImpl.swift in Sources */, 753392A8291FEBFD00570461 /* TabletViewControllerFactory.swift in Sources */, 750EDEBC2949215900212A4E /* MainBrowserViewModel.swift in Sources */, 8AFDBB0429F93980006C0E47 /* TabletSearchBarViewV2.swift in Sources */, diff --git a/catowseriOS/catowser/Environment/TabsEnvironment.swift b/catowseriOS/catowser/Environment/TabsEnvironment.swift index 60684a4e..ccdacba2 100644 --- a/catowseriOS/catowser/Environment/TabsEnvironment.swift +++ b/catowseriOS/catowser/Environment/TabsEnvironment.swift @@ -23,7 +23,7 @@ private final class TabsEnvironment { static private var internalInstance: ManagerHolder? fileprivate actor ManagerHolder { - let cachedTabsManager: TabsDataService + let tabsDataService: TabsDataService private let database: Database init() async { @@ -42,9 +42,9 @@ private final class TabsEnvironment { } return dbInterface.newPrivateContext() } - let cacheProvider = TabsCacheProvider(database.viewContext, contextClosure) + let cacheProvider = TabsRepositoryImpl(database.viewContext, contextClosure) let strategy = NearbySelectionStrategy() - cachedTabsManager = await .init(cacheProvider, DefaultTabProvider.shared, strategy) + tabsDataService = await .init(cacheProvider, DefaultTabProvider.shared, strategy) } } } @@ -52,7 +52,7 @@ private final class TabsEnvironment { extension TabsDataService { static var shared: TabsDataService { get async { - await TabsEnvironment.shared().cachedTabsManager + await TabsEnvironment.shared().tabsDataService } } } diff --git a/catowseriOS/catowser/WebTabs/TabsCache.swift b/catowseriOS/catowser/WebTabs/TabsRepositoryImpl.swift similarity index 82% rename from catowseriOS/catowser/WebTabs/TabsCache.swift rename to catowseriOS/catowser/WebTabs/TabsRepositoryImpl.swift index d6c4fefd..7cd6369d 100644 --- a/catowseriOS/catowser/WebTabs/TabsCache.swift +++ b/catowseriOS/catowser/WebTabs/TabsRepositoryImpl.swift @@ -11,13 +11,12 @@ import CoreBrowser import CoreData /** - Declaring Tab storage type in host app instead of `CoreBrowser` - to allow use app settings like default tab content which only can be stored in host app, - because it can't be passed as an argument to Tabs manager since it is a singleton. - Anyway, now it's not a singletone, since we're passing tabs store instance to it, but - with environment class which holds reference to tabs list manager it's kind of singletone. + Declaring Tab storage type in host app instead of `CoreBrowser` framework, + to allow use app settings like default tab content which only can be stored in host app. + + Later need to add tabs rest/firebase client dependency to use it as a 2nd (remote) data source. */ -final class TabsCacheProvider { +final class TabsRepositoryImpl { private let tabsDbResource: TabsResource init(_ temporaryContext: NSManagedObjectContext, @@ -27,7 +26,7 @@ final class TabsCacheProvider { } } -extension TabsCacheProvider: TabsStoragable { +extension TabsRepositoryImpl: TabsRepository { func select(tab: Tab) async throws -> UUID { do { try await tabsDbResource.selectTab(tab) From 173be978e46a3c94d4f1fa5f3dfbc479cb48000f Mon Sep 17 00:00:00 2001 From: Andrei Ermoshin Date: Mon, 8 Apr 2024 14:54:46 +0300 Subject: [PATCH 02/32] Enable strict concurency checks and fix compilation --- .../catowser.xcodeproj/project.pbxproj | 18 +++++++++++++++ .../WebView/WebViewController.swift | 7 ++---- .../Coordinators/AppCoordinator.swift | 1 + .../catowser/Environment/UseCaseFactory.swift | 19 ++++++---------- .../Environment/ViewModelFactory.swift | 22 +++++++++---------- .../catowser/Utils/AlertPresenter.swift | 1 + 6 files changed, 40 insertions(+), 28 deletions(-) diff --git a/catowseriOS/catowser.xcodeproj/project.pbxproj b/catowseriOS/catowser.xcodeproj/project.pbxproj index e2fa0009..286926ed 100644 --- a/catowseriOS/catowser.xcodeproj/project.pbxproj +++ b/catowseriOS/catowser.xcodeproj/project.pbxproj @@ -4333,6 +4333,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; @@ -4372,6 +4373,7 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; @@ -4410,6 +4412,7 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; @@ -4447,6 +4450,7 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; @@ -4616,6 +4620,7 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -4647,6 +4652,7 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -4684,6 +4690,7 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; @@ -4721,6 +4728,7 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; @@ -4842,6 +4850,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; @@ -4881,6 +4890,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; @@ -5200,6 +5210,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; @@ -5240,6 +5251,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; @@ -5281,6 +5293,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; @@ -5321,6 +5334,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; @@ -5413,6 +5427,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; @@ -5450,6 +5465,7 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; @@ -5490,6 +5506,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; @@ -5529,6 +5546,7 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; diff --git a/catowseriOS/catowser/BrowserContent/WebView/WebViewController.swift b/catowseriOS/catowser/BrowserContent/WebView/WebViewController.swift index d5dede99..882d9ca4 100644 --- a/catowseriOS/catowser/BrowserContent/WebView/WebViewController.swift +++ b/catowseriOS/catowser/BrowserContent/WebView/WebViewController.swift @@ -82,11 +82,6 @@ final class WebViewController: BaseViewController, fatalError("init(coder:) has not been implemented") } - deinit { - authHandlers.removeAll() - unsubscribe() - } - override func loadView() { view = UIView(frame: .zero) } @@ -121,6 +116,8 @@ final class WebViewController: BaseViewController, // maybe top sites, so, we have to reset navigation controls // and it can be done by sending `nil` interface viewModel.siteNavigation?.webViewDidReplace(nil) + authHandlers.removeAll() + unsubscribe() } override func touchesBegan(_ touches: Set, with event: UIEvent?) { diff --git a/catowseriOS/catowser/Coordinators/AppCoordinator.swift b/catowseriOS/catowser/Coordinators/AppCoordinator.swift index 77036a3b..3dccd6d9 100644 --- a/catowseriOS/catowser/Coordinators/AppCoordinator.swift +++ b/catowseriOS/catowser/Coordinators/AppCoordinator.swift @@ -101,6 +101,7 @@ final class AppCoordinator: Coordinator, BrowserContentCoordinators { } Task { + await UseCaseFactory.shared.registerUseCases() let defaultTabContent = await DefaultTabProvider.shared.contentState let pluginsSource = JSPluginsBuilder() .setBase(self) diff --git a/catowseriOS/catowser/Environment/UseCaseFactory.swift b/catowseriOS/catowser/Environment/UseCaseFactory.swift index 7b61bad5..3283cb88 100644 --- a/catowseriOS/catowser/Environment/UseCaseFactory.swift +++ b/catowseriOS/catowser/Environment/UseCaseFactory.swift @@ -16,23 +16,18 @@ extension String { static let googleResolveDnsUseCase = "googleResolveDnsUseCase" } +@globalActor final class UseCaseFactory { - static func shared() async -> UseCasesHolder { - if let instance = internalInstance { - return instance - } - let created = await UseCasesHolder() - internalInstance = created - return created - } - - static private var internalInstance: UseCasesHolder? - + static let shared = UseCasesHolder() + actor UseCasesHolder { private let locator: UseCaseLocator - init() async { + init() { locator = .init() + } + + func registerUseCases() async { await registerTabsUseCases() registerSearchAutocompleteUseCases() registerDnsResolveUseCases() diff --git a/catowseriOS/catowser/Environment/ViewModelFactory.swift b/catowseriOS/catowser/Environment/ViewModelFactory.swift index 95783b1c..cdb61d78 100644 --- a/catowseriOS/catowser/Environment/ViewModelFactory.swift +++ b/catowseriOS/catowser/Environment/ViewModelFactory.swift @@ -28,11 +28,11 @@ final class ViewModelFactory { switch searchProviderType { case .google: let type = (any AutocompleteSearchUseCase).self - let autocompleteUseCase = await UseCaseFactory.shared().findUseCase(type, .googleAutocompleteUseCase) + let autocompleteUseCase = await UseCaseFactory.shared.findUseCase(type, .googleAutocompleteUseCase) return SearchSuggestionsViewModelImpl(autocompleteUseCase, vmContext) case .duckduckgo: let type = (any AutocompleteSearchUseCase).self - let autocompleteUseCase = await UseCaseFactory.shared().findUseCase(type, .duckDuckGoAutocompleteUseCase) + let autocompleteUseCase = await UseCaseFactory.shared.findUseCase(type, .duckDuckGoAutocompleteUseCase) return SearchSuggestionsViewModelImpl(autocompleteUseCase, vmContext) } } @@ -41,32 +41,32 @@ final class ViewModelFactory { _ context: WebViewContext, _ siteNavigation: SiteExternalNavigationDelegate?) async -> any WebViewModel { let type = (any ResolveDNSUseCase).self - let googleDnsUseCase = await UseCaseFactory.shared().findUseCase(type, .googleResolveDnsUseCase) - let selectTabUseCase = await UseCaseFactory.shared().findUseCase(SelectedTabUseCase.self) - let writeUseCase = await UseCaseFactory.shared().findUseCase(WriteTabsUseCase.self) + let googleDnsUseCase = await UseCaseFactory.shared.findUseCase(type, .googleResolveDnsUseCase) + let selectTabUseCase = await UseCaseFactory.shared.findUseCase(SelectedTabUseCase.self) + let writeUseCase = await UseCaseFactory.shared.findUseCase(WriteTabsUseCase.self) return WebViewModelImpl(googleDnsUseCase, context, selectTabUseCase, writeUseCase, siteNavigation, site) } func tabViewModel(_ tab: Tab) async -> TabViewModel { - let readUseCase = await UseCaseFactory.shared().findUseCase(ReadTabsUseCase.self) - let writeUseCase = await UseCaseFactory.shared().findUseCase(WriteTabsUseCase.self) + let readUseCase = await UseCaseFactory.shared.findUseCase(ReadTabsUseCase.self) + let writeUseCase = await UseCaseFactory.shared.findUseCase(WriteTabsUseCase.self) return TabViewModel(tab, readUseCase, writeUseCase) } func tabsPreviewsViewModel() async -> TabsPreviewsViewModel { - let readUseCase = await UseCaseFactory.shared().findUseCase(ReadTabsUseCase.self) - let writeUseCase = await UseCaseFactory.shared().findUseCase(WriteTabsUseCase.self) + let readUseCase = await UseCaseFactory.shared.findUseCase(ReadTabsUseCase.self) + let writeUseCase = await UseCaseFactory.shared.findUseCase(WriteTabsUseCase.self) return TabsPreviewsViewModel(readUseCase, writeUseCase) } func allTabsViewModel() async -> AllTabsViewModel { - let writeUseCase = await UseCaseFactory.shared().findUseCase(WriteTabsUseCase.self) + let writeUseCase = await UseCaseFactory.shared.findUseCase(WriteTabsUseCase.self) return AllTabsViewModel(writeUseCase) } func topSitesViewModel() async -> TopSitesViewModel { let isJsEnabled = await FeatureManager.shared.boolValue(of: .javaScriptEnabled) - let writeUseCase = await UseCaseFactory.shared().findUseCase(WriteTabsUseCase.self) + let writeUseCase = await UseCaseFactory.shared.findUseCase(WriteTabsUseCase.self) let sites = DefaultTabProvider.shared.topSites(isJsEnabled) return TopSitesViewModel(sites, writeUseCase) } diff --git a/catowseriOS/catowser/Utils/AlertPresenter.swift b/catowseriOS/catowser/Utils/AlertPresenter.swift index 0608aae4..024aa88b 100644 --- a/catowseriOS/catowser/Utils/AlertPresenter.swift +++ b/catowseriOS/catowser/Utils/AlertPresenter.swift @@ -8,6 +8,7 @@ import UIKit +@MainActor final class AlertPresenter { static func present(on presenter: UIViewController, message: String? = nil, From ba920830054942b425cd68a8cb29ccc399491430 Mon Sep 17 00:00:00 2001 From: Andrei Ermoshin Date: Mon, 8 Apr 2024 14:59:15 +0300 Subject: [PATCH 03/32] Fix the sequence of app start to register use cases before using them for the Phone tab previews coordinator --- .../Coordinators/AppCoordinator.swift | 66 ++++++++++--------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/catowseriOS/catowser/Coordinators/AppCoordinator.swift b/catowseriOS/catowser/Coordinators/AppCoordinator.swift index 3dccd6d9..9d82e641 100644 --- a/catowseriOS/catowser/Coordinators/AppCoordinator.swift +++ b/catowseriOS/catowser/Coordinators/AppCoordinator.swift @@ -89,6 +89,10 @@ final class AppCoordinator: Coordinator, BrowserContentCoordinators { } func start() { + Task { + await prepareBeforeStart() + } + if uiFramework.swiftUIBased { // Must do coordinators init earlier // to allow to use some of them in SwiftUI views @@ -99,37 +103,37 @@ final class AppCoordinator: Coordinator, BrowserContentCoordinators { toolbarCoordinator?.showNext(.tabs) } } - - Task { - await UseCaseFactory.shared.registerUseCases() - let defaultTabContent = await DefaultTabProvider.shared.contentState - let pluginsSource = JSPluginsBuilder() - .setBase(self) - .setInstagram(self) - jsPluginsBuilder = pluginsSource - let allTabsVM = await ViewModelFactory.shared.allTabsViewModel() - self.allTabsVM = allTabsVM - let topSitesVM = await ViewModelFactory.shared.topSitesViewModel() - let searchProvider = await FeatureManager.shared.webSearchAutoCompleteValue() - let suggestionsVM = await ViewModelFactory.shared.searchSuggestionsViewModel(searchProvider) - let webContext = WebViewContextImpl(pluginsSource) - let webViewModel = await ViewModelFactory.shared.getWebViewModel(nil, webContext, nil) - let vc = vcFactory.rootViewController(self, - uiFramework, - defaultTabContent, - allTabsVM, - topSitesVM, - suggestionsVM, - webViewModel) - startedVC = vc - - window.rootViewController = startedVC?.viewController - window.makeKeyAndVisible() - // Now, with introducing the actors model - // we need to attach observer only after adding all child coordinators - if case .uiKit = uiFramework { - await TabsDataService.shared.attach(self, notify: true) - } + } + + private func prepareBeforeStart() async { + await UseCaseFactory.shared.registerUseCases() + let defaultTabContent = await DefaultTabProvider.shared.contentState + let pluginsSource = JSPluginsBuilder() + .setBase(self) + .setInstagram(self) + jsPluginsBuilder = pluginsSource + let allTabsVM = await ViewModelFactory.shared.allTabsViewModel() + self.allTabsVM = allTabsVM + let topSitesVM = await ViewModelFactory.shared.topSitesViewModel() + let searchProvider = await FeatureManager.shared.webSearchAutoCompleteValue() + let suggestionsVM = await ViewModelFactory.shared.searchSuggestionsViewModel(searchProvider) + let webContext = WebViewContextImpl(pluginsSource) + let webViewModel = await ViewModelFactory.shared.getWebViewModel(nil, webContext, nil) + let vc = vcFactory.rootViewController(self, + uiFramework, + defaultTabContent, + allTabsVM, + topSitesVM, + suggestionsVM, + webViewModel) + startedVC = vc + + window.rootViewController = startedVC?.viewController + window.makeKeyAndVisible() + // Now, with introducing the actors model + // we need to attach observer only after adding all child coordinators + if case .uiKit = uiFramework { + await TabsDataService.shared.attach(self, notify: true) } } From 67230b640a78a2f32294d3635bd6f948e39360d0 Mon Sep 17 00:00:00 2001 From: Andrei Ermoshin Date: Tue, 9 Apr 2024 09:15:56 +0300 Subject: [PATCH 04/32] Make DefaultTabProvider as a global actor and fix compilation --- .../InMemoryDomainSearchProvider.swift | 10 +-- .../CoreBrowser/Tabs/TabAddStates.swift | 2 +- .../CoreBrowser/Tabs/TabContentState.swift | 2 +- .../CoreBrowser/Tabs/TabsDataService.swift | 9 ++- catowseriOS/CoreBrowser/Tabs/TabsStates.swift | 2 +- .../FeaturesFlagsKit/FeatureManager.swift | 4 +- .../Coordinators/SearchBarCoordinator.swift | 2 +- .../Coordinators/TopSitesCoordinator.swift | 2 +- .../catowser/Environment/UseCaseFactory.swift | 4 +- .../Environment/ViewModelFactory.swift | 2 +- .../catowser/Utils/DefaultTabProvider.swift | 79 ++++++++++--------- .../FeatureManager+SpecificEnums.swift | 4 +- .../WebTabs/TabsPreviewsViewController.swift | 6 +- 13 files changed, 67 insertions(+), 61 deletions(-) diff --git a/catowseriOS/CoreBrowser/History/InMemoryDomainSearchProvider.swift b/catowseriOS/CoreBrowser/History/InMemoryDomainSearchProvider.swift index f1c18e6a..b0fdda8a 100644 --- a/catowseriOS/CoreBrowser/History/InMemoryDomainSearchProvider.swift +++ b/catowseriOS/CoreBrowser/History/InMemoryDomainSearchProvider.swift @@ -13,14 +13,14 @@ private let filename = "topdomains" @globalActor public final class InMemoryDomainSearchProvider { - public static let shared = Provider() + public static let shared = StateHolder() - public actor Provider { + public actor StateHolder { fileprivate let storage: Trie init() { storage = Trie() - let bundle = Bundle(for: Provider.self) + let bundle = Bundle(for: StateHolder.self) guard let filePath = bundle.path(forResource: filename, ofType: "txt") else { assertionFailure("Failed to find \"\(filename)\" file in framework bundle") @@ -39,7 +39,7 @@ public final class InMemoryDomainSearchProvider { } } -extension InMemoryDomainSearchProvider.Provider: DomainsHistory { +extension InMemoryDomainSearchProvider.StateHolder: DomainsHistory { public func remember(host: CottonBase.Host) async { storage.insert(word: host.rawString) if let withoutWww = host.rawString.withoutPrefix("www.") { @@ -48,7 +48,7 @@ extension InMemoryDomainSearchProvider.Provider: DomainsHistory { } } -extension InMemoryDomainSearchProvider.Provider: KnownDomainsSource { +extension InMemoryDomainSearchProvider.StateHolder: KnownDomainsSource { public func domainNames(whereURLContains filter: String) async -> [String] { let words: [String] = storage.findWordsWithPrefix(prefix: filter) return words diff --git a/catowseriOS/CoreBrowser/Tabs/TabAddStates.swift b/catowseriOS/CoreBrowser/Tabs/TabAddStates.swift index e70c99cc..a7fd8197 100644 --- a/catowseriOS/CoreBrowser/Tabs/TabAddStates.swift +++ b/catowseriOS/CoreBrowser/Tabs/TabAddStates.swift @@ -10,7 +10,7 @@ import Foundation /// Describes how new tab is added to the list. /// Uses `Int` as raw value to be able to store it in settings. -public enum AddedTabPosition: Int, CaseIterable { +public enum AddedTabPosition: Int, CaseIterable, Sendable { case listEnd = 0 case afterSelected = 1 } diff --git a/catowseriOS/CoreBrowser/Tabs/TabContentState.swift b/catowseriOS/CoreBrowser/Tabs/TabContentState.swift index af5d723b..3ed02bf2 100644 --- a/catowseriOS/CoreBrowser/Tabs/TabContentState.swift +++ b/catowseriOS/CoreBrowser/Tabs/TabContentState.swift @@ -10,7 +10,7 @@ import Foundation /// Twin type for `Tab.ContentType` to have `rawValue` /// and use it for settings. -public enum TabContentDefaultState: Int, CaseIterable, CustomStringConvertible { +public enum TabContentDefaultState: Int, CaseIterable, CustomStringConvertible, Sendable { case blank case homepage case favorites diff --git a/catowseriOS/CoreBrowser/Tabs/TabsDataService.swift b/catowseriOS/CoreBrowser/Tabs/TabsDataService.swift index 788c5be5..d87a0243 100644 --- a/catowseriOS/CoreBrowser/Tabs/TabsDataService.swift +++ b/catowseriOS/CoreBrowser/Tabs/TabsDataService.swift @@ -207,7 +207,8 @@ private extension TabsDataService { } func handleUpdateSelectedTabPreviewCommand(_ image: Data?) async -> TabsServiceDataOutput { - guard selectedTabIdentifier != positioning.defaultSelectedTabId else { + let defaultValue = positioning.defaultSelectedTabId + guard selectedTabIdentifier != defaultValue else { return .tabPreviewUpdated(TabsListError.notInitializedYet) } guard let tabTuple = tabs.element(by: selectedTabIdentifier) else { @@ -263,7 +264,8 @@ extension TabsDataService: TabsSubject { } await observer.updateTabsCount(with: tabs.count) await observer.initializeObserver(with: tabs) - guard selectedTabIdentifier != positioning.defaultSelectedTabId else { + let defaultValue = positioning.defaultSelectedTabId + guard selectedTabIdentifier != defaultValue else { return } guard let tabTuple = tabs.element(by: selectedTabIdentifier) else { @@ -378,7 +380,8 @@ private extension TabsDataService { guard let self else { return false } - return identifier == self.positioning.defaultSelectedTabId + let defaultValue = self.positioning.defaultSelectedTabId + return identifier == defaultValue }) for await newSelectedTabId in filteredId { diff --git a/catowseriOS/CoreBrowser/Tabs/TabsStates.swift b/catowseriOS/CoreBrowser/Tabs/TabsStates.swift index 4f9f9751..2861d5ce 100644 --- a/catowseriOS/CoreBrowser/Tabs/TabsStates.swift +++ b/catowseriOS/CoreBrowser/Tabs/TabsStates.swift @@ -9,7 +9,7 @@ import Foundation import AutoMockable -public protocol TabsStates: AutoMockable { +public protocol TabsStates: AutoMockable, Sendable { var addPosition: AddedTabPosition { get async } var contentState: Tab.ContentType { get async } var addSpeed: TabAddSpeed { get } diff --git a/catowseriOS/FeaturesFlagsKit/FeatureManager.swift b/catowseriOS/FeaturesFlagsKit/FeatureManager.swift index 51bb6150..d4b63012 100644 --- a/catowseriOS/FeaturesFlagsKit/FeatureManager.swift +++ b/catowseriOS/FeaturesFlagsKit/FeatureManager.swift @@ -13,9 +13,9 @@ import Combine @globalActor public final class FeatureManager { - public static let shared = FManager() + public static let shared = StateHolder() - public actor FManager { + public actor StateHolder { /// Flag value data sources. private let sources: [FeatureSource] = [LocalFeatureSource() /*, RemoteFeatureSource()*/] /// Temporarily create a separate array of sources for enum features diff --git a/catowseriOS/catowser/Coordinators/SearchBarCoordinator.swift b/catowseriOS/catowser/Coordinators/SearchBarCoordinator.swift index f2e485ba..12b93211 100644 --- a/catowseriOS/catowser/Coordinators/SearchBarCoordinator.swift +++ b/catowseriOS/catowser/Coordinators/SearchBarCoordinator.swift @@ -311,7 +311,7 @@ extension SearchBarCoordinator: SearchSuggestionsListDelegate { } } -extension FeatureManager.FManager { +extension FeatureManager.StateHolder { func searchPluginName() -> KnownSearchPluginName { switch webSearchAutoCompleteValue() { case .google: diff --git a/catowseriOS/catowser/Coordinators/TopSitesCoordinator.swift b/catowseriOS/catowser/Coordinators/TopSitesCoordinator.swift index 6dc8cc1c..dd00b78f 100644 --- a/catowseriOS/catowser/Coordinators/TopSitesCoordinator.swift +++ b/catowseriOS/catowser/Coordinators/TopSitesCoordinator.swift @@ -44,7 +44,7 @@ final class TopSitesCoordinator: Coordinator { /// Async start should be fine here, because layout is in the same closure Task { let isJsEnabled = await FeatureManager.shared.boolValue(of: .javaScriptEnabled) - vc.reload(with: DefaultTabProvider.shared.topSites(isJsEnabled)) + vc.reload(with: await DefaultTabProvider.shared.topSites(isJsEnabled)) presenterVC?.viewController.add(asChildViewController: vc.viewController, to: contentContainerView) let topSitesView: UIView = vc.controllerView diff --git a/catowseriOS/catowser/Environment/UseCaseFactory.swift b/catowseriOS/catowser/Environment/UseCaseFactory.swift index 3283cb88..53248511 100644 --- a/catowseriOS/catowser/Environment/UseCaseFactory.swift +++ b/catowseriOS/catowser/Environment/UseCaseFactory.swift @@ -18,9 +18,9 @@ extension String { @globalActor final class UseCaseFactory { - static let shared = UseCasesHolder() + static let shared = StateHolder() - actor UseCasesHolder { + actor StateHolder { private let locator: UseCaseLocator init() { diff --git a/catowseriOS/catowser/Environment/ViewModelFactory.swift b/catowseriOS/catowser/Environment/ViewModelFactory.swift index cdb61d78..78a31120 100644 --- a/catowseriOS/catowser/Environment/ViewModelFactory.swift +++ b/catowseriOS/catowser/Environment/ViewModelFactory.swift @@ -67,7 +67,7 @@ final class ViewModelFactory { func topSitesViewModel() async -> TopSitesViewModel { let isJsEnabled = await FeatureManager.shared.boolValue(of: .javaScriptEnabled) let writeUseCase = await UseCaseFactory.shared.findUseCase(WriteTabsUseCase.self) - let sites = DefaultTabProvider.shared.topSites(isJsEnabled) + let sites = await DefaultTabProvider.shared.topSites(isJsEnabled) return TopSitesViewModel(sites, writeUseCase) } } diff --git a/catowseriOS/catowser/Utils/DefaultTabProvider.swift b/catowseriOS/catowser/Utils/DefaultTabProvider.swift index ef67635f..c7f69c5c 100644 --- a/catowseriOS/catowser/Utils/DefaultTabProvider.swift +++ b/catowseriOS/catowser/Utils/DefaultTabProvider.swift @@ -12,53 +12,54 @@ import FeaturesFlagsKit import CottonBase /// Provides default tab related constants, no need to be global actor, because read-only +@globalActor final class DefaultTabProvider { - static let shared = DefaultTabProvider() - - let selected: Bool - - let blockPopups: Bool = false + static let shared = StateHolder() + + actor StateHolder: TabsStates { + var selected: Bool { + get async { + await UIDevice.current.userInterfaceIdiom == .pad + } + } - func topSites(_ isJsEnabled: Bool) -> [Site] { - let array: [Site?] - let settings: Site.Settings = .init(isPrivate: false, - blockPopups: blockPopups, - isJSEnabled: isJsEnabled, - canLoadPlugins: true) - let opennet = Site("https://opennet.ru", "OpenNet", settings) - let yahooFinance = Site("https://finance.yahoo.com", "Yahoo Finance", settings) - let github = Site("https://github.com", "GitHub", settings) - #if DEBUG - let mailToLink = Site("https://www.k8oms.net/links/mailto-link", "k8oms", settings) - let mapsV1 = Site("https://www.apple.com/maps/", "apple maps", settings) - array = [opennet, yahooFinance, github, mailToLink, mapsV1] - #else - array = [opennet, yahooFinance, github] - #endif - return array.compactMap {$0} - } + let blockPopups: Bool = false - private init() { - selected = UIDevice.current.userInterfaceIdiom == .pad - } -} - -extension DefaultTabProvider: TabsStates { - var addPosition: AddedTabPosition { - get async { - await FeatureManager.shared.tabAddPositionValue() + func topSites(_ isJsEnabled: Bool) -> [Site] { + let array: [Site?] + let settings: Site.Settings = .init(isPrivate: false, + blockPopups: blockPopups, + isJSEnabled: isJsEnabled, + canLoadPlugins: true) + let opennet = Site("https://opennet.ru", "OpenNet", settings) + let yahooFinance = Site("https://finance.yahoo.com", "Yahoo Finance", settings) + let github = Site("https://github.com", "GitHub", settings) + #if DEBUG + let mailToLink = Site("https://www.k8oms.net/links/mailto-link", "k8oms", settings) + let mapsV1 = Site("https://www.apple.com/maps/", "apple maps", settings) + array = [opennet, yahooFinance, github, mailToLink, mapsV1] + #else + array = [opennet, yahooFinance, github] + #endif + return array.compactMap {$0} + } + + var addPosition: AddedTabPosition { + get async { + await FeatureManager.shared.tabAddPositionValue() + } } - } - var contentState: Tab.ContentType { - get async { - await FeatureManager.shared.tabDefaultContentValue().contentType + var contentState: Tab.ContentType { + get async { + await FeatureManager.shared.tabDefaultContentValue().contentType + } } - } - var addSpeed: TabAddSpeed { .after(.milliseconds(300)) } + nonisolated var addSpeed: TabAddSpeed { .after(.milliseconds(300)) } - var defaultSelectedTabId: UUID { .notPossibleId } + nonisolated var defaultSelectedTabId: UUID { .notPossibleId } + } } private extension UUID { diff --git a/catowseriOS/catowser/Utils/Features/FeatureManager+SpecificEnums.swift b/catowseriOS/catowser/Utils/Features/FeatureManager+SpecificEnums.swift index f0bd7fb5..7e0b4b7f 100644 --- a/catowseriOS/catowser/Utils/Features/FeatureManager+SpecificEnums.swift +++ b/catowseriOS/catowser/Utils/Features/FeatureManager+SpecificEnums.swift @@ -11,7 +11,7 @@ import FeaturesFlagsKit // MARK: - generic GETTER method -extension FeatureManager.FManager { +extension FeatureManager.StateHolder { func enumValue(_ enumCase: F) -> F? where F.RawValue == Int { let keyStr: String @@ -36,7 +36,7 @@ extension FeatureManager.FManager { // MARK: - GETTER methods specific to Enum features -extension FeatureManager.FManager { +extension FeatureManager.StateHolder { func tabAddPositionValue() -> AddedTabPosition { let feature: ApplicationEnumFeature = .tabAddPosition guard let source = source(for: feature) else { diff --git a/catowseriOS/catowser/WebTabs/TabsPreviewsViewController.swift b/catowseriOS/catowser/WebTabs/TabsPreviewsViewController.swift index 227bc101..b6b49b0a 100644 --- a/catowseriOS/catowser/WebTabs/TabsPreviewsViewController.swift +++ b/catowseriOS/catowser/WebTabs/TabsPreviewsViewController.swift @@ -211,8 +211,10 @@ where C.R == TabsScreenRoute { coordinator?.showNext(.addTab) // on previews screen will make new added tab always selected // same behaviour has Safari and Firefox - if DefaultTabProvider.shared.selected { - coordinator?.stop() + Task { + if await DefaultTabProvider.shared.selected { + coordinator?.stop() + } } } } From 40ff848dfdbaef187c1f9140a97872a694b68e17 Mon Sep 17 00:00:00 2001 From: Andrei Ermoshin Date: Wed, 10 Apr 2024 09:35:02 +0300 Subject: [PATCH 05/32] Update alamofire, fix several warnings --- .../CoreBrowser/Filesystem/ResourceReader.swift | 2 +- .../History/InMemoryDomainSearchProvider.swift | 2 ++ catowseriOS/CoreBrowser/Tabs/TabsServiceCommand.swift | 2 +- .../CoreBrowser/Tabs/TabsServiceDataOutput.swift | 2 +- catowseriOS/CottonRestKit/RestClientError.swift | 2 ++ catowseriOS/catowser.xcodeproj/project.pbxproj | 4 ++-- .../xcshareddata/swiftpm/Package.resolved | 11 ++++++----- .../catowser/Coordinators/AppCoordinator.swift | 3 ++- .../Coordinators/MainToolbarCoordinator.swift | 1 + .../catowser/Coordinators/TopSitesCoordinator.swift | 1 + .../catowser/Coordinators/WebContentCoordinator.swift | 1 + catowseriOS/catowser/Menu/GlobalMenuDelegate.swift | 1 + .../catowser/WebTabs/TabsPreviewsViewController.swift | 9 +++------ 13 files changed, 24 insertions(+), 17 deletions(-) diff --git a/catowseriOS/CoreBrowser/Filesystem/ResourceReader.swift b/catowseriOS/CoreBrowser/Filesystem/ResourceReader.swift index fa875b70..8bf768ac 100644 --- a/catowseriOS/CoreBrowser/Filesystem/ResourceReader.swift +++ b/catowseriOS/CoreBrowser/Filesystem/ResourceReader.swift @@ -8,7 +8,7 @@ import Foundation -public enum KnownSearchPluginName: String { +public enum KnownSearchPluginName: String, Sendable { case google case duckduckgo } diff --git a/catowseriOS/CoreBrowser/History/InMemoryDomainSearchProvider.swift b/catowseriOS/CoreBrowser/History/InMemoryDomainSearchProvider.swift index b0fdda8a..61a4496d 100644 --- a/catowseriOS/CoreBrowser/History/InMemoryDomainSearchProvider.swift +++ b/catowseriOS/CoreBrowser/History/InMemoryDomainSearchProvider.swift @@ -39,6 +39,8 @@ public final class InMemoryDomainSearchProvider { } } +extension CottonBase.Host: @unchecked Sendable {} + extension InMemoryDomainSearchProvider.StateHolder: DomainsHistory { public func remember(host: CottonBase.Host) async { storage.insert(word: host.rawString) diff --git a/catowseriOS/CoreBrowser/Tabs/TabsServiceCommand.swift b/catowseriOS/CoreBrowser/Tabs/TabsServiceCommand.swift index f3b00341..a9a671ad 100644 --- a/catowseriOS/CoreBrowser/Tabs/TabsServiceCommand.swift +++ b/catowseriOS/CoreBrowser/Tabs/TabsServiceCommand.swift @@ -12,7 +12,7 @@ import Foundation Tabs data service commands for the Command design pattern. Each command case can carry the input data. */ -public enum TabsServiceCommand { +public enum TabsServiceCommand: Sendable { case getTabsCount case getSelectedTabId case getAllTabs diff --git a/catowseriOS/CoreBrowser/Tabs/TabsServiceDataOutput.swift b/catowseriOS/CoreBrowser/Tabs/TabsServiceDataOutput.swift index 81bbf140..68fb7752 100644 --- a/catowseriOS/CoreBrowser/Tabs/TabsServiceDataOutput.swift +++ b/catowseriOS/CoreBrowser/Tabs/TabsServiceDataOutput.swift @@ -11,7 +11,7 @@ import Foundation /** Tabs service data output/response type. */ -public enum TabsServiceDataOutput { +public enum TabsServiceDataOutput: Sendable { case tabsCount(Int) case selectedTabId(UUID) case allTabs([Tab]) diff --git a/catowseriOS/CottonRestKit/RestClientError.swift b/catowseriOS/CottonRestKit/RestClientError.swift index 27520e9d..7e14c281 100644 --- a/catowseriOS/CottonRestKit/RestClientError.swift +++ b/catowseriOS/CottonRestKit/RestClientError.swift @@ -9,6 +9,8 @@ import Foundation import CottonBase +extension DomainName.Error: @unchecked Sendable {} + public enum HttpError: LocalizedError, Equatable { /* Comon errors related to http client */ diff --git a/catowseriOS/catowser.xcodeproj/project.pbxproj b/catowseriOS/catowser.xcodeproj/project.pbxproj index 286926ed..5f3a8800 100644 --- a/catowseriOS/catowser.xcodeproj/project.pbxproj +++ b/catowseriOS/catowser.xcodeproj/project.pbxproj @@ -5768,7 +5768,7 @@ repositoryURL = "https://github.com/Alamofire/Alamofire.git"; requirement = { kind = exactVersion; - version = 5.6.2; + version = 5.9.1; }; }; 8AC4365F247911F6008DD663 /* XCRemoteSwiftPackageReference "AlamofireImage" */ = { @@ -5776,7 +5776,7 @@ repositoryURL = "https://github.com/Alamofire/AlamofireImage.git"; requirement = { kind = exactVersion; - version = 4.1.0; + version = 4.3.0; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/catowseriOS/catowser.xcworkspace/xcshareddata/swiftpm/Package.resolved b/catowseriOS/catowser.xcworkspace/xcshareddata/swiftpm/Package.resolved index 7b80bf81..65a0b8c6 100644 --- a/catowseriOS/catowser.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/catowseriOS/catowser.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,4 +1,5 @@ { + "originHash" : "29574935b175a0ed7b4cd37b499db86e4bbe4391b57214b44fdfa51e5b2ad0b7", "pins" : [ { "identity" : "aexml", @@ -14,8 +15,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/Alamofire/Alamofire.git", "state" : { - "revision" : "8dd85aee02e39dd280c75eef88ffdb86eed4b07b", - "version" : "5.6.2" + "revision" : "f455c2975872ccd2d9c81594c658af65716e9b9a", + "version" : "5.9.1" } }, { @@ -23,8 +24,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/Alamofire/AlamofireImage.git", "state" : { - "revision" : "3e8edbeb75227f8542aa87f90240cf0424d6362f", - "version" : "4.1.0" + "revision" : "1eaf3b6c6882bed10f6e7b119665599dd2329aa1", + "version" : "4.3.0" } }, { @@ -136,5 +137,5 @@ } } ], - "version" : 2 + "version" : 3 } diff --git a/catowseriOS/catowser/Coordinators/AppCoordinator.swift b/catowseriOS/catowser/Coordinators/AppCoordinator.swift index 9d82e641..6bbd5d22 100644 --- a/catowseriOS/catowser/Coordinators/AppCoordinator.swift +++ b/catowseriOS/catowser/Coordinators/AppCoordinator.swift @@ -14,7 +14,8 @@ import CottonPlugins import CottonData /// Browser content related coordinators -protocol BrowserContentCoordinators: AnyObject { +@MainActor +protocol BrowserContentCoordinators: AnyObject, Sendable { var topSitesCoordinator: TopSitesCoordinator? { get } var webContentCoordinator: WebContentCoordinator? { get } var globalMenuDelegate: GlobalMenuDelegate? { get } diff --git a/catowseriOS/catowser/Coordinators/MainToolbarCoordinator.swift b/catowseriOS/catowser/Coordinators/MainToolbarCoordinator.swift index 53eb55f0..e11a85ef 100644 --- a/catowseriOS/catowser/Coordinators/MainToolbarCoordinator.swift +++ b/catowseriOS/catowser/Coordinators/MainToolbarCoordinator.swift @@ -10,6 +10,7 @@ import UIKit import CoreBrowser import FeaturesFlagsKit +@MainActor final class MainToolbarCoordinator: Coordinator { let vcFactory: ViewControllerFactory var startedCoordinator: Coordinator? diff --git a/catowseriOS/catowser/Coordinators/TopSitesCoordinator.swift b/catowseriOS/catowser/Coordinators/TopSitesCoordinator.swift index dd00b78f..12c6a620 100644 --- a/catowseriOS/catowser/Coordinators/TopSitesCoordinator.swift +++ b/catowseriOS/catowser/Coordinators/TopSitesCoordinator.swift @@ -11,6 +11,7 @@ import CottonBase import CoreBrowser import FeaturesFlagsKit +@MainActor final class TopSitesCoordinator: Coordinator { let vcFactory: ViewControllerFactory var startedCoordinator: Coordinator? diff --git a/catowseriOS/catowser/Coordinators/WebContentCoordinator.swift b/catowseriOS/catowser/Coordinators/WebContentCoordinator.swift index c4861005..6530a59b 100644 --- a/catowseriOS/catowser/Coordinators/WebContentCoordinator.swift +++ b/catowseriOS/catowser/Coordinators/WebContentCoordinator.swift @@ -18,6 +18,7 @@ protocol WebContentDelegate: AnyObject { func showLoadingProgress(_ show: Bool) } +@MainActor final class WebContentCoordinator: Coordinator { let vcFactory: ViewControllerFactory var startedCoordinator: Coordinator? diff --git a/catowseriOS/catowser/Menu/GlobalMenuDelegate.swift b/catowseriOS/catowser/Menu/GlobalMenuDelegate.swift index 7794eb97..cfc0c0e6 100644 --- a/catowseriOS/catowser/Menu/GlobalMenuDelegate.swift +++ b/catowseriOS/catowser/Menu/GlobalMenuDelegate.swift @@ -8,6 +8,7 @@ import UIKit +@MainActor protocol GlobalMenuDelegate: AnyObject { func settingsDidPress(from sourceView: UIView, and sourceRect: CGRect) } diff --git a/catowseriOS/catowser/WebTabs/TabsPreviewsViewController.swift b/catowseriOS/catowser/WebTabs/TabsPreviewsViewController.swift index b6b49b0a..6b80742f 100644 --- a/catowseriOS/catowser/WebTabs/TabsPreviewsViewController.swift +++ b/catowseriOS/catowser/WebTabs/TabsPreviewsViewController.swift @@ -119,9 +119,6 @@ where C.R == TabsScreenRoute { Task { await TabsDataService.shared.detach(self) } - } - - deinit { stateHandlerCancellable?.cancel() } @@ -208,10 +205,10 @@ where C.R == TabsScreenRoute { // MARK: - private functions @objc func addTabPressed() { - coordinator?.showNext(.addTab) - // on previews screen will make new added tab always selected - // same behaviour has Safari and Firefox Task { + coordinator?.showNext(.addTab) + // on previews screen will make new added tab always selected + // same behaviour has Safari and Firefox if await DefaultTabProvider.shared.selected { coordinator?.stop() } From 8e1e802e529901fbc5a5057cf731bc0d17efa5a9 Mon Sep 17 00:00:00 2001 From: Andrei Ermoshin Date: Wed, 10 Apr 2024 10:06:56 +0300 Subject: [PATCH 06/32] Saved not finished warning fixes --- .../CoreBrowser/History/DomainsHistory.swift | 12 ++++--- .../Tabs/TabSelectionStrategy.swift | 4 ++- .../CoreBrowser/Tabs/TabsObserver.swift | 4 ++- .../CoreBrowser/Tabs/TabsRepository.swift | 5 ++- .../UseCases/ReadTabsUseCase.swift | 4 ++- .../UseCases/SelectedTabUseCase.swift | 4 ++- .../UseCases/WriteTabsUseCase.swift | 4 ++- .../CottonData/RestClientContext.swift | 4 ++- .../AutocompleteSearchUseCase.swift | 4 ++- .../SearchAutocompleteStrategy.swift | 2 +- .../DNSResolving/DNSResolvingStrategy.swift | 2 +- .../DNSResolving/ResolveDNSUseCase.swift | 4 ++- .../CottonPlugins/JSPluginFactory.swift | 33 ++++++++++--------- .../CottonPlugins/JSPluginsProgram.swift | 1 + .../CottonPlugins/JSPluginsProgramImpl.swift | 16 ++++++--- .../CottonPlugins/JSPluginsSource.swift | 1 + .../Plugins/JavaScriptPlugin.swift | 7 ++-- .../JavaScriptPluginVisitor.swift | 3 +- .../WKUserContentController+Visitor.swift | 22 ++++++------- .../CottonRestKit/ClientSubscriber.swift | 2 +- .../Interfaces/RequestInterfaces.swift | 2 +- .../NetworkReachabilityAdapter.swift | 2 +- catowseriOS/CottonRestKit/RestClient.swift | 8 +++-- 23 files changed, 93 insertions(+), 57 deletions(-) diff --git a/catowseriOS/CoreBrowser/History/DomainsHistory.swift b/catowseriOS/CoreBrowser/History/DomainsHistory.swift index 7464db35..6396b733 100644 --- a/catowseriOS/CoreBrowser/History/DomainsHistory.swift +++ b/catowseriOS/CoreBrowser/History/DomainsHistory.swift @@ -9,12 +9,16 @@ import CottonBase import AutoMockable -/// Interface for known domain checks. Has to have async methods because actual class is a global actor -public protocol KnownDomainsSource: AutoMockable { +/// Interface for known domain checks. Has to have async methods because actual class is a global actor. +/// +/// Can be sendable because implementation is a global actor. +public protocol KnownDomainsSource: AutoMockable, Sendable { func domainNames(whereURLContains filter: String) async -> [String] } -/// Interface for domain checks. Has to have async methods because actual class is a global actor -public protocol DomainsHistory { +/// Interface for domain checks. Has to have async methods because actual class is a global actor. +/// +/// Can be sendable because implementation is a global actor. +public protocol DomainsHistory: Sendable { func remember(host: CottonBase.Host) async } diff --git a/catowseriOS/CoreBrowser/Tabs/TabSelectionStrategy.swift b/catowseriOS/CoreBrowser/Tabs/TabSelectionStrategy.swift index cefc0d45..89033ee7 100644 --- a/catowseriOS/CoreBrowser/Tabs/TabSelectionStrategy.swift +++ b/catowseriOS/CoreBrowser/Tabs/TabSelectionStrategy.swift @@ -14,7 +14,9 @@ public protocol IndexSelectionContext: AutoMockable { var currentlySelectedIndex: Int { get async } } -public protocol TabSelectionStrategy: AutoMockable { +/// Tab selection protocol can be sendable, because implementation +/// only hold a constant which can't be mutated, so that, no any mutable state for now. +public protocol TabSelectionStrategy: AutoMockable, Sendable { /** A Tab selection strategy (Compositor) defines the algorithms of tab selection in specific cases - when tab was removed and need to select another diff --git a/catowseriOS/CoreBrowser/Tabs/TabsObserver.swift b/catowseriOS/CoreBrowser/Tabs/TabsObserver.swift index 1dbc0405..5fc449dc 100644 --- a/catowseriOS/CoreBrowser/Tabs/TabsObserver.swift +++ b/catowseriOS/CoreBrowser/Tabs/TabsObserver.swift @@ -14,8 +14,10 @@ import Foundation /// https://github.com/apple/swift-evolution/blob/main/proposals/0395-observability.md /// /// Tab did remove function is not needed, because we want to remove it from UI right away +/// +/// This can be sendable, because all actor types are, and this one is Main actor @MainActor -public protocol TabsObserver { +public protocol TabsObserver: Sendable { /// To be able to search specific observer. var tabsObserverName: String { get async } /// Updates observer with tabs count. diff --git a/catowseriOS/CoreBrowser/Tabs/TabsRepository.swift b/catowseriOS/CoreBrowser/Tabs/TabsRepository.swift index 7884b8bb..e55884a8 100644 --- a/catowseriOS/CoreBrowser/Tabs/TabsRepository.swift +++ b/catowseriOS/CoreBrowser/Tabs/TabsRepository.swift @@ -9,7 +9,10 @@ import Foundation import AutoMockable -public protocol TabsRepository: AutoMockable { +/// Tabs repository protocol can be sendable, because implementation +/// doesn't hold any state, only interface to the DB for now +/// so that, no need any synhronization. +public protocol TabsRepository: AutoMockable, Sendable { /// Defines human redable name for Int if it is describes index. /// e.g. implementation could use Index type instead. typealias TabIndex = Int diff --git a/catowseriOS/CoreBrowser/UseCases/ReadTabsUseCase.swift b/catowseriOS/CoreBrowser/UseCases/ReadTabsUseCase.swift index 5ff9b2c7..90e1d1a8 100644 --- a/catowseriOS/CoreBrowser/UseCases/ReadTabsUseCase.swift +++ b/catowseriOS/CoreBrowser/UseCases/ReadTabsUseCase.swift @@ -9,7 +9,9 @@ import Foundation import AutoMockable -public protocol ReadTabsUseCase: BaseUseCase, AutoMockable { +/// Read tabs use case. +/// Use cases do not hold any mutable state, so that, any of them can be sendable. +public protocol ReadTabsUseCase: BaseUseCase, AutoMockable, Sendable { /// Returns tabs count var tabsCount: Int { get async } /// Returns selected UUID, could be invalid one which is defined (to handle always not empty condition) diff --git a/catowseriOS/CoreBrowser/UseCases/SelectedTabUseCase.swift b/catowseriOS/CoreBrowser/UseCases/SelectedTabUseCase.swift index b1927de3..9a58dd90 100644 --- a/catowseriOS/CoreBrowser/UseCases/SelectedTabUseCase.swift +++ b/catowseriOS/CoreBrowser/UseCases/SelectedTabUseCase.swift @@ -9,6 +9,8 @@ import Foundation import AutoMockable -public protocol SelectedTabUseCase: BaseUseCase, AutoMockable { +/// Selected tabs use case. +/// Use cases do not hold any mutable state, so that, any of them can be sendable. +public protocol SelectedTabUseCase: BaseUseCase, AutoMockable, Sendable { func setSelectedPreview(_ image: Data?) async } diff --git a/catowseriOS/CoreBrowser/UseCases/WriteTabsUseCase.swift b/catowseriOS/CoreBrowser/UseCases/WriteTabsUseCase.swift index 27af4375..55487d20 100644 --- a/catowseriOS/CoreBrowser/UseCases/WriteTabsUseCase.swift +++ b/catowseriOS/CoreBrowser/UseCases/WriteTabsUseCase.swift @@ -9,7 +9,9 @@ import Foundation import AutoMockable -public protocol WriteTabsUseCase: BaseUseCase, AutoMockable { +/// Write tabs use case. +/// Use cases do not hold any mutable state, so that, any of them can be sendable. +public protocol WriteTabsUseCase: BaseUseCase, AutoMockable, Sendable { /// Adds tab to memory and storage. Tab can be blank or it can contain URL address. /// Tab will be added no matter what happen, so, function doesn't return any result. /// diff --git a/catowseriOS/CottonData/RestClientContext.swift b/catowseriOS/CottonData/RestClientContext.swift index 562cf4e0..91c3e93e 100644 --- a/catowseriOS/CottonData/RestClientContext.swift +++ b/catowseriOS/CottonData/RestClientContext.swift @@ -15,6 +15,8 @@ import ReactiveHttpKit import BrowserNetworking import AutoMockable +extension CottonBase.ServerDescription: @unchecked Sendable {} + // swiftlint:disable comment_spacing //sourcery: associatedtype = "R: ResponseType" //sourcery: associatedtype = "S: ServerDescription" @@ -26,7 +28,7 @@ import AutoMockable //sourcery: typealias = "ReachabilityAdapter = RA" //sourcery: typealias = "Encoder = E" //sourcery: typealias = "Client = C" -public protocol RestClientContext: AnyObject, AutoMockable { +public protocol RestClientContext: AnyObject, AutoMockable, Sendable { // swiftlint:enable comment_spacing associatedtype Response: ResponseType associatedtype Server: ServerDescription diff --git a/catowseriOS/CottonData/SearchViewModel/Autocomplete/AutocompleteSearchUseCase.swift b/catowseriOS/CottonData/SearchViewModel/Autocomplete/AutocompleteSearchUseCase.swift index ee4628ed..82dace91 100644 --- a/catowseriOS/CottonData/SearchViewModel/Autocomplete/AutocompleteSearchUseCase.swift +++ b/catowseriOS/CottonData/SearchViewModel/Autocomplete/AutocompleteSearchUseCase.swift @@ -16,9 +16,11 @@ import AutoMockable public typealias WebSearchSuggestionsProducer = SignalProducer<[String], HttpError> public typealias WebSearchSuggestionsPublisher = AnyPublisher<[String], HttpError> +/// Autocomplete search use case. +/// Use cases do not hold any mutable state, so that, any of them can be sendable. // swiftlint:disable comment_spacing //sourcery: associatedtype = "Strategy: SearchAutocompleteStrategy" -public protocol AutocompleteSearchUseCase: BaseUseCase, AutoMockable { +public protocol AutocompleteSearchUseCase: BaseUseCase, AutoMockable, Sendable { // swiftlint:enable comment_spacing associatedtype Strategy: SearchAutocompleteStrategy diff --git a/catowseriOS/CottonData/SearchViewModel/Autocomplete/SearchAutocompleteStrategy.swift b/catowseriOS/CottonData/SearchViewModel/Autocomplete/SearchAutocompleteStrategy.swift index 25a2f157..7592b1fb 100644 --- a/catowseriOS/CottonData/SearchViewModel/Autocomplete/SearchAutocompleteStrategy.swift +++ b/catowseriOS/CottonData/SearchViewModel/Autocomplete/SearchAutocompleteStrategy.swift @@ -14,7 +14,7 @@ import AutoMockable // swiftlint:disable comment_spacing //sourcery: associatedtype = "Context: RestClientContext" -public protocol SearchAutocompleteStrategy: AnyObject, AutoMockable { +public protocol SearchAutocompleteStrategy: AnyObject, AutoMockable, Sendable { // swiftlint:enable comment_spacing associatedtype Context: RestClientContext diff --git a/catowseriOS/CottonData/WebViewModel/DNSResolving/DNSResolvingStrategy.swift b/catowseriOS/CottonData/WebViewModel/DNSResolving/DNSResolvingStrategy.swift index 83f4ca0f..872751ed 100644 --- a/catowseriOS/CottonData/WebViewModel/DNSResolving/DNSResolvingStrategy.swift +++ b/catowseriOS/CottonData/WebViewModel/DNSResolving/DNSResolvingStrategy.swift @@ -14,7 +14,7 @@ import AutoMockable // swiftlint:disable comment_spacing //sourcery: associatedtype = "Context: RestClientContext" -public protocol DNSResolvingStrategy: AnyObject, AutoMockable { +public protocol DNSResolvingStrategy: AnyObject, AutoMockable, Sendable { // swiftlint:enable comment_spacing associatedtype Context: RestClientContext diff --git a/catowseriOS/CottonData/WebViewModel/DNSResolving/ResolveDNSUseCase.swift b/catowseriOS/CottonData/WebViewModel/DNSResolving/ResolveDNSUseCase.swift index d5f13435..af568bd7 100644 --- a/catowseriOS/CottonData/WebViewModel/DNSResolving/ResolveDNSUseCase.swift +++ b/catowseriOS/CottonData/WebViewModel/DNSResolving/ResolveDNSUseCase.swift @@ -16,9 +16,11 @@ import AutoMockable public typealias DNSResolvingProducer = SignalProducer public typealias DNSResolvingPublisher = AnyPublisher +/// Resolve Domain name use case. +/// Use cases do not hold any mutable state, so that, any of them can be sendable. // swiftlint:disable comment_spacing //sourcery: associatedtype = "Strategy: DNSResolvingStrategy" -public protocol ResolveDNSUseCase: BaseUseCase, AutoMockable { +public protocol ResolveDNSUseCase: BaseUseCase, AutoMockable, Sendable { // swiftlint:enable comment_spacing associatedtype Strategy: DNSResolvingStrategy diff --git a/catowseriOS/CottonPlugins/JSPluginFactory.swift b/catowseriOS/CottonPlugins/JSPluginFactory.swift index defe6881..6cd02a6f 100644 --- a/catowseriOS/CottonPlugins/JSPluginFactory.swift +++ b/catowseriOS/CottonPlugins/JSPluginFactory.swift @@ -9,29 +9,30 @@ import Foundation import WebKit +@globalActor final class JSPluginFactory { - static let shared = JSPluginFactory() + static let shared = StateHolder() - private let scripts = NSCache() + actor StateHolder { + private let scripts = NSCache() - private init() {} - - func script(for plugin: any JavaScriptPlugin, - with injectionTime: WKUserScriptInjectionTime, - isMainFrameOnly: Bool) throws -> WKUserScript { - let typeName = plugin.jsFileName - if let existingJS = scripts.object(forKey: typeName as NSString) { - return existingJS - } else { - let source = try Self.loadScriptSource(typeName) - let wkScript = WKUserScript(source: source, injectionTime: injectionTime, forMainFrameOnly: isMainFrameOnly) - scripts.setObject(wkScript, forKey: typeName as NSString) - return wkScript + func script(for plugin: any JavaScriptPlugin, + with injectionTime: WKUserScriptInjectionTime, + isMainFrameOnly: Bool) throws -> WKUserScript { + let typeName = plugin.jsFileName + if let existingJS = scripts.object(forKey: typeName as NSString) { + return existingJS + } else { + let source = try Self.loadScriptSource(typeName) + let wkScript = WKUserScript(source: source, injectionTime: injectionTime, forMainFrameOnly: isMainFrameOnly) + scripts.setObject(wkScript, forKey: typeName as NSString) + return wkScript + } } } } -fileprivate extension JSPluginFactory { +fileprivate extension JSPluginFactory.StateHolder { static func loadScriptSource(_ resourceName: String) throws -> String { guard let filepath = Bundle.init(for: self).path(forResource: resourceName, ofType: "js") else { print("\(resourceName).js not found!") diff --git a/catowseriOS/CottonPlugins/JSPluginsProgram.swift b/catowseriOS/CottonPlugins/JSPluginsProgram.swift index 10eb92f4..e7fa93e1 100644 --- a/catowseriOS/CottonPlugins/JSPluginsProgram.swift +++ b/catowseriOS/CottonPlugins/JSPluginsProgram.swift @@ -10,6 +10,7 @@ import Foundation import WebKit import CottonBase +@MainActor public protocol JSPluginsProgram: AnyObject, Equatable { var plugins: [any JavaScriptPlugin] { get } diff --git a/catowseriOS/CottonPlugins/JSPluginsProgramImpl.swift b/catowseriOS/CottonPlugins/JSPluginsProgramImpl.swift index 800fb1be..d3c9dbc3 100644 --- a/catowseriOS/CottonPlugins/JSPluginsProgramImpl.swift +++ b/catowseriOS/CottonPlugins/JSPluginsProgramImpl.swift @@ -10,6 +10,8 @@ import Foundation import WebKit import CottonBase +extension CottonBase.Host: @unchecked Sendable {} + /** An Object Structure (Program) from visitor desgin pattern. Could be a Composite */ @@ -32,10 +34,14 @@ public final class JSPluginsProgramImpl: JSPluginsProgram { return } visitor.removeAllUserScripts() // reset old state - do { - try plugins.forEach { try $0.accept(visitor, context, canInject) } - } catch { - print("\(#function) failed to load plugin: \(error.localizedDescription)") + plugins.forEach { plugin in + Task { + do { + try await plugin.accept(visitor, context, canInject) + } catch { + print("\(#function) failed to load plugin: \(error.localizedDescription)") + } + } } } @@ -69,7 +75,7 @@ public final class JSPluginsProgramImpl: JSPluginsProgram { */ public extension JSPluginsProgramImpl { - static func == (lhs: JSPluginsProgramImpl, rhs: JSPluginsProgramImpl) -> Bool { + nonisolated static func == (lhs: JSPluginsProgramImpl, rhs: JSPluginsProgramImpl) -> Bool { guard lhs.plugins.count == rhs.plugins.count else { return false } diff --git a/catowseriOS/CottonPlugins/JSPluginsSource.swift b/catowseriOS/CottonPlugins/JSPluginsSource.swift index 9dec052e..98fe5b90 100644 --- a/catowseriOS/CottonPlugins/JSPluginsSource.swift +++ b/catowseriOS/CottonPlugins/JSPluginsSource.swift @@ -7,6 +7,7 @@ import Foundation +@MainActor public protocol JSPluginsSource: AnyObject { associatedtype Program: JSPluginsProgram var jsProgram: Program { get } diff --git a/catowseriOS/CottonPlugins/Plugins/JavaScriptPlugin.swift b/catowseriOS/CottonPlugins/Plugins/JavaScriptPlugin.swift index c76ea995..471f83ca 100644 --- a/catowseriOS/CottonPlugins/Plugins/JavaScriptPlugin.swift +++ b/catowseriOS/CottonPlugins/Plugins/JavaScriptPlugin.swift @@ -23,6 +23,7 @@ public enum PluginHandlerDelegateType { Describes the JavaScript plugin model. An Element from visitor design pattern. */ +@MainActor public protocol JavaScriptPlugin: Equatable { var jsFileName: String { get } var messageHandlerName: String { get } @@ -47,15 +48,15 @@ public protocol JavaScriptPlugin: Equatable { - host represents the hostname from web view (can be used to determine if specific plugin is applicable or not) - canInject shows if this specific plugin needs to be injected or can be skipped. */ - func accept(_ visitor: JavaScriptPluginVisitor, _ host: CottonBase.Host, _ canInject: Bool) throws + func accept(_ visitor: JavaScriptPluginVisitor, _ host: CottonBase.Host, _ canInject: Bool) async throws } extension JavaScriptPlugin { - public func accept(_ visitor: JavaScriptPluginVisitor, _ host: CottonBase.Host, _ canInject: Bool) throws { + public func accept(_ visitor: JavaScriptPluginVisitor, _ host: CottonBase.Host, _ canInject: Bool) async throws { guard visitor.canVisit(self, host, canInject) else { return } - try visitor.visit(self) + try await visitor.visit(self) } } diff --git a/catowseriOS/CottonPlugins/PluginsVisitor/JavaScriptPluginVisitor.swift b/catowseriOS/CottonPlugins/PluginsVisitor/JavaScriptPluginVisitor.swift index 4fa4d42c..1689220a 100644 --- a/catowseriOS/CottonPlugins/PluginsVisitor/JavaScriptPluginVisitor.swift +++ b/catowseriOS/CottonPlugins/PluginsVisitor/JavaScriptPluginVisitor.swift @@ -10,6 +10,7 @@ import Foundation import CottonBase /// Will be used for `WKUserContentController` because it is actually the only possible visitor +@MainActor public protocol JavaScriptPluginVisitor: AnyObject { /** Determines if specific plugin can be used on specific host @@ -26,5 +27,5 @@ public protocol JavaScriptPluginVisitor: AnyObject { - Parameters: - plugin JavaScript plugin */ - func visit(_ plugin: any JavaScriptPlugin) throws + func visit(_ plugin: any JavaScriptPlugin) async throws } diff --git a/catowseriOS/CottonPlugins/PluginsVisitor/WKUserContentController+Visitor.swift b/catowseriOS/CottonPlugins/PluginsVisitor/WKUserContentController+Visitor.swift index c73821e0..5d935081 100644 --- a/catowseriOS/CottonPlugins/PluginsVisitor/WKUserContentController+Visitor.swift +++ b/catowseriOS/CottonPlugins/PluginsVisitor/WKUserContentController+Visitor.swift @@ -24,25 +24,25 @@ extension WKUserContentController: JavaScriptPluginVisitor { return true } - public func visit(_ plugin: any JavaScriptPlugin) throws { + public func visit(_ plugin: any JavaScriptPlugin) async throws { if let base = plugin as? BasePlugin { - try visit(basePlugin: base) + try await visit(basePlugin: base) } else if let instagram = plugin as? InstagramContentPlugin { - try visit(instagramPlugin: instagram) + try await visit(instagramPlugin: instagram) } } - private func visit(basePlugin: BasePlugin) throws { - let wkScript = try JSPluginFactory.shared.script(for: basePlugin, - with: .atDocumentEnd, - isMainFrameOnly: true) + private func visit(basePlugin: BasePlugin) async throws { + let wkScript = try await JSPluginFactory.shared.script(for: basePlugin, + with: .atDocumentEnd, + isMainFrameOnly: true) addHandler(wkScript, basePlugin.messageHandlerName, basePlugin.handler) } - private func visit(instagramPlugin: InstagramContentPlugin) throws { - let wkScript = try JSPluginFactory.shared.script(for: instagramPlugin, - with: .atDocumentStart, - isMainFrameOnly: instagramPlugin.isMainFrameOnly) + private func visit(instagramPlugin: InstagramContentPlugin) async throws { + let wkScript = try await JSPluginFactory.shared.script(for: instagramPlugin, + with: .atDocumentStart, + isMainFrameOnly: instagramPlugin.isMainFrameOnly) addHandler(wkScript, instagramPlugin.messageHandlerName, instagramPlugin.handler) } diff --git a/catowseriOS/CottonRestKit/ClientSubscriber.swift b/catowseriOS/CottonRestKit/ClientSubscriber.swift index 5c8fc15d..cc0bafb0 100644 --- a/catowseriOS/CottonRestKit/ClientSubscriber.swift +++ b/catowseriOS/CottonRestKit/ClientSubscriber.swift @@ -31,7 +31,7 @@ public typealias Subscriber = ClientSubsc /// It lead to the issue that clsoures or Rx observers should be stored somewhere outside async `RestClient` methods. /// Because they can't be deallocated during async requests. /// It must be a reference type because we will pass it to `RestClient` methods. -public class ClientRxSubscriber where RX.Observer.Response == R, RX.Server == S { +public final class ClientRxSubscriber where RX.Observer.Response == R, RX.Server == S { /// Can't use protocol type because it has associated type, should be associated with Endpoint response type var handlers = Set>() diff --git a/catowseriOS/CottonRestKit/Interfaces/RequestInterfaces.swift b/catowseriOS/CottonRestKit/Interfaces/RequestInterfaces.swift index b214effc..7b651338 100644 --- a/catowseriOS/CottonRestKit/Interfaces/RequestInterfaces.swift +++ b/catowseriOS/CottonRestKit/Interfaces/RequestInterfaces.swift @@ -22,7 +22,7 @@ extension URLRequest: URLRequestCreatable { /// Interface for some JSON encoder (e.g. Alamofire implementation) to hide it and /// not use it directly and be able to mock it for unit testing -public protocol JSONRequestEncodable: AutoMockable { +public protocol JSONRequestEncodable: AutoMockable, Sendable { func encodeRequest(_ urlRequest: URLRequestCreatable, with parameters: [String: Any]?) throws -> URLRequest } diff --git a/catowseriOS/CottonRestKit/NetworkReachabilityAdapter.swift b/catowseriOS/CottonRestKit/NetworkReachabilityAdapter.swift index eee7e31f..4d44c3f1 100644 --- a/catowseriOS/CottonRestKit/NetworkReachabilityAdapter.swift +++ b/catowseriOS/CottonRestKit/NetworkReachabilityAdapter.swift @@ -39,7 +39,7 @@ public enum NetworkReachabilityStatus { // swiftlint:disable comment_spacing //sourcery: associatedtype = "Server: ServerDescription" -public protocol NetworkReachabilityAdapter: AnyObject, AutoMockable { +public protocol NetworkReachabilityAdapter: AnyObject, AutoMockable, Sendable { // swiftlint:enable comment_spacing associatedtype Server: ServerDescription typealias Listener = (NetworkReachabilityStatus) -> Void diff --git a/catowseriOS/CottonRestKit/RestClient.swift b/catowseriOS/CottonRestKit/RestClient.swift index e3f9b267..4204fa6a 100644 --- a/catowseriOS/CottonRestKit/RestClient.swift +++ b/catowseriOS/CottonRestKit/RestClient.swift @@ -19,9 +19,11 @@ fileprivate extension String { public typealias HttpTypedResult = Result public typealias TypedResponseClosure = (HttpTypedResult) -> Void -public class RestClient: RestInterface where R.Server == S { +extension CottonBase.ServerDescription: @unchecked Sendable {} + +public final class RestClient: RestInterface where R.Server == S { public typealias Server = S public typealias Reachability = R public typealias Encoder = E From 1c946991d846b15743bd147b5aa2354a94f0da1d Mon Sep 17 00:00:00 2001 From: Andrei Ermoshin Date: Fri, 14 Jun 2024 20:11:45 +0300 Subject: [PATCH 07/32] Fix compilation for iso 18 beta - SwiftUI framework now also has Tab type --- .../BrowserNetworking/Downloadable.swift | 2 +- .../GoogleJSONDNSoHTTPsEndpoint.swift | 2 +- .../DuckDuckGoSearchEndpoint.swift | 2 +- .../Google/GoogleSearchEndpoint.swift | 2 +- .../HttpClient+Alamofire.swift | 2 +- .../AlamofireHTTPAdaptee.swift | 2 +- .../AlamofireHTTPRxAdaptee.swift | 2 +- .../AlamofireHTTPRxVoidAdaptee.swift | 2 +- catowseriOS/CoreBrowser/Models/Tab.swift | 16 +++++----- .../CoreBrowser/Tabs/TabContentState.swift | 4 +-- .../Tabs/TabSelectionStrategy.swift | 4 +-- .../CoreBrowser/Tabs/TabsDataService.swift | 28 ++++++++-------- .../CoreBrowser/Tabs/TabsObserver.swift | 20 ++++++------ .../CoreBrowser/Tabs/TabsRepository.swift | 10 +++--- .../CoreBrowser/Tabs/TabsServiceCommand.swift | 8 ++--- .../Tabs/TabsServiceDataOutput.swift | 2 +- .../UseCases/ReadTabsUseCase.swift | 2 +- .../UseCases/ReadTabsUseCaseImpl.swift | 2 +- .../UseCases/WriteTabsUseCase.swift | 4 +-- .../UseCases/WriteTabsUseCaseImpl.swift | 8 ++--- .../Sourcery/Templates/AutoMockable.stencil | 2 +- .../TabsDataServiceTests.swift | 6 ++-- .../CottonData/RestClientContext.swift | 2 +- .../AutocompleteSearchUseCase.swift | 2 +- .../AutocompleteSearchUseCaseImpl.swift | 12 ++++--- .../SearchAutocompleteStrategy.swift | 2 +- .../DDGoAutocompleteStrategy.swift | 2 +- .../GoogleAutocompleteStrategy.swift | 2 +- .../DNSResolving/DNSResolvingStrategy.swift | 2 +- .../DNSResolving/ResolveDNSUseCase.swift | 2 +- .../DNSResolving/ResolveDNSUseCaseImpl.swift | 2 +- .../GoogleDNSStrategy.swift | 2 +- .../Fixtures/SearchSuggestionsVMFixture.swift | 2 +- .../SearchSuggestionsVMConcurrencyTests.swift | 2 +- .../WebSearchAutocompleteTests.swift | 2 +- .../CottonPlugins/JSPluginFactory.swift | 32 +++++++++---------- .../CottonPlugins/JavaScriptEvaluateble.swift | 2 +- .../NetworkReachabilityAdapter.swift | 4 +-- .../FeaturesFlagsKit/FeatureManager.swift | 2 +- .../FeaturesFlagsKit/LocalFeatureSource.swift | 2 +- .../Models/EnumFeatureSource.swift | 2 +- .../Models/FeatureSource.swift | 2 +- .../ReactiveHttpKit/HTTPAdapter+Rx.swift | 2 +- .../ReactiveHttpKit/HttpClient+RxSwift.swift | 2 +- .../ReactiveHttpKit/RxObserverWrapper.swift | 2 +- catowseriOS/ReactiveHttpKit/URL+Rx.swift | 2 +- .../Browser/MainBrowserV2ViewController.swift | 2 +- .../Browser/View/MainBrowserView.swift | 4 +-- .../catowser/Browser/View/PhoneView.swift | 4 +-- .../catowser/Browser/View/TabletView.swift | 4 +-- .../BrowserContent/BrowserContentView.swift | 4 +-- .../BrowserContentViewModel.swift | 10 +++--- .../TopSites/TopSitesViewModel.swift | 2 +- .../WebView/WebViewController.swift | 2 +- .../Coordinators/AppCoordinator.swift | 12 +++---- .../Coordinators/MainToolbarCoordinator.swift | 2 +- .../Coordinators/PhoneTabsCoordinator.swift | 6 ++-- .../Coordinators/SearchBarCoordinator.swift | 2 +- .../SearchSuggestionsCoordinator.swift | 2 +- .../CottonDbModel.xcdatamodel/contents | 22 +++++-------- catowseriOS/catowser/DB/TabsDBClient.swift | 24 +++++++------- catowseriOS/catowser/DB/TabsResource.swift | 10 +++--- .../Downloads/FileDownloadViewModel.swift | 2 +- .../Views/DownloadButtonCellView.swift | 2 +- .../Environment/ViewControllerFactory.swift | 4 +-- .../Environment/ViewModelFactory.swift | 2 +- .../catowser/Extensions/Tab+Extension.swift | 4 +-- .../Search/SearchBarBaseViewController.swift | 4 +-- .../catowser/Search/SearchBarState.swift | 2 +- .../catowser/Toolbar/BrowserToolbarView.swift | 2 +- .../catowser/Utils/DefaultTabProvider.swift | 2 +- .../catowser/WebTabs/AllTabsViewModel.swift | 2 +- .../catowser/WebTabs/TabPreviewCell.swift | 2 +- .../catowser/WebTabs/TabViewModel.swift | 8 ++--- .../WebTabs/TabsPreviewsViewController.swift | 6 ++-- .../WebTabs/TabsPreviewsViewModel.swift | 2 +- .../catowser/WebTabs/TabsRepositoryImpl.swift | 12 +++---- .../catowser/WebTabs/TabsViewController.swift | 8 ++--- 78 files changed, 196 insertions(+), 202 deletions(-) diff --git a/catowseriOS/BrowserNetworking/Downloadable.swift b/catowseriOS/BrowserNetworking/Downloadable.swift index 44e07daf..912843fb 100644 --- a/catowseriOS/BrowserNetworking/Downloadable.swift +++ b/catowseriOS/BrowserNetworking/Downloadable.swift @@ -8,7 +8,7 @@ import Foundation import Alamofire -import ReactiveSwift +@preconcurrency import ReactiveSwift #if canImport(CryptoKit) import CryptoKit #endif diff --git a/catowseriOS/BrowserNetworking/Endpoints/DNSoverHTTPS/GoogleJSONDNSoHTTPsEndpoint.swift b/catowseriOS/BrowserNetworking/Endpoints/DNSoverHTTPS/GoogleJSONDNSoHTTPsEndpoint.swift index 0155393a..0cf94695 100644 --- a/catowseriOS/BrowserNetworking/Endpoints/DNSoverHTTPS/GoogleJSONDNSoHTTPsEndpoint.swift +++ b/catowseriOS/BrowserNetworking/Endpoints/DNSoverHTTPS/GoogleJSONDNSoHTTPsEndpoint.swift @@ -9,7 +9,7 @@ import CottonRestKit import CottonBase import ReactiveHttpKit -import ReactiveSwift +@preconcurrency import ReactiveSwift #if canImport(Combine) import Combine #endif diff --git a/catowseriOS/BrowserNetworking/Endpoints/DuckDuckGoAutocomplete/DuckDuckGoSearchEndpoint.swift b/catowseriOS/BrowserNetworking/Endpoints/DuckDuckGoAutocomplete/DuckDuckGoSearchEndpoint.swift index a981de4f..7f2b9aed 100644 --- a/catowseriOS/BrowserNetworking/Endpoints/DuckDuckGoAutocomplete/DuckDuckGoSearchEndpoint.swift +++ b/catowseriOS/BrowserNetworking/Endpoints/DuckDuckGoAutocomplete/DuckDuckGoSearchEndpoint.swift @@ -8,7 +8,7 @@ import CottonRestKit import Combine -import ReactiveSwift +@preconcurrency import ReactiveSwift import CottonBase import ReactiveHttpKit import Alamofire diff --git a/catowseriOS/BrowserNetworking/Endpoints/Google/GoogleSearchEndpoint.swift b/catowseriOS/BrowserNetworking/Endpoints/Google/GoogleSearchEndpoint.swift index 4a3017ea..5895c51e 100644 --- a/catowseriOS/BrowserNetworking/Endpoints/Google/GoogleSearchEndpoint.swift +++ b/catowseriOS/BrowserNetworking/Endpoints/Google/GoogleSearchEndpoint.swift @@ -9,7 +9,7 @@ import CottonRestKit import CottonBase import ReactiveHttpKit -import ReactiveSwift +@preconcurrency import ReactiveSwift #if canImport(Combine) import Combine #endif diff --git a/catowseriOS/BrowserNetworking/HttpClient+Alamofire.swift b/catowseriOS/BrowserNetworking/HttpClient+Alamofire.swift index 3767fc14..6a222417 100644 --- a/catowseriOS/BrowserNetworking/HttpClient+Alamofire.swift +++ b/catowseriOS/BrowserNetworking/HttpClient+Alamofire.swift @@ -7,7 +7,7 @@ // import CottonRestKit -import ReactiveSwift +@preconcurrency import ReactiveSwift import CottonBase /// This typealias could be an issue, because the same defined in ReactiveSwift HttpClient+RxSwift.swift diff --git a/catowseriOS/BrowserNetworking/TransportAdapters/AlamofireHTTPAdaptee.swift b/catowseriOS/BrowserNetworking/TransportAdapters/AlamofireHTTPAdaptee.swift index 5cb53222..d75e73c2 100644 --- a/catowseriOS/BrowserNetworking/TransportAdapters/AlamofireHTTPAdaptee.swift +++ b/catowseriOS/BrowserNetworking/TransportAdapters/AlamofireHTTPAdaptee.swift @@ -9,7 +9,7 @@ import CottonRestKit import ReactiveHttpKit import Alamofire -import ReactiveSwift +@preconcurrency import ReactiveSwift #if canImport(Combine) import Combine #endif diff --git a/catowseriOS/BrowserNetworking/TransportAdapters/AlamofireHTTPRxAdaptee.swift b/catowseriOS/BrowserNetworking/TransportAdapters/AlamofireHTTPRxAdaptee.swift index 4c7b2e1c..1824d67e 100644 --- a/catowseriOS/BrowserNetworking/TransportAdapters/AlamofireHTTPRxAdaptee.swift +++ b/catowseriOS/BrowserNetworking/TransportAdapters/AlamofireHTTPRxAdaptee.swift @@ -9,7 +9,7 @@ import CottonRestKit import ReactiveHttpKit import Alamofire -import ReactiveSwift +@preconcurrency import ReactiveSwift #if canImport(Combine) import Combine #endif diff --git a/catowseriOS/BrowserNetworking/TransportAdapters/AlamofireHTTPRxVoidAdaptee.swift b/catowseriOS/BrowserNetworking/TransportAdapters/AlamofireHTTPRxVoidAdaptee.swift index 3151e728..76804768 100644 --- a/catowseriOS/BrowserNetworking/TransportAdapters/AlamofireHTTPRxVoidAdaptee.swift +++ b/catowseriOS/BrowserNetworking/TransportAdapters/AlamofireHTTPRxVoidAdaptee.swift @@ -8,7 +8,7 @@ import CottonRestKit import Alamofire -import ReactiveSwift +@preconcurrency import ReactiveSwift #if canImport(Combine) import Combine #endif diff --git a/catowseriOS/CoreBrowser/Models/Tab.swift b/catowseriOS/CoreBrowser/Models/Tab.swift index cb74448b..33fbfe8b 100644 --- a/catowseriOS/CoreBrowser/Models/Tab.swift +++ b/catowseriOS/CoreBrowser/Models/Tab.swift @@ -1,5 +1,5 @@ // -// Tab.swift +// CoreBrowser.Tab.swift // catowser // // Created by admin on 12/06/2017. @@ -43,7 +43,7 @@ public extension Tab { return .blank case 1: guard let actualSite = site else { - print("No site instance for Tab.ContentType site \(rawValue)") + print("No site instance for CoreBrowser.Tab.ContentType site \(rawValue)") return nil } return .site(actualSite) @@ -54,7 +54,7 @@ public extension Tab { case 4: return .topSites default: - print("Unexpected Tab.ContentType \(rawValue)") + print("Unexpected CoreBrowser.Tab.ContentType \(rawValue)") return nil } } @@ -101,7 +101,7 @@ public extension Tab { } extension Tab.ContentType: CaseIterable { - public static var allCases: [Tab.ContentType] { + public static var allCases: [CoreBrowser.Tab.ContentType] { [.blank, .homepage, .topSites, .favorites] } } @@ -127,7 +127,7 @@ extension Tab.ContentType: CustomDebugStringConvertible { } extension Tab.ContentType: Equatable { - public static func == (lhs: Tab.ContentType, rhs: Tab.ContentType) -> Bool { + public static func == (lhs: CoreBrowser.Tab.ContentType, rhs: CoreBrowser.Tab.ContentType) -> Bool { switch (lhs, rhs) { case (.site(let lSite), .site(let rSite)): return lSite.compareWith(rSite) @@ -210,7 +210,7 @@ public struct Tab: Sendable { } /** - Initializes an instance of `Tab` type. + Initializes an instance of `CoreBrowser.Tab` type. */ public init(contentType: ContentType, idenifier: UUID = .init(), @@ -220,11 +220,11 @@ public struct Tab: Sendable { id = idenifier } - public static let blank: Tab = Tab(contentType: .blank) + public static let blank: CoreBrowser.Tab = CoreBrowser.Tab(contentType: .blank) } extension Tab: Equatable { - public static func == (lhs: Tab, rhs: Tab) -> Bool { + public static func == (lhs: CoreBrowser.Tab, rhs: CoreBrowser.Tab) -> Bool { return lhs.id == rhs.id } } diff --git a/catowseriOS/CoreBrowser/Tabs/TabContentState.swift b/catowseriOS/CoreBrowser/Tabs/TabContentState.swift index 3ed02bf2..2856e57d 100644 --- a/catowseriOS/CoreBrowser/Tabs/TabContentState.swift +++ b/catowseriOS/CoreBrowser/Tabs/TabContentState.swift @@ -8,7 +8,7 @@ import Foundation -/// Twin type for `Tab.ContentType` to have `rawValue` +/// Twin type for `CoreBrowser.Tab.ContentType` to have `rawValue` /// and use it for settings. public enum TabContentDefaultState: Int, CaseIterable, CustomStringConvertible, Sendable { case blank @@ -16,7 +16,7 @@ public enum TabContentDefaultState: Int, CaseIterable, CustomStringConvertible, case favorites case topSites - public var contentType: Tab.ContentType { + public var contentType: CoreBrowser.Tab.ContentType { switch self { case .blank: return .blank diff --git a/catowseriOS/CoreBrowser/Tabs/TabSelectionStrategy.swift b/catowseriOS/CoreBrowser/Tabs/TabSelectionStrategy.swift index 89033ee7..2d369d28 100644 --- a/catowseriOS/CoreBrowser/Tabs/TabSelectionStrategy.swift +++ b/catowseriOS/CoreBrowser/Tabs/TabSelectionStrategy.swift @@ -14,11 +14,11 @@ public protocol IndexSelectionContext: AutoMockable { var currentlySelectedIndex: Int { get async } } -/// Tab selection protocol can be sendable, because implementation +/// CoreBrowser.Tab selection protocol can be sendable, because implementation /// only hold a constant which can't be mutated, so that, no any mutable state for now. public protocol TabSelectionStrategy: AutoMockable, Sendable { /** - A Tab selection strategy (Compositor) defines the algorithms of tab selection in specific cases + A CoreBrowser.Tab selection strategy (Compositor) defines the algorithms of tab selection in specific cases - when tab was removed and need to select another */ func autoSelectedIndexAfterTabRemove(_ context: IndexSelectionContext, removedIndex: Int) async -> Int diff --git a/catowseriOS/CoreBrowser/Tabs/TabsDataService.swift b/catowseriOS/CoreBrowser/Tabs/TabsDataService.swift index d87a0243..c51374fd 100644 --- a/catowseriOS/CoreBrowser/Tabs/TabsDataService.swift +++ b/catowseriOS/CoreBrowser/Tabs/TabsDataService.swift @@ -18,7 +18,7 @@ public actor TabsDataService { /// Tabs selection strategy private let selectionStrategy: TabSelectionStrategy /// In memory storage for the tabs - private var tabs: [Tab] = [] + private var tabs: [CoreBrowser.Tab] = [] /// Async stream for the selected tab id instead of using Combine's @Published private var selectedTabIdStream: UUIDStream! /// Async's stream continuation to notify about new id @@ -117,7 +117,7 @@ private extension TabsDataService { return .allTabs(tabs) } - func handleAddTabCommand(_ tab: Tab) async -> TabsServiceDataOutput { + func handleAddTabCommand(_ tab: CoreBrowser.Tab) async -> TabsServiceDataOutput { let positionType = await positioning.addPosition let newIndex = positionType.addTab(tab, to: &tabs, selectedTabIdentifier) tabsCountInput.yield(tabs.count) @@ -132,7 +132,7 @@ private extension TabsDataService { return .tabAdded } - func handleCloseTabCommand(_ tab: Tab) async -> TabsServiceDataOutput { + func handleCloseTabCommand(_ tab: CoreBrowser.Tab) async -> TabsServiceDataOutput { do { let removedTabs = try await tabsRepository.remove(tabs: [tab]) // swiftlint:disable:next force_unwrapping @@ -157,7 +157,7 @@ private extension TabsDataService { _ = try await tabsRepository.remove(tabs: tabs) tabs.removeAll() tabsCountInput.yield(0) - let tab: Tab = .init(contentType: contentState) + let tab: CoreBrowser.Tab = .init(contentType: contentState) _ = try await tabsRepository.add(tab, select: true) } catch { // tab view should be removed immediately on view level anyway @@ -166,7 +166,7 @@ private extension TabsDataService { return .allTabsClosed } - func handleSelectTabCommand(_ tab: Tab) async -> TabsServiceDataOutput { + func handleSelectTabCommand(_ tab: CoreBrowser.Tab) async -> TabsServiceDataOutput { do { let identifier = try await tabsRepository.select(tab: tab) guard identifier != selectedTabIdentifier else { @@ -180,7 +180,7 @@ private extension TabsDataService { return .tabSelected } - func handleReplaceTabContentCommand(_ tabContent: Tab.ContentType) async -> TabsServiceDataOutput { + func handleReplaceTabContentCommand(_ tabContent: CoreBrowser.Tab.ContentType) async -> TabsServiceDataOutput { guard let tabTuple = tabs.element(by: selectedTabIdentifier) else { return .tabContentReplaced(TabsListError.notInitializedYet) } @@ -284,7 +284,7 @@ extension TabsDataService: TabsSubject { } private extension TabsDataService { - func handleTabAdded(_ tab: Tab, index: Int, select: Bool) async { + func handleTabAdded(_ tab: CoreBrowser.Tab, index: Int, select: Bool) async { /// can select new tab only after adding it, this is because corresponding view should be in the list switch positioning.addSpeed { case .immediately: @@ -316,7 +316,7 @@ private extension TabsDataService { } } - func handleCachedTabRemove(_ tab: Tab) async { + func handleCachedTabRemove(_ tab: CoreBrowser.Tab) async { /// if it is a last tab - replace it with a tab with default content /// browser can't function without at least one tab /// so, this is kind of a side effect of removing the only one last tab @@ -325,7 +325,7 @@ private extension TabsDataService { tabsCountInput.yield(0) Task { let contentState = await positioning.contentState - let tab: Tab = .init(contentType: contentState) + let tab: CoreBrowser.Tab = .init(contentType: contentState) _ = await sendCommand(.addTab(tab)) } } else { @@ -348,7 +348,7 @@ private extension TabsDataService { func fetchTabs() async throws { var cachedTabs = try await tabsRepository.fetchAllTabs() if cachedTabs.isEmpty { - let tab = Tab(contentType: await positioning.contentState) + let tab = CoreBrowser.Tab(contentType: await positioning.contentState) let savedTab = try await tabsRepository.add(tab, select: true) cachedTabs = [savedTab] } @@ -396,8 +396,8 @@ private extension TabsDataService { } } -fileprivate extension Array where Element == Tab { - func element(by uuid: UUID) -> (tab: Tab, index: Int)? { +fileprivate extension Array where Element == CoreBrowser.Tab { + func element(by uuid: UUID) -> (tab: CoreBrowser.Tab, index: Int)? { for (ix, tab) in self.enumerated() where tab.id == uuid { return (tab, ix) } @@ -406,8 +406,8 @@ fileprivate extension Array where Element == Tab { } extension AddedTabPosition { - func addTab(_ tab: Tab, - to tabs: inout [Tab], + func addTab(_ tab: CoreBrowser.Tab, + to tabs: inout [CoreBrowser.Tab], _ currentlySelectedId: UUID) -> Int { let newIndex: Int switch self { diff --git a/catowseriOS/CoreBrowser/Tabs/TabsObserver.swift b/catowseriOS/CoreBrowser/Tabs/TabsObserver.swift index 5fc449dc..8cca4208 100644 --- a/catowseriOS/CoreBrowser/Tabs/TabsObserver.swift +++ b/catowseriOS/CoreBrowser/Tabs/TabsObserver.swift @@ -13,7 +13,7 @@ import Foundation /// Future directions: /// https://github.com/apple/swift-evolution/blob/main/proposals/0395-observability.md /// -/// Tab did remove function is not needed, because we want to remove it from UI right away +/// CoreBrowser.Tab did remove function is not needed, because we want to remove it from UI right away /// /// This can be sendable, because all actor types are, and this one is Main actor @MainActor @@ -27,7 +27,7 @@ public protocol TabsObserver: Sendable { /// Prodive necessary data to render UI on tablets /// /// - Parameter tabs: Tabs from cache at application start. - func initializeObserver(with tabs: [Tab]) async + func initializeObserver(with tabs: [CoreBrowser.Tab]) async /// Tells other observers about new tab. /// We can pause drawing new tab on view layer /// to be able firstly determine type of initial tab state. @@ -35,20 +35,20 @@ public protocol TabsObserver: Sendable { /// - parameters: /// - tab: new tab /// - index: where to add new object - func tabDidAdd(_ tab: Tab, at index: Int) async + func tabDidAdd(_ tab: CoreBrowser.Tab, at index: Int) async /// Tells observer that index has changed. /// /// - parameters: /// - index: new selected index. - /// - content: Tab content, e.g. can be site. Need to pass it to allow browser to change content in web view. + /// - content: CoreBrowser.Tab content, e.g. can be site. Need to pass it to allow browser to change content in web view. /// - identifier: needed to quickly determine visual state (selected view or not) - func tabDidSelect(_ index: Int, _ content: Tab.ContentType, _ identifier: UUID) async + func tabDidSelect(_ index: Int, _ content: CoreBrowser.Tab.ContentType, _ identifier: UUID) async /// Notifies about tab content type changes or `site` changes /// /// - parameters: /// - tab: new tab for replacement /// - index: original tab's index whichneeds to be replaced - func tabDidReplace(_ tab: Tab, at index: Int) async + func tabDidReplace(_ tab: CoreBrowser.Tab, at index: Int) async } /// Marks optional functions for protocol @@ -60,18 +60,18 @@ public extension TabsObserver { } } - func tabDidSelect(_ index: Int, _ content: Tab.ContentType, _ identifier: UUID) async { + func tabDidSelect(_ index: Int, _ content: CoreBrowser.Tab.ContentType, _ identifier: UUID) async { // Only landscape/regular tabs list view use that } - func tabDidAdd(_ tab: Tab, at index: Int) async { + func tabDidAdd(_ tab: CoreBrowser.Tab, at index: Int) async { // e.g. Counter view doesn't need to handle that // as it uses another delegate method with `tabsCount` } - /* optional */ func tabDidReplace(_ tab: Tab, at index: Int) async {} + /* optional */ func tabDidReplace(_ tab: CoreBrowser.Tab, at index: Int) async {} /* optional */ func updateTabsCount(with tabsCount: Int) async {} - /* optional */ func initializeObserver(with tabs: [Tab]) async {} + /* optional */ func initializeObserver(with tabs: [CoreBrowser.Tab]) async {} } diff --git a/catowseriOS/CoreBrowser/Tabs/TabsRepository.swift b/catowseriOS/CoreBrowser/Tabs/TabsRepository.swift index e55884a8..10ef9bd1 100644 --- a/catowseriOS/CoreBrowser/Tabs/TabsRepository.swift +++ b/catowseriOS/CoreBrowser/Tabs/TabsRepository.swift @@ -24,23 +24,23 @@ public protocol TabsRepository: AutoMockable, Sendable { /// - Parameter tab: The tab object to be selected. /// /// - Returns: An identifier of the selected tab. - func select(tab: Tab) async throws -> UUID + func select(tab: CoreBrowser.Tab) async throws -> UUID /// Loads tabs data from storage. /// /// - Returns: A producer with tabs array or error. - func fetchAllTabs() async throws -> [Tab] + func fetchAllTabs() async throws -> [CoreBrowser.Tab] /// Adds a tab to storage /// /// - Parameter tab: The tab object to be added. - func add(_ tab: Tab, select: Bool) async throws -> Tab + func add(_ tab: CoreBrowser.Tab, select: Bool) async throws -> CoreBrowser.Tab /// Updates tab content /// /// - Parameter tab: The tab object to be updated. Usually only tab content needs to be updated. - func update(tab: Tab) throws -> Tab + func update(tab: CoreBrowser.Tab) throws -> CoreBrowser.Tab /// Removes some tabs for current session - func remove(tabs: [Tab]) async throws -> [Tab] + func remove(tabs: [CoreBrowser.Tab]) async throws -> [CoreBrowser.Tab] } diff --git a/catowseriOS/CoreBrowser/Tabs/TabsServiceCommand.swift b/catowseriOS/CoreBrowser/Tabs/TabsServiceCommand.swift index a9a671ad..e6aa1649 100644 --- a/catowseriOS/CoreBrowser/Tabs/TabsServiceCommand.swift +++ b/catowseriOS/CoreBrowser/Tabs/TabsServiceCommand.swift @@ -16,11 +16,11 @@ public enum TabsServiceCommand: Sendable { case getTabsCount case getSelectedTabId case getAllTabs - case addTab(Tab) - case closeTab(Tab) + case addTab(CoreBrowser.Tab) + case closeTab(CoreBrowser.Tab) case closeTabWithId(UUID) case closeAll - case selectTab(Tab) - case replaceSelectedContent(Tab.ContentType) + case selectTab(CoreBrowser.Tab) + case replaceSelectedContent(CoreBrowser.Tab.ContentType) case updateSelectedTabPreview(Data?) } diff --git a/catowseriOS/CoreBrowser/Tabs/TabsServiceDataOutput.swift b/catowseriOS/CoreBrowser/Tabs/TabsServiceDataOutput.swift index 68fb7752..3956c6bc 100644 --- a/catowseriOS/CoreBrowser/Tabs/TabsServiceDataOutput.swift +++ b/catowseriOS/CoreBrowser/Tabs/TabsServiceDataOutput.swift @@ -14,7 +14,7 @@ import Foundation public enum TabsServiceDataOutput: Sendable { case tabsCount(Int) case selectedTabId(UUID) - case allTabs([Tab]) + case allTabs([CoreBrowser.Tab]) case tabAdded case tabClosed(UUID?) case allTabsClosed diff --git a/catowseriOS/CoreBrowser/UseCases/ReadTabsUseCase.swift b/catowseriOS/CoreBrowser/UseCases/ReadTabsUseCase.swift index 90e1d1a8..780589b5 100644 --- a/catowseriOS/CoreBrowser/UseCases/ReadTabsUseCase.swift +++ b/catowseriOS/CoreBrowser/UseCases/ReadTabsUseCase.swift @@ -17,5 +17,5 @@ public protocol ReadTabsUseCase: BaseUseCase, AutoMockable, Sendable { /// Returns selected UUID, could be invalid one which is defined (to handle always not empty condition) var selectedId: UUID { get async } /// Fetches latest tabs. - var allTabs: [Tab] { get async } + var allTabs: [CoreBrowser.Tab] { get async } } diff --git a/catowseriOS/CoreBrowser/UseCases/ReadTabsUseCaseImpl.swift b/catowseriOS/CoreBrowser/UseCases/ReadTabsUseCaseImpl.swift index 86ebb624..02728de1 100644 --- a/catowseriOS/CoreBrowser/UseCases/ReadTabsUseCaseImpl.swift +++ b/catowseriOS/CoreBrowser/UseCases/ReadTabsUseCaseImpl.swift @@ -37,7 +37,7 @@ public final class ReadTabsUseCaseImpl: ReadTabsUseCase { } } - public var allTabs: [Tab] { + public var allTabs: [CoreBrowser.Tab] { get async { let response = await tabsDataService.sendCommand(.getAllTabs) guard case .allTabs(let value) = response else { diff --git a/catowseriOS/CoreBrowser/UseCases/WriteTabsUseCase.swift b/catowseriOS/CoreBrowser/UseCases/WriteTabsUseCase.swift index 55487d20..f945619a 100644 --- a/catowseriOS/CoreBrowser/UseCases/WriteTabsUseCase.swift +++ b/catowseriOS/CoreBrowser/UseCases/WriteTabsUseCase.swift @@ -12,8 +12,8 @@ import AutoMockable /// Write tabs use case. /// Use cases do not hold any mutable state, so that, any of them can be sendable. public protocol WriteTabsUseCase: BaseUseCase, AutoMockable, Sendable { - /// Adds tab to memory and storage. Tab can be blank or it can contain URL address. - /// Tab will be added no matter what happen, so, function doesn't return any result. + /// Adds tab to memory and storage. CoreBrowser.Tab can be blank or it can contain URL address. + /// CoreBrowser.Tab will be added no matter what happen, so, function doesn't return any result. /// /// - Parameter tab: A tab. func add(tab: Tab) async diff --git a/catowseriOS/CoreBrowser/UseCases/WriteTabsUseCaseImpl.swift b/catowseriOS/CoreBrowser/UseCases/WriteTabsUseCaseImpl.swift index 00e57c76..9d5a3d7b 100644 --- a/catowseriOS/CoreBrowser/UseCases/WriteTabsUseCaseImpl.swift +++ b/catowseriOS/CoreBrowser/UseCases/WriteTabsUseCaseImpl.swift @@ -15,11 +15,11 @@ public final class WriteTabsUseCaseImpl: WriteTabsUseCase { self.tabsDataService = tabsDataService } - public func add(tab: Tab) async { + public func add(tab: CoreBrowser.Tab) async { _ = await tabsDataService.sendCommand(.addTab(tab)) } - public func close(tab: Tab) async { + public func close(tab: CoreBrowser.Tab) async { _ = await tabsDataService.sendCommand(.closeTab(tab)) } @@ -27,11 +27,11 @@ public final class WriteTabsUseCaseImpl: WriteTabsUseCase { _ = await tabsDataService.sendCommand(.closeAll) } - public func select(tab: Tab) async { + public func select(tab: CoreBrowser.Tab) async { _ = await tabsDataService.sendCommand(.selectTab(tab)) } - public func replaceSelected(_ tabContent: Tab.ContentType) async { + public func replaceSelected(_ tabContent: CoreBrowser.Tab.ContentType) async { _ = await tabsDataService.sendCommand(.replaceSelectedContent(tabContent)) } } diff --git a/catowseriOS/CoreBrowserTests/Sourcery/Templates/AutoMockable.stencil b/catowseriOS/CoreBrowserTests/Sourcery/Templates/AutoMockable.stencil index 9d898abc..907f3ce0 100644 --- a/catowseriOS/CoreBrowserTests/Sourcery/Templates/AutoMockable.stencil +++ b/catowseriOS/CoreBrowserTests/Sourcery/Templates/AutoMockable.stencil @@ -2,7 +2,7 @@ import Foundation import CoreBrowser -import ReactiveSwift +@preconcurrency import ReactiveSwift import CottonBase {% for import in argument.imports %} diff --git a/catowseriOS/CoreBrowserTests/TabsDataServiceTests.swift b/catowseriOS/CoreBrowserTests/TabsDataServiceTests.swift index 891c9a70..20f0b7dd 100644 --- a/catowseriOS/CoreBrowserTests/TabsDataServiceTests.swift +++ b/catowseriOS/CoreBrowserTests/TabsDataServiceTests.swift @@ -74,9 +74,9 @@ class TabsDataServiceTests: XCTestCase { } func testInit() async throws { - let tab1: Tab = .init(contentType: .site(exampleSite), idenifier: exampleTabId) - let tab2: Tab = .init(contentType: .site(knownSite), idenifier: knownTabId) - let tabsV1: [Tab] = [tab1, tab2] + let tab1: CoreBrowser.Tab = .init(contentType: .site(exampleSite), idenifier: exampleTabId) + let tab2: CoreBrowser.Tab = .init(contentType: .site(knownSite), idenifier: knownTabId) + let tabsV1: [CoreBrowser.Tab] = [tab1, tab2] tabsStates.defaultSelectedTabId = .notPossibleId tabsStorageMock.fetchAllTabsReturnValue = tabsV1 diff --git a/catowseriOS/CottonData/RestClientContext.swift b/catowseriOS/CottonData/RestClientContext.swift index 91c3e93e..187a7af8 100644 --- a/catowseriOS/CottonData/RestClientContext.swift +++ b/catowseriOS/CottonData/RestClientContext.swift @@ -8,7 +8,7 @@ import Foundation import CottonRestKit -import ReactiveSwift +@preconcurrency import ReactiveSwift import Combine import CottonBase import ReactiveHttpKit diff --git a/catowseriOS/CottonData/SearchViewModel/Autocomplete/AutocompleteSearchUseCase.swift b/catowseriOS/CottonData/SearchViewModel/Autocomplete/AutocompleteSearchUseCase.swift index 82dace91..07d2c206 100644 --- a/catowseriOS/CottonData/SearchViewModel/Autocomplete/AutocompleteSearchUseCase.swift +++ b/catowseriOS/CottonData/SearchViewModel/Autocomplete/AutocompleteSearchUseCase.swift @@ -8,7 +8,7 @@ import Foundation import CoreBrowser -import ReactiveSwift +@preconcurrency import ReactiveSwift import Combine import CottonRestKit import AutoMockable diff --git a/catowseriOS/CottonData/SearchViewModel/Autocomplete/AutocompleteSearchUseCaseImpl.swift b/catowseriOS/CottonData/SearchViewModel/Autocomplete/AutocompleteSearchUseCaseImpl.swift index bbcd1fd4..bece647e 100644 --- a/catowseriOS/CottonData/SearchViewModel/Autocomplete/AutocompleteSearchUseCaseImpl.swift +++ b/catowseriOS/CottonData/SearchViewModel/Autocomplete/AutocompleteSearchUseCaseImpl.swift @@ -7,7 +7,7 @@ // import Foundation -import ReactiveSwift +@preconcurrency import ReactiveSwift import Combine import CottonRestKit @@ -20,13 +20,15 @@ public final class AutocompleteSearchUseCaseImpl: AutocompleteSearchUs where Strategy: SearchAutocompleteStrategy { public let strategy: Strategy - private lazy var waitingQueue = DispatchQueue(label: .waitingQueueName) - private lazy var waitingScheduler = QueueScheduler(qos: .userInitiated, - name: .waitingQueueName, - targeting: waitingQueue) + private let waitingQueue: DispatchQueue + private let waitingScheduler: QueueScheduler public init(_ strategy: Strategy) { self.strategy = strategy + waitingQueue = DispatchQueue(label: .waitingQueueName) + waitingScheduler = QueueScheduler(qos: .userInitiated, + name: .waitingQueueName, + targeting: waitingQueue) } public func rxFetchSuggestions(_ query: String) -> WebSearchSuggestionsProducer { diff --git a/catowseriOS/CottonData/SearchViewModel/Autocomplete/SearchAutocompleteStrategy.swift b/catowseriOS/CottonData/SearchViewModel/Autocomplete/SearchAutocompleteStrategy.swift index 7592b1fb..a0e55b7f 100644 --- a/catowseriOS/CottonData/SearchViewModel/Autocomplete/SearchAutocompleteStrategy.swift +++ b/catowseriOS/CottonData/SearchViewModel/Autocomplete/SearchAutocompleteStrategy.swift @@ -8,7 +8,7 @@ import Foundation import CottonRestKit -import ReactiveSwift +@preconcurrency import ReactiveSwift import Combine import AutoMockable diff --git a/catowseriOS/CottonData/SearchViewModel/Autocomplete/SpecificStrategies/DDGoAutocompleteStrategy.swift b/catowseriOS/CottonData/SearchViewModel/Autocomplete/SpecificStrategies/DDGoAutocompleteStrategy.swift index 735d6e38..0a69cda9 100644 --- a/catowseriOS/CottonData/SearchViewModel/Autocomplete/SpecificStrategies/DDGoAutocompleteStrategy.swift +++ b/catowseriOS/CottonData/SearchViewModel/Autocomplete/SpecificStrategies/DDGoAutocompleteStrategy.swift @@ -8,7 +8,7 @@ import Foundation import BrowserNetworking -import ReactiveSwift +@preconcurrency import ReactiveSwift import Combine import CottonRestKit import Alamofire diff --git a/catowseriOS/CottonData/SearchViewModel/Autocomplete/SpecificStrategies/GoogleAutocompleteStrategy.swift b/catowseriOS/CottonData/SearchViewModel/Autocomplete/SpecificStrategies/GoogleAutocompleteStrategy.swift index 3febc152..f2d57890 100644 --- a/catowseriOS/CottonData/SearchViewModel/Autocomplete/SpecificStrategies/GoogleAutocompleteStrategy.swift +++ b/catowseriOS/CottonData/SearchViewModel/Autocomplete/SpecificStrategies/GoogleAutocompleteStrategy.swift @@ -8,7 +8,7 @@ import Foundation import BrowserNetworking -import ReactiveSwift +@preconcurrency import ReactiveSwift import Combine import CottonRestKit import Alamofire diff --git a/catowseriOS/CottonData/WebViewModel/DNSResolving/DNSResolvingStrategy.swift b/catowseriOS/CottonData/WebViewModel/DNSResolving/DNSResolvingStrategy.swift index 872751ed..13eb9d4a 100644 --- a/catowseriOS/CottonData/WebViewModel/DNSResolving/DNSResolvingStrategy.swift +++ b/catowseriOS/CottonData/WebViewModel/DNSResolving/DNSResolvingStrategy.swift @@ -8,7 +8,7 @@ import Foundation import CottonRestKit -import ReactiveSwift +@preconcurrency import ReactiveSwift import Combine import AutoMockable diff --git a/catowseriOS/CottonData/WebViewModel/DNSResolving/ResolveDNSUseCase.swift b/catowseriOS/CottonData/WebViewModel/DNSResolving/ResolveDNSUseCase.swift index af568bd7..ced7a40f 100644 --- a/catowseriOS/CottonData/WebViewModel/DNSResolving/ResolveDNSUseCase.swift +++ b/catowseriOS/CottonData/WebViewModel/DNSResolving/ResolveDNSUseCase.swift @@ -8,7 +8,7 @@ import Foundation import CoreBrowser -import ReactiveSwift +@preconcurrency import ReactiveSwift import Combine import CottonRestKit import AutoMockable diff --git a/catowseriOS/CottonData/WebViewModel/DNSResolving/ResolveDNSUseCaseImpl.swift b/catowseriOS/CottonData/WebViewModel/DNSResolving/ResolveDNSUseCaseImpl.swift index e6cf726f..80a07e92 100644 --- a/catowseriOS/CottonData/WebViewModel/DNSResolving/ResolveDNSUseCaseImpl.swift +++ b/catowseriOS/CottonData/WebViewModel/DNSResolving/ResolveDNSUseCaseImpl.swift @@ -7,7 +7,7 @@ // import Foundation -import ReactiveSwift +@preconcurrency import ReactiveSwift import Combine import CottonRestKit diff --git a/catowseriOS/CottonData/WebViewModel/DNSResolving/SpecificStrategies/GoogleDNSStrategy.swift b/catowseriOS/CottonData/WebViewModel/DNSResolving/SpecificStrategies/GoogleDNSStrategy.swift index 7433ef77..d5e6a06f 100644 --- a/catowseriOS/CottonData/WebViewModel/DNSResolving/SpecificStrategies/GoogleDNSStrategy.swift +++ b/catowseriOS/CottonData/WebViewModel/DNSResolving/SpecificStrategies/GoogleDNSStrategy.swift @@ -8,7 +8,7 @@ import Foundation import BrowserNetworking -import ReactiveSwift +@preconcurrency import ReactiveSwift import Combine import CottonRestKit import Alamofire diff --git a/catowseriOS/CottonDataTests/Fixtures/SearchSuggestionsVMFixture.swift b/catowseriOS/CottonDataTests/Fixtures/SearchSuggestionsVMFixture.swift index 7931d9f7..26c6086d 100644 --- a/catowseriOS/CottonDataTests/Fixtures/SearchSuggestionsVMFixture.swift +++ b/catowseriOS/CottonDataTests/Fixtures/SearchSuggestionsVMFixture.swift @@ -11,7 +11,7 @@ import XCTest import CottonBase import CottonRestKit import ReactiveHttpKit -import ReactiveSwift +@preconcurrency import ReactiveSwift import Combine import BrowserNetworking import SwiftyMocky diff --git a/catowseriOS/CottonDataTests/SearchSuggestionsVM/SearchSuggestionsVMConcurrencyTests.swift b/catowseriOS/CottonDataTests/SearchSuggestionsVM/SearchSuggestionsVMConcurrencyTests.swift index 959caa76..def31c42 100644 --- a/catowseriOS/CottonDataTests/SearchSuggestionsVM/SearchSuggestionsVMConcurrencyTests.swift +++ b/catowseriOS/CottonDataTests/SearchSuggestionsVM/SearchSuggestionsVMConcurrencyTests.swift @@ -11,7 +11,7 @@ import XCTest import CottonBase import CottonRestKit import ReactiveHttpKit -import ReactiveSwift +@preconcurrency import ReactiveSwift import BrowserNetworking import Combine import SwiftyMocky diff --git a/catowseriOS/CottonDataTests/WebSearchAutocompleteTests.swift b/catowseriOS/CottonDataTests/WebSearchAutocompleteTests.swift index 2526fe2b..336a09d9 100644 --- a/catowseriOS/CottonDataTests/WebSearchAutocompleteTests.swift +++ b/catowseriOS/CottonDataTests/WebSearchAutocompleteTests.swift @@ -11,7 +11,7 @@ import XCTest import CottonBase import CottonRestKit import ReactiveHttpKit -import ReactiveSwift +@preconcurrency import ReactiveSwift import Combine import BrowserNetworking import SwiftyMocky diff --git a/catowseriOS/CottonPlugins/JSPluginFactory.swift b/catowseriOS/CottonPlugins/JSPluginFactory.swift index 6cd02a6f..2245d473 100644 --- a/catowseriOS/CottonPlugins/JSPluginFactory.swift +++ b/catowseriOS/CottonPlugins/JSPluginFactory.swift @@ -9,30 +9,28 @@ import Foundation import WebKit -@globalActor +@MainActor final class JSPluginFactory { - static let shared = StateHolder() + static let shared = JSPluginFactory() - actor StateHolder { - private let scripts = NSCache() + private let scripts = NSCache() - func script(for plugin: any JavaScriptPlugin, - with injectionTime: WKUserScriptInjectionTime, - isMainFrameOnly: Bool) throws -> WKUserScript { - let typeName = plugin.jsFileName - if let existingJS = scripts.object(forKey: typeName as NSString) { - return existingJS - } else { - let source = try Self.loadScriptSource(typeName) - let wkScript = WKUserScript(source: source, injectionTime: injectionTime, forMainFrameOnly: isMainFrameOnly) - scripts.setObject(wkScript, forKey: typeName as NSString) - return wkScript - } + func script(for plugin: any JavaScriptPlugin, + with injectionTime: WKUserScriptInjectionTime, + isMainFrameOnly: Bool) throws -> WKUserScript { + let typeName = plugin.jsFileName + if let existingJS = scripts.object(forKey: typeName as NSString) { + return existingJS + } else { + let source = try Self.loadScriptSource(typeName) + let wkScript = WKUserScript(source: source, injectionTime: injectionTime, forMainFrameOnly: isMainFrameOnly) + scripts.setObject(wkScript, forKey: typeName as NSString) + return wkScript } } } -fileprivate extension JSPluginFactory.StateHolder { +fileprivate extension JSPluginFactory { static func loadScriptSource(_ resourceName: String) throws -> String { guard let filepath = Bundle.init(for: self).path(forResource: resourceName, ofType: "js") else { print("\(resourceName).js not found!") diff --git a/catowseriOS/CottonPlugins/JavaScriptEvaluateble.swift b/catowseriOS/CottonPlugins/JavaScriptEvaluateble.swift index 75ca4903..b1df8085 100644 --- a/catowseriOS/CottonPlugins/JavaScriptEvaluateble.swift +++ b/catowseriOS/CottonPlugins/JavaScriptEvaluateble.swift @@ -8,7 +8,7 @@ import Foundation import Combine -import ReactiveSwift +@preconcurrency import ReactiveSwift public protocol JavaScriptEvaluateble: AnyObject { func evaluateJavaScript(_ javaScriptString: String, completionHandler: ((Any?, Error?) -> Void)?) diff --git a/catowseriOS/CottonRestKit/NetworkReachabilityAdapter.swift b/catowseriOS/CottonRestKit/NetworkReachabilityAdapter.swift index 4d44c3f1..53bd36a9 100644 --- a/catowseriOS/CottonRestKit/NetworkReachabilityAdapter.swift +++ b/catowseriOS/CottonRestKit/NetworkReachabilityAdapter.swift @@ -9,7 +9,7 @@ import CottonBase import AutoMockable -public enum NetworkReachabilityStatus { +public enum NetworkReachabilityStatus: Sendable { /// It is unknown whether the network is reachable. case unknown /// The network is not reachable. @@ -18,7 +18,7 @@ public enum NetworkReachabilityStatus { case reachable(ConnectionType) /// Defines the various connection types detected by reachability flags. - public enum ConnectionType { + public enum ConnectionType: Sendable { /// The connection type is either over Ethernet or WiFi. case ethernetOrWiFi /// The connection type is a cellular connection. diff --git a/catowseriOS/FeaturesFlagsKit/FeatureManager.swift b/catowseriOS/FeaturesFlagsKit/FeatureManager.swift index d4b63012..9c3af68b 100644 --- a/catowseriOS/FeaturesFlagsKit/FeatureManager.swift +++ b/catowseriOS/FeaturesFlagsKit/FeatureManager.swift @@ -8,7 +8,7 @@ import Foundation import CoreBrowser -import ReactiveSwift +@preconcurrency import ReactiveSwift import Combine @globalActor diff --git a/catowseriOS/FeaturesFlagsKit/LocalFeatureSource.swift b/catowseriOS/FeaturesFlagsKit/LocalFeatureSource.swift index cee03a8c..558e0485 100644 --- a/catowseriOS/FeaturesFlagsKit/LocalFeatureSource.swift +++ b/catowseriOS/FeaturesFlagsKit/LocalFeatureSource.swift @@ -7,7 +7,7 @@ // import Foundation -import ReactiveSwift +@preconcurrency import ReactiveSwift import Combine /// FeatureSource that uses UserDefaults diff --git a/catowseriOS/FeaturesFlagsKit/Models/EnumFeatureSource.swift b/catowseriOS/FeaturesFlagsKit/Models/EnumFeatureSource.swift index c8f63ad2..aadff966 100644 --- a/catowseriOS/FeaturesFlagsKit/Models/EnumFeatureSource.swift +++ b/catowseriOS/FeaturesFlagsKit/Models/EnumFeatureSource.swift @@ -6,7 +6,7 @@ // Copyright © 2022 Cotton (former Catowser). All rights reserved. // -import ReactiveSwift +@preconcurrency import ReactiveSwift #if canImport(Combine) import Combine #endif diff --git a/catowseriOS/FeaturesFlagsKit/Models/FeatureSource.swift b/catowseriOS/FeaturesFlagsKit/Models/FeatureSource.swift index fb722ad4..0a39a83b 100644 --- a/catowseriOS/FeaturesFlagsKit/Models/FeatureSource.swift +++ b/catowseriOS/FeaturesFlagsKit/Models/FeatureSource.swift @@ -6,7 +6,7 @@ // Copyright © 2022 Cotton/Catowser Andrei Ermoshin. All rights reserved. // -import ReactiveSwift +@preconcurrency import ReactiveSwift #if canImport(Combine) import Combine #endif diff --git a/catowseriOS/ReactiveHttpKit/HTTPAdapter+Rx.swift b/catowseriOS/ReactiveHttpKit/HTTPAdapter+Rx.swift index 05048718..1d426d7a 100644 --- a/catowseriOS/ReactiveHttpKit/HTTPAdapter+Rx.swift +++ b/catowseriOS/ReactiveHttpKit/HTTPAdapter+Rx.swift @@ -7,7 +7,7 @@ // import CottonRestKit -import ReactiveSwift +@preconcurrency import ReactiveSwift import CottonBase extension HTTPRxAdapter { diff --git a/catowseriOS/ReactiveHttpKit/HttpClient+RxSwift.swift b/catowseriOS/ReactiveHttpKit/HttpClient+RxSwift.swift index 6681e7ab..6a0f920f 100644 --- a/catowseriOS/ReactiveHttpKit/HttpClient+RxSwift.swift +++ b/catowseriOS/ReactiveHttpKit/HttpClient+RxSwift.swift @@ -7,7 +7,7 @@ // import CottonRestKit -import ReactiveSwift +@preconcurrency import ReactiveSwift import CottonBase /// This typealias could be an issue, because the same defined in BrowserNetworking HttpClient+Alamofire.swift diff --git a/catowseriOS/ReactiveHttpKit/RxObserverWrapper.swift b/catowseriOS/ReactiveHttpKit/RxObserverWrapper.swift index a21db9f4..d10067b7 100644 --- a/catowseriOS/ReactiveHttpKit/RxObserverWrapper.swift +++ b/catowseriOS/ReactiveHttpKit/RxObserverWrapper.swift @@ -7,7 +7,7 @@ // import CottonRestKit -import ReactiveSwift +@preconcurrency import ReactiveSwift import CottonBase extension Signal.Observer: RxAnyObserver where Value: ResponseType, Error == HttpError { diff --git a/catowseriOS/ReactiveHttpKit/URL+Rx.swift b/catowseriOS/ReactiveHttpKit/URL+Rx.swift index f5991db0..9df52913 100644 --- a/catowseriOS/ReactiveHttpKit/URL+Rx.swift +++ b/catowseriOS/ReactiveHttpKit/URL+Rx.swift @@ -6,7 +6,7 @@ // Copyright © 2022 Cotton/Catowser Andrei Ermoshin. All rights reserved. // -import ReactiveSwift +@preconcurrency import ReactiveSwift import CottonRestKit public typealias HostProducer = SignalProducer diff --git a/catowseriOS/catowser/Browser/MainBrowserV2ViewController.swift b/catowseriOS/catowser/Browser/MainBrowserV2ViewController.swift index 8263441d..20a09e5b 100644 --- a/catowseriOS/catowser/Browser/MainBrowserV2ViewController.swift +++ b/catowseriOS/catowser/Browser/MainBrowserV2ViewController.swift @@ -38,7 +38,7 @@ final class MainBrowserV2ViewController< init(_ coordinator: C, _ uiFramework: UIFrameworkType, - _ defaultContent: Tab.ContentType, + _ defaultContent: CoreBrowser.Tab.ContentType, _ allTabsVM: AllTabsViewModel, _ topSitesVM: TopSitesViewModel, _ searchSuggestionsVM: S, diff --git a/catowseriOS/catowser/Browser/View/MainBrowserView.swift b/catowseriOS/catowser/Browser/View/MainBrowserView.swift index b5eddca1..700ace1a 100644 --- a/catowseriOS/catowser/Browser/View/MainBrowserView.swift +++ b/catowseriOS/catowser/Browser/View/MainBrowserView.swift @@ -54,11 +54,11 @@ struct MainBrowserView /// Web view model without a specific site @StateObject private var webVM: W /// Default content type is determined in async way, so, would be good to pass it like this - private let defaultContentType: Tab.ContentType + private let defaultContentType: CoreBrowser.Tab.ContentType init(_ coordinatorsInterface: C, _ uiFrameworkType: UIFrameworkType, - _ defaultContentType: Tab.ContentType, + _ defaultContentType: CoreBrowser.Tab.ContentType, _ allTabsVM: AllTabsViewModel, _ topSitesVM: TopSitesViewModel, _ searchSuggestionsVM: S, diff --git a/catowseriOS/catowser/Browser/View/PhoneView.swift b/catowseriOS/catowser/Browser/View/PhoneView.swift index 757db07f..8919c2a1 100644 --- a/catowseriOS/catowser/Browser/View/PhoneView.swift +++ b/catowseriOS/catowser/Browser/View/PhoneView.swift @@ -47,7 +47,7 @@ struct PhoneView: View { // MARK: - browser content state @State private var isLoading: Bool = true - @State private var contentType: Tab.ContentType + @State private var contentType: CoreBrowser.Tab.ContentType /// A workaround to avoid unnecessary web view updates @State private var webViewNeedsUpdate: Bool = false @@ -83,7 +83,7 @@ struct PhoneView: View { return MenuViewModel(style, isDohEnabled, isJavaScriptEnabled, nativeAppRedirectEnabled) } - init(_ mode: SwiftUIMode, _ defaultContentType: Tab.ContentType, _ webVM: W, _ searchVM: S) { + init(_ mode: SwiftUIMode, _ defaultContentType: CoreBrowser.Tab.ContentType, _ webVM: W, _ searchVM: S) { self.webVM = webVM self.searchSuggestionsVM = searchVM searchBarAction = .clearView diff --git a/catowseriOS/catowser/Browser/View/TabletView.swift b/catowseriOS/catowser/Browser/View/TabletView.swift index c0f53d7f..79c14ded 100644 --- a/catowseriOS/catowser/Browser/View/TabletView.swift +++ b/catowseriOS/catowser/Browser/View/TabletView.swift @@ -51,7 +51,7 @@ struct TabletView: View { // MARK: - browser content state @State private var isLoading: Bool = true - @State private var contentType: Tab.ContentType + @State private var contentType: CoreBrowser.Tab.ContentType /// A workaround to avoid unnecessary web view updates @State private var webViewNeedsUpdate: Bool = false @@ -81,7 +81,7 @@ struct TabletView: View { } init(_ mode: SwiftUIMode, - _ defaultContentType: Tab.ContentType, + _ defaultContentType: CoreBrowser.Tab.ContentType, _ webVM: W, _ searchVM: S) { self.webVM = webVM diff --git a/catowseriOS/catowser/BrowserContent/BrowserContentView.swift b/catowseriOS/catowser/BrowserContent/BrowserContentView.swift index c0f1fada..277c405e 100644 --- a/catowseriOS/catowser/BrowserContent/BrowserContentView.swift +++ b/catowseriOS/catowser/BrowserContent/BrowserContentView.swift @@ -19,7 +19,7 @@ struct BrowserContentView: View { /// but the reference needs to be holded/created by another vm on upper level private let jsPluginsBuilder: any JSPluginsSource /// The main state of the browser content view - private let contentType: Tab.ContentType + private let contentType: CoreBrowser.Tab.ContentType /// Determines if the state is still loading to not show wrong content type (like default one). /// Depends on main view state, because this model's init is getting called unexpectedly. private let isLoading: Bool @@ -37,7 +37,7 @@ struct BrowserContentView: View { init(_ jsPluginsBuilder: any JSPluginsSource, _ siteNavigation: SiteExternalNavigationDelegate?, _ isLoading: Bool, - _ contentType: Tab.ContentType, + _ contentType: CoreBrowser.Tab.ContentType, _ webViewNeedsUpdate: Binding, _ mode: SwiftUIMode, _ webVM: W) { diff --git a/catowseriOS/catowser/BrowserContent/BrowserContentViewModel.swift b/catowseriOS/catowser/BrowserContent/BrowserContentViewModel.swift index 7a8a7ee9..3b1a3906 100644 --- a/catowseriOS/catowser/BrowserContent/BrowserContentViewModel.swift +++ b/catowseriOS/catowser/BrowserContent/BrowserContentViewModel.swift @@ -14,7 +14,7 @@ import CottonPlugins /// This reference type should be used to update the view if content changes. final class BrowserContentViewModel: ObservableObject { /// View content type. https://stackoverflow.com/a/56724174 - @Published var contentType: Tab.ContentType + @Published var contentType: CoreBrowser.Tab.ContentType /// Tab's content loading @Published var loading: Bool /// Web view needs an update after changing selected tab content. @@ -26,11 +26,11 @@ final class BrowserContentViewModel: ObservableObject { let jsPluginsBuilder: any JSPluginsSource /// Not initialized, will be initialized after `TabsListManager` /// during tab opening. Used only during tab opening for optimization - private var previousTabContent: Tab.ContentType? + private var previousTabContent: CoreBrowser.Tab.ContentType? /// To avoid app start case private var firstTabContentSelect: Bool - init(_ jsPluginsBuilder: any JSPluginsSource, _ defaultContentType: Tab.ContentType) { + init(_ jsPluginsBuilder: any JSPluginsSource, _ defaultContentType: CoreBrowser.Tab.ContentType) { firstTabContentSelect = true self.jsPluginsBuilder = jsPluginsBuilder self.contentType = defaultContentType @@ -41,7 +41,7 @@ final class BrowserContentViewModel: ObservableObject { } extension BrowserContentViewModel: TabsObserver { - func tabDidSelect(_ index: Int, _ content: Tab.ContentType, _ identifier: UUID) async { + func tabDidSelect(_ index: Int, _ content: CoreBrowser.Tab.ContentType, _ identifier: UUID) async { if let previousValue = previousTabContent, previousValue.isStatic && previousValue == content { // Optimization to not do remove & insert of the same static view return @@ -66,7 +66,7 @@ extension BrowserContentViewModel: TabsObserver { } } - func tabDidReplace(_ tab: Tab, at index: Int) async { + func tabDidReplace(_ tab: CoreBrowser.Tab, at index: Int) async { if loading { loading = false } diff --git a/catowseriOS/catowser/BrowserContent/TopSites/TopSitesViewModel.swift b/catowseriOS/catowser/BrowserContent/TopSites/TopSitesViewModel.swift index d9e3f4e6..faf7fbc4 100644 --- a/catowseriOS/catowser/BrowserContent/TopSites/TopSitesViewModel.swift +++ b/catowseriOS/catowser/BrowserContent/TopSites/TopSitesViewModel.swift @@ -20,7 +20,7 @@ final class TopSitesViewModel: ObservableObject { self.writeTabUseCase = writeTabUseCase } - func replaceSelected(tabContent: Tab.ContentType) { + func replaceSelected(tabContent: CoreBrowser.Tab.ContentType) { Task { _ = await writeTabUseCase.replaceSelected(tabContent) } diff --git a/catowseriOS/catowser/BrowserContent/WebView/WebViewController.swift b/catowseriOS/catowser/BrowserContent/WebView/WebViewController.swift index 882d9ca4..8f7ce18f 100644 --- a/catowseriOS/catowser/BrowserContent/WebView/WebViewController.swift +++ b/catowseriOS/catowser/BrowserContent/WebView/WebViewController.swift @@ -13,7 +13,7 @@ import CottonPlugins import CottonBase import BrowserNetworking import FeaturesFlagsKit -import ReactiveSwift +@preconcurrency import ReactiveSwift #if canImport(Combine) import Combine #endif diff --git a/catowseriOS/catowser/Coordinators/AppCoordinator.swift b/catowseriOS/catowser/Coordinators/AppCoordinator.swift index 6bbd5d22..383394ea 100644 --- a/catowseriOS/catowser/Coordinators/AppCoordinator.swift +++ b/catowseriOS/catowser/Coordinators/AppCoordinator.swift @@ -61,7 +61,7 @@ final class AppCoordinator: Coordinator, BrowserContentCoordinators { }() /// Not initialized, will be initialized after `TabsListManager` /// during tab opening. Used only during tab opening for optimization - private var previousTabContent: Tab.ContentType? + private var previousTabContent: CoreBrowser.Tab.ContentType? /// Not a constant because can't be initialized in init private var jsPluginsBuilder: (any JSPluginsSource)? @@ -179,7 +179,7 @@ extension AppCoordinator: CoordinatorOwner { enum MainScreenRoute: Route { case menu(MenuViewModel, UIView, CGRect) - case openTab(Tab.ContentType) + case openTab(CoreBrowser.Tab.ContentType) } extension AppCoordinator: Navigating { @@ -567,7 +567,7 @@ private extension AppCoordinator { startedCoordinator = coordinator } - func open(tabContent: Tab.ContentType) { + func open(tabContent: CoreBrowser.Tab.ContentType) { linkTagsCoordinator?.showNext(.closeTags) // hide suggestions as well searchBarCoordinator?.showNext(.hideSuggestions) @@ -625,11 +625,11 @@ private extension AppCoordinator { } extension AppCoordinator: TabsObserver { - func tabDidSelect(_ index: Int, _ content: Tab.ContentType, _ identifier: UUID) async { + func tabDidSelect(_ index: Int, _ content: CoreBrowser.Tab.ContentType, _ identifier: UUID) async { open(tabContent: content) } - func tabDidReplace(_ tab: Tab, at index: Int) async { + func tabDidReplace(_ tab: CoreBrowser.Tab, at index: Int) async { switch previousTabContent { case .site: break @@ -685,7 +685,7 @@ extension AppCoordinator: WebContentDelegate { } extension AppCoordinator: SearchBarDelegate { - func openTab(_ content: Tab.ContentType) { + func openTab(_ content: CoreBrowser.Tab.ContentType) { showNext(.openTab(content)) } diff --git a/catowseriOS/catowser/Coordinators/MainToolbarCoordinator.swift b/catowseriOS/catowser/Coordinators/MainToolbarCoordinator.swift index e11a85ef..d1c917f2 100644 --- a/catowseriOS/catowser/Coordinators/MainToolbarCoordinator.swift +++ b/catowseriOS/catowser/Coordinators/MainToolbarCoordinator.swift @@ -166,7 +166,7 @@ extension MainToolbarCoordinator: PhoneTabsDelegate { func didTabAdd() async { let contentState = await DefaultTabProvider.shared.contentState - let tab = Tab(contentType: contentState) + let tab = CoreBrowser.Tab(contentType: contentState) /// newly added tab moves selection to itself /// so, it is opened by manager by default /// but user maybe don't want to move that tab right away diff --git a/catowseriOS/catowser/Coordinators/PhoneTabsCoordinator.swift b/catowseriOS/catowser/Coordinators/PhoneTabsCoordinator.swift index bd2fb925..752d16a7 100644 --- a/catowseriOS/catowser/Coordinators/PhoneTabsCoordinator.swift +++ b/catowseriOS/catowser/Coordinators/PhoneTabsCoordinator.swift @@ -11,7 +11,7 @@ import CoreBrowser import FeaturesFlagsKit protocol PhoneTabsDelegate: AnyObject { - func didTabSelect(_ tab: Tab) async + func didTabSelect(_ tab: CoreBrowser.Tab) async func didTabAdd() async } @@ -58,7 +58,7 @@ final class PhoneTabsCoordinator: Coordinator { enum TabsScreenRoute: Route { case error - case selectTab(Tab) + case selectTab(CoreBrowser.Tab) case addTab } @@ -91,7 +91,7 @@ extension PhoneTabsCoordinator: Navigating { } private extension PhoneTabsCoordinator { - func showSelected(_ tab: Tab) { + func showSelected(_ tab: CoreBrowser.Tab) { Task { await delegate?.didTabSelect(tab) } diff --git a/catowseriOS/catowser/Coordinators/SearchBarCoordinator.swift b/catowseriOS/catowser/Coordinators/SearchBarCoordinator.swift index 12b93211..e8ac9d2d 100644 --- a/catowseriOS/catowser/Coordinators/SearchBarCoordinator.swift +++ b/catowseriOS/catowser/Coordinators/SearchBarCoordinator.swift @@ -15,7 +15,7 @@ import CottonData @MainActor protocol SearchBarDelegate: AnyObject { - func openTab(_ content: Tab.ContentType) + func openTab(_ content: CoreBrowser.Tab.ContentType) func layoutSuggestions() } diff --git a/catowseriOS/catowser/Coordinators/SearchSuggestionsCoordinator.swift b/catowseriOS/catowser/Coordinators/SearchSuggestionsCoordinator.swift index 5a17d73a..d1618a81 100644 --- a/catowseriOS/catowser/Coordinators/SearchSuggestionsCoordinator.swift +++ b/catowseriOS/catowser/Coordinators/SearchSuggestionsCoordinator.swift @@ -7,7 +7,7 @@ // import UIKit -import ReactiveSwift +@preconcurrency import ReactiveSwift import FeaturesFlagsKit import CottonData diff --git a/catowseriOS/catowser/DB/CottonDbModel.xcdatamodeld/CottonDbModel.xcdatamodel/contents b/catowseriOS/catowser/DB/CottonDbModel.xcdatamodeld/CottonDbModel.xcdatamodel/contents index f4598e91..86bff4d0 100644 --- a/catowseriOS/catowser/DB/CottonDbModel.xcdatamodeld/CottonDbModel.xcdatamodel/contents +++ b/catowseriOS/catowser/DB/CottonDbModel.xcdatamodeld/CottonDbModel.xcdatamodel/contents @@ -1,14 +1,20 @@ - + + + + + + + - + @@ -17,16 +23,4 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/catowseriOS/catowser/DB/TabsDBClient.swift b/catowseriOS/catowser/DB/TabsDBClient.swift index 6357f55d..ad422ad5 100644 --- a/catowseriOS/catowser/DB/TabsDBClient.swift +++ b/catowseriOS/catowser/DB/TabsDBClient.swift @@ -25,7 +25,7 @@ final class TabsDBClient { } /// Adds the tab without selecting it - func insert(tab: Tab) throws { + func insert(tab: CoreBrowser.Tab) throws { var saveError: Error? managedContext.performAndWait { _ = CDTab(context: managedContext, tab: tab) @@ -42,7 +42,7 @@ final class TabsDBClient { /// Updates existing tab record with new content. /// E.g. when it was topSites and now it is actual web site - func update(tab: Tab) throws { + func update(tab: CoreBrowser.Tab) throws { var saveError: Error? let fetchRequest: NSFetchRequest = CDTab.fetchRequest() let query = NSPredicate(format: "%K = %@", "id", tab.id as CVarArg) @@ -77,7 +77,7 @@ final class TabsDBClient { /// Removes the tab, if it was selected it doesn't do de-selection logic /// De-selection should happen on application side because different auto-selection /// strategies could be used and it shouldn't be performed as a side-effect - func remove(tab: Tab) throws { + func remove(tab: CoreBrowser.Tab) throws { var cdError: Error? let fetchRequest: NSFetchRequest = CDTab.fetchRequest() let query = NSPredicate(format: "%K = %@", "id", tab.id as CVarArg) @@ -96,7 +96,7 @@ final class TabsDBClient { } /// Removes all the records of tabs - func removeAll(tabs: [Tab]) throws { + func removeAll(tabs: [CoreBrowser.Tab]) throws { var cdError: Error? let fetchRequest: NSFetchRequest = CDTab.fetchRequest() let query = NSPredicate(format: "id = %@", argumentArray: tabs.map {$0.id}) @@ -115,7 +115,7 @@ final class TabsDBClient { } /// Updates selected tab identifier using it from the `tab` provided as a argument - func select(tab: Tab) throws { + func select(tab: CoreBrowser.Tab) throws { try setSelectedTab(uuid: tab.id) } @@ -194,7 +194,7 @@ fileprivate extension Site { } } -fileprivate extension Tab { +fileprivate extension CoreBrowser.Tab { init?(cdTab: CDTab) { let cachedSite: Site? if let cdSite = cdTab.site { @@ -203,7 +203,7 @@ fileprivate extension Tab { cachedSite = nil } - guard let cachedContentType = Tab.ContentType.create(rawValue: cdTab.contentType, site: cachedSite) else { + guard let cachedContentType = CoreBrowser.Tab.ContentType.create(rawValue: cdTab.contentType, site: cachedSite) else { return nil } guard let identifier = cdTab.id else { @@ -220,7 +220,7 @@ fileprivate extension Tab { } fileprivate extension CDTab { - convenience init(context: NSManagedObjectContext, tab: Tab) { + convenience init(context: NSManagedObjectContext, tab: CoreBrowser.Tab) { self.init(context: context) id = tab.id contentType = tab.contentType.rawValue @@ -260,15 +260,15 @@ fileprivate extension CDAppSettings { extension TabsDBClient { /// Gets all stored tabs - func fetchAllTabs() async throws -> [Tab] { + func fetchAllTabs() async throws -> [CoreBrowser.Tab] { return try await managedContext.perform { let request: NSFetchRequest = CDTab.fetchRequest() let result = try request.execute() - return result.compactMap {Tab(cdTab: $0)} + return result.compactMap {CoreBrowser.Tab(cdTab: $0)} } } - func insert(tab: Tab) async throws { + func insert(tab: CoreBrowser.Tab) async throws { return try await managedContext.perform { [weak managedContext] in guard let managedContext else { return @@ -278,7 +278,7 @@ extension TabsDBClient { } } - func select(tab: Tab) async throws { + func select(tab: CoreBrowser.Tab) async throws { try await setSettingsSelectedTabId(tab.id) } diff --git a/catowseriOS/catowser/DB/TabsResource.swift b/catowseriOS/catowser/DB/TabsResource.swift index 9ef4e9f2..0d1e9ccb 100644 --- a/catowseriOS/catowser/DB/TabsResource.swift +++ b/catowseriOS/catowser/DB/TabsResource.swift @@ -50,7 +50,7 @@ final class TabsResource { } /// Updates tab content if tab with same identifier was found in DB or creates completely new tab - func update(tab: Tab) throws -> Tab { + func update(tab: CoreBrowser.Tab) throws -> CoreBrowser.Tab { guard isStoreInitialized else { throw TabResourceError.storeNotInitializedYet } @@ -64,7 +64,7 @@ final class TabsResource { } /// Remove all the tabs - func forget(tabs: [Tab]) async throws -> [Tab] { + func forget(tabs: [CoreBrowser.Tab]) async throws -> [CoreBrowser.Tab] { guard isStoreInitialized else { throw TabResourceError.storeNotInitializedYet } @@ -82,7 +82,7 @@ final class TabsResource { } /// Remembers tab identifier as selected one - func selectTab(_ tab: Tab) async throws { + func selectTab(_ tab: CoreBrowser.Tab) async throws { guard isStoreInitialized else { throw TabResourceError.storeNotInitializedYet } @@ -97,14 +97,14 @@ final class TabsResource { /// Gets all tabs recorded in DB. Currently there is only one session, but later /// it should be possible to store and read tabs from different sessions like /// private browser session tabs & usual tabs. - func tabsFromLastSession() async throws -> [Tab] { + func tabsFromLastSession() async throws -> [CoreBrowser.Tab] { guard isStoreInitialized else { throw TabResourceError.storeNotInitializedYet } return try await dbClient.fetchAllTabs() } - func remember(tab: Tab, andSelect select: Bool) async throws -> Tab { + func remember(tab: CoreBrowser.Tab, andSelect select: Bool) async throws -> CoreBrowser.Tab { guard isStoreInitialized else { throw TabResourceError.storeNotInitializedYet } diff --git a/catowseriOS/catowser/Downloads/FileDownloadViewModel.swift b/catowseriOS/catowser/Downloads/FileDownloadViewModel.swift index 21177ca5..94d9fe22 100644 --- a/catowseriOS/catowser/Downloads/FileDownloadViewModel.swift +++ b/catowseriOS/catowser/Downloads/FileDownloadViewModel.swift @@ -7,7 +7,7 @@ // import Foundation -import ReactiveSwift +@preconcurrency import ReactiveSwift // needed for `Downloadable` import BrowserNetworking diff --git a/catowseriOS/catowser/Downloads/Views/DownloadButtonCellView.swift b/catowseriOS/catowser/Downloads/Views/DownloadButtonCellView.swift index 384cde7c..d89be012 100644 --- a/catowseriOS/catowser/Downloads/Views/DownloadButtonCellView.swift +++ b/catowseriOS/catowser/Downloads/Views/DownloadButtonCellView.swift @@ -8,7 +8,7 @@ import UIKit import AlamofireImage -import ReactiveSwift +@preconcurrency import ReactiveSwift import CoreBrowser final class DownloadButtonCellView: UITableViewCell { diff --git a/catowseriOS/catowser/Environment/ViewControllerFactory.swift b/catowseriOS/catowser/Environment/ViewControllerFactory.swift index 8a7a501e..2e938876 100644 --- a/catowseriOS/catowser/Environment/ViewControllerFactory.swift +++ b/catowseriOS/catowser/Environment/ViewControllerFactory.swift @@ -28,7 +28,7 @@ import CoreBrowser protocol ViewControllerFactory: AnyObject { func rootViewController(_ coordinator: AppCoordinator, _ uiFramework: UIFrameworkType, - _ defaultContentType: Tab.ContentType, + _ defaultContentType: CoreBrowser.Tab.ContentType, _ allTabsVM: AllTabsViewModel, _ topSitesVM: TopSitesViewModel, _ searchSuggestionsVM: S, @@ -87,7 +87,7 @@ protocol ViewControllerFactory: AnyObject { extension ViewControllerFactory { func rootViewController(_ coordinator: AppCoordinator, _ uiFramework: UIFrameworkType, - _ defaultContentType: Tab.ContentType, + _ defaultContentType: CoreBrowser.Tab.ContentType, _ allTabsVM: AllTabsViewModel, _ topSitesVM: TopSitesViewModel, _ searchSuggestionsVM: S, diff --git a/catowseriOS/catowser/Environment/ViewModelFactory.swift b/catowseriOS/catowser/Environment/ViewModelFactory.swift index 78a31120..2ceaa888 100644 --- a/catowseriOS/catowser/Environment/ViewModelFactory.swift +++ b/catowseriOS/catowser/Environment/ViewModelFactory.swift @@ -47,7 +47,7 @@ final class ViewModelFactory { return WebViewModelImpl(googleDnsUseCase, context, selectTabUseCase, writeUseCase, siteNavigation, site) } - func tabViewModel(_ tab: Tab) async -> TabViewModel { + func tabViewModel(_ tab: CoreBrowser.Tab) async -> TabViewModel { let readUseCase = await UseCaseFactory.shared.findUseCase(ReadTabsUseCase.self) let writeUseCase = await UseCaseFactory.shared.findUseCase(WriteTabsUseCase.self) return TabViewModel(tab, readUseCase, writeUseCase) diff --git a/catowseriOS/catowser/Extensions/Tab+Extension.swift b/catowseriOS/catowser/Extensions/Tab+Extension.swift index cb74fa3d..9dd24c56 100644 --- a/catowseriOS/catowser/Extensions/Tab+Extension.swift +++ b/catowseriOS/catowser/Extensions/Tab+Extension.swift @@ -1,5 +1,5 @@ // -// Tab+Extension.swift +// CoreBrowser.Tab+Extension.swift // catowser // // Created by Andrei Ermoshin on 10/18/22. @@ -9,7 +9,7 @@ import CoreBrowser import UIKit -extension Tab { +extension CoreBrowser.Tab { /// Preview image of the site if content is .site var preview: UIImage? { mutating get { diff --git a/catowseriOS/catowser/Search/SearchBarBaseViewController.swift b/catowseriOS/catowser/Search/SearchBarBaseViewController.swift index dd16cb83..dea5e66d 100644 --- a/catowseriOS/catowser/Search/SearchBarBaseViewController.swift +++ b/catowseriOS/catowser/Search/SearchBarBaseViewController.swift @@ -62,7 +62,7 @@ final class SearchBarBaseViewController: BaseViewController { } extension SearchBarBaseViewController: TabsObserver { - func tabDidReplace(_ tab: Tab, at index: Int) async { + func tabDidReplace(_ tab: CoreBrowser.Tab, at index: Int) async { // this also can be called on non active tab // but at the same time it really doesn't make sense // to replace site on tab which is not active @@ -71,7 +71,7 @@ extension SearchBarBaseViewController: TabsObserver { handleAction(.updateView(tab.title, tab.searchBarContent)) } - func tabDidSelect(_ index: Int, _ content: Tab.ContentType, _ identifier: UUID) async { + func tabDidSelect(_ index: Int, _ content: CoreBrowser.Tab.ContentType, _ identifier: UUID) async { switch content { case .site(let site): handleAction(.updateView(site.title, site.searchBarContent)) diff --git a/catowseriOS/catowser/Search/SearchBarState.swift b/catowseriOS/catowser/Search/SearchBarState.swift index 323b3c26..f566d9cf 100644 --- a/catowseriOS/catowser/Search/SearchBarState.swift +++ b/catowseriOS/catowser/Search/SearchBarState.swift @@ -20,7 +20,7 @@ enum SearchBarAction: Equatable { /// Update to clear state case clearView - static func create(_ value: Tab.ContentType) -> SearchBarAction { + static func create(_ value: CoreBrowser.Tab.ContentType) -> SearchBarAction { switch value { case .blank, .favorites, .topSites, .homepage: return .clearView diff --git a/catowseriOS/catowser/Toolbar/BrowserToolbarView.swift b/catowseriOS/catowser/Toolbar/BrowserToolbarView.swift index 500d5f7a..3de46a91 100644 --- a/catowseriOS/catowser/Toolbar/BrowserToolbarView.swift +++ b/catowseriOS/catowser/Toolbar/BrowserToolbarView.swift @@ -298,7 +298,7 @@ private extension BrowserToolbarView { } extension BrowserToolbarView: TabsObserver { - func tabDidSelect(_ index: Int, _ content: Tab.ContentType, _ identifier: UUID) async { + func tabDidSelect(_ index: Int, _ content: CoreBrowser.Tab.ContentType, _ identifier: UUID) async { switch content { case .site: updateToolbar(downloadsAvailable: false, actionsAvailable: true) diff --git a/catowseriOS/catowser/Utils/DefaultTabProvider.swift b/catowseriOS/catowser/Utils/DefaultTabProvider.swift index c7f69c5c..437429a0 100644 --- a/catowseriOS/catowser/Utils/DefaultTabProvider.swift +++ b/catowseriOS/catowser/Utils/DefaultTabProvider.swift @@ -50,7 +50,7 @@ final class DefaultTabProvider { } } - var contentState: Tab.ContentType { + var contentState: CoreBrowser.Tab.ContentType { get async { await FeatureManager.shared.tabDefaultContentValue().contentType } diff --git a/catowseriOS/catowser/WebTabs/AllTabsViewModel.swift b/catowseriOS/catowser/WebTabs/AllTabsViewModel.swift index b9db1605..7b51e379 100644 --- a/catowseriOS/catowser/WebTabs/AllTabsViewModel.swift +++ b/catowseriOS/catowser/WebTabs/AllTabsViewModel.swift @@ -17,7 +17,7 @@ final class AllTabsViewModel: ObservableObject { self.writeTabUseCase = writeTabUseCase } - func addTab(_ tab: Tab) { + func addTab(_ tab: CoreBrowser.Tab) { Task { await writeTabUseCase.add(tab: tab) } diff --git a/catowseriOS/catowser/WebTabs/TabPreviewCell.swift b/catowseriOS/catowser/WebTabs/TabPreviewCell.swift index 8ee44d7b..ab6faea5 100644 --- a/catowseriOS/catowser/WebTabs/TabPreviewCell.swift +++ b/catowseriOS/catowser/WebTabs/TabPreviewCell.swift @@ -184,7 +184,7 @@ final class TabPreviewCell: UICollectionViewCell, ReusableItem, FaviconImageView } } - func configure(with tab: Tab, at index: Int, delegate: TabPreviewCellDelegate) { + func configure(with tab: CoreBrowser.Tab, at index: Int, delegate: TabPreviewCellDelegate) { // `TabViewModel` can be used instead, but cell view init doesn't allow to inject it normally var tabCopy = tab screenshotView.image = tabCopy.preview diff --git a/catowseriOS/catowser/WebTabs/TabViewModel.swift b/catowseriOS/catowser/WebTabs/TabViewModel.swift index 95c970a6..acb11204 100644 --- a/catowseriOS/catowser/WebTabs/TabViewModel.swift +++ b/catowseriOS/catowser/WebTabs/TabViewModel.swift @@ -13,13 +13,13 @@ import FeaturesFlagsKit @MainActor final class TabViewModel { - private var tab: Tab + private var tab: CoreBrowser.Tab private let readTabUseCase: ReadTabsUseCase private let writeTabUseCase: WriteTabsUseCase @Published var state: TabViewState - init(_ tab: Tab, + init(_ tab: CoreBrowser.Tab, _ readTabUseCase: ReadTabsUseCase, _ writeTabUseCase: WriteTabsUseCase) { self.tab = tab @@ -97,7 +97,7 @@ final class TabViewModel { } extension TabViewModel: TabsObserver { - func tabDidSelect(_ index: Int, _ content: Tab.ContentType, _ identifier: UUID) async { + func tabDidSelect(_ index: Int, _ content: CoreBrowser.Tab.ContentType, _ identifier: UUID) async { if tab.contentType != content { /// Need to reload favicon and title as well. /// Not sure if it is possible during simple select? @@ -110,7 +110,7 @@ extension TabViewModel: TabsObserver { } } - func tabDidReplace(_ tab: Tab, at index: Int) async { + func tabDidReplace(_ tab: CoreBrowser.Tab, at index: Int) async { guard self.tab.id == tab.id else { return } diff --git a/catowseriOS/catowser/WebTabs/TabsPreviewsViewController.swift b/catowseriOS/catowser/WebTabs/TabsPreviewsViewController.swift index 6b80742f..d9cf3cb4 100644 --- a/catowseriOS/catowser/WebTabs/TabsPreviewsViewController.swift +++ b/catowseriOS/catowser/WebTabs/TabsPreviewsViewController.swift @@ -165,7 +165,7 @@ where C.R == TabsScreenRoute { func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - var tab: Tab? + var tab: CoreBrowser.Tab? switch viewModel.uxState { case .tabs(let dataSource) where indexPath.item < dataSource.value.count: // must use `item` for UICollectionView @@ -185,7 +185,7 @@ where C.R == TabsScreenRoute { // MARK: - UICollectionViewDelegate func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - var tab: Tab? + var tab: CoreBrowser.Tab? switch viewModel.uxState { case .tabs(let dataSource) where indexPath.item < dataSource.value.count: tab = dataSource.value[safe: indexPath.item] @@ -227,7 +227,7 @@ private extension TabsPreviewsViewController { } extension TabsPreviewsViewController: TabsObserver { - func tabDidAdd(_ tab: Tab, at index: Int) async { + func tabDidAdd(_ tab: CoreBrowser.Tab, at index: Int) async { let state = viewModel.uxState guard case let .tabs(box) = state else { return diff --git a/catowseriOS/catowser/WebTabs/TabsPreviewsViewModel.swift b/catowseriOS/catowser/WebTabs/TabsPreviewsViewModel.swift index 503814bb..bade8b0f 100644 --- a/catowseriOS/catowser/WebTabs/TabsPreviewsViewModel.swift +++ b/catowseriOS/catowser/WebTabs/TabsPreviewsViewModel.swift @@ -9,7 +9,7 @@ import Foundation import CoreBrowser -typealias TabsBox = Box<[Tab]> +typealias TabsBox = Box<[CoreBrowser.Tab]> enum TabsPreviewState { /// Maybe it is not needed state, but it is required for scalability when some user will have 100 tabs diff --git a/catowseriOS/catowser/WebTabs/TabsRepositoryImpl.swift b/catowseriOS/catowser/WebTabs/TabsRepositoryImpl.swift index 7cd6369d..ff23e33f 100644 --- a/catowseriOS/catowser/WebTabs/TabsRepositoryImpl.swift +++ b/catowseriOS/catowser/WebTabs/TabsRepositoryImpl.swift @@ -11,7 +11,7 @@ import CoreBrowser import CoreData /** - Declaring Tab storage type in host app instead of `CoreBrowser` framework, + Declaring CoreBrowser.Tab storage type in host app instead of `CoreBrowser` framework, to allow use app settings like default tab content which only can be stored in host app. Later need to add tabs rest/firebase client dependency to use it as a 2nd (remote) data source. @@ -27,7 +27,7 @@ final class TabsRepositoryImpl { } extension TabsRepositoryImpl: TabsRepository { - func select(tab: Tab) async throws -> UUID { + func select(tab: CoreBrowser.Tab) async throws -> UUID { do { try await tabsDbResource.selectTab(tab) return tab.id @@ -36,7 +36,7 @@ extension TabsRepositoryImpl: TabsRepository { } } - func update(tab: Tab) throws -> Tab { + func update(tab: CoreBrowser.Tab) throws -> CoreBrowser.Tab { do { return try tabsDbResource.update(tab: tab) } catch { @@ -44,7 +44,7 @@ extension TabsRepositoryImpl: TabsRepository { } } - func remove(tabs: [Tab]) async throws -> [Tab] { + func remove(tabs: [CoreBrowser.Tab]) async throws -> [CoreBrowser.Tab] { do { return try await tabsDbResource.forget(tabs: tabs) } catch { @@ -52,11 +52,11 @@ extension TabsRepositoryImpl: TabsRepository { } } - func fetchAllTabs() async throws -> [Tab] { + func fetchAllTabs() async throws -> [CoreBrowser.Tab] { try await tabsDbResource.tabsFromLastSession() } - func add(_ tab: Tab, select: Bool) async throws -> Tab { + func add(_ tab: CoreBrowser.Tab, select: Bool) async throws -> CoreBrowser.Tab { try await tabsDbResource.remember(tab: tab, andSelect: select) } diff --git a/catowseriOS/catowser/WebTabs/TabsViewController.swift b/catowseriOS/catowser/WebTabs/TabsViewController.swift index 7eb21fc4..0ac9ad45 100644 --- a/catowseriOS/catowser/WebTabs/TabsViewController.swift +++ b/catowseriOS/catowser/WebTabs/TabsViewController.swift @@ -141,7 +141,7 @@ private extension TabsViewController { print("\(#function): add pressed") Task { - let tab = Tab(contentType: await DefaultTabProvider.shared.contentState) + let tab = CoreBrowser.Tab(contentType: await DefaultTabProvider.shared.contentState) viewModel.addTab(tab) } } @@ -229,7 +229,7 @@ private extension TabsViewController { // MARK: Tabs observer extension TabsViewController: TabsObserver { - func tabDidSelect(_ index: Int, _ content: Tab.ContentType, _ identifier: UUID) async { + func tabDidSelect(_ index: Int, _ content: CoreBrowser.Tab.ContentType, _ identifier: UUID) async { guard !tabsStackView.arrangedSubviews.isEmpty else { assertionFailure("Tried to make tab view active but there are no any of them") return @@ -257,7 +257,7 @@ extension TabsViewController: TabsObserver { #endif } - func initializeObserver(with tabs: [Tab]) async { + func initializeObserver(with tabs: [CoreBrowser.Tab]) async { for tab in tabs { let vm = await ViewModelFactory.shared.tabViewModel(tab) viewModels.append(vm) @@ -269,7 +269,7 @@ extension TabsViewController: TabsObserver { view.layoutIfNeeded() } - func tabDidAdd(_ tab: Tab, at index: Int) async { + func tabDidAdd(_ tab: CoreBrowser.Tab, at index: Int) async { let vm = await ViewModelFactory.shared.tabViewModel(tab) viewModels.insert(vm, at: index) let tabView = TabView(calculateNextTabFrame(), vm, self) From 664ff6aa7172a7937d4f586886e41b9880d72295 Mon Sep 17 00:00:00 2001 From: Andrei Ermoshin Date: Sat, 27 Jul 2024 20:17:30 +0300 Subject: [PATCH 08/32] Fix compilation in Xcode 16 beta 4 - WebKit function was updated --- catowseriOS/CottonPlugins/JavaScriptEvaluateble.swift | 5 ++++- .../catowser/BrowserContent/WebView/WebViewController.swift | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/catowseriOS/CottonPlugins/JavaScriptEvaluateble.swift b/catowseriOS/CottonPlugins/JavaScriptEvaluateble.swift index b1df8085..24974e10 100644 --- a/catowseriOS/CottonPlugins/JavaScriptEvaluateble.swift +++ b/catowseriOS/CottonPlugins/JavaScriptEvaluateble.swift @@ -11,7 +11,10 @@ import Combine @preconcurrency import ReactiveSwift public protocol JavaScriptEvaluateble: AnyObject { - func evaluateJavaScript(_ javaScriptString: String, completionHandler: ((Any?, Error?) -> Void)?) + func evaluateJavaScript( + _ javaScriptString: String, + completionHandler: (@MainActor @Sendable (Any?, (any Error)?) -> Void)? + ) } extension JavaScriptEvaluateble { diff --git a/catowseriOS/catowser/BrowserContent/WebView/WebViewController.swift b/catowseriOS/catowser/BrowserContent/WebView/WebViewController.swift index 8f7ce18f..8b47fbdf 100644 --- a/catowseriOS/catowser/BrowserContent/WebView/WebViewController.swift +++ b/catowseriOS/catowser/BrowserContent/WebView/WebViewController.swift @@ -15,7 +15,7 @@ import BrowserNetworking import FeaturesFlagsKit @preconcurrency import ReactiveSwift #if canImport(Combine) -import Combine +@preconcurrency import Combine #endif import CottonData From 6aaa061ea4a6a209c020dbc1d3320fd4d599be3d Mon Sep 17 00:00:00 2001 From: Andrei Ermoshin Date: Sun, 28 Jul 2024 13:21:29 +0300 Subject: [PATCH 09/32] Fix compilation in current Xcode 15.4 by partly reverting older function --- .../CottonPlugins/JavaScriptEvaluateble.swift | 27 ++++++++++++++++--- .../WebView/WebViewController.swift | 15 ++++++++++- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/catowseriOS/CottonPlugins/JavaScriptEvaluateble.swift b/catowseriOS/CottonPlugins/JavaScriptEvaluateble.swift index 24974e10..ce2d8ca8 100644 --- a/catowseriOS/CottonPlugins/JavaScriptEvaluateble.swift +++ b/catowseriOS/CottonPlugins/JavaScriptEvaluateble.swift @@ -11,17 +11,36 @@ import Combine @preconcurrency import ReactiveSwift public protocol JavaScriptEvaluateble: AnyObject { - func evaluateJavaScript( + func evaluateJavaScriptV2( _ javaScriptString: String, completionHandler: (@MainActor @Sendable (Any?, (any Error)?) -> Void)? ) + func evaluateJavaScriptV1( + _ javaScriptString: String, + completionHandler: ((Any?, Error?) -> Void)? + ) +} + +extension JavaScriptEvaluateble { + func commonHandleJavaScript( + _ javaScriptString: String, + _ completionHandler: (@MainActor @Sendable (Any?, (any Error)?) -> Void)? + ) { + if #available(iOS 18.0, *) { + evaluateJavaScriptV2(javaScriptString, completionHandler: completionHandler) + } else { + #if swift(<6.0) + evaluateJavaScriptV1(javaScriptString, completionHandler: completionHandler) + #endif + } + } } extension JavaScriptEvaluateble { func evaluate(jsScript: String) { // swiftlint:disable:next line_length // https://github.com/WebKit/webkit/blob/39a299616172a4d4fe1f7aaf573b41020a1d7358/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm#L1009 - evaluateJavaScript(jsScript, completionHandler: {(something, error) in + commonHandleJavaScript(jsScript, {(something, error) in if let err = error { print("Error evaluating JavaScript: \(err)") } else if let thing = something { @@ -37,7 +56,7 @@ extension JavaScriptEvaluateble { promise(.failure(CottonPluginError.zombiError)) return } - self.evaluateJavaScript(jsScript) { (something, error) in + self.commonHandleJavaScript(jsScript) { (something, error) in if let realError = error { promise(.failure(realError)) return @@ -61,7 +80,7 @@ extension JavaScriptEvaluateble { observer.send(error: CottonPluginError.zombiError) return } - self.evaluateJavaScript(jsScript) { (something, error) in + self.commonHandleJavaScript(jsScript) { (something, error) in if let realError = error { observer.send(error: realError) return diff --git a/catowseriOS/catowser/BrowserContent/WebView/WebViewController.swift b/catowseriOS/catowser/BrowserContent/WebView/WebViewController.swift index 8b47fbdf..71a9cefe 100644 --- a/catowseriOS/catowser/BrowserContent/WebView/WebViewController.swift +++ b/catowseriOS/catowser/BrowserContent/WebView/WebViewController.swift @@ -19,7 +19,20 @@ import FeaturesFlagsKit #endif import CottonData -extension WKWebView: JavaScriptEvaluateble {} +extension WKWebView: JavaScriptEvaluateble { + public func evaluateJavaScriptV2( + _ javaScriptString: String, + completionHandler: (@MainActor @Sendable (Any?, (any Error)?) -> Void)? + ) { + evaluateJavaScript(javaScriptString, completionHandler: completionHandler) + } + public func evaluateJavaScriptV1( + _ javaScriptString: String, + completionHandler: ((Any?, Error?) -> Void)? + ) { + evaluateJavaScript(javaScriptString, completionHandler: completionHandler) + } +} final class WebViewController: BaseViewController, WKUIDelegate, From 55ab985d6a62dd7072087b5b1210fc620f127443 Mon Sep 17 00:00:00 2001 From: Andrei Ermoshin Date: Tue, 30 Jul 2024 22:07:15 +0300 Subject: [PATCH 10/32] Fix CoreData crash after renaming of Tab type --- .../CottonDbModel.xcdatamodel/contents | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/catowseriOS/catowser/DB/CottonDbModel.xcdatamodeld/CottonDbModel.xcdatamodel/contents b/catowseriOS/catowser/DB/CottonDbModel.xcdatamodeld/CottonDbModel.xcdatamodel/contents index 86bff4d0..2e76854f 100644 --- a/catowseriOS/catowser/DB/CottonDbModel.xcdatamodeld/CottonDbModel.xcdatamodel/contents +++ b/catowseriOS/catowser/DB/CottonDbModel.xcdatamodeld/CottonDbModel.xcdatamodel/contents @@ -1,20 +1,14 @@ - + - - - - - - - + @@ -23,4 +17,10 @@ + + + + + + \ No newline at end of file From e5057f276d2fed5a60ba767e4b0faee91bda8c56 Mon Sep 17 00:00:00 2001 From: Andrei Ermoshin Date: Sun, 4 Aug 2024 21:56:18 +0300 Subject: [PATCH 11/32] Fix swift 6 errors - part 1 --- .../CottonPlugins/JSPluginsBuilder.swift | 13 +- .../CottonPlugins/JSPluginsProgram.swift | 4 +- .../CottonPlugins/JSPluginsProgramImpl.swift | 63 +- .../CottonPlugins/JavaScriptEvaluateble.swift | 4 +- .../CottonPlugins/Plugins/BasePlugin.swift | 17 - .../Plugins/InstagramContentPlugin.swift | 17 - .../Plugins/JavaScriptPlugin.swift | 24 +- .../JavaScriptPluginVisitor.swift | 13 +- .../WKUserContentController+Visitor.swift | 21 +- .../FeaturesFlagsKit/LocalFeatureSource.swift | 22 +- .../FeaturesFlagsKit/LocalSettings.swift | 49 +- .../Models/EnumFeatureSource.swift | 4 +- .../Models/FeatureSource.swift | 4 +- .../catowser.xcodeproj/project.pbxproj | 766 +----------------- 14 files changed, 128 insertions(+), 893 deletions(-) diff --git a/catowseriOS/CottonPlugins/JSPluginsBuilder.swift b/catowseriOS/CottonPlugins/JSPluginsBuilder.swift index 4f614df2..166eb196 100644 --- a/catowseriOS/CottonPlugins/JSPluginsBuilder.swift +++ b/catowseriOS/CottonPlugins/JSPluginsBuilder.swift @@ -6,13 +6,14 @@ // import Foundation +import WebKit /** Creates the plugins by connecting them with observers (delegates). */ public final class JSPluginsBuilder: JSPluginsSource { public typealias Program = JSPluginsProgramImpl - private var plugins: [any JavaScriptPlugin] + private var plugins: [(any JavaScriptPlugin, WKScriptMessageHandler)] public var jsProgram: Program { JSPluginsProgramImpl(plugins) @@ -23,16 +24,14 @@ public final class JSPluginsBuilder: JSPluginsSource { } public func setBase(_ baseDelegate: BasePluginContentDelegate) -> Self { - if let basePlugin = BasePlugin(delegate: .base(baseDelegate)) { - plugins.append(basePlugin) - } + let basePlugin = BasePlugin() + plugins.append((basePlugin, BaseJSHandler(baseDelegate))) return self } public func setInstagram(_ instagramDelegate: InstagramContentDelegate) -> Self { - if let igPlugin = InstagramContentPlugin(delegate: .instagram(instagramDelegate)) { - plugins.append(igPlugin) - } + let igPlugin = InstagramContentPlugin() + plugins.append((igPlugin, InstagramHandler(instagramDelegate))) return self } } diff --git a/catowseriOS/CottonPlugins/JSPluginsProgram.swift b/catowseriOS/CottonPlugins/JSPluginsProgram.swift index e7fa93e1..dd0ea1df 100644 --- a/catowseriOS/CottonPlugins/JSPluginsProgram.swift +++ b/catowseriOS/CottonPlugins/JSPluginsProgram.swift @@ -11,8 +11,8 @@ import WebKit import CottonBase @MainActor -public protocol JSPluginsProgram: AnyObject, Equatable { - var plugins: [any JavaScriptPlugin] { get } +public protocol JSPluginsProgram: AnyObject { + var plugins: [(any JavaScriptPlugin, WKScriptMessageHandler)] { get } func inject(to visitor: WKUserContentController, context: CottonBase.Host, canInject: Bool) func enable(on webView: JavaScriptEvaluateble, context: CottonBase.Host, jsEnabled: Bool) diff --git a/catowseriOS/CottonPlugins/JSPluginsProgramImpl.swift b/catowseriOS/CottonPlugins/JSPluginsProgramImpl.swift index d3c9dbc3..6800cca0 100644 --- a/catowseriOS/CottonPlugins/JSPluginsProgramImpl.swift +++ b/catowseriOS/CottonPlugins/JSPluginsProgramImpl.swift @@ -10,17 +10,20 @@ import Foundation import WebKit import CottonBase -extension CottonBase.Host: @unchecked Sendable {} +extension CottonBase.Host: @unchecked @retroactive Sendable {} /** An Object Structure (Program) from visitor desgin pattern. Could be a Composite */ +@MainActor public final class JSPluginsProgramImpl: JSPluginsProgram { - public let plugins: [any JavaScriptPlugin] + public let plugins: [(any JavaScriptPlugin, WKScriptMessageHandler)] - public init(_ plugins: [any JavaScriptPlugin]) { - if plugins.count == 0 { - print("Plugins program was initialized with 0 JS plugins") + public init( + _ plugins: [(any JavaScriptPlugin, WKScriptMessageHandler)] + ) { + guard !plugins.isEmpty else { + fatalError("Plugins program was initialized with 0 JS plugins") } self.plugins = plugins } @@ -34,13 +37,11 @@ public final class JSPluginsProgramImpl: JSPluginsProgram { return } visitor.removeAllUserScripts() // reset old state - plugins.forEach { plugin in - Task { - do { - try await plugin.accept(visitor, context, canInject) - } catch { - print("\(#function) failed to load plugin: \(error.localizedDescription)") - } + plugins.forEach { pair in + do { + try pair.0.accept(visitor, context, canInject, pair.1) + } catch { + print("\(#function) failed to load plugin: \(error.localizedDescription)") } } } @@ -50,8 +51,8 @@ public final class JSPluginsProgramImpl: JSPluginsProgram { return } plugins - .filter { plugin in - guard let pluginHostName = plugin.hostKeyword else { + .filter { pair in + guard let pluginHostName = pair.0.hostKeyword else { return true } guard context.isSimilar(name: pluginHostName) else { @@ -59,39 +60,7 @@ public final class JSPluginsProgramImpl: JSPluginsProgram { } return true } - .compactMap { $0.scriptString(jsEnabled) } + .compactMap { $0.0.scriptString(jsEnabled) } .forEach { webView.evaluate(jsScript: $0)} } } - -/** - cannot use the == operator to compare 2 instances of existential type. - https://swiftsenpai.com/swift/understanding-some-and-any/ - https://www.hackingwithswift.com/swift/5.7/unlock-existentials - - Also see "type erasure" techniques but without using any keyword: - https://www.swiftbysundell.com/articles/different-flavors-of-type-erasure-in-swift/ - https://khawerkhaliq.com/blog/swift-protocols-equatable-part-two/ - */ - -public extension JSPluginsProgramImpl { - nonisolated static func == (lhs: JSPluginsProgramImpl, rhs: JSPluginsProgramImpl) -> Bool { - guard lhs.plugins.count == rhs.plugins.count else { - return false - } - - var index = 0 - while index < lhs.plugins.count { - let lv = lhs.plugins[index] - let rv = rhs.plugins[index] - if let blv = lv as? BasePlugin, let brv = rv as? BasePlugin, blv != brv { - return false - } else if let ilv = lv as? InstagramContentPlugin, let irv = rv as? InstagramContentPlugin, ilv != irv { - return false - } - index += 1 - } - return true - - } -} diff --git a/catowseriOS/CottonPlugins/JavaScriptEvaluateble.swift b/catowseriOS/CottonPlugins/JavaScriptEvaluateble.swift index ce2d8ca8..fd0363d6 100644 --- a/catowseriOS/CottonPlugins/JavaScriptEvaluateble.swift +++ b/catowseriOS/CottonPlugins/JavaScriptEvaluateble.swift @@ -39,7 +39,7 @@ extension JavaScriptEvaluateble { extension JavaScriptEvaluateble { func evaluate(jsScript: String) { // swiftlint:disable:next line_length - // https://github.com/WebKit/webkit/blob/39a299616172a4d4fe1f7aaf573b41020a1d7358/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm#L1009 + // https://github.com/WebKit/WebKit/blob/main/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm commonHandleJavaScript(jsScript, {(something, error) in if let err = error { print("Error evaluating JavaScript: \(err)") @@ -56,7 +56,7 @@ extension JavaScriptEvaluateble { promise(.failure(CottonPluginError.zombiError)) return } - self.commonHandleJavaScript(jsScript) { (something, error) in + commonHandleJavaScript(jsScript) { (something, error) in if let realError = error { promise(.failure(realError)) return diff --git a/catowseriOS/CottonPlugins/Plugins/BasePlugin.swift b/catowseriOS/CottonPlugins/Plugins/BasePlugin.swift index 1b92ec54..c3c66629 100644 --- a/catowseriOS/CottonPlugins/Plugins/BasePlugin.swift +++ b/catowseriOS/CottonPlugins/Plugins/BasePlugin.swift @@ -27,23 +27,6 @@ public struct BasePlugin: JavaScriptPlugin { } public let isMainFrameOnly: Bool = true - - public let handler: WKScriptMessageHandler - - public init?(delegate: PluginHandlerDelegateType) { - guard case let .base(actualDelegate) = delegate else { - assertionFailure("failed to create BasePlugin because of wrong delegate") - return nil - } - handler = BaseJSHandler(actualDelegate) - } - - public init?(anyProtocol: Any) { - guard let baseDelegate = anyProtocol as? BasePluginContentDelegate else { - return nil - } - handler = BaseJSHandler(baseDelegate) - } } extension String { diff --git a/catowseriOS/CottonPlugins/Plugins/InstagramContentPlugin.swift b/catowseriOS/CottonPlugins/Plugins/InstagramContentPlugin.swift index 5eb1ac64..ac4d4009 100644 --- a/catowseriOS/CottonPlugins/Plugins/InstagramContentPlugin.swift +++ b/catowseriOS/CottonPlugins/Plugins/InstagramContentPlugin.swift @@ -14,8 +14,6 @@ public protocol InstagramContentDelegate: AnyObject { } public struct InstagramContentPlugin: JavaScriptPlugin { - public let handler: WKScriptMessageHandler - public let jsFileName: String = "ig" public let messageHandlerName: String = "igHandler" @@ -27,21 +25,6 @@ public struct InstagramContentPlugin: JavaScriptPlugin { } public let isMainFrameOnly: Bool = true - - public init?(delegate: PluginHandlerDelegateType) { - guard case let .instagram(actualDelegate) = delegate else { - assertionFailure("failed to create object") - return nil - } - handler = InstagramHandler(actualDelegate) - } - - public init?(anyProtocol: Any) { - guard let igDelegate = anyProtocol as? InstagramContentDelegate else { - return nil - } - handler = InstagramHandler(igDelegate) - } } extension InstagramContentPlugin: Equatable { diff --git a/catowseriOS/CottonPlugins/Plugins/JavaScriptPlugin.swift b/catowseriOS/CottonPlugins/Plugins/JavaScriptPlugin.swift index 471f83ca..67614e47 100644 --- a/catowseriOS/CottonPlugins/Plugins/JavaScriptPlugin.swift +++ b/catowseriOS/CottonPlugins/Plugins/JavaScriptPlugin.swift @@ -23,12 +23,10 @@ public enum PluginHandlerDelegateType { Describes the JavaScript plugin model. An Element from visitor design pattern. */ -@MainActor public protocol JavaScriptPlugin: Equatable { var jsFileName: String { get } var messageHandlerName: String { get } var isMainFrameOnly: Bool { get } - var handler: WKScriptMessageHandler { get } var hostKeyword: String? { get } /** Constructs a JavaScript string with specific variable @@ -38,8 +36,6 @@ public protocol JavaScriptPlugin: Equatable { - enable determines if specific plugin should be turned on/off */ func scriptString(_ enable: Bool) -> String? - init?(delegate: PluginHandlerDelegateType) - init?(anyProtocol: Any) /** Handles the visitor depending on context. @@ -48,15 +44,27 @@ public protocol JavaScriptPlugin: Equatable { - host represents the hostname from web view (can be used to determine if specific plugin is applicable or not) - canInject shows if this specific plugin needs to be injected or can be skipped. */ - func accept(_ visitor: JavaScriptPluginVisitor, _ host: CottonBase.Host, _ canInject: Bool) async throws + @MainActor + func accept( + _ visitor: JavaScriptPluginVisitor, + _ host: CottonBase.Host, + _ canInject: Bool, + _ handler: WKScriptMessageHandler + ) throws } extension JavaScriptPlugin { - public func accept(_ visitor: JavaScriptPluginVisitor, _ host: CottonBase.Host, _ canInject: Bool) async throws { - guard visitor.canVisit(self, host, canInject) else { + @MainActor + public func accept( + _ visitor: JavaScriptPluginVisitor, + _ host: CottonBase.Host, + _ canInject: Bool, + _ handler: WKScriptMessageHandler + ) throws { + guard visitor.canVisit(self, host, canInject, handler) else { return } - try await visitor.visit(self) + try visitor.visit(self) } } diff --git a/catowseriOS/CottonPlugins/PluginsVisitor/JavaScriptPluginVisitor.swift b/catowseriOS/CottonPlugins/PluginsVisitor/JavaScriptPluginVisitor.swift index 1689220a..8634d7fd 100644 --- a/catowseriOS/CottonPlugins/PluginsVisitor/JavaScriptPluginVisitor.swift +++ b/catowseriOS/CottonPlugins/PluginsVisitor/JavaScriptPluginVisitor.swift @@ -8,9 +8,9 @@ import Foundation import CottonBase +import WebKit /// Will be used for `WKUserContentController` because it is actually the only possible visitor -@MainActor public protocol JavaScriptPluginVisitor: AnyObject { /** Determines if specific plugin can be used on specific host @@ -19,13 +19,20 @@ public protocol JavaScriptPluginVisitor: AnyObject { - plugin JavaScript plugin - host hostname from the URL, can be used to determine if plugin is specific to web site - canInject A boolean value which should be used as a top level check. Describes feature availability. + - handler Shouled be stored separately from the plugin because it is a reference type. */ - func canVisit(_ plugin: any JavaScriptPlugin, _ host: CottonBase.Host, _ canInject: Bool) -> Bool + @MainActor + func canVisit( + _ plugin: any JavaScriptPlugin, + _ host: CottonBase.Host, + _ canInject: Bool, + _ handler: WKScriptMessageHandler + ) -> Bool /** Uses specific plugin in a visitor. - Parameters: - plugin JavaScript plugin */ - func visit(_ plugin: any JavaScriptPlugin) async throws + func visit(_ plugin: any JavaScriptPlugin) throws } diff --git a/catowseriOS/CottonPlugins/PluginsVisitor/WKUserContentController+Visitor.swift b/catowseriOS/CottonPlugins/PluginsVisitor/WKUserContentController+Visitor.swift index 5d935081..df5218f2 100644 --- a/catowseriOS/CottonPlugins/PluginsVisitor/WKUserContentController+Visitor.swift +++ b/catowseriOS/CottonPlugins/PluginsVisitor/WKUserContentController+Visitor.swift @@ -11,7 +11,12 @@ import CottonBase /// A Concrete Visitor which is in this case only one possible type from iOS SDK WebKit extension WKUserContentController: JavaScriptPluginVisitor { - public func canVisit(_ plugin: any JavaScriptPlugin, _ host: CottonBase.Host, _ canInject: Bool) -> Bool { + public func canVisit( + _ plugin: any JavaScriptPlugin, + _ host: CottonBase.Host, + _ canInject: Bool, + _ handler: WKScriptMessageHandler + ) -> Bool { guard canInject else { return false } @@ -24,23 +29,23 @@ extension WKUserContentController: JavaScriptPluginVisitor { return true } - public func visit(_ plugin: any JavaScriptPlugin) async throws { + public func visit(_ plugin: any JavaScriptPlugin) throws { if let base = plugin as? BasePlugin { - try await visit(basePlugin: base) + try visit(basePlugin: base) } else if let instagram = plugin as? InstagramContentPlugin { - try await visit(instagramPlugin: instagram) + try visit(instagramPlugin: instagram) } } - private func visit(basePlugin: BasePlugin) async throws { - let wkScript = try await JSPluginFactory.shared.script(for: basePlugin, + private func visit(basePlugin: BasePlugin) throws { + let wkScript = try JSPluginFactory.shared.script(for: basePlugin, with: .atDocumentEnd, isMainFrameOnly: true) addHandler(wkScript, basePlugin.messageHandlerName, basePlugin.handler) } - private func visit(instagramPlugin: InstagramContentPlugin) async throws { - let wkScript = try await JSPluginFactory.shared.script(for: instagramPlugin, + private func visit(instagramPlugin: InstagramContentPlugin) throws { + let wkScript = try JSPluginFactory.shared.script(for: instagramPlugin, with: .atDocumentStart, isMainFrameOnly: instagramPlugin.isMainFrameOnly) addHandler(wkScript, instagramPlugin.messageHandlerName, instagramPlugin.handler) diff --git a/catowseriOS/FeaturesFlagsKit/LocalFeatureSource.swift b/catowseriOS/FeaturesFlagsKit/LocalFeatureSource.swift index 558e0485..fa7a0f5e 100644 --- a/catowseriOS/FeaturesFlagsKit/LocalFeatureSource.swift +++ b/catowseriOS/FeaturesFlagsKit/LocalFeatureSource.swift @@ -28,18 +28,18 @@ extension LocalFeatureSource: ObservableFeatureSource { } extension LocalFeatureSource: FeatureSource { - public func currentValue(of feature: ApplicationFeature) -> F.Value { + public func currentValue(of feature: ApplicationFeature) async -> F.Value { switch F.defaultValue { case is String: - guard let result = LocalSettings.getGlobalStringSetting(for: F.key.prefixedBasic()) else { + guard let result = await LocalSettings.shared.getGlobalStringSetting(for: F.key.prefixedBasic()) else { return F.defaultValue } return result as? F.Value ?? F.defaultValue case is Int: - let savedNumber = LocalSettings.getGlobalIntSetting(for: F.key.prefixedBasic()) + let savedNumber = await LocalSettings.shared.getGlobalIntSetting(for: F.key.prefixedBasic()) return savedNumber as? F.Value ?? F.defaultValue case is Bool: - guard let globalSetting = LocalSettings.getGlobalBoolSetting(for: F.key.prefixedBasic()) else { + guard let globalSetting = await LocalSettings.shared.getGlobalBoolSetting(for: F.key.prefixedBasic()) else { return F.defaultValue } return globalSetting as? F.Value ?? F.defaultValue @@ -52,16 +52,16 @@ extension LocalFeatureSource: FeatureSource { } } - public func setValue(of feature: ApplicationFeature, value: F.Value?) where F: BasicFeature { + public func setValue(of feature: ApplicationFeature, value: F.Value?) async where F: BasicFeature { switch F.defaultValue { case is Bool: // swiftlint:disable:next force_cast let boolValue = value as! Bool - LocalSettings.setGlobalBoolSetting(for: F.key.prefixedBasic(), value: boolValue) + await LocalSettings.shared.setGlobalBoolSetting(for: F.key.prefixedBasic(), value: boolValue) case is Int: // swiftlint:disable:next force_cast let intValue = value as! Int - LocalSettings.setGlobalIntSetting(for: F.key.prefixedBasic(), value: intValue) + await LocalSettings.shared.setGlobalIntSetting(for: F.key.prefixedBasic(), value: intValue) default: assertionFailure("Value settings in Local source isn't implemented for other types") } @@ -75,20 +75,20 @@ extension LocalFeatureSource: FeatureSource { } extension LocalFeatureSource: EnumFeatureSource { - public func currentEnumValue(of feature: ApplicationEnumFeature) -> F.EnumValue + public func currentEnumValue(of feature: ApplicationEnumFeature) async -> F.EnumValue where F.EnumValue.RawValue == Int { - guard let result = LocalSettings.getGlobalIntSetting(for: feature.feature.key.prefixedEnum()) else { + guard let result = await LocalSettings.shared.getGlobalIntSetting(for: feature.feature.key.prefixedEnum()) else { return feature.defaultEnumValue } return F.EnumValue(rawValue: result) ?? feature.defaultEnumValue } public func setEnumValue(of feature: ApplicationEnumFeature, value: F.EnumValue?) - where F.EnumValue.RawValue == Int { + async where F.EnumValue.RawValue == Int { guard let intValue = value?.rawValue else { return } - LocalSettings.setGlobalIntSetting(for: feature.feature.key.prefixedEnum(), value: intValue) + await LocalSettings.shared.setGlobalIntSetting(for: feature.feature.key.prefixedEnum(), value: intValue) let value = AnyFeature(feature) if #available(iOS 13.0, *) { diff --git a/catowseriOS/FeaturesFlagsKit/LocalSettings.swift b/catowseriOS/FeaturesFlagsKit/LocalSettings.swift index d2161c2f..f64f32d7 100644 --- a/catowseriOS/FeaturesFlagsKit/LocalSettings.swift +++ b/catowseriOS/FeaturesFlagsKit/LocalSettings.swift @@ -8,38 +8,41 @@ import Foundation +@globalActor final class LocalSettings { - private static let shared: LocalSettings = .init() + static let shared = StateHolder() - private let userDefaults: UserDefaults + actor StateHolder { + private let userDefaults: UserDefaults - private init() { - userDefaults = .init() - } + init() { + userDefaults = .init() + } - static func getGlobalStringSetting(for key: String) -> String? { - return shared.userDefaults.string(forKey: key) - } + func getGlobalStringSetting(for key: String) -> String? { + return userDefaults.string(forKey: key) + } - static func getGlobalIntSetting(for key: String) -> Int? { - guard shared.userDefaults.object(forKey: key) != nil else { - return nil + func getGlobalIntSetting(for key: String) -> Int? { + guard userDefaults.object(forKey: key) != nil else { + return nil + } + return userDefaults.integer(forKey: key) } - return shared.userDefaults.integer(forKey: key) - } - static func getGlobalBoolSetting(for key: String) -> Bool? { - guard shared.userDefaults.object(forKey: key) != nil else { - return nil + func getGlobalBoolSetting(for key: String) -> Bool? { + guard userDefaults.object(forKey: key) != nil else { + return nil + } + return userDefaults.bool(forKey: key) } - return shared.userDefaults.bool(forKey: key) - } - static func setGlobalBoolSetting(for key: String, value: Bool) { - shared.userDefaults.set(value, forKey: key) - } + func setGlobalBoolSetting(for key: String, value: Bool) { + userDefaults.set(value, forKey: key) + } - static func setGlobalIntSetting(for key: String, value: Int) { - shared.userDefaults.set(value, forKey: key) + func setGlobalIntSetting(for key: String, value: Int) { + userDefaults.set(value, forKey: key) + } } } diff --git a/catowseriOS/FeaturesFlagsKit/Models/EnumFeatureSource.swift b/catowseriOS/FeaturesFlagsKit/Models/EnumFeatureSource.swift index aadff966..7c14d55c 100644 --- a/catowseriOS/FeaturesFlagsKit/Models/EnumFeatureSource.swift +++ b/catowseriOS/FeaturesFlagsKit/Models/EnumFeatureSource.swift @@ -12,8 +12,8 @@ import Combine #endif public protocol EnumFeatureSource { - func currentEnumValue(of feature: ApplicationEnumFeature) -> F.EnumValue + func currentEnumValue(of feature: ApplicationEnumFeature) async -> F.EnumValue where F.EnumValue.RawValue == Int func setEnumValue(of feature: ApplicationEnumFeature, value: F.EnumValue?) - where F.EnumValue.RawValue == Int + async where F.EnumValue.RawValue == Int } diff --git a/catowseriOS/FeaturesFlagsKit/Models/FeatureSource.swift b/catowseriOS/FeaturesFlagsKit/Models/FeatureSource.swift index 0a39a83b..c69a84a9 100644 --- a/catowseriOS/FeaturesFlagsKit/Models/FeatureSource.swift +++ b/catowseriOS/FeaturesFlagsKit/Models/FeatureSource.swift @@ -12,8 +12,8 @@ import Combine #endif public protocol FeatureSource { - func currentValue(of feature: ApplicationFeature) -> F.Value - func setValue(of feature: ApplicationFeature, value: F.Value?) + func currentValue(of feature: ApplicationFeature) async -> F.Value + func setValue(of feature: ApplicationFeature, value: F.Value?) async } /** diff --git a/catowseriOS/catowser.xcodeproj/project.pbxproj b/catowseriOS/catowser.xcodeproj/project.pbxproj index 5f3a8800..b7460b55 100644 --- a/catowseriOS/catowser.xcodeproj/project.pbxproj +++ b/catowseriOS/catowser.xcodeproj/project.pbxproj @@ -76,13 +76,9 @@ 750B6DE72B5C348E00063E75 /* TabsServiceCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 750B6DE62B5C348E00063E75 /* TabsServiceCommand.swift */; }; 750B6DE92B5C34D000063E75 /* TabsServiceDataOutput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 750B6DE82B5C34D000063E75 /* TabsServiceDataOutput.swift */; }; 750EDEBB2949215900212A4E /* MainBrowserViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 750EDEBA2949215900212A4E /* MainBrowserViewModel.swift */; }; - 750EDEBC2949215900212A4E /* MainBrowserViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 750EDEBA2949215900212A4E /* MainBrowserViewModel.swift */; }; 750EDEBE294924A800212A4E /* BrowserContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 750EDEBD294924A800212A4E /* BrowserContentView.swift */; }; - 750EDEBF294924A800212A4E /* BrowserContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 750EDEBD294924A800212A4E /* BrowserContentView.swift */; }; 7517D9C32A5F2E7C00D081AD /* CottonBase.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7517D9C22A5F2E7A00D081AD /* CottonBase.xcframework */; }; 7517D9C42A5F2E7C00D081AD /* CottonBase.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 7517D9C22A5F2E7A00D081AD /* CottonBase.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 7517D9C52A5F2E8D00D081AD /* CottonBase.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7517D9C22A5F2E7A00D081AD /* CottonBase.xcframework */; }; - 7517D9C62A5F2E8D00D081AD /* CottonBase.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 7517D9C22A5F2E7A00D081AD /* CottonBase.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 7517D9C72A5F2E9F00D081AD /* CottonBase.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7517D9C22A5F2E7A00D081AD /* CottonBase.xcframework */; }; 7517D9CA2A5F2EB800D081AD /* CottonBase.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7517D9C22A5F2E7A00D081AD /* CottonBase.xcframework */; }; 7517D9CD2A5F2EC000D081AD /* CottonBase.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7517D9C22A5F2E7A00D081AD /* CottonBase.xcframework */; }; @@ -90,32 +86,19 @@ 7517D9D32A5F2EDC00D081AD /* CottonBase.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7517D9C22A5F2E7A00D081AD /* CottonBase.xcframework */; }; 7517D9D62A5F2EE600D081AD /* CottonBase.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7517D9C22A5F2E7A00D081AD /* CottonBase.xcframework */; }; 75196D072944ABA700B3A45B /* MainBrowserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75196D062944ABA700B3A45B /* MainBrowserView.swift */; }; - 75196D082944ABA700B3A45B /* MainBrowserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75196D062944ABA700B3A45B /* MainBrowserView.swift */; }; 75196D0D2944AF8B00B3A45B /* SiteMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75196D0C2944AF8B00B3A45B /* SiteMenuViewController.swift */; }; - 75196D0E2944AF8B00B3A45B /* SiteMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75196D0C2944AF8B00B3A45B /* SiteMenuViewController.swift */; }; 75196D102944AFD800B3A45B /* MainBrowserV2ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75196D0F2944AFD800B3A45B /* MainBrowserV2ViewController.swift */; }; - 75196D112944AFD800B3A45B /* MainBrowserV2ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75196D0F2944AFD800B3A45B /* MainBrowserV2ViewController.swift */; }; 752A8C082A7058B100804319 /* TabsListError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 752A8C072A7058B100804319 /* TabsListError.swift */; }; 752B12622B5D5631002EFA41 /* TabsPreviewsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 752B12612B5D5631002EFA41 /* TabsPreviewsViewModel.swift */; }; - 752B12632B5D5631002EFA41 /* TabsPreviewsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 752B12612B5D5631002EFA41 /* TabsPreviewsViewModel.swift */; }; 752B12652B5D78C0002EFA41 /* AllTabsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 752B12642B5D78C0002EFA41 /* AllTabsViewModel.swift */; }; - 752B12662B5D78C0002EFA41 /* AllTabsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 752B12642B5D78C0002EFA41 /* AllTabsViewModel.swift */; }; 7533929A291FCEDD00570461 /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75339299291FCEDD00570461 /* Coordinator.swift */; }; - 7533929B291FCEDD00570461 /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75339299291FCEDD00570461 /* Coordinator.swift */; }; 753392A1291FD8DA00570461 /* PhoneViewControllerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753392A0291FD8DA00570461 /* PhoneViewControllerFactory.swift */; }; - 753392A2291FD8DA00570461 /* PhoneViewControllerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753392A0291FD8DA00570461 /* PhoneViewControllerFactory.swift */; }; 753392A4291FD9A500570461 /* ViewControllerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753392A3291FD9A500570461 /* ViewControllerFactory.swift */; }; - 753392A5291FD9A500570461 /* ViewControllerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753392A3291FD9A500570461 /* ViewControllerFactory.swift */; }; 753392A7291FEBFD00570461 /* TabletViewControllerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753392A6291FEBFD00570461 /* TabletViewControllerFactory.swift */; }; - 753392A8291FEBFD00570461 /* TabletViewControllerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753392A6291FEBFD00570461 /* TabletViewControllerFactory.swift */; }; 753392AA2920BB6700570461 /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753392A92920BB6700570461 /* AppCoordinator.swift */; }; - 753392AB2920BB6700570461 /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753392A92920BB6700570461 /* AppCoordinator.swift */; }; 753392B32921613E00570461 /* GlobalMenuCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753392B22921613E00570461 /* GlobalMenuCoordinator.swift */; }; - 753392B42921613E00570461 /* GlobalMenuCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753392B22921613E00570461 /* GlobalMenuCoordinator.swift */; }; 753392B62922C9EB00570461 /* MainToolbarCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753392B52922C9EB00570461 /* MainToolbarCoordinator.swift */; }; - 753392B72922C9EB00570461 /* MainToolbarCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753392B52922C9EB00570461 /* MainToolbarCoordinator.swift */; }; 754249D32944B3F9002D6505 /* AppUIFrameworkTypeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 754249D22944B3F9002D6505 /* AppUIFrameworkTypeModel.swift */; }; - 754249D42944B3F9002D6505 /* AppUIFrameworkTypeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 754249D22944B3F9002D6505 /* AppUIFrameworkTypeModel.swift */; }; 75458E432B468FE800004581 /* ReadTabsUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75458E422B468FE800004581 /* ReadTabsUseCase.swift */; }; 75458E452B468FF800004581 /* WriteTabsUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75458E442B468FF800004581 /* WriteTabsUseCase.swift */; }; 75458E482B46EC9700004581 /* ReadTabsUseCaseImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75458E472B46EC9700004581 /* ReadTabsUseCaseImpl.swift */; }; @@ -127,26 +110,17 @@ 75458E5A2B471A2100004581 /* SelectedTabUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75458E592B471A2100004581 /* SelectedTabUseCase.swift */; }; 75458E5C2B471A4700004581 /* SelectedTabUseCaseImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75458E5B2B471A4700004581 /* SelectedTabUseCaseImpl.swift */; }; 755E107127BC3DC500D90AD0 /* AppAsyncApiTypeViewPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755E107027BC3DC500D90AD0 /* AppAsyncApiTypeViewPreview.swift */; }; - 755E107227BC3DC500D90AD0 /* AppAsyncApiTypeViewPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755E107027BC3DC500D90AD0 /* AppAsyncApiTypeViewPreview.swift */; }; 755E107427BC3E0800D90AD0 /* AppAsyncApiTypeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755E107327BC3E0800D90AD0 /* AppAsyncApiTypeModel.swift */; }; - 755E107527BC3E0800D90AD0 /* AppAsyncApiTypeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755E107327BC3E0800D90AD0 /* AppAsyncApiTypeModel.swift */; }; 755E107727BFF85900D90AD0 /* AlamofireHTTPAdaptee.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755E107627BFF85900D90AD0 /* AlamofireHTTPAdaptee.swift */; }; 755E107A27C0FBC600D90AD0 /* DuckDuckGoSearchEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755E107927C0FBC600D90AD0 /* DuckDuckGoSearchEndpoint.swift */; }; 755E107C27C1241000D90AD0 /* DuckDuckGoServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755E107B27C1241000D90AD0 /* DuckDuckGoServer.swift */; }; - 755E107F27C1668A00D90AD0 /* WebSearchSettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755E107D27C1660200D90AD0 /* WebSearchSettingsModel.swift */; }; 755E108027C1668A00D90AD0 /* WebSearchSettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755E107D27C1660200D90AD0 /* WebSearchSettingsModel.swift */; }; 755E108227C169AC00D90AD0 /* BaseListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755E108127C169AC00D90AD0 /* BaseListViewModel.swift */; }; - 755E108327C169AC00D90AD0 /* BaseListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755E108127C169AC00D90AD0 /* BaseListViewModel.swift */; }; 755E108527C16AB100D90AD0 /* BaseListViewModelImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755E108427C16AB100D90AD0 /* BaseListViewModelImpl.swift */; }; - 755E108627C16AB100D90AD0 /* BaseListViewModelImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755E108427C16AB100D90AD0 /* BaseListViewModelImpl.swift */; }; 755E109D27CA3A3100D90AD0 /* SettingsEnumTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755E109C27CA3A3100D90AD0 /* SettingsEnumTypes.swift */; }; - 755E109E27CA3A3100D90AD0 /* SettingsEnumTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755E109C27CA3A3100D90AD0 /* SettingsEnumTypes.swift */; }; 755E10A527CA3FC300D90AD0 /* SpecificEnumFeatures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755E10A427CA3FC300D90AD0 /* SpecificEnumFeatures.swift */; }; - 755E10A627CA3FC300D90AD0 /* SpecificEnumFeatures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755E10A427CA3FC300D90AD0 /* SpecificEnumFeatures.swift */; }; 755E10B727CA81B600D90AD0 /* SpecificApplicationEnumFeatures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755E10B627CA81B600D90AD0 /* SpecificApplicationEnumFeatures.swift */; }; - 755E10B827CA81B600D90AD0 /* SpecificApplicationEnumFeatures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755E10B627CA81B600D90AD0 /* SpecificApplicationEnumFeatures.swift */; }; 7570F7462B5C07E90066ABF1 /* UseCaseFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7570F7452B5C07E90066ABF1 /* UseCaseFactory.swift */; }; - 7570F7472B5C07E90066ABF1 /* UseCaseFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7570F7452B5C07E90066ABF1 /* UseCaseFactory.swift */; }; 75A764302B64BBB100D9D158 /* SiteNavigationProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A510F622424ADC30070503C /* SiteNavigationProtocols.swift */; }; 75A764312B64BBDF00D9D158 /* WebViewNavigatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A8F038A293FA96200237DFF /* WebViewNavigatable.swift */; }; 75A764322B64BC3800D9D158 /* SiteNavigationComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A8F0390293FABD000237DFF /* SiteNavigationComponent.swift */; }; @@ -156,12 +130,9 @@ 75D81CF72B0B077200447DC7 /* JSPluginsSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60380EF622A15504007F06FB /* JSPluginsSource.swift */; }; 75D81CFB2B0B084300447DC7 /* JSPluginsBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60380EF422A1509F007F06FB /* JSPluginsBuilder.swift */; }; 75EA4E6F29F7055800165A07 /* TitledImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75EA4E6E29F7055800165A07 /* TitledImageView.swift */; }; - 75EA4E7029F7055800165A07 /* TitledImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75EA4E6E29F7055800165A07 /* TitledImageView.swift */; }; 75EA5425296436C900EEF533 /* BrowserToolbarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75EA5424296436C900EEF533 /* BrowserToolbarViewModel.swift */; }; - 75EA5426296436C900EEF533 /* BrowserToolbarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75EA5424296436C900EEF533 /* BrowserToolbarViewModel.swift */; }; 75F342472A6063A400E66192 /* AuthChallenge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75F342462A6063A400E66192 /* AuthChallenge.swift */; }; 75FAAB38292D56880086F2D1 /* SearchBarCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75FAAB37292D56880086F2D1 /* SearchBarCoordinator.swift */; }; - 75FAAB39292D56880086F2D1 /* SearchBarCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75FAAB37292D56880086F2D1 /* SearchBarCoordinator.swift */; }; 841022B9224781A20021516F /* LinkTagsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841022B8224781A20021516F /* LinkTagsViewController.swift */; }; 841022BD224785340021516F /* LinksBadgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841022BC224785340021516F /* LinksBadgeView.swift */; }; 841022C1224788F80021516F /* LinkTagsViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 841022C0224788F80021516F /* LinkTagsViewController.storyboard */; }; @@ -181,12 +152,9 @@ 847D7C8D1EF6C8AC00291B87 /* TabsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847D7C881EF6C30F00291B87 /* TabsViewController.swift */; }; 8A012FA422400F1200041487 /* InstagramHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A012FA322400F1200041487 /* InstagramHandler.swift */; }; 8A02AE802965CD9F004041D8 /* SearchSuggestionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A02AE7F2965CD9F004041D8 /* SearchSuggestionsView.swift */; }; - 8A02AE812965CD9F004041D8 /* SearchSuggestionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A02AE7F2965CD9F004041D8 /* SearchSuggestionsView.swift */; }; 8A0319DC29FE71A500AF3BE3 /* UIViewControllerRepresentable+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0319DB29FE71A500AF3BE3 /* UIViewControllerRepresentable+Extension.swift */; }; - 8A0319DD29FE71A500AF3BE3 /* UIViewControllerRepresentable+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0319DB29FE71A500AF3BE3 /* UIViewControllerRepresentable+Extension.swift */; }; 8A03F45A28F47ACB00E4A296 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = 8A03F45928F47ACB00E4A296 /* SwiftSoup */; }; 8A046DE02965AC9300BB6D63 /* SearchBarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A046DDF2965AC9300BB6D63 /* SearchBarViewModel.swift */; }; - 8A046DE12965AC9300BB6D63 /* SearchBarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A046DDF2965AC9300BB6D63 /* SearchBarViewModel.swift */; }; 8A06D6F328EC2BE6001CB63D /* RestClientContextMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A06D6F228EC2BE6001CB63D /* RestClientContextMocks.swift */; }; 8A06D6F528EC2C5D001CB63D /* ResponseTypeMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A06D6F428EC2C5D001CB63D /* ResponseTypeMocks.swift */; }; 8A06D6F728EC2CF8001CB63D /* ServerDescriptionMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A06D6F628EC2CF8001CB63D /* ServerDescriptionMocks.swift */; }; @@ -197,41 +165,26 @@ 8A0E4D5A29133E77008F7964 /* SwiftyMocky in Frameworks */ = {isa = PBXBuildFile; productRef = 8A0E4D5929133E77008F7964 /* SwiftyMocky */; }; 8A0E4D5B29133F65008F7964 /* WebSearchAutocompleteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AB9BE0928FEB17E002063C2 /* WebSearchAutocompleteTests.swift */; }; 8A0FC19B29F246640044829A /* SearchSuggestionsLegacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0FC19A29F246640044829A /* SearchSuggestionsLegacyView.swift */; }; - 8A0FC19C29F246640044829A /* SearchSuggestionsLegacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0FC19A29F246640044829A /* SearchSuggestionsLegacyView.swift */; }; 8A0FC1A029F24C950044829A /* PhoneSearchBarLegacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0FC19F29F24C950044829A /* PhoneSearchBarLegacyView.swift */; }; - 8A0FC1A129F24C950044829A /* PhoneSearchBarLegacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0FC19F29F24C950044829A /* PhoneSearchBarLegacyView.swift */; }; 8A0FC1A329F24D320044829A /* TabletSearchBarLegacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0FC1A229F24D320044829A /* TabletSearchBarLegacyView.swift */; }; - 8A0FC1A429F24D320044829A /* TabletSearchBarLegacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0FC1A229F24D320044829A /* TabletSearchBarLegacyView.swift */; }; 8A0FC1A629F2586B0044829A /* ToolbarViewV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0FC1A529F2586B0044829A /* ToolbarViewV2.swift */; }; - 8A0FC1A729F2586B0044829A /* ToolbarViewV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0FC1A529F2586B0044829A /* ToolbarViewV2.swift */; }; 8A0FC1A929F259D20044829A /* ToolbarLegacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0FC1A829F259D20044829A /* ToolbarLegacyView.swift */; }; - 8A0FC1AA29F259D20044829A /* ToolbarLegacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0FC1A829F259D20044829A /* ToolbarLegacyView.swift */; }; 8A10C77529F64A79002A429A /* TopSitesViewV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A10C77429F64A79002A429A /* TopSitesViewV2.swift */; }; - 8A10C77629F64A79002A429A /* TopSitesViewV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A10C77429F64A79002A429A /* TopSitesViewV2.swift */; }; 8A1115EB24837DA400E63114 /* TabAddPositionsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A1115EA24837DA400E63114 /* TabAddPositionsModel.swift */; }; - 8A1115EC24837DA400E63114 /* TabAddPositionsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A1115EA24837DA400E63114 /* TabAddPositionsModel.swift */; }; 8A1273DD2937B7FC0022B934 /* FilesGridCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A1273DC2937B7FC0022B934 /* FilesGridCoordinator.swift */; }; - 8A1273DE2937B7FC0022B934 /* FilesGridCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A1273DC2937B7FC0022B934 /* FilesGridCoordinator.swift */; }; 8A1A1CE727B1966E0052A2A3 /* AlamofireHTTPRxAdaptee.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AB0D4102755075700493EC3 /* AlamofireHTTPRxAdaptee.swift */; }; 8A1C1BD9280B1E2D009B9363 /* RestClient+Kotlin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A1C1BD8280B1E2D009B9363 /* RestClient+Kotlin.swift */; }; 8A1C1C172484E5900098F2F1 /* TabDefaultContentModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A1C1C162484E5900098F2F1 /* TabDefaultContentModel.swift */; }; - 8A1C1C182484E5900098F2F1 /* TabDefaultContentModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A1C1C162484E5900098F2F1 /* TabDefaultContentModel.swift */; }; 8A1C1C1A2484E6450098F2F1 /* TabDefaultContentViewPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A1C1C192484E6450098F2F1 /* TabDefaultContentViewPreview.swift */; }; - 8A1C1C1B2484E6450098F2F1 /* TabDefaultContentViewPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A1C1C192484E6450098F2F1 /* TabDefaultContentViewPreview.swift */; }; 8A1C1C1D24850F3F0098F2F1 /* WebViewController+SiteNavigationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A1C1C1C24850F3F0098F2F1 /* WebViewController+SiteNavigationDelegate.swift */; }; - 8A1C1C1E24850F3F0098F2F1 /* WebViewController+SiteNavigationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A1C1C1C24850F3F0098F2F1 /* WebViewController+SiteNavigationDelegate.swift */; }; 8A22D1B42923FDA1005D1F72 /* PhoneTabsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A22D1B32923FDA1005D1F72 /* PhoneTabsCoordinator.swift */; }; - 8A22D1B52923FDA1005D1F72 /* PhoneTabsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A22D1B32923FDA1005D1F72 /* PhoneTabsCoordinator.swift */; }; 8A239879286222F1005DA801 /* DuckDuckGoSearchEndpoint+AsyncAwait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A239878286222F1005DA801 /* DuckDuckGoSearchEndpoint+AsyncAwait.swift */; }; 8A23989328622FCA005DA801 /* ViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A23989228622FCA005DA801 /* ViewModelFactory.swift */; }; - 8A23989428622FCA005DA801 /* ViewModelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A23989228622FCA005DA801 /* ViewModelFactory.swift */; }; 8A258330284B45D30047077A /* JavaScriptPluginVisitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A25832F284B45D30047077A /* JavaScriptPluginVisitor.swift */; }; 8A258340284B4F090047077A /* WKUserContentController+Visitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A25833F284B4F090047077A /* WKUserContentController+Visitor.swift */; }; 8A27713B268E452000D8878C /* BrowserNetworking.h in Headers */ = {isa = PBXBuildFile; fileRef = 8A277139268E452000D8878C /* BrowserNetworking.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8A27713F268E452000D8878C /* BrowserNetworking.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8A277137268E452000D8878C /* BrowserNetworking.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 8A27715A268E467500D8878C /* BrowserNetworking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A277137268E452000D8878C /* BrowserNetworking.framework */; }; - 8A27715D268E467D00D8878C /* BrowserNetworking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A277137268E452000D8878C /* BrowserNetworking.framework */; }; - 8A27715E268E467D00D8878C /* BrowserNetworking.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8A277137268E452000D8878C /* BrowserNetworking.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 8A277192268E4AA400D8878C /* GoogleJSONDNSoHTTPsEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7DCCCC23643032006AAEE2 /* GoogleJSONDNSoHTTPsEndpoint.swift */; }; 8A2771A3268E4AA800D8878C /* GoogleJSONDNSoHTTPsEndpoint+AsyncAwait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A9069D9267F52BE0058F12D /* GoogleJSONDNSoHTTPsEndpoint+AsyncAwait.swift */; }; 8A2771AC268E4AAC00D8878C /* GoogleSearchEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A62AEE02351D1CE001A9C83 /* GoogleSearchEndpoint.swift */; }; @@ -245,22 +198,15 @@ 8A27721D268E4ACF00D8878C /* OpenSearchParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE46353244E07E600C8E063 /* OpenSearchParser.swift */; }; 8A27722E268E4AD400D8878C /* SearchEngine+OpenSearchParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A968C24462E8700E035F8 /* SearchEngine+OpenSearchParser.swift */; }; 8A3286F0294B585100ECCAEA /* SearchBarLegacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A3286EF294B585100ECCAEA /* SearchBarLegacyView.swift */; }; - 8A3286F1294B585100ECCAEA /* SearchBarLegacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A3286EF294B585100ECCAEA /* SearchBarLegacyView.swift */; }; 8A328F3527B690980021F6B0 /* NetworkReachabilityAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A328F3427B690980021F6B0 /* NetworkReachabilityAdapter.swift */; }; 8A328F3927B695490021F6B0 /* AlamofireReachabilityAdaptee.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A328F3827B695490021F6B0 /* AlamofireReachabilityAdaptee.swift */; }; 8A437DA5247E7210001C1E8B /* UIHostingController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A437DA4247E7210001C1E8B /* UIHostingController+Extension.swift */; }; - 8A437DA6247E7210001C1E8B /* UIHostingController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A437DA4247E7210001C1E8B /* UIHostingController+Extension.swift */; }; 8A437DA9247ED376001C1E8B /* TrimmedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A437DA8247ED376001C1E8B /* TrimmedString.swift */; }; 8A4815062922A4B700F9DA1F /* SearchSuggestionsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A4815052922A4B700F9DA1F /* SearchSuggestionsCoordinator.swift */; }; - 8A4815072922A4B700F9DA1F /* SearchSuggestionsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A4815052922A4B700F9DA1F /* SearchSuggestionsCoordinator.swift */; }; 8A49D80C2BA1EF7100F84700 /* MenuItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A49D80B2BA1EF7100F84700 /* MenuItems.swift */; }; - 8A49D80D2BA1EF7100F84700 /* MenuItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A49D80B2BA1EF7100F84700 /* MenuItems.swift */; }; 8A49D80F2BA21B9B00F84700 /* MenuStatefullLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A49D80E2BA21B9B00F84700 /* MenuStatefullLabel.swift */; }; - 8A49D8102BA21B9B00F84700 /* MenuStatefullLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A49D80E2BA21B9B00F84700 /* MenuStatefullLabel.swift */; }; 8A55351A247D740900E3B330 /* MenuViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A553519247D740900E3B330 /* MenuViewModel.swift */; }; - 8A55351B247D740900E3B330 /* MenuViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A553519247D740900E3B330 /* MenuViewModel.swift */; }; 8A566F7F245D7D7B00E71221 /* AlertPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A566F7E245D7D7B00E71221 /* AlertPresenter.swift */; }; - 8A566F80245D7D7B00E71221 /* AlertPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A566F7E245D7D7B00E71221 /* AlertPresenter.swift */; }; 8A5E6C80290B0E6C0034A78B /* AutoMockable.h in Headers */ = {isa = PBXBuildFile; fileRef = 8A5E6C7F290B0E6C0034A78B /* AutoMockable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8A5E6C83290B0E6C0034A78B /* AutoMockable.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A5E6C7D290B0E6C0034A78B /* AutoMockable.framework */; }; 8A5E6C84290B0E6C0034A78B /* AutoMockable.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8A5E6C7D290B0E6C0034A78B /* AutoMockable.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -277,14 +223,10 @@ 8A5FD6A526AF31FE0019384A /* CottonRestKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8AE6D1692350FF35006316AD /* CottonRestKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 8A68E40E245966190070F338 /* CombineExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A68E40D245966190070F338 /* CombineExtensions.swift */; }; 8A6CD6D424585D830068ACB5 /* Combine.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A6CD6D324585D830068ACB5 /* Combine.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; - 8A6CD6D524585D8C0068ACB5 /* Combine.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A6CD6D324585D830068ACB5 /* Combine.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; - 8A745C0A247FE93F00A379AD /* DefaultTabProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60724ED92204332B0071BECF /* DefaultTabProvider.swift */; }; 8A745C0B247FE94000A379AD /* DefaultTabProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60724ED92204332B0071BECF /* DefaultTabProvider.swift */; }; - 8A745C0C248027C600A379AD /* TabsRepositoryImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60724EA621FF17C90071BECF /* TabsRepositoryImpl.swift */; }; 8A745C0D248027C600A379AD /* TabsRepositoryImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60724EA621FF17C90071BECF /* TabsRepositoryImpl.swift */; }; 8A745C0F2480287700A379AD /* TabsRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A745C0E2480287700A379AD /* TabsRepository.swift */; }; 8A745C112480295B00A379AD /* TabsEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A745C102480295B00A379AD /* TabsEnvironment.swift */; }; - 8A745C122480295B00A379AD /* TabsEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A745C102480295B00A379AD /* TabsEnvironment.swift */; }; 8A745C1424802E4F00A379AD /* TabsObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A745C1324802E4F00A379AD /* TabsObserver.swift */; }; 8A7A9BAC23F1C477008850B6 /* URL+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A9BAB23F1C477008850B6 /* URL+Extensions.swift */; }; 8A7B1BA028EB131F004FC135 /* CottonData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A7B1B9828EB131E004FC135 /* CottonData.framework */; }; @@ -314,99 +256,37 @@ 8A7B1BDC28EB1736004FC135 /* CottonPlugins.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 608DA74A223A3BD4009F75BD /* CottonPlugins.framework */; }; 8A7B1BE428EB1771004FC135 /* CoreBrowser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6094E71C221FD2B9004269A2 /* CoreBrowser.framework */; }; 8A7B1BE828EB18A5004FC135 /* CottonData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A7B1B9828EB131E004FC135 /* CottonData.framework */; }; - 8A7B1BEB28EB18AD004FC135 /* CottonData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A7B1B9828EB131E004FC135 /* CottonData.framework */; }; - 8A7B1BEC28EB18AD004FC135 /* CottonData.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8A7B1B9828EB131E004FC135 /* CottonData.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 8A7B1BF228EB28D3004FC135 /* AppErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A5FD6A826AF34570019384A /* AppErrors.swift */; }; 8A7B1BF428EB2A4D004FC135 /* AsyncApiType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7B1BF328EB2A4D004FC135 /* AsyncApiType.swift */; }; 8A7B1BF528EB2C08004FC135 /* Host+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AC6972628BB35DE00981BBF /* Host+Extension.swift */; }; 8A7B1BF728EB2C80004FC135 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A7B1BF628EB2C7F004FC135 /* WebKit.framework */; }; 8A7B1BF828EB2D7D004FC135 /* SiteSettings+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A964E3924867A9400A0AABE /* SiteSettings+Extensions.swift */; }; 8A7B1BFC28EB2E59004FC135 /* WebViewContextImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7B1BFB28EB2E59004FC135 /* WebViewContextImpl.swift */; }; - 8A7B1BFD28EB2E59004FC135 /* WebViewContextImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7B1BFB28EB2E59004FC135 /* WebViewContextImpl.swift */; }; 8A7B1BFF28EB3269004FC135 /* Site+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7B1BFE28EB3269004FC135 /* Site+Extensions.swift */; }; 8A7ECCBB26AF2BEF000C902F /* CottonRestKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8AE6D1692350FF35006316AD /* CottonRestKit.framework */; }; 8A7EF3D5273924EF0071BB89 /* CottonRestKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8AE6D1692350FF35006316AD /* CottonRestKit.framework */; }; - 8A8035A224442F0F000E035F /* BaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8435A49C1EED5E700020102F /* BaseViewController.swift */; }; - 8A8035A724442F0F000E035F /* LightTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6010ECCA21F751B500972E28 /* LightTheme.swift */; }; - 8A8035A824442F0F000E035F /* WebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22599621F7BEF6D000E4C3A /* WebViewController.swift */; }; - 8A8035A924442F0F000E035F /* TabletSearchBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841A2D81203AD5500050A996 /* TabletSearchBarViewController.swift */; }; - 8A8035AA24442F0F000E035F /* SearchBarBaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841A2D85203B1FA50050A996 /* SearchBarBaseViewController.swift */; }; - 8A8035AB24442F0F000E035F /* TabsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847D7C881EF6C30F00291B87 /* TabsViewController.swift */; }; - 8A8035AD24442F0F000E035F /* TabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84704FC11EEE769800F89F6B /* TabView.swift */; }; - 8A8035AE24442F0F000E035F /* UIViewController+MainBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841022CA2247ACF00021516F /* UIViewController+MainBundle.swift */; }; - 8A8035AF24442F0F000E035F /* TopSitesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C2C8CF226DAB1700F5D869 /* TopSitesViewController.swift */; }; - 8A8035B224442F0F000E035F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8435A49A1EED5E700020102F /* AppDelegate.swift */; }; - 8A8035B324442F0F000E035F /* WebViewsReuseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60724EED220C4FDC0071BECF /* WebViewsReuseManager.swift */; }; - 8A8035B424442F0F000E035F /* TabsPreviewsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 605C54F421F8A8A100B3DC73 /* TabsPreviewsViewController.swift */; }; - 8A8035B524442F0F000E035F /* SmartphoneSearchBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841A2D83203AD5690050A996 /* SmartphoneSearchBarViewController.swift */; }; - 8A8035B624442F0F000E035F /* TabPreviewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 605C54F621F8A9E900B3DC73 /* TabPreviewCell.swift */; }; - 8A8035B724442F0F000E035F /* SiteCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C2C90422801F3400F5D869 /* SiteCollectionViewCell.swift */; }; - 8A8035B824442F0F000E035F /* ThemeProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6010ECCC21F7529400972E28 /* ThemeProvider.swift */; }; - 8A8035B924442F0F000E035F /* FilesGridViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60A82C4E22549453008210D9 /* FilesGridViewController.swift */; }; - 8A8035BB24442F0F000E035F /* TagsSiteDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A95D941226E0E5200862E8D /* TagsSiteDataSource.swift */; }; - 8A8035BC24442F0F000E035F /* LinksBadgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841022BC224785340021516F /* LinksBadgeView.swift */; }; - 8A8035BD24442F0F000E035F /* BrowserToolbarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C12BE5226750B7004FCB14 /* BrowserToolbarView.swift */; }; - 8A8035BE24442F0F000E035F /* BrowserToolbarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841A2D7F203AD0B50050A996 /* BrowserToolbarController.swift */; }; - 8A8035BF24442F0F000E035F /* MainBrowserViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22599661F7BF301000E4C3A /* MainBrowserViewController.swift */; }; - 8A8035C024442F0F000E035F /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6010ECC521F74E2900972E28 /* Theme.swift */; }; - 8A8035C124442F0F000E035F /* LinkTagsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841022B8224781A20021516F /* LinkTagsViewController.swift */; }; - 8A8035C224442F0F000E035F /* FileDownloadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C2C8EB226F32B700F5D869 /* FileDownloadViewModel.swift */; }; - 8A8035C424442F0F000E035F /* SearchSuggestionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C707CC11203F0BAF002EA258 /* SearchSuggestionsViewController.swift */; }; - 8A8035C524442F0F000E035F /* DomainNativeAppChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607CC66422A52DDE0037E5EC /* DomainNativeAppChecker.swift */; }; - 8A8035C724442F0F000E035F /* UIConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8412FF3C1F72AF050011FDD7 /* UIConstants.swift */; }; - 8A8035C924442F0F000E035F /* BlankWebPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841A2D7D203ACC220050A996 /* BlankWebPageViewController.swift */; }; - 8A8035CB24442F0F000E035F /* CounterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60724EA821FF1BD80071BECF /* CounterView.swift */; }; - 8A8035CC24442F0F000E035F /* Site+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7509BF9122AF832200F4D701 /* Site+Extension.swift */; }; - 8A8035CD24442F0F000E035F /* DownloadButtonCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C2C8ED2270739F00F5D869 /* DownloadButtonCellView.swift */; }; - 8A8035CF24442F0F000E035F /* CottonPlugins.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 608DA74A223A3BD4009F75BD /* CottonPlugins.framework */; }; - 8A8035D124442F0F000E035F /* CoreBrowser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6094E71C221FD2B9004269A2 /* CoreBrowser.framework */; }; - 8A8035D424442F0F000E035F /* LinkTagsViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 841022C0224788F80021516F /* LinkTagsViewController.storyboard */; }; - 8A8035D524442F0F000E035F /* FilesGridViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 60A82C4C22549446008210D9 /* FilesGridViewController.storyboard */; }; - 8A8035D624442F0F000E035F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8435A4A31EED5E700020102F /* LaunchScreen.storyboard */; }; - 8A8035D724442F0F000E035F /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C27371191F7A341900900F03 /* Localizable.strings */; }; - 8A8035D924442F0F000E035F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8435A4A11EED5E700020102F /* Assets.xcassets */; }; - 8A8035DA24442F0F000E035F /* TopSitesViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 60C2C8FF2280184B00F5D869 /* TopSitesViewController.xib */; }; - 8A8035DE24442F0F000E035F /* CottonPlugins.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 608DA74A223A3BD4009F75BD /* CottonPlugins.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 8A8035DF24442F0F000E035F /* CoreBrowser.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6094E71C221FD2B9004269A2 /* CoreBrowser.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 8A8035EA24442F45000E035F /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A8035E924442F45000E035F /* NetworkExtension.framework */; }; 8A8035F224446234000E035F /* AppProxyProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A8035F124446234000E035F /* AppProxyProvider.swift */; }; - 8A8035F724446234000E035F /* DNSProxy.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 8A8035EF24446234000E035F /* DNSProxy.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 8A815D3D28F16367007A6926 /* JSPluginsProgramImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A815D3C28F16367007A6926 /* JSPluginsProgramImpl.swift */; }; 8A81C6F92945C72F0075BEF9 /* View+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A81C6F82945C72F0075BEF9 /* View+Extension.swift */; }; - 8A81C6FA2945C72F0075BEF9 /* View+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A81C6F82945C72F0075BEF9 /* View+Extension.swift */; }; 8A81C6FC2945C8050075BEF9 /* ToolbarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A81C6FB2945C8050075BEF9 /* ToolbarView.swift */; }; - 8A81C6FD2945C8050075BEF9 /* ToolbarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A81C6FB2945C8050075BEF9 /* ToolbarView.swift */; }; 8A825947294E2E1A007B97CF /* TopSitesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A825946294E2E1A007B97CF /* TopSitesView.swift */; }; - 8A825948294E2E1A007B97CF /* TopSitesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A825946294E2E1A007B97CF /* TopSitesView.swift */; }; 8A82594A294E40F5007B97CF /* BrowserContentViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A825949294E40F5007B97CF /* BrowserContentViewModel.swift */; }; - 8A82594B294E40F5007B97CF /* BrowserContentViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A825949294E40F5007B97CF /* BrowserContentViewModel.swift */; }; 8A82594D294E47F9007B97CF /* TopSitesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A82594C294E47F9007B97CF /* TopSitesViewModel.swift */; }; - 8A82594E294E47F9007B97CF /* TopSitesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A82594C294E47F9007B97CF /* TopSitesViewModel.swift */; }; 8A82CDF02351095700607162 /* RestClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A82CDEF2351095700607162 /* RestClient.swift */; }; 8A89E22B29F4FA5D00FE5543 /* SearchBarState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A89E22A29F4FA5D00FE5543 /* SearchBarState.swift */; }; - 8A89E22C29F4FA5D00FE5543 /* SearchBarState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A89E22A29F4FA5D00FE5543 /* SearchBarState.swift */; }; 8A8EB007245EA1BA0028D35C /* WebViewAuthChallengeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A8EB006245EA1B90028D35C /* WebViewAuthChallengeHandler.swift */; }; - 8A8EB008245EA1BA0028D35C /* WebViewAuthChallengeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A8EB006245EA1B90028D35C /* WebViewAuthChallengeHandler.swift */; }; 8A8F038E293FAB3200237DFF /* GlobalMenuDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A8F038D293FAB3200237DFF /* GlobalMenuDelegate.swift */; }; - 8A8F038F293FAB3200237DFF /* GlobalMenuDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A8F038D293FAB3200237DFF /* GlobalMenuDelegate.swift */; }; 8A90636924332376009D786E /* URL+CoreExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A90636824332376009D786E /* URL+CoreExtensions.swift */; }; 8A90B16C248246D6000618F5 /* TabAddPositionsViewPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A90B16B248246D6000618F5 /* TabAddPositionsViewPreview.swift */; }; - 8A90B16D248246D6000618F5 /* TabAddPositionsViewPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A90B16B248246D6000618F5 /* TabAddPositionsViewPreview.swift */; }; 8A917CAD29D4454700404256 /* AutoMockable.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A917CAC29D4454700404256 /* AutoMockable.generated.swift */; }; 8A917CAF29D49CA000404256 /* Mock.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A917CAE29D49CA000404256 /* Mock.generated.swift */; }; 8A917CB129D4A39B00404256 /* SearchSuggestionsViewV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A917CB029D4A39B00404256 /* SearchSuggestionsViewV2.swift */; }; - 8A917CB229D4A39B00404256 /* SearchSuggestionsViewV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A917CB029D4A39B00404256 /* SearchSuggestionsViewV2.swift */; }; 8A917CB429D4E63700404256 /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A917CB329D4E63700404256 /* String+Extension.swift */; }; - 8A917CB529D4E63700404256 /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A917CB329D4E63700404256 /* String+Extension.swift */; }; 8A9495BE2A6C0023001D275D /* TabViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A9495BD2A6C0023001D275D /* TabViewModel.swift */; }; - 8A9495BF2A6C0023001D275D /* TabViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A9495BD2A6C0023001D275D /* TabViewModel.swift */; }; 8A95D942226E0E5200862E8D /* TagsSiteDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A95D941226E0E5200862E8D /* TagsSiteDataSource.swift */; }; 8A963C6D25212A55008EAB27 /* CottonDbModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 8A963C6B25212A55008EAB27 /* CottonDbModel.xcdatamodeld */; }; - 8A963C6E25212A55008EAB27 /* CottonDbModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 8A963C6B25212A55008EAB27 /* CottonDbModel.xcdatamodeld */; }; 8A96BEC92481237B00E81C38 /* SpecificBasicFeatures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A96BEC82481237B00E81C38 /* SpecificBasicFeatures.swift */; }; - 8A96BECA2481237B00E81C38 /* SpecificBasicFeatures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A96BEC82481237B00E81C38 /* SpecificBasicFeatures.swift */; }; 8A97EF25245B44D4003AC9D7 /* UIImageView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A97EF24245B44D4003AC9D7 /* UIImageView+Extension.swift */; }; - 8A97EF26245B44D4003AC9D7 /* UIImageView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A97EF24245B44D4003AC9D7 /* UIImageView+Extension.swift */; }; 8A9863692822441300F679D7 /* URLInfo+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A986366282171F000F679D7 /* URLInfo+Extension.swift */; }; 8A98636B2822612500F679D7 /* EndpointMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A98636A2822612500F679D7 /* EndpointMocks.swift */; }; 8A99805E28453DE900E18762 /* NearbySelectionStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A99805D28453DE900E18762 /* NearbySelectionStrategy.swift */; }; @@ -420,18 +300,13 @@ 8A99806F2845484100E18762 /* JavaScriptEvaluateble.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A99806E2845484100E18762 /* JavaScriptEvaluateble.swift */; }; 8A998071284548C200E18762 /* CottonPluginError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A998070284548C200E18762 /* CottonPluginError.swift */; }; 8A9D9D7F2971D2300047734C /* PhoneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A9D9D7E2971D2300047734C /* PhoneView.swift */; }; - 8A9D9D802971D2300047734C /* PhoneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A9D9D7E2971D2300047734C /* PhoneView.swift */; }; 8A9D9D832971D6B10047734C /* TabletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A9D9D822971D6B10047734C /* TabletView.swift */; }; - 8A9D9D842971D6B10047734C /* TabletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A9D9D822971D6B10047734C /* TabletView.swift */; }; 8A9D9D892971D8330047734C /* DummyDelegateForPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A9D9D882971D8330047734C /* DummyDelegateForPreview.swift */; }; - 8A9D9D8A2971D8330047734C /* DummyDelegateForPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A9D9D882971D8330047734C /* DummyDelegateForPreview.swift */; }; 8A9D9D8F2971E05F0047734C /* TabletTabsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A9D9D8E2971E05F0047734C /* TabletTabsView.swift */; }; - 8A9D9D902971E05F0047734C /* TabletTabsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A9D9D8E2971E05F0047734C /* TabletTabsView.swift */; }; 8AA4FFF527A013DC003C3BBA /* HTTPAdapteeMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AA4FFF427A013DC003C3BBA /* HTTPAdapteeMocks.swift */; }; 8AA60B2225A33568002EF8AF /* TabCacheErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AA60B2125A33568002EF8AF /* TabCacheErrors.swift */; }; 8AA6608A28F43F790058490F /* SWXMLHash in Frameworks */ = {isa = PBXBuildFile; productRef = 8AA6608928F43F790058490F /* SWXMLHash */; }; 8AA68A9F245BE47900263E22 /* HttpEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AA68A9E245BE47900263E22 /* HttpEnvironment.swift */; }; - 8AA68AA0245BE47900263E22 /* HttpEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AA68A9E245BE47900263E22 /* HttpEnvironment.swift */; }; 8AA89DE6296F4D6A00A6AC1A /* WebViewVMFixture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AA89DE5296F4D6A00A6AC1A /* WebViewVMFixture.swift */; }; 8AA89DEA296F55D900A6AC1A /* WebViewVMConcurrencyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AA89DE9296F55D900A6AC1A /* WebViewVMConcurrencyTests.swift */; }; 8AA89DED296F596600A6AC1A /* XCTestCase+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AA89DEC296F596600A6AC1A /* XCTestCase+Extension.swift */; }; @@ -447,7 +322,6 @@ 8AB4A1B727B56CC3008030FB /* ResponseVoidHandlingApiType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AB4A1B627B56CC3008030FB /* ResponseVoidHandlingApiType.swift */; }; 8AB4A1B927B583AB008030FB /* ClosureVoidWrappers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AB4A1B827B583AB008030FB /* ClosureVoidWrappers.swift */; }; 8AB5B120294F0CCA00C91383 /* EnvironmentValues+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AB5B11F294F0CCA00C91383 /* EnvironmentValues+Extension.swift */; }; - 8AB5B121294F0CCA00C91383 /* EnvironmentValues+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AB5B11F294F0CCA00C91383 /* EnvironmentValues+Extension.swift */; }; 8AB5B3C4280C8B3D001F8644 /* RequestInterfaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AB5B3C3280C8B3D001F8644 /* RequestInterfaces.swift */; }; 8AB5B3C6280C92B1001F8644 /* HttpKotlinTypes+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AB5B3C5280C92B1001F8644 /* HttpKotlinTypes+Extensions.swift */; }; 8AB70E8B27B7AACA003FB59B /* ReachabilityAdapteeMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AB70E8A27B7AACA003FB59B /* ReachabilityAdapteeMocks.swift */; }; @@ -457,48 +331,30 @@ 8AB70EA427B7B214003FB59B /* RxObserverWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AB70EA327B7B214003FB59B /* RxObserverWrapper.swift */; }; 8AB70EA627B7B518003FB59B /* HTTPAdapter+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AB70EA527B7B518003FB59B /* HTTPAdapter+Rx.swift */; }; 8AB8018A27C3E597002D6628 /* BaseMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AB8018927C3E597002D6628 /* BaseMenuView.swift */; }; - 8AB8018B27C3E597002D6628 /* BaseMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AB8018927C3E597002D6628 /* BaseMenuView.swift */; }; 8AB9BE0F28FEFD84002063C2 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 607CC66622A5505F0037E5EC /* UIKit.framework */; platformFilter = ios; }; 8AB9BE1128FF0C23002063C2 /* UIViewController+CatowserExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6094E712221EC8D4004269A2 /* UIViewController+CatowserExtensions.swift */; }; - 8AB9BE1228FF0C23002063C2 /* UIViewController+CatowserExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6094E712221EC8D4004269A2 /* UIViewController+CatowserExtensions.swift */; }; - 8AB9BE1328FF0C55002063C2 /* CollectionViewInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60D52CED22572F2300CB56F0 /* CollectionViewInterface.swift */; }; 8AB9BE1428FF0C55002063C2 /* CollectionViewInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60D52CED22572F2300CB56F0 /* CollectionViewInterface.swift */; }; - 8AB9BE1528FF0C74002063C2 /* UIColour+CatowserExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C705C0ED204340BD0018067F /* UIColour+CatowserExtensions.swift */; }; 8AB9BE1628FF0C75002063C2 /* UIColour+CatowserExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C705C0ED204340BD0018067F /* UIColour+CatowserExtensions.swift */; }; 8AB9BE1728FF0C98002063C2 /* ReloadWithCompletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C2C8F02270886A00F5D869 /* ReloadWithCompletion.swift */; }; - 8AB9BE1828FF0C98002063C2 /* ReloadWithCompletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C2C8F02270886A00F5D869 /* ReloadWithCompletion.swift */; }; - 8AB9BE1928FF0CC8002063C2 /* UIImage+CatowserExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A9C9FFC22134A5A0093756C /* UIImage+CatowserExtensions.swift */; }; 8AB9BE1A28FF0CC8002063C2 /* UIImage+CatowserExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A9C9FFC22134A5A0093756C /* UIImage+CatowserExtensions.swift */; }; 8AB9BE1B28FF0CE7002063C2 /* CAGradientLayer+CatowserExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60A82C5022549604008210D9 /* CAGradientLayer+CatowserExtension.swift */; }; - 8AB9BE1C28FF0CE7002063C2 /* CAGradientLayer+CatowserExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60A82C5022549604008210D9 /* CAGradientLayer+CatowserExtension.swift */; }; - 8AB9BE1D28FF0CFF002063C2 /* ReusableItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 605C550621F8AA5900B3DC73 /* ReusableItem.swift */; }; 8AB9BE1E28FF0CFF002063C2 /* ReusableItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 605C550621F8AA5900B3DC73 /* ReusableItem.swift */; }; - 8AB9BE1F28FF0D0D002063C2 /* AnyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60724EE82209C9A20071BECF /* AnyViewController.swift */; }; 8AB9BE2028FF0D0D002063C2 /* AnyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60724EE82209C9A20071BECF /* AnyViewController.swift */; }; 8AB9BE2228FF0D5C002063C2 /* Tab+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AB9BE2128FF0D5C002063C2 /* Tab+Extension.swift */; }; - 8AB9BE2328FF0D5C002063C2 /* Tab+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AB9BE2128FF0D5C002063C2 /* Tab+Extension.swift */; }; 8AB9BE2728FF1304002063C2 /* CssParser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8AF43375248EBB2A0047452B /* CssParser.framework */; }; 8AB9BE2C28FF149A002063C2 /* FeaturesFlagsKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8ABF503E27CC1A64007C6427 /* FeaturesFlagsKit.framework */; }; 8ABA3CCF2522601800E304E2 /* TabsDBClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ABA3CCE2522601800E304E2 /* TabsDBClient.swift */; }; - 8ABA3CD02522601800E304E2 /* TabsDBClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ABA3CCE2522601800E304E2 /* TabsDBClient.swift */; }; 8ABA3CD22522602900E304E2 /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8ABA3CD12522602900E304E2 /* CoreData.framework */; }; - 8ABA3CD32522603000E304E2 /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8ABA3CD12522602900E304E2 /* CoreData.framework */; }; 8ABA3CD52522608000E304E2 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ABA3CD42522608000E304E2 /* Database.swift */; }; - 8ABA3CD62522608000E304E2 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ABA3CD42522608000E304E2 /* Database.swift */; }; 8ABA3CD82522623200E304E2 /* TabsResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ABA3CD72522623200E304E2 /* TabsResource.swift */; }; - 8ABA3CD92522623200E304E2 /* TabsResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ABA3CD72522623200E304E2 /* TabsResource.swift */; }; 8ABABEF1267233D3000AA9CB /* RestClient+AsyncAwait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ABABEF0267233D3000AA9CB /* RestClient+AsyncAwait.swift */; }; 8ABCD75E2A65B81300E1AC6A /* FaviconImageViewable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ABCD75D2A65B81300E1AC6A /* FaviconImageViewable.swift */; }; - 8ABCD75F2A65B81300E1AC6A /* FaviconImageViewable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ABCD75D2A65B81300E1AC6A /* FaviconImageViewable.swift */; }; 8ABDB0B127B24E3900D6EDE7 /* Downloadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A74188B245086330019695E /* Downloadable.swift */; }; 8ABDB0B427B24FB300D6EDE7 /* RestClient+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AC45ECC2458327D007E47EE /* RestClient+Combine.swift */; }; 8ABDB0B627B269D500D6EDE7 /* HttpClient+Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AA4FFF227A006CD003C3BBA /* HttpClient+Alamofire.swift */; }; 8ABDB0B827B26A5C00D6EDE7 /* JSONEncodableMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ABDB0B727B26A5C00D6EDE7 /* JSONEncodableMocks.swift */; }; 8ABDB0BA27B2B6F200D6EDE7 /* HTTPAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ABDB0B927B2B6F200D6EDE7 /* HTTPAdapter.swift */; }; 8ABDD90827CCF4360028B9DE /* FeatureManager+SpecificEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ABDD90727CCF4360028B9DE /* FeatureManager+SpecificEnums.swift */; }; - 8ABDD90927CCF4360028B9DE /* FeatureManager+SpecificEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ABDD90727CCF4360028B9DE /* FeatureManager+SpecificEnums.swift */; }; - 8ABDD90A27CCFACE0028B9DE /* FeaturesFlagsKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8ABF503E27CC1A64007C6427 /* FeaturesFlagsKit.framework */; }; - 8ABDD90B27CCFACE0028B9DE /* FeaturesFlagsKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8ABF503E27CC1A64007C6427 /* FeaturesFlagsKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 8ABF504127CC1A64007C6427 /* FeaturesFlagsKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 8ABF504027CC1A64007C6427 /* FeaturesFlagsKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8ABF504427CC1A64007C6427 /* FeaturesFlagsKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8ABF503E27CC1A64007C6427 /* FeaturesFlagsKit.framework */; }; 8ABF504527CC1A64007C6427 /* FeaturesFlagsKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8ABF503E27CC1A64007C6427 /* FeaturesFlagsKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -518,9 +374,7 @@ 8ACAE08F27B7DF52001D9DCD /* DummyRxInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ACAE08E27B7DF52001D9DCD /* DummyRxInterface.swift */; }; 8ACE7BEC28ED4FC9001CBAED /* WebViewContextMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ACE7BEB28ED4FC9001CBAED /* WebViewContextMocks.swift */; }; 8ACE7BEE28ED6E0F001CBAED /* WebViewMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ACE7BED28ED6E0F001CBAED /* WebViewMocks.swift */; }; - 8AD18E092447564600224702 /* google.xml in Resources */ = {isa = PBXBuildFile; fileRef = 60724F79221695C80071BECF /* google.xml */; }; 8AD18E0A2447564600224702 /* google.xml in Resources */ = {isa = PBXBuildFile; fileRef = 60724F79221695C80071BECF /* google.xml */; }; - 8AD18E0B2447564B00224702 /* duckduckgo.xml in Resources */ = {isa = PBXBuildFile; fileRef = 8AD2CEE62444DD7C00FFCBC7 /* duckduckgo.xml */; }; 8AD18E0C2447564B00224702 /* duckduckgo.xml in Resources */ = {isa = PBXBuildFile; fileRef = 8AD2CEE62444DD7C00FFCBC7 /* duckduckgo.xml */; }; 8AD18E102447580A00224702 /* ResourceReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AD18E0F2447580A00224702 /* ResourceReader.swift */; }; 8AD4F51028F491A7009C4BDC /* ReactiveSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 8AD4F50F28F491A7009C4BDC /* ReactiveSwift */; }; @@ -532,86 +386,44 @@ 8AD4F51C28F49219009C4BDC /* ReactiveSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 8AD4F51B28F49219009C4BDC /* ReactiveSwift */; }; 8AD4F51E28F492DC009C4BDC /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = 8AD4F51D28F492DC009C4BDC /* AlamofireImage */; }; 8AD4F52028F49303009C4BDC /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 8AD4F51F28F49303009C4BDC /* Alamofire */; }; - 8AD4F52228F49317009C4BDC /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 8AD4F52128F49317009C4BDC /* Alamofire */; }; - 8AD4F52428F4931C009C4BDC /* ReactiveSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 8AD4F52328F4931C009C4BDC /* ReactiveSwift */; }; - 8AD4F52628F49321009C4BDC /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = 8AD4F52528F49321009C4BDC /* AlamofireImage */; }; - 8AD4F52928F49341009C4BDC /* BrowserNetworking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A277137268E452000D8878C /* BrowserNetworking.framework */; }; - 8AD4F52C28F49346009C4BDC /* CoreBrowser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6094E71C221FD2B9004269A2 /* CoreBrowser.framework */; }; - 8AD4F52F28F49349009C4BDC /* CottonData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A7B1B9828EB131E004FC135 /* CottonData.framework */; }; - 8AD4F53228F4934E009C4BDC /* CssParser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8AF43375248EBB2A0047452B /* CssParser.framework */; }; - 8AD4F53328F4934E009C4BDC /* CssParser.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8AF43375248EBB2A0047452B /* CssParser.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 8AD4F53628F49358009C4BDC /* FeaturesFlagsKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8ABF503E27CC1A64007C6427 /* FeaturesFlagsKit.framework */; }; - 8AD4F53928F4935D009C4BDC /* CottonRestKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8AE6D1692350FF35006316AD /* CottonRestKit.framework */; }; - 8AD4F53A28F4935D009C4BDC /* CottonRestKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8AE6D1692350FF35006316AD /* CottonRestKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 8AD4F53D28F49366009C4BDC /* CottonPlugins.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 608DA74A223A3BD4009F75BD /* CottonPlugins.framework */; }; - 8AD4F54028F4936A009C4BDC /* ReactiveHttpKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8AB70E9127B7AE7A003FB59B /* ReactiveHttpKit.framework */; }; - 8AD4F54128F4936B009C4BDC /* ReactiveHttpKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8AB70E9127B7AE7A003FB59B /* ReactiveHttpKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 8AD4F54528F493B8009C4BDC /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 8AD4F54428F493B8009C4BDC /* Alamofire */; }; 8AD4F54728F4941A009C4BDC /* ReactiveSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 8AD4F54628F4941A009C4BDC /* ReactiveSwift */; }; 8AD5E0402915485200901DA2 /* SearchViewContextImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AD5E03F2915485200901DA2 /* SearchViewContextImpl.swift */; }; - 8AD5E0412915485200901DA2 /* SearchViewContextImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AD5E03F2915485200901DA2 /* SearchViewContextImpl.swift */; }; 8AD62B7A28F564B8003F3940 /* CoreBrowser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6094E71C221FD2B9004269A2 /* CoreBrowser.framework */; }; 8AD857E02939425400DD5F49 /* WebContentContainerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AD857DF2939425400DD5F49 /* WebContentContainerCoordinator.swift */; }; - 8AD857E12939425400DD5F49 /* WebContentContainerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AD857DF2939425400DD5F49 /* WebContentContainerCoordinator.swift */; }; 8AD8783F29351BE400CE9253 /* LinkTagsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AD8783E29351BE400CE9253 /* LinkTagsCoordinator.swift */; }; - 8AD8784029351BE400CE9253 /* LinkTagsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AD8783E29351BE400CE9253 /* LinkTagsCoordinator.swift */; }; 8AD8A169245D6F7400BE8070 /* WebViewLoadingErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AD8A168245D6F7400BE8070 /* WebViewLoadingErrorHandler.swift */; }; - 8AD8A16A245D6F7400BE8070 /* WebViewLoadingErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AD8A168245D6F7400BE8070 /* WebViewLoadingErrorHandler.swift */; }; 8AD8B47E28F2CAB100E0857D /* JSPluginsProgramMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AD8B47D28F2CAB100E0857D /* JSPluginsProgramMocks.swift */; }; 8AD8C29B296616F40038FE5C /* WebViewController+Reusable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AD8C29A296616F40038FE5C /* WebViewController+Reusable.swift */; }; - 8AD8C29C296616F40038FE5C /* WebViewController+Reusable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AD8C29A296616F40038FE5C /* WebViewController+Reusable.swift */; }; 8ADA5822293E4EA200263767 /* DownloadPanelPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ADA5821293E4EA200263767 /* DownloadPanelPresenter.swift */; }; - 8ADA5823293E4EA200263767 /* DownloadPanelPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ADA5821293E4EA200263767 /* DownloadPanelPresenter.swift */; }; 8ADB51F4292A1A0F00E9F66F /* TopSitesCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ADB51F3292A1A0F00E9F66F /* TopSitesCoordinator.swift */; }; - 8ADB51F5292A1A0F00E9F66F /* TopSitesCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ADB51F3292A1A0F00E9F66F /* TopSitesCoordinator.swift */; }; 8ADC4971293A589200F37BF2 /* BottomViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ADC4970293A589200F37BF2 /* BottomViewCoordinator.swift */; }; - 8ADC4972293A589200F37BF2 /* BottomViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ADC4970293A589200F37BF2 /* BottomViewCoordinator.swift */; }; 8ADE447A29706382003D313A /* WebViewVmDNSoverHTTPSConcurrencyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ADE447929706382003D313A /* WebViewVmDNSoverHTTPSConcurrencyTests.swift */; }; 8AE0BDDA292D06D200A8BF9E /* LoadingProgressViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE0BDD9292D06D200A8BF9E /* LoadingProgressViewController.swift */; }; - 8AE0BDDB292D06D200A8BF9E /* LoadingProgressViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE0BDD9292D06D200A8BF9E /* LoadingProgressViewController.swift */; }; 8AE0BDDD292D079300A8BF9E /* LoadingProgressCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE0BDDC292D079300A8BF9E /* LoadingProgressCoordinator.swift */; }; - 8AE0BDDE292D079300A8BF9E /* LoadingProgressCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE0BDDC292D079300A8BF9E /* LoadingProgressCoordinator.swift */; }; 8AE31136247C664600FF28B1 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8AE31135247C664600FF28B1 /* SwiftUI.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; - 8AE31137247C665300FF28B1 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8AE31135247C664600FF28B1 /* SwiftUI.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 8AE31138247C666A00FF28B1 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 607CC66622A5505F0037E5EC /* UIKit.framework */; }; - 8AE31139247C667300FF28B1 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 607CC66622A5505F0037E5EC /* UIKit.framework */; }; 8AE3113C247C66DF00FF28B1 /* BrowserMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE3113B247C66DF00FF28B1 /* BrowserMenuView.swift */; }; - 8AE3113D247C66DF00FF28B1 /* BrowserMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE3113B247C66DF00FF28B1 /* BrowserMenuView.swift */; }; 8AE63B57248EC38300D6D2E4 /* CSSBackgroundImageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE63B56248EC38300D6D2E4 /* CSSBackgroundImageTests.swift */; }; 8AE63B59248EC38300D6D2E4 /* CottonPlugins.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 608DA74A223A3BD4009F75BD /* CottonPlugins.framework */; }; 8AE6D16D2350FF35006316AD /* CottonRestKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 8AE6D16B2350FF35006316AD /* CottonRestKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8AE70EAC29648927007D56C1 /* SearchBarViewV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE70EAB29648927007D56C1 /* SearchBarViewV2.swift */; }; - 8AE70EAD29648927007D56C1 /* SearchBarViewV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE70EAB29648927007D56C1 /* SearchBarViewV2.swift */; }; 8AE85DAC293491E10047F14F /* TabletTabsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE85DAB293491E10047F14F /* TabletTabsCoordinator.swift */; }; - 8AE85DAD293491E10047F14F /* TabletTabsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE85DAB293491E10047F14F /* TabletTabsCoordinator.swift */; }; 8AE8D045280D388800FADE3A /* ResponseType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE8D044280D388800FADE3A /* ResponseType.swift */; }; 8AE8D048280D3CB500FADE3A /* HttpKotlinTypes+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE8D047280D3CB500FADE3A /* HttpKotlinTypes+Extensions.swift */; }; 8AE9DD092950DBB40015E394 /* WebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE9DD082950DBB40015E394 /* WebView.swift */; }; - 8AE9DD0A2950DBB40015E394 /* WebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE9DD082950DBB40015E394 /* WebView.swift */; }; 8AEA735D2948FDB900B2E261 /* UIColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AEA735C2948FDB900B2E261 /* UIColor+Extension.swift */; }; - 8AEA735E2948FDB900B2E261 /* UIColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AEA735C2948FDB900B2E261 /* UIColor+Extension.swift */; }; 8AEA73602948FDDE00B2E261 /* Color+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AEA735F2948FDDE00B2E261 /* Color+Extension.swift */; }; - 8AEA73612948FDDE00B2E261 /* Color+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AEA735F2948FDDE00B2E261 /* Color+Extension.swift */; }; 8AED4198292BF94800A00DB2 /* BlankContentCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AED4197292BF94800A00DB2 /* BlankContentCoordinator.swift */; }; - 8AED4199292BF94800A00DB2 /* BlankContentCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AED4197292BF94800A00DB2 /* BlankContentCoordinator.swift */; }; 8AED419D292BFE9900A00DB2 /* WebContentCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AED419C292BFE9900A00DB2 /* WebContentCoordinator.swift */; }; - 8AED419E292BFE9900A00DB2 /* WebContentCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AED419C292BFE9900A00DB2 /* WebContentCoordinator.swift */; }; 8AED41A0292C004100A00DB2 /* ViewsEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AED419F292C004100A00DB2 /* ViewsEnvironment.swift */; }; - 8AED41A1292C004100A00DB2 /* ViewsEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AED419F292C004100A00DB2 /* ViewsEnvironment.swift */; }; 8AEEBBB329F7AA9300F23A02 /* ClearCancelPairButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AEEBBB229F7AA9300F23A02 /* ClearCancelPairButton.swift */; }; - 8AEEBBB429F7AA9300F23A02 /* ClearCancelPairButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AEEBBB229F7AA9300F23A02 /* ClearCancelPairButton.swift */; }; 8AEEBBB629F7AEA500F23A02 /* SearchFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AEEBBB529F7AEA500F23A02 /* SearchFieldView.swift */; }; - 8AEEBBB729F7AEA500F23A02 /* SearchFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AEEBBB529F7AEA500F23A02 /* SearchFieldView.swift */; }; 8AEEBBB929F7B63A00F23A02 /* TappableTextOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AEEBBB829F7B63A00F23A02 /* TappableTextOverlayView.swift */; }; - 8AEEBBBA29F7B63A00F23A02 /* TappableTextOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AEEBBB829F7B63A00F23A02 /* TappableTextOverlayView.swift */; }; 8AEFAF7729F5538A00EF430A /* TopSitesLegacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AEFAF7629F5538A00EF430A /* TopSitesLegacyView.swift */; }; - 8AEFAF7829F5538A00EF430A /* TopSitesLegacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AEFAF7629F5538A00EF430A /* TopSitesLegacyView.swift */; }; 8AF305162A6D40720080C93D /* CottonErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AF305152A6D40720080C93D /* CottonErrors.swift */; }; - 8AF305172A6D40720080C93D /* CottonErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AF305152A6D40720080C93D /* CottonErrors.swift */; }; 8AF3352029E4A33100A486CF /* SuggestionRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AF3351F29E4A33100A486CF /* SuggestionRowView.swift */; }; - 8AF3352129E4A33100A486CF /* SuggestionRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AF3351F29E4A33100A486CF /* SuggestionRowView.swift */; }; 8AF3D424297312E90053C0C7 /* WebViewControllerProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AF3D423297312E90053C0C7 /* WebViewControllerProxy.swift */; }; - 8AF3D425297312E90053C0C7 /* WebViewControllerProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AF3D423297312E90053C0C7 /* WebViewControllerProxy.swift */; }; 8AF4336C248E129F0047452B /* HTMLContentMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AF4336B248E129F0047452B /* HTMLContentMessage.swift */; }; 8AF4336F248EA3BE0047452B /* CSSBackgroundImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AF4336E248EA3BE0047452B /* CSSBackgroundImage.swift */; }; 8AF43379248EBB2A0047452B /* CssParser.h in Headers */ = {isa = PBXBuildFile; fileRef = 8AF43377248EBB2A0047452B /* CssParser.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -622,21 +434,15 @@ 8AF43386248EBBA70047452B /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AF43383248EBBA70047452B /* Token.swift */; }; 8AF4922B235248CE00183579 /* RestClientError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AF4922A235248CE00183579 /* RestClientError.swift */; }; 8AFCDBD42A6C41C10018A501 /* TabViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFCDBD32A6C41C10018A501 /* TabViewState.swift */; }; - 8AFCDBD52A6C41C10018A501 /* TabViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFCDBD32A6C41C10018A501 /* TabViewState.swift */; }; 8AFDBB0029F938D8006C0E47 /* TabletTabsLegacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFDBAFF29F938D8006C0E47 /* TabletTabsLegacyView.swift */; }; - 8AFDBB0129F938D8006C0E47 /* TabletTabsLegacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFDBAFF29F938D8006C0E47 /* TabletTabsLegacyView.swift */; }; 8AFDBB0329F93980006C0E47 /* TabletSearchBarViewV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFDBB0229F93980006C0E47 /* TabletSearchBarViewV2.swift */; }; - 8AFDBB0429F93980006C0E47 /* TabletSearchBarViewV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFDBB0229F93980006C0E47 /* TabletSearchBarViewV2.swift */; }; 8AFDBB0629F93A05006C0E47 /* MenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFDBB0529F93A05006C0E47 /* MenuButton.swift */; }; - 8AFDBB0729F93A05006C0E47 /* MenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFDBB0529F93A05006C0E47 /* MenuButton.swift */; }; 8AFDBB0929F979D8006C0E47 /* DisableableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFDBB0829F979D8006C0E47 /* DisableableButton.swift */; }; - 8AFDBB0A29F979D8006C0E47 /* DisableableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFDBB0829F979D8006C0E47 /* DisableableButton.swift */; }; 8AFE7D92296B488D00B2CF55 /* ErrorTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFE7D91296B488D00B2CF55 /* ErrorTypes.swift */; }; 8AFE7D94296B53EB00B2CF55 /* SearchSuggestionsVMConcurrencyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFE7D93296B53EB00B2CF55 /* SearchSuggestionsVMConcurrencyTests.swift */; }; 8AFE7D96296B590A00B2CF55 /* SearchSuggestionsVMFixture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFE7D95296B590A00B2CF55 /* SearchSuggestionsVMFixture.swift */; }; 8AFE7D99296B5B0400B2CF55 /* SearchSuggestionsViewStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFE7D98296B5B0400B2CF55 /* SearchSuggestionsViewStateTests.swift */; }; 8AFFBE1C29F2E1B600AEB2EE /* TabsPreviewsLegacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFFBE1B29F2E1B600AEB2EE /* TabsPreviewsLegacyView.swift */; }; - 8AFFBE1D29F2E1B600AEB2EE /* TabsPreviewsLegacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFFBE1B29F2E1B600AEB2EE /* TabsPreviewsLegacyView.swift */; }; C22599631F7BEF6D000E4C3A /* WebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22599621F7BEF6D000E4C3A /* WebViewController.swift */; }; C22599671F7BF301000E4C3A /* MainBrowserViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C22599661F7BF301000E4C3A /* MainBrowserViewController.swift */; }; C27371171F7A341900900F03 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C27371191F7A341900900F03 /* Localizable.strings */; }; @@ -673,13 +479,6 @@ remoteGlobalIDString = 8A277136268E452000D8878C; remoteInfo = BrowserNetworking; }; - 8A27715F268E467D00D8878C /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 8435A48F1EED5E700020102F /* Project object */; - proxyType = 1; - remoteGlobalIDString = 8A277136268E452000D8878C; - remoteInfo = BrowserNetworking; - }; 8A5E6C81290B0E6C0034A78B /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 8435A48F1EED5E700020102F /* Project object */; @@ -778,13 +577,6 @@ remoteGlobalIDString = 8A7B1B9728EB131E004FC135; remoteInfo = CoreCatowser; }; - 8A7B1BED28EB18AD004FC135 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 8435A48F1EED5E700020102F /* Project object */; - proxyType = 1; - remoteGlobalIDString = 8A7B1B9728EB131E004FC135; - remoteInfo = CoreCatowser; - }; 8A7EF3D6273924EF0071BB89 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 8435A48F1EED5E700020102F /* Project object */; @@ -792,27 +584,6 @@ remoteGlobalIDString = 8AE6D1682350FF35006316AD; remoteInfo = HttpKit; }; - 8A80359B24442F0F000E035F /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 8435A48F1EED5E700020102F /* Project object */; - proxyType = 1; - remoteGlobalIDString = 6094E71B221FD2B9004269A2; - remoteInfo = CoreBrowser; - }; - 8A80359D24442F0F000E035F /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 8435A48F1EED5E700020102F /* Project object */; - proxyType = 1; - remoteGlobalIDString = 608DA749223A3BD4009F75BD; - remoteInfo = JSPlugins; - }; - 8A8035F524446234000E035F /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 8435A48F1EED5E700020102F /* Project object */; - proxyType = 1; - remoteGlobalIDString = 8A8035EE24446234000E035F; - remoteInfo = DNSProxy; - }; 8AB022F226258B110053AFF0 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 8435A48F1EED5E700020102F /* Project object */; @@ -848,13 +619,6 @@ remoteGlobalIDString = 8ABF503D27CC1A64007C6427; remoteInfo = FeaturesFlagsKit; }; - 8ABDD90C27CCFACE0028B9DE /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 8435A48F1EED5E700020102F /* Project object */; - proxyType = 1; - remoteGlobalIDString = 8ABF503D27CC1A64007C6427; - remoteInfo = FeaturesFlagsKit; - }; 8ABF504227CC1A64007C6427 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 8435A48F1EED5E700020102F /* Project object */; @@ -862,62 +626,6 @@ remoteGlobalIDString = 8ABF503D27CC1A64007C6427; remoteInfo = FeaturesFlagsKit; }; - 8AD4F52A28F49341009C4BDC /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 8435A48F1EED5E700020102F /* Project object */; - proxyType = 1; - remoteGlobalIDString = 8A277136268E452000D8878C; - remoteInfo = BrowserNetworking; - }; - 8AD4F52D28F49346009C4BDC /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 8435A48F1EED5E700020102F /* Project object */; - proxyType = 1; - remoteGlobalIDString = 6094E71B221FD2B9004269A2; - remoteInfo = CoreBrowser; - }; - 8AD4F53028F49349009C4BDC /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 8435A48F1EED5E700020102F /* Project object */; - proxyType = 1; - remoteGlobalIDString = 8A7B1B9728EB131E004FC135; - remoteInfo = CoreCatowser; - }; - 8AD4F53428F4934E009C4BDC /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 8435A48F1EED5E700020102F /* Project object */; - proxyType = 1; - remoteGlobalIDString = 8AF43374248EBB2A0047452B; - remoteInfo = CssParser; - }; - 8AD4F53728F49358009C4BDC /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 8435A48F1EED5E700020102F /* Project object */; - proxyType = 1; - remoteGlobalIDString = 8ABF503D27CC1A64007C6427; - remoteInfo = FeaturesFlagsKit; - }; - 8AD4F53B28F4935D009C4BDC /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 8435A48F1EED5E700020102F /* Project object */; - proxyType = 1; - remoteGlobalIDString = 8AE6D1682350FF35006316AD; - remoteInfo = HttpKit; - }; - 8AD4F53E28F49366009C4BDC /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 8435A48F1EED5E700020102F /* Project object */; - proxyType = 1; - remoteGlobalIDString = 608DA749223A3BD4009F75BD; - remoteInfo = JSPlugins; - }; - 8AD4F54228F4936B009C4BDC /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 8435A48F1EED5E700020102F /* Project object */; - proxyType = 1; - remoteGlobalIDString = 8AB70E9027B7AE7A003FB59B; - remoteInfo = ReactiveHttpKit; - }; 8AD62B7C28F564B8003F3940 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 8435A48F1EED5E700020102F /* Project object */; @@ -972,36 +680,6 @@ name = "Embed App Extensions"; runOnlyForDeploymentPostprocessing = 0; }; - 8A8035DD24442F0F000E035F /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - 8A7B1BEC28EB18AD004FC135 /* CottonData.framework in Embed Frameworks */, - 8AD4F54128F4936B009C4BDC /* ReactiveHttpKit.framework in Embed Frameworks */, - 8A8035DE24442F0F000E035F /* CottonPlugins.framework in Embed Frameworks */, - 8A8035DF24442F0F000E035F /* CoreBrowser.framework in Embed Frameworks */, - 8ABDD90B27CCFACE0028B9DE /* FeaturesFlagsKit.framework in Embed Frameworks */, - 7517D9C62A5F2E8D00D081AD /* CottonBase.xcframework in Embed Frameworks */, - 8A27715E268E467D00D8878C /* BrowserNetworking.framework in Embed Frameworks */, - 8AD4F53A28F4935D009C4BDC /* CottonRestKit.framework in Embed Frameworks */, - 8AD4F53328F4934E009C4BDC /* CssParser.framework in Embed Frameworks */, - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; - 8A8035E124442F0F000E035F /* Embed App Extensions */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 13; - files = ( - 8A8035F724446234000E035F /* DNSProxy.appex in Embed App Extensions */, - ); - name = "Embed App Extensions"; - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ @@ -1215,7 +893,6 @@ 8A7B1BFE28EB3269004FC135 /* Site+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Site+Extensions.swift"; sourceTree = ""; }; 8A7DCCCC23643032006AAEE2 /* GoogleJSONDNSoHTTPsEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleJSONDNSoHTTPsEndpoint.swift; sourceTree = ""; }; 8A7EF3D1273924EF0071BB89 /* CottonRestKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CottonRestKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 8A8035E624442F0F000E035F /* Cotton dnsext.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Cotton dnsext.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 8A8035E724442F0F000E035F /* Info-dnsext.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "Info-dnsext.plist"; path = "/Users/aermoshin/prj/catowser/catowser/Info-dnsext.plist"; sourceTree = ""; }; 8A8035E824442F45000E035F /* catowser dnsextDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "catowser dnsextDebug.entitlements"; sourceTree = ""; }; 8A8035E924442F45000E035F /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; }; @@ -1499,35 +1176,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 8A8035CE24442F0F000E035F /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 8AD4F52F28F49349009C4BDC /* CottonData.framework in Frameworks */, - 8AD4F53928F4935D009C4BDC /* CottonRestKit.framework in Frameworks */, - 8AD4F54028F4936A009C4BDC /* ReactiveHttpKit.framework in Frameworks */, - 8AD4F53628F49358009C4BDC /* FeaturesFlagsKit.framework in Frameworks */, - 8AD4F53D28F49366009C4BDC /* CottonPlugins.framework in Frameworks */, - 8AD4F52628F49321009C4BDC /* AlamofireImage in Frameworks */, - 8A7B1BEB28EB18AD004FC135 /* CottonData.framework in Frameworks */, - 8AD4F53228F4934E009C4BDC /* CssParser.framework in Frameworks */, - 8AD4F52428F4931C009C4BDC /* ReactiveSwift in Frameworks */, - 8AE31137247C665300FF28B1 /* SwiftUI.framework in Frameworks */, - 8ABA3CD32522603000E304E2 /* CoreData.framework in Frameworks */, - 8AE31139247C667300FF28B1 /* UIKit.framework in Frameworks */, - 8A6CD6D524585D8C0068ACB5 /* Combine.framework in Frameworks */, - 8AD4F52228F49317009C4BDC /* Alamofire in Frameworks */, - 7517D9C52A5F2E8D00D081AD /* CottonBase.xcframework in Frameworks */, - 8A8035CF24442F0F000E035F /* CottonPlugins.framework in Frameworks */, - 8AD4F52928F49341009C4BDC /* BrowserNetworking.framework in Frameworks */, - 8A8035D124442F0F000E035F /* CoreBrowser.framework in Frameworks */, - 8A8035EA24442F45000E035F /* NetworkExtension.framework in Frameworks */, - 8ABDD90A27CCFACE0028B9DE /* FeaturesFlagsKit.framework in Frameworks */, - 8AD4F52C28F49346009C4BDC /* CoreBrowser.framework in Frameworks */, - 8A27715D268E467D00D8878C /* BrowserNetworking.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 8A8035EC24446234000E035F /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -1940,7 +1588,6 @@ 6094E71C221FD2B9004269A2 /* CoreBrowser.framework */, 608DA74A223A3BD4009F75BD /* CottonPlugins.framework */, 8AE6D1692350FF35006316AD /* CottonRestKit.framework */, - 8A8035E624442F0F000E035F /* Cotton dnsext.app */, 8A8035EF24446234000E035F /* DNSProxy.appex */, 8AF43375248EBB2A0047452B /* CssParser.framework */, 8AE63B54248EC38300D6D2E4 /* CottonPluginsTests.xctest */, @@ -2928,44 +2575,6 @@ productReference = 8A7EF3D1273924EF0071BB89 /* CottonRestKitTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; - 8A80359924442F0F000E035F /* Cotton dnsext */ = { - isa = PBXNativeTarget; - buildConfigurationList = 8A8035E324442F0F000E035F /* Build configuration list for PBXNativeTarget "Cotton dnsext" */; - buildPhases = ( - 8A8035A124442F0F000E035F /* Sources */, - 8A8035CE24442F0F000E035F /* Frameworks */, - 8A8035D324442F0F000E035F /* Resources */, - 8A8035DD24442F0F000E035F /* Embed Frameworks */, - 8A8035E124442F0F000E035F /* Embed App Extensions */, - ); - buildRules = ( - ); - dependencies = ( - 8A80359A24442F0F000E035F /* PBXTargetDependency */, - 8A80359C24442F0F000E035F /* PBXTargetDependency */, - 8A8035F624446234000E035F /* PBXTargetDependency */, - 8A277160268E467D00D8878C /* PBXTargetDependency */, - 8ABDD90D27CCFACE0028B9DE /* PBXTargetDependency */, - 8A7B1BEE28EB18AD004FC135 /* PBXTargetDependency */, - 8AD4F52B28F49341009C4BDC /* PBXTargetDependency */, - 8AD4F52E28F49346009C4BDC /* PBXTargetDependency */, - 8AD4F53128F49349009C4BDC /* PBXTargetDependency */, - 8AD4F53528F4934E009C4BDC /* PBXTargetDependency */, - 8AD4F53828F49358009C4BDC /* PBXTargetDependency */, - 8AD4F53C28F4935D009C4BDC /* PBXTargetDependency */, - 8AD4F53F28F49366009C4BDC /* PBXTargetDependency */, - 8AD4F54328F4936B009C4BDC /* PBXTargetDependency */, - ); - name = "Cotton dnsext"; - packageProductDependencies = ( - 8AD4F52128F49317009C4BDC /* Alamofire */, - 8AD4F52328F4931C009C4BDC /* ReactiveSwift */, - 8AD4F52528F49321009C4BDC /* AlamofireImage */, - ); - productName = catowser; - productReference = 8A8035E624442F0F000E035F /* Cotton dnsext.app */; - productType = "com.apple.product-type.application"; - }; 8A8035EE24446234000E035F /* DNSProxy */ = { isa = PBXNativeTarget; buildConfigurationList = 8A8035F824446234000E035F /* Build configuration list for PBXNativeTarget "DNSProxy" */; @@ -3144,9 +2753,6 @@ 8A7EF3D0273924EF0071BB89 = { CreatedOnToolsVersion = 13.0; }; - 8A80359924442F0F000E035F = { - ProvisioningStyle = Manual; - }; 8A8035EE24446234000E035F = { CreatedOnToolsVersion = 11.4; ProvisioningStyle = Automatic; @@ -3196,7 +2802,6 @@ projectRoot = ""; targets = ( 8435A4961EED5E700020102F /* Cotton */, - 8A80359924442F0F000E035F /* Cotton dnsext */, 6094E71B221FD2B9004269A2 /* CoreBrowser */, 8A7B1B9728EB131E004FC135 /* CottonData */, 608DA749223A3BD4009F75BD /* CottonPlugins */, @@ -3285,21 +2890,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 8A8035D324442F0F000E035F /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 8A8035D424442F0F000E035F /* LinkTagsViewController.storyboard in Resources */, - 8A8035D524442F0F000E035F /* FilesGridViewController.storyboard in Resources */, - 8AD18E0B2447564B00224702 /* duckduckgo.xml in Resources */, - 8A8035D624442F0F000E035F /* LaunchScreen.storyboard in Resources */, - 8A8035D724442F0F000E035F /* Localizable.strings in Resources */, - 8A8035D924442F0F000E035F /* Assets.xcassets in Resources */, - 8A8035DA24442F0F000E035F /* TopSitesViewController.xib in Resources */, - 8AD18E092447564600224702 /* google.xml in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 8A8035ED24446234000E035F /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -3768,167 +3358,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 8A8035A124442F0F000E035F /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 8A0FC1A729F2586B0044829A /* ToolbarViewV2.swift in Sources */, - 8A10C77629F64A79002A429A /* TopSitesViewV2.swift in Sources */, - 8AE3113D247C66DF00FF28B1 /* BrowserMenuView.swift in Sources */, - 8A8035A224442F0F000E035F /* BaseViewController.swift in Sources */, - 7533929B291FCEDD00570461 /* Coordinator.swift in Sources */, - 8A8035A724442F0F000E035F /* LightTheme.swift in Sources */, - 8AFFBE1D29F2E1B600AEB2EE /* TabsPreviewsLegacyView.swift in Sources */, - 8A8035A824442F0F000E035F /* WebViewController.swift in Sources */, - 8A8035A924442F0F000E035F /* TabletSearchBarViewController.swift in Sources */, - 8AFDBB0A29F979D8006C0E47 /* DisableableButton.swift in Sources */, - 8AB9BE1828FF0C98002063C2 /* ReloadWithCompletion.swift in Sources */, - 8A917CB529D4E63700404256 /* String+Extension.swift in Sources */, - 8A8035AA24442F0F000E035F /* SearchBarBaseViewController.swift in Sources */, - 8A8035AB24442F0F000E035F /* TabsViewController.swift in Sources */, - 8A8035AD24442F0F000E035F /* TabView.swift in Sources */, - 75EA5426296436C900EEF533 /* BrowserToolbarViewModel.swift in Sources */, - 8AD857E12939425400DD5F49 /* WebContentContainerCoordinator.swift in Sources */, - 753392B42921613E00570461 /* GlobalMenuCoordinator.swift in Sources */, - 8A8035AE24442F0F000E035F /* UIViewController+MainBundle.swift in Sources */, - 8A8035AF24442F0F000E035F /* TopSitesViewController.swift in Sources */, - 8A917CB229D4A39B00404256 /* SearchSuggestionsViewV2.swift in Sources */, - 8A1C1C182484E5900098F2F1 /* TabDefaultContentModel.swift in Sources */, - 8A81C6FA2945C72F0075BEF9 /* View+Extension.swift in Sources */, - 8A825948294E2E1A007B97CF /* TopSitesView.swift in Sources */, - 8A81C6FD2945C8050075BEF9 /* ToolbarView.swift in Sources */, - 8A0FC1A129F24C950044829A /* PhoneSearchBarLegacyView.swift in Sources */, - 8A8035B224442F0F000E035F /* AppDelegate.swift in Sources */, - 8A8035B324442F0F000E035F /* WebViewsReuseManager.swift in Sources */, - 8A8035B424442F0F000E035F /* TabsPreviewsViewController.swift in Sources */, - 8ADC4972293A589200F37BF2 /* BottomViewCoordinator.swift in Sources */, - 755E108627C16AB100D90AD0 /* BaseListViewModelImpl.swift in Sources */, - 755E107227BC3DC500D90AD0 /* AppAsyncApiTypeViewPreview.swift in Sources */, - 753392A2291FD8DA00570461 /* PhoneViewControllerFactory.swift in Sources */, - 8AE70EAD29648927007D56C1 /* SearchBarViewV2.swift in Sources */, - 755E10B827CA81B600D90AD0 /* SpecificApplicationEnumFeatures.swift in Sources */, - 8A49D80D2BA1EF7100F84700 /* MenuItems.swift in Sources */, - 752B12632B5D5631002EFA41 /* TabsPreviewsViewModel.swift in Sources */, - 8A8035B524442F0F000E035F /* SmartphoneSearchBarViewController.swift in Sources */, - 8AD5E0412915485200901DA2 /* SearchViewContextImpl.swift in Sources */, - 8AB9BE2328FF0D5C002063C2 /* Tab+Extension.swift in Sources */, - 8A8035B624442F0F000E035F /* TabPreviewCell.swift in Sources */, - 8A8035B724442F0F000E035F /* SiteCollectionViewCell.swift in Sources */, - 8A8035B824442F0F000E035F /* ThemeProvider.swift in Sources */, - 8AEFAF7829F5538A00EF430A /* TopSitesLegacyView.swift in Sources */, - 755E107527BC3E0800D90AD0 /* AppAsyncApiTypeModel.swift in Sources */, - 8A82594E294E47F9007B97CF /* TopSitesViewModel.swift in Sources */, - 8ADB51F5292A1A0F00E9F66F /* TopSitesCoordinator.swift in Sources */, - 8AEEBBB729F7AEA500F23A02 /* SearchFieldView.swift in Sources */, - 8A9495BF2A6C0023001D275D /* TabViewModel.swift in Sources */, - 8AFDBB0129F938D8006C0E47 /* TabletTabsLegacyView.swift in Sources */, - 8A9D9D8A2971D8330047734C /* DummyDelegateForPreview.swift in Sources */, - 8AB9BE1528FF0C74002063C2 /* UIColour+CatowserExtensions.swift in Sources */, - 8A0FC1A429F24D320044829A /* TabletSearchBarLegacyView.swift in Sources */, - 8A02AE812965CD9F004041D8 /* SearchSuggestionsView.swift in Sources */, - 753392B72922C9EB00570461 /* MainToolbarCoordinator.swift in Sources */, - 8A90B16D248246D6000618F5 /* TabAddPositionsViewPreview.swift in Sources */, - 8A8035B924442F0F000E035F /* FilesGridViewController.swift in Sources */, - 8AF3D425297312E90053C0C7 /* WebViewControllerProxy.swift in Sources */, - 8ABA3CD62522608000E304E2 /* Database.swift in Sources */, - 8AB8018B27C3E597002D6628 /* BaseMenuView.swift in Sources */, - 8AE85DAD293491E10047F14F /* TabletTabsCoordinator.swift in Sources */, - 8A046DE12965AC9300BB6D63 /* SearchBarViewModel.swift in Sources */, - 8AB9BE1D28FF0CFF002063C2 /* ReusableItem.swift in Sources */, - 7570F7472B5C07E90066ABF1 /* UseCaseFactory.swift in Sources */, - 8AFCDBD52A6C41C10018A501 /* TabViewState.swift in Sources */, - 8A8EB008245EA1BA0028D35C /* WebViewAuthChallengeHandler.swift in Sources */, - 752B12662B5D78C0002EFA41 /* AllTabsViewModel.swift in Sources */, - 75EA4E7029F7055800165A07 /* TitledImageView.swift in Sources */, - 8A745C0A247FE93F00A379AD /* DefaultTabProvider.swift in Sources */, - 8AE0BDDB292D06D200A8BF9E /* LoadingProgressViewController.swift in Sources */, - 8A0FC1AA29F259D20044829A /* ToolbarLegacyView.swift in Sources */, - 8AD8784029351BE400CE9253 /* LinkTagsCoordinator.swift in Sources */, - 75FAAB39292D56880086F2D1 /* SearchBarCoordinator.swift in Sources */, - 8A1C1C1B2484E6450098F2F1 /* TabDefaultContentViewPreview.swift in Sources */, - 753392AB2920BB6700570461 /* AppCoordinator.swift in Sources */, - 75196D082944ABA700B3A45B /* MainBrowserView.swift in Sources */, - 8A1115EC24837DA400E63114 /* TabAddPositionsModel.swift in Sources */, - 8AEEBBBA29F7B63A00F23A02 /* TappableTextOverlayView.swift in Sources */, - 8AFDBB0729F93A05006C0E47 /* MenuButton.swift in Sources */, - 8A8035BB24442F0F000E035F /* TagsSiteDataSource.swift in Sources */, - 8AEEBBB429F7AA9300F23A02 /* ClearCancelPairButton.swift in Sources */, - 8A8035BC24442F0F000E035F /* LinksBadgeView.swift in Sources */, - 8A8035BD24442F0F000E035F /* BrowserToolbarView.swift in Sources */, - 8AED4199292BF94800A00DB2 /* BlankContentCoordinator.swift in Sources */, - 75196D0E2944AF8B00B3A45B /* SiteMenuViewController.swift in Sources */, - 8AEA735E2948FDB900B2E261 /* UIColor+Extension.swift in Sources */, - 8A8035BE24442F0F000E035F /* BrowserToolbarController.swift in Sources */, - 8AED419E292BFE9900A00DB2 /* WebContentCoordinator.swift in Sources */, - 8A8035BF24442F0F000E035F /* MainBrowserViewController.swift in Sources */, - 750EDEBF294924A800212A4E /* BrowserContentView.swift in Sources */, - 8A97EF26245B44D4003AC9D7 /* UIImageView+Extension.swift in Sources */, - 8A8F038F293FAB3200237DFF /* GlobalMenuDelegate.swift in Sources */, - 8A8035C024442F0F000E035F /* Theme.swift in Sources */, - 8A8035C124442F0F000E035F /* LinkTagsViewController.swift in Sources */, - 75196D112944AFD800B3A45B /* MainBrowserV2ViewController.swift in Sources */, - 8A745C0C248027C600A379AD /* TabsRepositoryImpl.swift in Sources */, - 753392A8291FEBFD00570461 /* TabletViewControllerFactory.swift in Sources */, - 750EDEBC2949215900212A4E /* MainBrowserViewModel.swift in Sources */, - 8AFDBB0429F93980006C0E47 /* TabletSearchBarViewV2.swift in Sources */, - 8A8035C224442F0F000E035F /* FileDownloadViewModel.swift in Sources */, - 8A1C1C1E24850F3F0098F2F1 /* WebViewController+SiteNavigationDelegate.swift in Sources */, - 8A3286F1294B585100ECCAEA /* SearchBarLegacyView.swift in Sources */, - 8AB9BE1C28FF0CE7002063C2 /* CAGradientLayer+CatowserExtension.swift in Sources */, - 8AD8A16A245D6F7400BE8070 /* WebViewLoadingErrorHandler.swift in Sources */, - 8AB9BE1328FF0C55002063C2 /* CollectionViewInterface.swift in Sources */, - 8A8035C424442F0F000E035F /* SearchSuggestionsViewController.swift in Sources */, - 8A8035C524442F0F000E035F /* DomainNativeAppChecker.swift in Sources */, - 8AF305172A6D40720080C93D /* CottonErrors.swift in Sources */, - 8AE0BDDE292D079300A8BF9E /* LoadingProgressCoordinator.swift in Sources */, - 8A49D8102BA21B9B00F84700 /* MenuStatefullLabel.swift in Sources */, - 755E10A627CA3FC300D90AD0 /* SpecificEnumFeatures.swift in Sources */, - 8A22D1B52923FDA1005D1F72 /* PhoneTabsCoordinator.swift in Sources */, - 8A963C6E25212A55008EAB27 /* CottonDbModel.xcdatamodeld in Sources */, - 8A8035C724442F0F000E035F /* UIConstants.swift in Sources */, - 8AEA73612948FDDE00B2E261 /* Color+Extension.swift in Sources */, - 754249D42944B3F9002D6505 /* AppUIFrameworkTypeModel.swift in Sources */, - 8A4815072922A4B700F9DA1F /* SearchSuggestionsCoordinator.swift in Sources */, - 8A745C122480295B00A379AD /* TabsEnvironment.swift in Sources */, - 8AA68AA0245BE47900263E22 /* HttpEnvironment.swift in Sources */, - 8ABDD90927CCF4360028B9DE /* FeatureManager+SpecificEnums.swift in Sources */, - 8ABA3CD92522623200E304E2 /* TabsResource.swift in Sources */, - 8A7B1BFD28EB2E59004FC135 /* WebViewContextImpl.swift in Sources */, - 8ABCD75F2A65B81300E1AC6A /* FaviconImageViewable.swift in Sources */, - 753392A5291FD9A500570461 /* ViewControllerFactory.swift in Sources */, - 8AB5B121294F0CCA00C91383 /* EnvironmentValues+Extension.swift in Sources */, - 8AF3352129E4A33100A486CF /* SuggestionRowView.swift in Sources */, - 755E107F27C1668A00D90AD0 /* WebSearchSettingsModel.swift in Sources */, - 8A9D9D902971E05F0047734C /* TabletTabsView.swift in Sources */, - 8A9D9D842971D6B10047734C /* TabletView.swift in Sources */, - 8A23989428622FCA005DA801 /* ViewModelFactory.swift in Sources */, - 755E109E27CA3A3100D90AD0 /* SettingsEnumTypes.swift in Sources */, - 8AD8C29C296616F40038FE5C /* WebViewController+Reusable.swift in Sources */, - 8ADA5823293E4EA200263767 /* DownloadPanelPresenter.swift in Sources */, - 8A89E22C29F4FA5D00FE5543 /* SearchBarState.swift in Sources */, - 8AB9BE1228FF0C23002063C2 /* UIViewController+CatowserExtensions.swift in Sources */, - 8A9D9D802971D2300047734C /* PhoneView.swift in Sources */, - 8A96BECA2481237B00E81C38 /* SpecificBasicFeatures.swift in Sources */, - 8AB9BE1F28FF0D0D002063C2 /* AnyViewController.swift in Sources */, - 8A1273DE2937B7FC0022B934 /* FilesGridCoordinator.swift in Sources */, - 8A0FC19C29F246640044829A /* SearchSuggestionsLegacyView.swift in Sources */, - 8A55351B247D740900E3B330 /* MenuViewModel.swift in Sources */, - 8A0319DD29FE71A500AF3BE3 /* UIViewControllerRepresentable+Extension.swift in Sources */, - 755E108327C169AC00D90AD0 /* BaseListViewModel.swift in Sources */, - 8A8035C924442F0F000E035F /* BlankWebPageViewController.swift in Sources */, - 8A8035CB24442F0F000E035F /* CounterView.swift in Sources */, - 8A82594B294E40F5007B97CF /* BrowserContentViewModel.swift in Sources */, - 8AB9BE1928FF0CC8002063C2 /* UIImage+CatowserExtensions.swift in Sources */, - 8AED41A1292C004100A00DB2 /* ViewsEnvironment.swift in Sources */, - 8AE9DD0A2950DBB40015E394 /* WebView.swift in Sources */, - 8A437DA6247E7210001C1E8B /* UIHostingController+Extension.swift in Sources */, - 8A8035CC24442F0F000E035F /* Site+Extension.swift in Sources */, - 8A8035CD24442F0F000E035F /* DownloadButtonCellView.swift in Sources */, - 8ABA3CD02522601800E304E2 /* TabsDBClient.swift in Sources */, - 8A566F80245D7D7B00E71221 /* AlertPresenter.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 8A8035EB24446234000E035F /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -4045,11 +3474,6 @@ target = 8A277136268E452000D8878C /* BrowserNetworking */; targetProxy = 8A27715B268E467500D8878C /* PBXContainerItemProxy */; }; - 8A277160268E467D00D8878C /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 8A277136268E452000D8878C /* BrowserNetworking */; - targetProxy = 8A27715F268E467D00D8878C /* PBXContainerItemProxy */; - }; 8A5E6C82290B0E6C0034A78B /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 8A5E6C7C290B0E6C0034A78B /* AutoMockable */; @@ -4120,31 +3544,11 @@ target = 8A7B1B9728EB131E004FC135 /* CottonData */; targetProxy = 8A7B1BE928EB18A5004FC135 /* PBXContainerItemProxy */; }; - 8A7B1BEE28EB18AD004FC135 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 8A7B1B9728EB131E004FC135 /* CottonData */; - targetProxy = 8A7B1BED28EB18AD004FC135 /* PBXContainerItemProxy */; - }; 8A7EF3D7273924EF0071BB89 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 8AE6D1682350FF35006316AD /* CottonRestKit */; targetProxy = 8A7EF3D6273924EF0071BB89 /* PBXContainerItemProxy */; }; - 8A80359A24442F0F000E035F /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 6094E71B221FD2B9004269A2 /* CoreBrowser */; - targetProxy = 8A80359B24442F0F000E035F /* PBXContainerItemProxy */; - }; - 8A80359C24442F0F000E035F /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 608DA749223A3BD4009F75BD /* CottonPlugins */; - targetProxy = 8A80359D24442F0F000E035F /* PBXContainerItemProxy */; - }; - 8A8035F624446234000E035F /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 8A8035EE24446234000E035F /* DNSProxy */; - targetProxy = 8A8035F524446234000E035F /* PBXContainerItemProxy */; - }; 8AB022F326258B110053AFF0 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 6094E71B221FD2B9004269A2 /* CoreBrowser */; @@ -4170,56 +3574,11 @@ target = 8ABF503D27CC1A64007C6427 /* FeaturesFlagsKit */; targetProxy = 8AB9BE2E28FF149A002063C2 /* PBXContainerItemProxy */; }; - 8ABDD90D27CCFACE0028B9DE /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 8ABF503D27CC1A64007C6427 /* FeaturesFlagsKit */; - targetProxy = 8ABDD90C27CCFACE0028B9DE /* PBXContainerItemProxy */; - }; 8ABF504327CC1A64007C6427 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 8ABF503D27CC1A64007C6427 /* FeaturesFlagsKit */; targetProxy = 8ABF504227CC1A64007C6427 /* PBXContainerItemProxy */; }; - 8AD4F52B28F49341009C4BDC /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 8A277136268E452000D8878C /* BrowserNetworking */; - targetProxy = 8AD4F52A28F49341009C4BDC /* PBXContainerItemProxy */; - }; - 8AD4F52E28F49346009C4BDC /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 6094E71B221FD2B9004269A2 /* CoreBrowser */; - targetProxy = 8AD4F52D28F49346009C4BDC /* PBXContainerItemProxy */; - }; - 8AD4F53128F49349009C4BDC /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 8A7B1B9728EB131E004FC135 /* CottonData */; - targetProxy = 8AD4F53028F49349009C4BDC /* PBXContainerItemProxy */; - }; - 8AD4F53528F4934E009C4BDC /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 8AF43374248EBB2A0047452B /* CssParser */; - targetProxy = 8AD4F53428F4934E009C4BDC /* PBXContainerItemProxy */; - }; - 8AD4F53828F49358009C4BDC /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 8ABF503D27CC1A64007C6427 /* FeaturesFlagsKit */; - targetProxy = 8AD4F53728F49358009C4BDC /* PBXContainerItemProxy */; - }; - 8AD4F53C28F4935D009C4BDC /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 8AE6D1682350FF35006316AD /* CottonRestKit */; - targetProxy = 8AD4F53B28F4935D009C4BDC /* PBXContainerItemProxy */; - }; - 8AD4F53F28F49366009C4BDC /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 608DA749223A3BD4009F75BD /* CottonPlugins */; - targetProxy = 8AD4F53E28F49366009C4BDC /* PBXContainerItemProxy */; - }; - 8AD4F54328F4936B009C4BDC /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 8AB70E9027B7AE7A003FB59B /* ReactiveHttpKit */; - targetProxy = 8AD4F54228F4936B009C4BDC /* PBXContainerItemProxy */; - }; 8AD62B7D28F564B8003F3940 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 6094E71B221FD2B9004269A2 /* CoreBrowser */; @@ -4334,7 +3693,7 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_STRICT_CONCURRENCY = complete; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -4374,7 +3733,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_STRICT_CONCURRENCY = complete; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -4413,7 +3772,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_STRICT_CONCURRENCY = complete; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -4451,7 +3810,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_STRICT_CONCURRENCY = complete; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -4529,6 +3888,7 @@ SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 6.0; }; name = Debug; }; @@ -4585,6 +3945,7 @@ SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 6.0; VALIDATE_PRODUCT = YES; }; name = Release; @@ -4621,7 +3982,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_STRICT_CONCURRENCY = complete; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -4653,7 +4014,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_STRICT_CONCURRENCY = complete; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -4691,7 +4052,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_STRICT_CONCURRENCY = complete; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -4729,7 +4090,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_STRICT_CONCURRENCY = complete; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -4770,7 +4131,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -4810,7 +4171,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -4851,7 +4212,7 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_STRICT_CONCURRENCY = complete; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -4891,7 +4252,7 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_STRICT_CONCURRENCY = complete; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -5013,65 +4374,6 @@ }; name = Release; }; - 8A8035E424442F0F000E035F /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = "catowser dnsextDebug.entitlements"; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Manual; - COTTON_APP_NAME = "Cotton Dev"; - COTTON_BUNDLE_IDENTIFIER = "com.ae.cotton-browser.debug"; - CURRENT_PROJECT_VERSION = 5; - DEVELOPMENT_TEAM = ERT5B59458; - INFOPLIST_FILE = "Info-dnsext.plist"; - INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Cotton/Catowser Andrei Ermoshin. All rights reserved."; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = "com.ae.cotton-browser.dns-ext.debug"; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "Cotton debug with Network ext"; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; - SUPPORTS_MACCATALYST = NO; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 8A8035E524442F0F000E035F /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_IDENTITY = "Apple Distribution"; - CODE_SIGN_STYLE = Manual; - COTTON_APP_NAME = Cotton; - COTTON_BUNDLE_IDENTIFIER = "com.ae.cotton-browser"; - CURRENT_PROJECT_VERSION = 5; - DEVELOPMENT_TEAM = ERT5B59458; - INFOPLIST_FILE = "Info-dnsext.plist"; - INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Cotton/Catowser Andrei Ermoshin. All rights reserved."; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = "com.ae.cotton-browser.dns-ext"; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "Cotton browser AdHoc"; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; - SUPPORTS_MACCATALYST = NO; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; 8A8035F924446234000E035F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -5211,7 +4513,7 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_STRICT_CONCURRENCY = complete; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -5252,7 +4554,7 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_STRICT_CONCURRENCY = complete; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -5294,7 +4596,7 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_STRICT_CONCURRENCY = complete; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -5335,7 +4637,7 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_STRICT_CONCURRENCY = complete; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -5428,7 +4730,7 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_STRICT_CONCURRENCY = complete; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -5466,7 +4768,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_STRICT_CONCURRENCY = complete; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -5507,7 +4809,7 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_STRICT_CONCURRENCY = complete; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -5547,7 +4849,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_STRICT_CONCURRENCY = complete; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -5638,15 +4940,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 8A8035E324442F0F000E035F /* Build configuration list for PBXNativeTarget "Cotton dnsext" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 8A8035E424442F0F000E035F /* Debug */, - 8A8035E524442F0F000E035F /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; 8A8035F824446234000E035F /* Build configuration list for PBXNativeTarget "DNSProxy" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -5847,21 +5140,6 @@ package = 8AC4365C247910E1008DD663 /* XCRemoteSwiftPackageReference "Alamofire" */; productName = Alamofire; }; - 8AD4F52128F49317009C4BDC /* Alamofire */ = { - isa = XCSwiftPackageProductDependency; - package = 8AC4365C247910E1008DD663 /* XCRemoteSwiftPackageReference "Alamofire" */; - productName = Alamofire; - }; - 8AD4F52328F4931C009C4BDC /* ReactiveSwift */ = { - isa = XCSwiftPackageProductDependency; - package = 8A03F44028F4769B00E4A296 /* XCRemoteSwiftPackageReference "ReactiveSwift" */; - productName = ReactiveSwift; - }; - 8AD4F52528F49321009C4BDC /* AlamofireImage */ = { - isa = XCSwiftPackageProductDependency; - package = 8AC4365F247911F6008DD663 /* XCRemoteSwiftPackageReference "AlamofireImage" */; - productName = AlamofireImage; - }; 8AD4F54428F493B8009C4BDC /* Alamofire */ = { isa = XCSwiftPackageProductDependency; package = 8AC4365C247910E1008DD663 /* XCRemoteSwiftPackageReference "Alamofire" */; From dc45f79e26e12f1caa4ef7e9ee12cde7dee39aa6 Mon Sep 17 00:00:00 2001 From: Andrei Ermoshin Date: Tue, 6 Aug 2024 00:22:14 +0300 Subject: [PATCH 12/32] Fix swift 6 errors - part 2 --- .../AlamofireReachabilityAdaptee.swift | 2 +- .../AlamofireHTTPRxAdaptee.swift | 5 ++- .../CottonPlugins/JavaScriptEvaluateble.swift | 38 +++++++++---------- .../Plugins/JavaScriptPlugin.swift | 2 +- .../JavaScriptPluginVisitor.swift | 7 +++- .../WKUserContentController+Visitor.swift | 23 +++++++---- .../FeaturesFlagsKit/FeatureManager.swift | 27 ++++++++----- .../FeaturesFlagsKit/Models/Feature.swift | 2 +- .../Models/GenericEnumFeature.swift | 4 +- .../WebView/WebViewController.swift | 6 ++- 10 files changed, 70 insertions(+), 46 deletions(-) diff --git a/catowseriOS/BrowserNetworking/AlamofireReachabilityAdaptee.swift b/catowseriOS/BrowserNetworking/AlamofireReachabilityAdaptee.swift index 821faa7f..93a240c3 100644 --- a/catowseriOS/BrowserNetworking/AlamofireReachabilityAdaptee.swift +++ b/catowseriOS/BrowserNetworking/AlamofireReachabilityAdaptee.swift @@ -7,7 +7,7 @@ // import CottonRestKit -import Alamofire +@preconcurrency import Alamofire import CottonBase public final class AlamofireReachabilityAdaptee: NetworkReachabilityAdapter { diff --git a/catowseriOS/BrowserNetworking/TransportAdapters/AlamofireHTTPRxAdaptee.swift b/catowseriOS/BrowserNetworking/TransportAdapters/AlamofireHTTPRxAdaptee.swift index 1824d67e..262dd5c5 100644 --- a/catowseriOS/BrowserNetworking/TransportAdapters/AlamofireHTTPRxAdaptee.swift +++ b/catowseriOS/BrowserNetworking/TransportAdapters/AlamofireHTTPRxAdaptee.swift @@ -6,6 +6,7 @@ // Copyright © 2021 Cotton/Catowser Andrei Ermoshin. All rights reserved. // +import AutoMockable import CottonRestKit import ReactiveHttpKit import Alamofire @@ -105,7 +106,9 @@ extension URLRequest /* : URLRequestCreatable */ { } /// Wrapper around Alamofire method -extension JSONEncoding: JSONRequestEncodable { +extension JSONEncoding: @unchecked @retroactive Sendable {} +extension JSONEncoding: @retroactive AutoMockable {} +extension JSONEncoding: @retroactive JSONRequestEncodable { public func encodeRequest(_ urlRequest: URLRequestCreatable, with parameters: [String: Any]?) throws -> URLRequest { return try encode(urlRequest.convertToURLRequest(), with: parameters) } diff --git a/catowseriOS/CottonPlugins/JavaScriptEvaluateble.swift b/catowseriOS/CottonPlugins/JavaScriptEvaluateble.swift index fd0363d6..1eb68db4 100644 --- a/catowseriOS/CottonPlugins/JavaScriptEvaluateble.swift +++ b/catowseriOS/CottonPlugins/JavaScriptEvaluateble.swift @@ -10,6 +10,8 @@ import Foundation import Combine @preconcurrency import ReactiveSwift +/// Протокол для вэб вью по выполнения JavaScript, должно быть на main thread +@MainActor public protocol JavaScriptEvaluateble: AnyObject { func evaluateJavaScriptV2( _ javaScriptString: String, @@ -24,7 +26,7 @@ public protocol JavaScriptEvaluateble: AnyObject { extension JavaScriptEvaluateble { func commonHandleJavaScript( _ javaScriptString: String, - _ completionHandler: (@MainActor @Sendable (Any?, (any Error)?) -> Void)? + _ completionHandler: (@Sendable (Any?, (any Error)?) -> Void)? ) { if #available(iOS 18.0, *) { evaluateJavaScriptV2(javaScriptString, completionHandler: completionHandler) @@ -38,7 +40,6 @@ extension JavaScriptEvaluateble { extension JavaScriptEvaluateble { func evaluate(jsScript: String) { - // swiftlint:disable:next line_length // https://github.com/WebKit/WebKit/blob/main/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm commonHandleJavaScript(jsScript, {(something, error) in if let err = error { @@ -50,27 +51,32 @@ extension JavaScriptEvaluateble { } @available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) - func evaluatePublisher(jsScript: String) -> AnyPublisher { - let p = Future { [weak self] (promise) in + func evaluatePublisher(jsScript: String) -> AnyPublisher { + let future = Future { @MainActor @Sendable [weak self] promise in guard let self = self else { promise(.failure(CottonPluginError.zombiError)) return } - commonHandleJavaScript(jsScript) { (something, error) in - if let realError = error { - promise(.failure(realError)) +#if swift(>=6) + // workaround until Apple Combine fixes the future/promise by adding Sendable conformance + nonisolated(unsafe) let promise = promise +#endif + commonHandleJavaScript(jsScript) { @Sendable (something, error) in + if let error { + promise(.failure(error)) return } - guard let anyResult = something else { + guard let anyResult = something, let stringResult = anyResult as? String else { promise(.failure(CottonPluginError.nilJSEvaluationResult)) return } - promise(.success(anyResult)) + promise(.success(stringResult)) } } - + // Internally it is a WebView, so that, scheduler should be a Main thread return Deferred { - return p + future + .subscribe(on: RunLoop.main) }.eraseToAnyPublisher() } @@ -99,10 +105,7 @@ extension JavaScriptEvaluateble { @available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) public func titlePublisher() -> AnyPublisher { typealias StringResult = Result - return evaluatePublisher(jsScript: "document.title").flatMap { (anyResult) -> StringResult.Publisher in - guard let documentTitle = anyResult as? String else { - return StringResult.Publisher(.failure(CottonPluginError.jsEvaluationIsNotString)) - } + return evaluatePublisher(jsScript: "document.title").flatMap { (documentTitle) -> StringResult.Publisher in return StringResult.Publisher(.success(documentTitle)) }.eraseToAnyPublisher() } @@ -111,10 +114,7 @@ extension JavaScriptEvaluateble { public func finalURLPublisher() -> AnyPublisher { typealias URLResult = Result // If we have JavaScript blocked, these will be empty. - return evaluatePublisher(jsScript: .locationHREF).flatMap { (anyResult) -> URLResult.Publisher in - guard let urlString = anyResult as? String else { - return URLResult.Publisher(.failure(CottonPluginError.jsEvaluationIsNotString)) - } + return evaluatePublisher(jsScript: .locationHREF).flatMap { (urlString) -> URLResult.Publisher in guard let url = URL(string: urlString) else { return URLResult.Publisher(.failure(CottonPluginError.jsEvaluationIsNotURL)) } diff --git a/catowseriOS/CottonPlugins/Plugins/JavaScriptPlugin.swift b/catowseriOS/CottonPlugins/Plugins/JavaScriptPlugin.swift index 67614e47..11d17588 100644 --- a/catowseriOS/CottonPlugins/Plugins/JavaScriptPlugin.swift +++ b/catowseriOS/CottonPlugins/Plugins/JavaScriptPlugin.swift @@ -64,7 +64,7 @@ extension JavaScriptPlugin { guard visitor.canVisit(self, host, canInject, handler) else { return } - try visitor.visit(self) + try visitor.visit(self, handler) } } diff --git a/catowseriOS/CottonPlugins/PluginsVisitor/JavaScriptPluginVisitor.swift b/catowseriOS/CottonPlugins/PluginsVisitor/JavaScriptPluginVisitor.swift index 8634d7fd..803de755 100644 --- a/catowseriOS/CottonPlugins/PluginsVisitor/JavaScriptPluginVisitor.swift +++ b/catowseriOS/CottonPlugins/PluginsVisitor/JavaScriptPluginVisitor.swift @@ -11,6 +11,7 @@ import CottonBase import WebKit /// Will be used for `WKUserContentController` because it is actually the only possible visitor +@MainActor public protocol JavaScriptPluginVisitor: AnyObject { /** Determines if specific plugin can be used on specific host @@ -21,7 +22,6 @@ public protocol JavaScriptPluginVisitor: AnyObject { - canInject A boolean value which should be used as a top level check. Describes feature availability. - handler Shouled be stored separately from the plugin because it is a reference type. */ - @MainActor func canVisit( _ plugin: any JavaScriptPlugin, _ host: CottonBase.Host, @@ -34,5 +34,8 @@ public protocol JavaScriptPluginVisitor: AnyObject { - Parameters: - plugin JavaScript plugin */ - func visit(_ plugin: any JavaScriptPlugin) throws + func visit( + _ plugin: any JavaScriptPlugin, + _ handler: WKScriptMessageHandler + ) throws } diff --git a/catowseriOS/CottonPlugins/PluginsVisitor/WKUserContentController+Visitor.swift b/catowseriOS/CottonPlugins/PluginsVisitor/WKUserContentController+Visitor.swift index df5218f2..24783ede 100644 --- a/catowseriOS/CottonPlugins/PluginsVisitor/WKUserContentController+Visitor.swift +++ b/catowseriOS/CottonPlugins/PluginsVisitor/WKUserContentController+Visitor.swift @@ -29,26 +29,35 @@ extension WKUserContentController: JavaScriptPluginVisitor { return true } - public func visit(_ plugin: any JavaScriptPlugin) throws { + public func visit( + _ plugin: any JavaScriptPlugin, + _ handler: WKScriptMessageHandler + ) throws { if let base = plugin as? BasePlugin { - try visit(basePlugin: base) + try visit(basePlugin: base, handler: handler) } else if let instagram = plugin as? InstagramContentPlugin { - try visit(instagramPlugin: instagram) + try visit(instagramPlugin: instagram, handler: handler) } } - private func visit(basePlugin: BasePlugin) throws { + private func visit( + basePlugin: BasePlugin, + handler: WKScriptMessageHandler + ) throws { let wkScript = try JSPluginFactory.shared.script(for: basePlugin, with: .atDocumentEnd, isMainFrameOnly: true) - addHandler(wkScript, basePlugin.messageHandlerName, basePlugin.handler) + addHandler(wkScript, basePlugin.messageHandlerName, handler) } - private func visit(instagramPlugin: InstagramContentPlugin) throws { + private func visit( + instagramPlugin: InstagramContentPlugin, + handler: WKScriptMessageHandler + ) throws { let wkScript = try JSPluginFactory.shared.script(for: instagramPlugin, with: .atDocumentStart, isMainFrameOnly: instagramPlugin.isMainFrameOnly) - addHandler(wkScript, instagramPlugin.messageHandlerName, instagramPlugin.handler) + addHandler(wkScript, instagramPlugin.messageHandlerName, handler) } private func addHandler(_ script: WKUserScript, _ handlerName: String, _ handler: WKScriptMessageHandler) { diff --git a/catowseriOS/FeaturesFlagsKit/FeatureManager.swift b/catowseriOS/FeaturesFlagsKit/FeatureManager.swift index 9c3af68b..dad32fa3 100644 --- a/catowseriOS/FeaturesFlagsKit/FeatureManager.swift +++ b/catowseriOS/FeaturesFlagsKit/FeatureManager.swift @@ -23,45 +23,52 @@ public final class FeatureManager { // MARK: - read - public func boolValue(of feature: ApplicationFeature) -> Bool where F.Value == Bool { + public func boolValue( + of feature: ApplicationFeature + ) async -> Bool where F.Value == Bool { guard let source = source(for: feature) else { return F.defaultValue } - return source.currentValue(of: feature) + return await source.currentValue(of: feature) } - public func intValue(of feature: ApplicationFeature) -> Int where F.Value == Int { + public func intValue( + of feature: ApplicationFeature + ) async -> Int where F.Value == Int { guard let source = source(for: feature) else { return F.defaultValue } - return source.currentValue(of: feature) + return await source.currentValue(of: feature) } - public func enumValue(_ enumCase: F, _ key: String) -> F? + public func enumValue( + _ enumCase: F, + _ key: String + ) async -> F? where F.RawValue == Int { let enumFeature = GenericEnumFeature(key) let feature: ApplicationEnumFeature = .init(feature: enumFeature) guard let source = source(for: feature) else { return feature.defaultEnumValue } - return source.currentEnumValue(of: feature) + return await source.currentEnumValue(of: feature) } // MARK: - write - public func setFeature(_ feature: ApplicationFeature, value: F.Value?) { + public func setFeature(_ feature: ApplicationFeature, value: F.Value?) async { guard let source = source(for: feature) else { return } - source.setValue(of: feature, value: value) + await source.setValue(of: feature, value: value) } - public func setFeature(_ feature: ApplicationEnumFeature, value: F.EnumValue?) + public func setFeature(_ feature: ApplicationEnumFeature, value: F.EnumValue?) async where F.EnumValue.RawValue == Int { guard let source = source(for: feature) else { return } - source.setEnumValue(of: feature, value: value) + await source.setEnumValue(of: feature, value: value) } // MARK: - sources diff --git a/catowseriOS/FeaturesFlagsKit/Models/Feature.swift b/catowseriOS/FeaturesFlagsKit/Models/Feature.swift index 8d101a35..0408044e 100644 --- a/catowseriOS/FeaturesFlagsKit/Models/Feature.swift +++ b/catowseriOS/FeaturesFlagsKit/Models/Feature.swift @@ -28,7 +28,7 @@ extension Feature { } /// For `syntatic sugar` -public struct ApplicationFeature { +public struct ApplicationFeature: Sendable { public var defaultValue: F.Value { return F.defaultValue } diff --git a/catowseriOS/FeaturesFlagsKit/Models/GenericEnumFeature.swift b/catowseriOS/FeaturesFlagsKit/Models/GenericEnumFeature.swift index eaa49d15..925654dd 100644 --- a/catowseriOS/FeaturesFlagsKit/Models/GenericEnumFeature.swift +++ b/catowseriOS/FeaturesFlagsKit/Models/GenericEnumFeature.swift @@ -12,9 +12,9 @@ public protocol EnumDefaultValueSupportable where Self: CaseIterable { var defaultValue: Self { get } } -public typealias FullEnumTypeConstraints = CaseIterable & RawRepresentable & EnumDefaultValueSupportable +public typealias FullEnumTypeConstraints = CaseIterable & RawRepresentable & EnumDefaultValueSupportable & Sendable -public struct GenericEnumFeature: EnumFeature where E.RawValue == Int { +public struct GenericEnumFeature: EnumFeature, Sendable where E.RawValue == Int { public typealias RawValue = E.RawValue public typealias EnumValue = E diff --git a/catowseriOS/catowser/BrowserContent/WebView/WebViewController.swift b/catowseriOS/catowser/BrowserContent/WebView/WebViewController.swift index 71a9cefe..70edf122 100644 --- a/catowseriOS/catowser/BrowserContent/WebView/WebViewController.swift +++ b/catowseriOS/catowser/BrowserContent/WebView/WebViewController.swift @@ -19,7 +19,7 @@ import FeaturesFlagsKit #endif import CottonData -extension WKWebView: JavaScriptEvaluateble { +extension WKWebView: @retroactive JavaScriptEvaluateble { public func evaluateJavaScriptV2( _ javaScriptString: String, completionHandler: (@MainActor @Sendable (Any?, (any Error)?) -> Void)? @@ -30,7 +30,9 @@ extension WKWebView: JavaScriptEvaluateble { _ javaScriptString: String, completionHandler: ((Any?, Error?) -> Void)? ) { - evaluateJavaScript(javaScriptString, completionHandler: completionHandler) +#if swift(<6.0) + evaluateJavaScript(javaScriptString, completionHandler: completionHandler) +#endif } } From 071f3990dbcc00533a61a34f71521144650afee0 Mon Sep 17 00:00:00 2001 From: Andrei Ermoshin Date: Wed, 7 Aug 2024 23:29:12 +0300 Subject: [PATCH 13/32] Fix swift 6 errors - part 3 --- .../FeaturesFlagsKit/LocalFeatureSource.swift | 26 ++++++++++++------- .../FeaturesFlagsKit/LocalSettings.swift | 16 +++++++----- .../FeaturesFlagsKit/Models/EnumFeature.swift | 6 ++--- .../Models/EnumFeatureSource.swift | 2 +- .../FeaturesFlagsKit/Models/Feature.swift | 2 +- .../Models/FeatureSource.swift | 2 +- 6 files changed, 31 insertions(+), 23 deletions(-) diff --git a/catowseriOS/FeaturesFlagsKit/LocalFeatureSource.swift b/catowseriOS/FeaturesFlagsKit/LocalFeatureSource.swift index fa7a0f5e..51ff2e36 100644 --- a/catowseriOS/FeaturesFlagsKit/LocalFeatureSource.swift +++ b/catowseriOS/FeaturesFlagsKit/LocalFeatureSource.swift @@ -11,11 +11,17 @@ import Foundation import Combine /// FeatureSource that uses UserDefaults -public final class LocalFeatureSource { +public final class LocalFeatureSource: @unchecked Sendable { private let (featureChangeSignal, featureObserver) = Signal.pipe() - private lazy var featureSubject: PassthroughSubject = .init() + private let featureSubject: PassthroughSubject + private let settings: LocalSettings.StateHolder - init() {} + init( + settings: LocalSettings.StateHolder = LocalSettings.shared + ) { + self.settings = settings + featureSubject = .init() + } } extension LocalFeatureSource: ObservableFeatureSource { @@ -31,15 +37,15 @@ extension LocalFeatureSource: FeatureSource { public func currentValue(of feature: ApplicationFeature) async -> F.Value { switch F.defaultValue { case is String: - guard let result = await LocalSettings.shared.getGlobalStringSetting(for: F.key.prefixedBasic()) else { + guard let result = await settings.getString(for: F.key.prefixedBasic()) else { return F.defaultValue } return result as? F.Value ?? F.defaultValue case is Int: - let savedNumber = await LocalSettings.shared.getGlobalIntSetting(for: F.key.prefixedBasic()) + let savedNumber = await settings.getInt(for: F.key.prefixedBasic()) return savedNumber as? F.Value ?? F.defaultValue case is Bool: - guard let globalSetting = await LocalSettings.shared.getGlobalBoolSetting(for: F.key.prefixedBasic()) else { + guard let globalSetting = await settings.getBool(for: F.key.prefixedBasic()) else { return F.defaultValue } return globalSetting as? F.Value ?? F.defaultValue @@ -57,11 +63,11 @@ extension LocalFeatureSource: FeatureSource { case is Bool: // swiftlint:disable:next force_cast let boolValue = value as! Bool - await LocalSettings.shared.setGlobalBoolSetting(for: F.key.prefixedBasic(), value: boolValue) + await settings.setBool(for: F.key.prefixedBasic(), value: boolValue) case is Int: // swiftlint:disable:next force_cast let intValue = value as! Int - await LocalSettings.shared.setGlobalIntSetting(for: F.key.prefixedBasic(), value: intValue) + await settings.setInt(for: F.key.prefixedBasic(), value: intValue) default: assertionFailure("Value settings in Local source isn't implemented for other types") } @@ -77,7 +83,7 @@ extension LocalFeatureSource: FeatureSource { extension LocalFeatureSource: EnumFeatureSource { public func currentEnumValue(of feature: ApplicationEnumFeature) async -> F.EnumValue where F.EnumValue.RawValue == Int { - guard let result = await LocalSettings.shared.getGlobalIntSetting(for: feature.feature.key.prefixedEnum()) else { + guard let result = await settings.getInt(for: feature.feature.key.prefixedEnum()) else { return feature.defaultEnumValue } return F.EnumValue(rawValue: result) ?? feature.defaultEnumValue @@ -88,7 +94,7 @@ extension LocalFeatureSource: EnumFeatureSource { guard let intValue = value?.rawValue else { return } - await LocalSettings.shared.setGlobalIntSetting(for: feature.feature.key.prefixedEnum(), value: intValue) + await settings.setInt(for: feature.feature.key.prefixedEnum(), value: intValue) let value = AnyFeature(feature) if #available(iOS 13.0, *) { diff --git a/catowseriOS/FeaturesFlagsKit/LocalSettings.swift b/catowseriOS/FeaturesFlagsKit/LocalSettings.swift index f64f32d7..fffd223b 100644 --- a/catowseriOS/FeaturesFlagsKit/LocalSettings.swift +++ b/catowseriOS/FeaturesFlagsKit/LocalSettings.swift @@ -15,33 +15,35 @@ final class LocalSettings { actor StateHolder { private let userDefaults: UserDefaults - init() { - userDefaults = .init() + init( + userDefaults: UserDefaults = .init() + ) { + self.userDefaults = userDefaults } - func getGlobalStringSetting(for key: String) -> String? { + func getString(for key: String) -> String? { return userDefaults.string(forKey: key) } - func getGlobalIntSetting(for key: String) -> Int? { + func getInt(for key: String) -> Int? { guard userDefaults.object(forKey: key) != nil else { return nil } return userDefaults.integer(forKey: key) } - func getGlobalBoolSetting(for key: String) -> Bool? { + func getBool(for key: String) -> Bool? { guard userDefaults.object(forKey: key) != nil else { return nil } return userDefaults.bool(forKey: key) } - func setGlobalBoolSetting(for key: String, value: Bool) { + func setBool(for key: String, value: Bool) { userDefaults.set(value, forKey: key) } - func setGlobalIntSetting(for key: String, value: Int) { + func setInt(for key: String, value: Int) { userDefaults.set(value, forKey: key) } } diff --git a/catowseriOS/FeaturesFlagsKit/Models/EnumFeature.swift b/catowseriOS/FeaturesFlagsKit/Models/EnumFeature.swift index 691b505e..d3f5226f 100644 --- a/catowseriOS/FeaturesFlagsKit/Models/EnumFeature.swift +++ b/catowseriOS/FeaturesFlagsKit/Models/EnumFeature.swift @@ -10,8 +10,8 @@ import Foundation /// Should be used for generic enum types, so, no static properties are allowed /// Can't be a subset of a `BasicFeature` or `Feature` which have static properties -public protocol EnumFeature { - associatedtype EnumValue: CaseIterable & RawRepresentable +public protocol EnumFeature: Sendable { + associatedtype EnumValue: CaseIterable & RawRepresentable & Sendable associatedtype RawValue var defaultEnumValue: EnumValue { get } @@ -34,7 +34,7 @@ extension EnumFeature { } } -public struct ApplicationEnumFeature { +public struct ApplicationEnumFeature: Sendable { let feature: F public init(feature: F) { diff --git a/catowseriOS/FeaturesFlagsKit/Models/EnumFeatureSource.swift b/catowseriOS/FeaturesFlagsKit/Models/EnumFeatureSource.swift index 7c14d55c..f9fe3014 100644 --- a/catowseriOS/FeaturesFlagsKit/Models/EnumFeatureSource.swift +++ b/catowseriOS/FeaturesFlagsKit/Models/EnumFeatureSource.swift @@ -11,7 +11,7 @@ import Combine #endif -public protocol EnumFeatureSource { +public protocol EnumFeatureSource: Sendable { func currentEnumValue(of feature: ApplicationEnumFeature) async -> F.EnumValue where F.EnumValue.RawValue == Int func setEnumValue(of feature: ApplicationEnumFeature, value: F.EnumValue?) diff --git a/catowseriOS/FeaturesFlagsKit/Models/Feature.swift b/catowseriOS/FeaturesFlagsKit/Models/Feature.swift index 0408044e..ee919b6e 100644 --- a/catowseriOS/FeaturesFlagsKit/Models/Feature.swift +++ b/catowseriOS/FeaturesFlagsKit/Models/Feature.swift @@ -9,7 +9,7 @@ import Foundation public protocol Feature { - associatedtype Value + associatedtype Value: Sendable static var source: FeatureSource.Type { get } static var defaultValue: Value { get } diff --git a/catowseriOS/FeaturesFlagsKit/Models/FeatureSource.swift b/catowseriOS/FeaturesFlagsKit/Models/FeatureSource.swift index c69a84a9..9b8e4ed6 100644 --- a/catowseriOS/FeaturesFlagsKit/Models/FeatureSource.swift +++ b/catowseriOS/FeaturesFlagsKit/Models/FeatureSource.swift @@ -11,7 +11,7 @@ import Combine #endif -public protocol FeatureSource { +public protocol FeatureSource: Sendable { func currentValue(of feature: ApplicationFeature) async -> F.Value func setValue(of feature: ApplicationFeature, value: F.Value?) async } From 16f3e930ad9db696f04dcdd9e7de1e39bf7d4512 Mon Sep 17 00:00:00 2001 From: Andrei Ermoshin Date: Wed, 7 Aug 2024 23:47:08 +0300 Subject: [PATCH 14/32] Fix swift 6 errors - part 4 --- catowseriOS/CoreBrowser/Tabs/TabsDataService.swift | 8 ++++++-- .../DNSResolving/ResolveDNSUseCaseImpl.swift | 10 ++++++---- .../CottonData/WebViewModel/WebViewAction.swift | 4 ++-- .../CottonData/WebViewModel/WebViewModelState.swift | 5 ++++- catowseriOS/CottonPlugins/JSPluginsProgram.swift | 2 +- catowseriOS/CottonPlugins/JavaScriptEvaluateble.swift | 2 +- catowseriOS/CottonRestKit/ClientSubscriber.swift | 2 +- catowseriOS/CottonRestKit/RestClient.swift | 4 ++-- 8 files changed, 23 insertions(+), 14 deletions(-) diff --git a/catowseriOS/CoreBrowser/Tabs/TabsDataService.swift b/catowseriOS/CoreBrowser/Tabs/TabsDataService.swift index c51374fd..59b15050 100644 --- a/catowseriOS/CoreBrowser/Tabs/TabsDataService.swift +++ b/catowseriOS/CoreBrowser/Tabs/TabsDataService.swift @@ -154,7 +154,9 @@ private extension TabsDataService { func handleCloseAllCommand() async -> TabsServiceDataOutput { let contentState = await positioning.contentState do { - _ = try await tabsRepository.remove(tabs: tabs) + // workaround https://forums.swift.org/t/why-does-sending-a-sendable-value-risk-causing-data-races/73074/4 + var tabsCopy = tabs + _ = try await tabsRepository.remove(tabs: tabsCopy) tabs.removeAll() tabsCountInput.yield(0) let tab: CoreBrowser.Tab = .init(contentType: contentState) @@ -263,7 +265,9 @@ extension TabsDataService: TabsSubject { return } await observer.updateTabsCount(with: tabs.count) - await observer.initializeObserver(with: tabs) + // workaround https://forums.swift.org/t/why-does-sending-a-sendable-value-risk-causing-data-races/73074/4 + var tabsCopy = tabs + await observer.initializeObserver(with: tabsCopy) let defaultValue = positioning.defaultSelectedTabId guard selectedTabIdentifier != defaultValue else { return diff --git a/catowseriOS/CottonData/WebViewModel/DNSResolving/ResolveDNSUseCaseImpl.swift b/catowseriOS/CottonData/WebViewModel/DNSResolving/ResolveDNSUseCaseImpl.swift index 80a07e92..e5805a68 100644 --- a/catowseriOS/CottonData/WebViewModel/DNSResolving/ResolveDNSUseCaseImpl.swift +++ b/catowseriOS/CottonData/WebViewModel/DNSResolving/ResolveDNSUseCaseImpl.swift @@ -19,13 +19,15 @@ public final class ResolveDNSUseCaseImpl: ResolveDNSUseCase where Strategy: DNSResolvingStrategy { public let strategy: Strategy - private lazy var waitingQueue = DispatchQueue(label: .waitingQueueName) - private lazy var waitingScheduler = QueueScheduler(qos: .userInitiated, - name: .waitingQueueName, - targeting: waitingQueue) + private let waitingQueue: DispatchQueue + private let waitingScheduler: QueueScheduler public init(_ strategy: Strategy) { self.strategy = strategy + waitingQueue = DispatchQueue(label: .waitingQueueName) + waitingScheduler = QueueScheduler(qos: .userInitiated, + name: .waitingQueueName, + targeting: waitingQueue) } public func rxResolveDomainName(_ url: URL) -> DNSResolvingProducer { diff --git a/catowseriOS/CottonData/WebViewModel/WebViewAction.swift b/catowseriOS/CottonData/WebViewModel/WebViewAction.swift index a3e10d44..a42d6aed 100644 --- a/catowseriOS/CottonData/WebViewModel/WebViewAction.swift +++ b/catowseriOS/CottonData/WebViewModel/WebViewAction.swift @@ -7,7 +7,7 @@ // import Foundation -import CottonBase +@preconcurrency import CottonBase import CottonPlugins protocol Actionable { @@ -18,7 +18,7 @@ protocol Actionable { typealias IPAddress = String -enum WebViewAction { +enum WebViewAction: Sendable { case loadSite case resetToSite(Site) case loadNextLink(_ url: URL) diff --git a/catowseriOS/CottonData/WebViewModel/WebViewModelState.swift b/catowseriOS/CottonData/WebViewModel/WebViewModelState.swift index 171f80ca..3a36c600 100644 --- a/catowseriOS/CottonData/WebViewModel/WebViewModelState.swift +++ b/catowseriOS/CottonData/WebViewModel/WebViewModelState.swift @@ -11,7 +11,10 @@ import CottonBase import CottonPlugins import CoreBrowser -enum WebViewModelState { +extension CottonBase.Site.Settings: @unchecked @retroactive Sendable {} +extension CottonBase.URLInfo: @unchecked @retroactive Sendable {} + +enum WebViewModelState: Sendable { /// SwiftUI specific state to avoid waiting for the specific `Site` and create VM right away /// to call `load(site)` method when SwiftUI aware of specific `Site` case pendingLoad diff --git a/catowseriOS/CottonPlugins/JSPluginsProgram.swift b/catowseriOS/CottonPlugins/JSPluginsProgram.swift index dd0ea1df..1b0566b5 100644 --- a/catowseriOS/CottonPlugins/JSPluginsProgram.swift +++ b/catowseriOS/CottonPlugins/JSPluginsProgram.swift @@ -11,7 +11,7 @@ import WebKit import CottonBase @MainActor -public protocol JSPluginsProgram: AnyObject { +public protocol JSPluginsProgram: AnyObject, Sendable { var plugins: [(any JavaScriptPlugin, WKScriptMessageHandler)] { get } func inject(to visitor: WKUserContentController, context: CottonBase.Host, canInject: Bool) diff --git a/catowseriOS/CottonPlugins/JavaScriptEvaluateble.swift b/catowseriOS/CottonPlugins/JavaScriptEvaluateble.swift index 1eb68db4..426c5b80 100644 --- a/catowseriOS/CottonPlugins/JavaScriptEvaluateble.swift +++ b/catowseriOS/CottonPlugins/JavaScriptEvaluateble.swift @@ -12,7 +12,7 @@ import Combine /// Протокол для вэб вью по выполнения JavaScript, должно быть на main thread @MainActor -public protocol JavaScriptEvaluateble: AnyObject { +public protocol JavaScriptEvaluateble: AnyObject, Sendable { func evaluateJavaScriptV2( _ javaScriptString: String, completionHandler: (@MainActor @Sendable (Any?, (any Error)?) -> Void)? diff --git a/catowseriOS/CottonRestKit/ClientSubscriber.swift b/catowseriOS/CottonRestKit/ClientSubscriber.swift index cc0bafb0..36f954e8 100644 --- a/catowseriOS/CottonRestKit/ClientSubscriber.swift +++ b/catowseriOS/CottonRestKit/ClientSubscriber.swift @@ -31,7 +31,7 @@ public typealias Subscriber = ClientSubsc /// It lead to the issue that clsoures or Rx observers should be stored somewhere outside async `RestClient` methods. /// Because they can't be deallocated during async requests. /// It must be a reference type because we will pass it to `RestClient` methods. -public final class ClientRxSubscriber where RX.Observer.Response == R, RX.Server == S { +public final class ClientRxSubscriber: @unchecked Sendable where RX.Observer.Response == R, RX.Server == S { /// Can't use protocol type because it has associated type, should be associated with Endpoint response type var handlers = Set>() diff --git a/catowseriOS/CottonRestKit/RestClient.swift b/catowseriOS/CottonRestKit/RestClient.swift index 4204fa6a..3b2c793a 100644 --- a/catowseriOS/CottonRestKit/RestClient.swift +++ b/catowseriOS/CottonRestKit/RestClient.swift @@ -19,11 +19,11 @@ fileprivate extension String { public typealias HttpTypedResult = Result public typealias TypedResponseClosure = (HttpTypedResult) -> Void -extension CottonBase.ServerDescription: @unchecked Sendable {} +extension CottonBase.ServerDescription: @unchecked @retroactive Sendable {} public final class RestClient: RestInterface where R.Server == S { + E: JSONRequestEncodable>: @unchecked Sendable, RestInterface where R.Server == S { public typealias Server = S public typealias Reachability = R public typealias Encoder = E From ee1c1a0c06e2f371b21e510d61c22a8b4d4fc8ec Mon Sep 17 00:00:00 2001 From: Andrei Ermoshin Date: Thu, 8 Aug 2024 00:04:14 +0300 Subject: [PATCH 15/32] Fix swift 6 errors - part 5 --- catowseriOS/CottonData/SiteSettings+Extensions.swift | 2 +- .../CottonData/WebViewModel/SiteNavigationProtocols.swift | 1 + catowseriOS/CottonData/WebViewModel/WebViewContext.swift | 2 +- catowseriOS/CottonData/WebViewModel/WebViewModelState.swift | 2 +- catowseriOS/CottonPlugins/JSPluginsSource.swift | 2 +- .../catowser/BrowserContent/WebView/WebViewContextImpl.swift | 2 -- catowseriOS/catowser/Downloads/LinkTagsViewController.swift | 1 + 7 files changed, 6 insertions(+), 6 deletions(-) diff --git a/catowseriOS/CottonData/SiteSettings+Extensions.swift b/catowseriOS/CottonData/SiteSettings+Extensions.swift index c42c7c72..f2e7c3d5 100644 --- a/catowseriOS/CottonData/SiteSettings+Extensions.swift +++ b/catowseriOS/CottonData/SiteSettings+Extensions.swift @@ -11,7 +11,7 @@ import CottonBase extension Site.Settings { /// This will be ignored for old WebViews because it can't be changed for existing WebView without recration. - var webViewConfig: WKWebViewConfiguration { + @MainActor var webViewConfig: WKWebViewConfiguration { let configuration = WKWebViewConfiguration() if #available(iOS 14, *) { let preferences = WKWebpagePreferences() diff --git a/catowseriOS/CottonData/WebViewModel/SiteNavigationProtocols.swift b/catowseriOS/CottonData/WebViewModel/SiteNavigationProtocols.swift index 6c61e49f..7a3d4810 100644 --- a/catowseriOS/CottonData/WebViewModel/SiteNavigationProtocols.swift +++ b/catowseriOS/CottonData/WebViewModel/SiteNavigationProtocols.swift @@ -9,6 +9,7 @@ import Foundation import CoreBrowser +@MainActor public protocol SiteExternalNavigationDelegate: AnyObject { func provisionalNavigationDidStart() func didSiteOpen(appName: String) diff --git a/catowseriOS/CottonData/WebViewModel/WebViewContext.swift b/catowseriOS/CottonData/WebViewModel/WebViewContext.swift index 9799ca3d..8f5c6a91 100644 --- a/catowseriOS/CottonData/WebViewModel/WebViewContext.swift +++ b/catowseriOS/CottonData/WebViewModel/WebViewContext.swift @@ -19,7 +19,7 @@ import CoreBrowser /// web view context should carry some data or dependencies which can't be stored as a state and always are present. /// protocol with async functions which do not belong to specific actor -public protocol WebViewContext { +public protocol WebViewContext: Sendable { /// Plugins are optional because there is possibility that js files are not present or plugins delegates are not set var pluginsSource: any JSPluginsSource { get } /// Hides app specific implementation for host check diff --git a/catowseriOS/CottonData/WebViewModel/WebViewModelState.swift b/catowseriOS/CottonData/WebViewModel/WebViewModelState.swift index 3a36c600..49221ecc 100644 --- a/catowseriOS/CottonData/WebViewModel/WebViewModelState.swift +++ b/catowseriOS/CottonData/WebViewModel/WebViewModelState.swift @@ -301,7 +301,7 @@ extension WebViewModelState: Equatable { return lData == rData && lSettings == rSettings case (.injectingPlugins(let lProgram, let lData, let lSettings), .injectingPlugins(let rProgram, let rData, let rSettings)): - if let lp = lProgram as? JSPluginsProgramImpl, let rp = rProgram as? JSPluginsProgramImpl, lp != rp { + if let lp = lProgram as? JSPluginsProgramImpl, let rp = rProgram as? JSPluginsProgramImpl /*, lp != rp */ { return false } return lData == rData && lSettings == rSettings diff --git a/catowseriOS/CottonPlugins/JSPluginsSource.swift b/catowseriOS/CottonPlugins/JSPluginsSource.swift index 98fe5b90..66c8b8b9 100644 --- a/catowseriOS/CottonPlugins/JSPluginsSource.swift +++ b/catowseriOS/CottonPlugins/JSPluginsSource.swift @@ -8,7 +8,7 @@ import Foundation @MainActor -public protocol JSPluginsSource: AnyObject { +public protocol JSPluginsSource: AnyObject, Sendable { associatedtype Program: JSPluginsProgram var jsProgram: Program { get } } diff --git a/catowseriOS/catowser/BrowserContent/WebView/WebViewContextImpl.swift b/catowseriOS/catowser/BrowserContent/WebView/WebViewContextImpl.swift index c1b24382..9bf0c9a3 100644 --- a/catowseriOS/catowser/BrowserContent/WebView/WebViewContextImpl.swift +++ b/catowseriOS/catowser/BrowserContent/WebView/WebViewContextImpl.swift @@ -13,8 +13,6 @@ import CoreBrowser import CottonPlugins import FeaturesFlagsKit -private var logTabUpdate = false - public final class WebViewContextImpl: WebViewContext { public let pluginsSource: any JSPluginsSource diff --git a/catowseriOS/catowser/Downloads/LinkTagsViewController.swift b/catowseriOS/catowser/Downloads/LinkTagsViewController.swift index e0339dec..3a700569 100644 --- a/catowseriOS/catowser/Downloads/LinkTagsViewController.swift +++ b/catowseriOS/catowser/Downloads/LinkTagsViewController.swift @@ -30,6 +30,7 @@ enum LinksType: CustomStringConvertible { case unrecognized } +@MainActor protocol LinkTagsPresenter: AnyObject { func setLinks(_ count: Int, for type: LinksType) func clearLinks() From 08f985f78dbeb5e3bb8088a0848ece4d2d89c5b7 Mon Sep 17 00:00:00 2001 From: Andrei Ermoshin Date: Sun, 11 Aug 2024 08:19:07 +0300 Subject: [PATCH 16/32] Fix swift 6 errors - part 6 --- .../SiteNavigationComponent.swift | 1 + .../SiteNavigationProtocols.swift | 1 + .../WebViewModel/WebViewNavigatable.swift | 1 + .../Handlers/BaseJSHandler.swift | 1 + .../Handlers/InstagramHandler.swift | 1 + .../CottonPlugins/Plugins/BasePlugin.swift | 1 + .../Plugins/InstagramContentPlugin.swift | 1 + .../Browser/View/MainBrowserViewModel.swift | 1 + .../SearchSuggestionsCoordinator.swift | 12 +-- catowseriOS/catowser/DB/Database.swift | 2 +- catowseriOS/catowser/DB/TabsDBClient.swift | 3 +- catowseriOS/catowser/DB/TabsResource.swift | 5 +- .../Environment/HttpEnvironment.swift | 99 ++++++++++--------- .../catowser/Theme/ThemeProvider.swift | 1 + .../catowser/WebTabs/TabsRepositoryImpl.swift | 2 +- 15 files changed, 73 insertions(+), 59 deletions(-) diff --git a/catowseriOS/CottonData/WebViewModel/SiteNavigationComponent.swift b/catowseriOS/CottonData/WebViewModel/SiteNavigationComponent.swift index 98d0a59b..e3e91590 100644 --- a/catowseriOS/CottonData/WebViewModel/SiteNavigationComponent.swift +++ b/catowseriOS/CottonData/WebViewModel/SiteNavigationComponent.swift @@ -8,6 +8,7 @@ import Foundation +@MainActor public protocol SiteNavigationComponent: AnyObject { /// Use `nil` to tell that navigation actions should be disabled var siteNavigator: WebViewNavigatable? { get set } diff --git a/catowseriOS/CottonData/WebViewModel/SiteNavigationProtocols.swift b/catowseriOS/CottonData/WebViewModel/SiteNavigationProtocols.swift index 7a3d4810..7372d6f7 100644 --- a/catowseriOS/CottonData/WebViewModel/SiteNavigationProtocols.swift +++ b/catowseriOS/CottonData/WebViewModel/SiteNavigationProtocols.swift @@ -25,6 +25,7 @@ public protocol SiteExternalNavigationDelegate: AnyObject { func webViewDidReplace(_ interface: WebViewNavigatable?) } +@MainActor public protocol SiteNavigationChangable: AnyObject { func changeBackButton(to canGoBack: Bool) func changeForwardButton(to canGoForward: Bool) diff --git a/catowseriOS/CottonData/WebViewModel/WebViewNavigatable.swift b/catowseriOS/CottonData/WebViewModel/WebViewNavigatable.swift index 6829bf15..003d3f69 100644 --- a/catowseriOS/CottonData/WebViewModel/WebViewNavigatable.swift +++ b/catowseriOS/CottonData/WebViewModel/WebViewNavigatable.swift @@ -9,6 +9,7 @@ import Foundation import CottonBase +@MainActor public protocol WebViewNavigatable: AnyObject { var canGoBack: Bool { get } var canGoForward: Bool { get } diff --git a/catowseriOS/CottonPlugins/Handlers/BaseJSHandler.swift b/catowseriOS/CottonPlugins/Handlers/BaseJSHandler.swift index 4d3085da..2e759b08 100644 --- a/catowseriOS/CottonPlugins/Handlers/BaseJSHandler.swift +++ b/catowseriOS/CottonPlugins/Handlers/BaseJSHandler.swift @@ -8,6 +8,7 @@ import WebKit +@MainActor final class BaseJSHandler: NSObject { private weak var delegate: BasePluginContentDelegate? diff --git a/catowseriOS/CottonPlugins/Handlers/InstagramHandler.swift b/catowseriOS/CottonPlugins/Handlers/InstagramHandler.swift index 383116fc..bcb6fc5f 100644 --- a/catowseriOS/CottonPlugins/Handlers/InstagramHandler.swift +++ b/catowseriOS/CottonPlugins/Handlers/InstagramHandler.swift @@ -9,6 +9,7 @@ import Foundation import WebKit +@MainActor final class InstagramHandler: NSObject { private weak var delegate: InstagramContentDelegate? init(_ delegate: InstagramContentDelegate) { diff --git a/catowseriOS/CottonPlugins/Plugins/BasePlugin.swift b/catowseriOS/CottonPlugins/Plugins/BasePlugin.swift index c3c66629..d418e95e 100644 --- a/catowseriOS/CottonPlugins/Plugins/BasePlugin.swift +++ b/catowseriOS/CottonPlugins/Plugins/BasePlugin.swift @@ -9,6 +9,7 @@ import Foundation import WebKit +@MainActor public protocol BasePluginContentDelegate: AnyObject { func didReceiveVideoTags(_ tags: [HTMLVideoTag]) } diff --git a/catowseriOS/CottonPlugins/Plugins/InstagramContentPlugin.swift b/catowseriOS/CottonPlugins/Plugins/InstagramContentPlugin.swift index ac4d4009..d31b58e6 100644 --- a/catowseriOS/CottonPlugins/Plugins/InstagramContentPlugin.swift +++ b/catowseriOS/CottonPlugins/Plugins/InstagramContentPlugin.swift @@ -9,6 +9,7 @@ import Foundation import WebKit +@MainActor public protocol InstagramContentDelegate: AnyObject { func didReceiveVideoNodes(_ nodes: [InstagramVideoNode]) } diff --git a/catowseriOS/catowser/Browser/View/MainBrowserViewModel.swift b/catowseriOS/catowser/Browser/View/MainBrowserViewModel.swift index 49e8898c..f0d0e921 100644 --- a/catowseriOS/catowser/Browser/View/MainBrowserViewModel.swift +++ b/catowseriOS/catowser/Browser/View/MainBrowserViewModel.swift @@ -10,6 +10,7 @@ import Combine import SwiftUI import CottonPlugins +@MainActor final class MainBrowserViewModel: ObservableObject { weak var coordinatorsInterface: C? /// Not a constant because can't be initialized in init diff --git a/catowseriOS/catowser/Coordinators/SearchSuggestionsCoordinator.swift b/catowseriOS/catowser/Coordinators/SearchSuggestionsCoordinator.swift index d1618a81..7055fecb 100644 --- a/catowseriOS/catowser/Coordinators/SearchSuggestionsCoordinator.swift +++ b/catowseriOS/catowser/Coordinators/SearchSuggestionsCoordinator.swift @@ -126,22 +126,22 @@ extension SearchSuggestionsCoordinator: Layouting { } private extension SearchSuggestionsCoordinator { - func keyboardWillChangeFrameClosure() -> @MainActor (Notification) -> Void { - func handling(_ notification: Notification) { + func keyboardWillChangeFrameClosure() -> @MainActor @Sendable (Notification) -> Void { + let handling: @MainActor @Sendable (Notification) -> Void = { [weak self] notification in guard let info = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] else { return } guard let value = info as? NSValue else { return } let rect = value.cgRectValue // need to reduce search suggestions list height - _keyboardHeight = rect.size.height + self?._keyboardHeight = rect.size.height } return handling } - func keyboardWillHideClosure() -> @MainActor (Notification) -> Void { - func handling(_ notification: Notification) { - _keyboardHeight = nil + func keyboardWillHideClosure() -> @MainActor @Sendable (Notification) -> Void { + let handling: @MainActor @Sendable (Notification) -> Void = { [weak self] notification in + self?._keyboardHeight = nil } return handling diff --git a/catowseriOS/catowser/DB/Database.swift b/catowseriOS/catowser/DB/Database.swift index 3216015f..2ea58603 100644 --- a/catowseriOS/catowser/DB/Database.swift +++ b/catowseriOS/catowser/DB/Database.swift @@ -22,7 +22,7 @@ final class Database { } /// A read-only flag indicating if the persistent store is loaded. - private (set) var isStoreLoaded = false + private(set) var isStoreLoaded = false /// The managed object context associated with the main queue (read-only). /// To perform tasks on a private background queue see diff --git a/catowseriOS/catowser/DB/TabsDBClient.swift b/catowseriOS/catowser/DB/TabsDBClient.swift index ad422ad5..770ac5e6 100644 --- a/catowseriOS/catowser/DB/TabsDBClient.swift +++ b/catowseriOS/catowser/DB/TabsDBClient.swift @@ -17,7 +17,8 @@ enum TabsCoreDataError: Swift.Error { case selectedTabIdNotPresent } -final class TabsDBClient { +/// Клиент для файлов базы данных, имеет синхронизацию через `performAndWait` +final class TabsDBClient: @unchecked Sendable { private let managedContext: NSManagedObjectContext init(_ managedContext: NSManagedObjectContext) { diff --git a/catowseriOS/catowser/DB/TabsResource.swift b/catowseriOS/catowser/DB/TabsResource.swift index 0d1e9ccb..b3147e14 100644 --- a/catowseriOS/catowser/DB/TabsResource.swift +++ b/catowseriOS/catowser/DB/TabsResource.swift @@ -14,7 +14,8 @@ fileprivate extension String { static let threadName = "tabsStore" } -final class TabsResource { +/// Класс обертка над клиентом базы данных, имеет синхронизацию через DispatchQueue +final class TabsResource: @unchecked Sendable { private var dbClient: TabsDBClient /// Needs to be checked on every access to `dbClient` to not use wrong context @@ -32,7 +33,7 @@ final class TabsResource { /// - privateContextCreator: We have to call this closure on specific thread and /// use same thread for any other usages of this context. init(temporaryContext: NSManagedObjectContext, - privateContextCreator: @escaping () -> NSManagedObjectContext?) { + privateContextCreator: @escaping @Sendable () -> NSManagedObjectContext?) { // Creating temporary instance to be able to use background thread // to properly create private CoreData context let dummyStore: TabsDBClient = .init(temporaryContext) diff --git a/catowseriOS/catowser/Environment/HttpEnvironment.swift b/catowseriOS/catowser/Environment/HttpEnvironment.swift index 75b2e588..53fcfa66 100644 --- a/catowseriOS/catowser/Environment/HttpEnvironment.swift +++ b/catowseriOS/catowser/Environment/HttpEnvironment.swift @@ -13,66 +13,69 @@ import BrowserNetworking import Alamofire // only needed for `JSONEncoding` import FeaturesFlagsKit +@globalActor final class HttpEnvironment { - static let shared: HttpEnvironment = .init() + static let shared = StateHolder() - let dnsClient: GoogleDnsClient - let googleClient: GoogleSuggestionsClient - let duckduckgoClient: DDGoSuggestionsClient + actor StateHolder { + let dnsClient: GoogleDnsClient + let googleClient: GoogleSuggestionsClient + let duckduckgoClient: DDGoSuggestionsClient - let dnsAlReachability: AlamofireReachabilityAdaptee - let googleAlReachability: AlamofireReachabilityAdaptee - let ddGoAlReachability: AlamofireReachabilityAdaptee + let dnsAlReachability: AlamofireReachabilityAdaptee + let googleAlReachability: AlamofireReachabilityAdaptee + let ddGoAlReachability: AlamofireReachabilityAdaptee - let googleClientRxSubscriber: GSearchClientRxSubscriber = .init() - let googleClientSubscriber: GSearchClientSubscriber = .init() - let duckduckgoClientSubscriber: DDGoSuggestionsClientSubscriber = .init() + let googleClientRxSubscriber: GSearchClientRxSubscriber = .init() + let googleClientSubscriber: GSearchClientSubscriber = .init() + let duckduckgoClientSubscriber: DDGoSuggestionsClientSubscriber = .init() - let dnsClientRxSubscriber: GDNSJsonClientRxSubscriber = .init() - let dnsClientSubscriber: GDNSJsonClientSubscriber = .init() - let duckduckgoClientRxSubscriber: DDGoSuggestionsClientRxSubscriber = .init() + let dnsClientRxSubscriber: GDNSJsonClientRxSubscriber = .init() + let dnsClientSubscriber: GDNSJsonClientSubscriber = .init() + let duckduckgoClientRxSubscriber: DDGoSuggestionsClientRxSubscriber = .init() - private init() { - let googleDNSserver = GoogleDnsServer() - // swiftlint:disable:next force_unwrapping - dnsAlReachability = .init(server: googleDNSserver)! - dnsClient = .init(server: googleDNSserver, - jsonEncoder: JSONEncoding.default, - reachability: dnsAlReachability, - httpTimeout: 2) - let googleServer = GoogleServer() - // swiftlint:disable:next force_unwrapping - googleAlReachability = .init(server: googleServer)! - googleClient = .init(server: googleServer, - jsonEncoder: JSONEncoding.default, - reachability: googleAlReachability, - httpTimeout: 10) - - let duckduckgoServer = DuckDuckGoServer() - // swiftlint:disable:next force_unwrapping - ddGoAlReachability = .init(server: duckduckgoServer)! - duckduckgoClient = .init(server: duckduckgoServer, + init() { + let googleDNSserver = GoogleDnsServer() + // swiftlint:disable:next force_unwrapping + dnsAlReachability = .init(server: googleDNSserver)! + dnsClient = .init(server: googleDNSserver, + jsonEncoder: JSONEncoding.default, + reachability: dnsAlReachability, + httpTimeout: 2) + let googleServer = GoogleServer() + // swiftlint:disable:next force_unwrapping + googleAlReachability = .init(server: googleServer)! + googleClient = .init(server: googleServer, jsonEncoder: JSONEncoding.default, - reachability: ddGoAlReachability, + reachability: googleAlReachability, httpTimeout: 10) - } - func searchSuggestClient() async -> SearchEngine { - let selectedPluginName = await FeatureManager.shared.searchPluginName() - let optionalXmlData = ResourceReader.readXmlSearchPlugin(with: selectedPluginName, on: .main) - guard let xmlData = optionalXmlData else { - return .googleSearchEngine() + let duckduckgoServer = DuckDuckGoServer() + // swiftlint:disable:next force_unwrapping + ddGoAlReachability = .init(server: duckduckgoServer)! + duckduckgoClient = .init(server: duckduckgoServer, + jsonEncoder: JSONEncoding.default, + reachability: ddGoAlReachability, + httpTimeout: 10) } - let osDescription: OpenSearch.Description - do { - osDescription = try OpenSearch.Description(data: xmlData) - } catch { - print("Open search xml parser error: \(error.localizedDescription)") - return .googleSearchEngine() - } + func searchSuggestClient() async -> SearchEngine { + let selectedPluginName = await FeatureManager.shared.searchPluginName() + let optionalXmlData = ResourceReader.readXmlSearchPlugin(with: selectedPluginName, on: .main) + guard let xmlData = optionalXmlData else { + return .googleSearchEngine() + } - return osDescription.html + let osDescription: OpenSearch.Description + do { + osDescription = try OpenSearch.Description(data: xmlData) + } catch { + print("Open search xml parser error: \(error.localizedDescription)") + return .googleSearchEngine() + } + + return osDescription.html + } } } diff --git a/catowseriOS/catowser/Theme/ThemeProvider.swift b/catowseriOS/catowser/Theme/ThemeProvider.swift index 7925c65b..92114778 100644 --- a/catowseriOS/catowser/Theme/ThemeProvider.swift +++ b/catowseriOS/catowser/Theme/ThemeProvider.swift @@ -8,6 +8,7 @@ import UIKit +@MainActor final class ThemeProvider { static let shared = ThemeProvider() diff --git a/catowseriOS/catowser/WebTabs/TabsRepositoryImpl.swift b/catowseriOS/catowser/WebTabs/TabsRepositoryImpl.swift index ff23e33f..68ffb189 100644 --- a/catowseriOS/catowser/WebTabs/TabsRepositoryImpl.swift +++ b/catowseriOS/catowser/WebTabs/TabsRepositoryImpl.swift @@ -20,7 +20,7 @@ final class TabsRepositoryImpl { private let tabsDbResource: TabsResource init(_ temporaryContext: NSManagedObjectContext, - _ privateContextCreator: @escaping () -> NSManagedObjectContext?) { + _ privateContextCreator: @escaping @Sendable () -> NSManagedObjectContext?) { tabsDbResource = .init(temporaryContext: temporaryContext, privateContextCreator: privateContextCreator) } From 52e1f729409b3aaddb172b0190d3edfa78f38d82 Mon Sep 17 00:00:00 2001 From: Andrei Ermoshin Date: Sun, 11 Aug 2024 08:47:28 +0300 Subject: [PATCH 17/32] Fix swift 6 errors - part 7 --- .../SearchEngine+OpenSearchParser.swift | 2 +- .../SearchEngines/SearchEngine.swift | 6 +++-- .../Models/AsyncApiType.swift | 2 +- .../WebView/WebViewLoadingErrorHandler.swift | 1 + .../catowser/Constants/UIConstants.swift | 1 + .../Coordinators/PhoneTabsCoordinator.swift | 1 + .../Coordinators/WebContentCoordinator.swift | 1 + catowseriOS/catowser/DB/Database.swift | 21 +++++++++++----- .../Downloads/FilesGridViewController.swift | 2 ++ .../Environment/TabsEnvironment.swift | 5 ++-- .../Extensions/CollectionViewInterface.swift | 1 + catowseriOS/catowser/Menu/MenuViewModel.swift | 3 ++- .../Search/Phone/SearchFieldView.swift | 3 +++ .../SearchSuggestionsViewController.swift | 4 +++- .../FeatureManager+SpecificEnums.swift | 24 +++++++++---------- 15 files changed, 51 insertions(+), 26 deletions(-) diff --git a/catowseriOS/BrowserNetworking/OpenSearch/SearchEngine+OpenSearchParser.swift b/catowseriOS/BrowserNetworking/OpenSearch/SearchEngine+OpenSearchParser.swift index b1178dde..6342d29f 100644 --- a/catowseriOS/BrowserNetworking/OpenSearch/SearchEngine+OpenSearchParser.swift +++ b/catowseriOS/BrowserNetworking/OpenSearch/SearchEngine+OpenSearchParser.swift @@ -108,7 +108,7 @@ extension OpenSearch { case xIcon = "image/x-icon" } - public enum ImageParseResult { + public enum ImageParseResult: Sendable { case base64(Data) case url(URL) case none diff --git a/catowseriOS/BrowserNetworking/SearchEngines/SearchEngine.swift b/catowseriOS/BrowserNetworking/SearchEngines/SearchEngine.swift index e38c21c5..773f454a 100644 --- a/catowseriOS/BrowserNetworking/SearchEngines/SearchEngine.swift +++ b/catowseriOS/BrowserNetworking/SearchEngines/SearchEngine.swift @@ -7,10 +7,12 @@ // import Foundation -import CottonBase +@preconcurrency import CottonBase + +extension HTTPMethod: @unchecked @retroactive Sendable { } /// The model for OpenSearch format URL -public struct SearchEngine { +public struct SearchEngine: Sendable { /// Name e.g. "DuckDuckGo" let shortName: String /// Valid components for final URL diff --git a/catowseriOS/FeaturesFlagsKit/Models/AsyncApiType.swift b/catowseriOS/FeaturesFlagsKit/Models/AsyncApiType.swift index 4578dc67..a2df1fb8 100644 --- a/catowseriOS/FeaturesFlagsKit/Models/AsyncApiType.swift +++ b/catowseriOS/FeaturesFlagsKit/Models/AsyncApiType.swift @@ -9,7 +9,7 @@ import Foundation /// More simple analog for HttpKit.ResponseHandlingApi -public enum AsyncApiType: Int, CaseIterable { +public enum AsyncApiType: Int, CaseIterable, Sendable { case reactive case combine case asyncAwait diff --git a/catowseriOS/catowser/BrowserContent/WebView/WebViewLoadingErrorHandler.swift b/catowseriOS/catowser/BrowserContent/WebView/WebViewLoadingErrorHandler.swift index 6dc39ce9..84eeb60d 100644 --- a/catowseriOS/catowser/BrowserContent/WebView/WebViewLoadingErrorHandler.swift +++ b/catowseriOS/catowser/BrowserContent/WebView/WebViewLoadingErrorHandler.swift @@ -21,6 +21,7 @@ import UIKit user to go back or continue. */ +@MainActor final class WebViewLoadingErrorHandler { let error: NSError let webView: WKWebView diff --git a/catowseriOS/catowser/Constants/UIConstants.swift b/catowseriOS/catowser/Constants/UIConstants.swift index a76c0a32..e4ad1c66 100644 --- a/catowseriOS/catowser/Constants/UIConstants.swift +++ b/catowseriOS/catowser/Constants/UIConstants.swift @@ -18,6 +18,7 @@ extension CGFloat { static let tagLabelHorizontalMargin = CGFloat(10.0) static let safeAreaBottomMargin = CGFloat(20.0) /* view.safeAreaInsets.bottom */ + @MainActor static var tabWidth: CGFloat { if UIDevice.current.userInterfaceIdiom == .phone { return 40.0 diff --git a/catowseriOS/catowser/Coordinators/PhoneTabsCoordinator.swift b/catowseriOS/catowser/Coordinators/PhoneTabsCoordinator.swift index 752d16a7..de0b2b0c 100644 --- a/catowseriOS/catowser/Coordinators/PhoneTabsCoordinator.swift +++ b/catowseriOS/catowser/Coordinators/PhoneTabsCoordinator.swift @@ -10,6 +10,7 @@ import UIKit import CoreBrowser import FeaturesFlagsKit +@MainActor protocol PhoneTabsDelegate: AnyObject { func didTabSelect(_ tab: CoreBrowser.Tab) async func didTabAdd() async diff --git a/catowseriOS/catowser/Coordinators/WebContentCoordinator.swift b/catowseriOS/catowser/Coordinators/WebContentCoordinator.swift index 6530a59b..f69d82d8 100644 --- a/catowseriOS/catowser/Coordinators/WebContentCoordinator.swift +++ b/catowseriOS/catowser/Coordinators/WebContentCoordinator.swift @@ -12,6 +12,7 @@ import CoreBrowser import CottonPlugins import CottonData +@MainActor protocol WebContentDelegate: AnyObject { func provisionalNavigationDidStart() func loadingProgressdDidChange(_ progress: Float) diff --git a/catowseriOS/catowser/DB/Database.swift b/catowseriOS/catowser/DB/Database.swift index 2ea58603..3ebde2aa 100644 --- a/catowseriOS/catowser/DB/Database.swift +++ b/catowseriOS/catowser/DB/Database.swift @@ -9,7 +9,7 @@ import Foundation import CoreData -final class Database { +final class Database: Sendable { /// The default directory for the persistent stores on the current platform. /// @@ -22,7 +22,8 @@ final class Database { } /// A read-only flag indicating if the persistent store is loaded. - private(set) var isStoreLoaded = false + /// Can be nonisolated unsafe, because it is a Scalar bool type. + nonisolated(unsafe) private(set) var isStoreLoaded = false /// The managed object context associated with the main queue (read-only). /// To perform tasks on a private background queue see @@ -56,25 +57,33 @@ final class Database { /// to YES before loading the persistent store if you want a read-only /// store (for example if loading from the application bundle). /// Default is false. - private var isReadOnly = false + /// + /// Can be nonisolated unsafe, because it is a Scalar bool type. + nonisolated(unsafe) private var isReadOnly = false /// A flag that indicates whether the store is added asynchronously. /// Set this value before loading the persistent store. /// Default is true. - private var shouldAddStoreAsynchronously = false + /// + /// Can be nonisolated unsafe, because it is a Scalar bool type. + nonisolated(unsafe) private var shouldAddStoreAsynchronously = false /// A flag that indicates whether the store should be migrated /// automatically if the store model version does not match the /// coordinators model version. /// Set this value before loading the persistent store. /// Default is true. - private var shouldMigrateStoreAutomatically = true + /// + /// Can be nonisolated unsafe, because it is a Scalar bool type. + nonisolated(unsafe) private var shouldMigrateStoreAutomatically = true /// A flag that indicates whether a mapping model should be inferred /// when migrating a store. /// Set this value before loading the persistent store. /// Default is true. - private var shouldInferMappingModelAutomatically = true + /// + /// Can be nonisolated unsafe, because it is a Scalar bool type. + nonisolated(unsafe) private var shouldInferMappingModelAutomatically = true /// Creates and returns a `CoreDataController` object. This is the designated /// initializer for the class. It creates the managed object model, diff --git a/catowseriOS/catowser/Downloads/FilesGridViewController.swift b/catowseriOS/catowser/Downloads/FilesGridViewController.swift index db2b9edb..08caba5f 100644 --- a/catowseriOS/catowser/Downloads/FilesGridViewController.swift +++ b/catowseriOS/catowser/Downloads/FilesGridViewController.swift @@ -12,11 +12,13 @@ import CoreBrowser import BrowserNetworking import CottonPlugins +@MainActor protocol FilesGridPresenter: AnyObject { func reloadWith(source: TagsSiteDataSource, completion: (() -> Void)?) func clearFiles() } +@MainActor protocol FileDownloadViewDelegate: AnyObject { func didRequestOpen(local url: URL, from sourceView: DownloadButtonCellView) func didPressDownload(callback: @escaping (FileDownloadViewModel?) -> Void) diff --git a/catowseriOS/catowser/Environment/TabsEnvironment.swift b/catowseriOS/catowser/Environment/TabsEnvironment.swift index ccdacba2..f9458056 100644 --- a/catowseriOS/catowser/Environment/TabsEnvironment.swift +++ b/catowseriOS/catowser/Environment/TabsEnvironment.swift @@ -20,7 +20,8 @@ private final class TabsEnvironment { return created } - static private var internalInstance: ManagerHolder? + /// Nonisolsated unsafe because it is private field and actual `shared()` is async and that is why it is ok + static nonisolated(unsafe) private var internalInstance: ManagerHolder? fileprivate actor ManagerHolder { let tabsDataService: TabsDataService @@ -36,7 +37,7 @@ private final class TabsEnvironment { fatalError("Failed to initialize Database \(error.localizedDescription)") } self.database = database - let contextClosure = { [weak database] () -> NSManagedObjectContext? in + let contextClosure = { @Sendable [weak database] () -> NSManagedObjectContext? in guard let dbInterface = database else { fatalError("Cotton db reference is nil") } diff --git a/catowseriOS/catowser/Extensions/CollectionViewInterface.swift b/catowseriOS/catowser/Extensions/CollectionViewInterface.swift index a55ffd4e..95f2be93 100644 --- a/catowseriOS/catowser/Extensions/CollectionViewInterface.swift +++ b/catowseriOS/catowser/Extensions/CollectionViewInterface.swift @@ -14,6 +14,7 @@ private struct CollectionViewSizes { static let numberOfColumnsWide = 3 } +@MainActor protocol CollectionViewInterface: AnyObject { var traitCollection: UITraitCollection { get } } diff --git a/catowseriOS/catowser/Menu/MenuViewModel.swift b/catowseriOS/catowser/Menu/MenuViewModel.swift index 68afd0f5..a98bca7b 100644 --- a/catowseriOS/catowser/Menu/MenuViewModel.swift +++ b/catowseriOS/catowser/Menu/MenuViewModel.swift @@ -7,7 +7,7 @@ // #if canImport(Combine) -import Combine +@preconcurrency import Combine #endif import CottonBase import CoreBrowser @@ -18,6 +18,7 @@ enum BrowserMenuStyle { case onlyGlobalMenu } +@MainActor protocol DeveloperMenuPresenter: AnyObject { func emulateLinkTags() /// This method is not for dev only and should be available in any build types diff --git a/catowseriOS/catowser/Search/Phone/SearchFieldView.swift b/catowseriOS/catowser/Search/Phone/SearchFieldView.swift index faa32dbe..6a9e1f3e 100644 --- a/catowseriOS/catowser/Search/Phone/SearchFieldView.swift +++ b/catowseriOS/catowser/Search/Phone/SearchFieldView.swift @@ -50,6 +50,9 @@ struct SearchFieldView: View { } } +/// A value type struct from SwiftUICore system module which wasn't sendable out of the box +extension LocalizedStringKey: @unchecked @retroactive Sendable { } + private extension LocalizedStringKey { static let placeholderTextKey: LocalizedStringKey = "placeholder_searchbar" } diff --git a/catowseriOS/catowser/SearchSuggestions/SearchSuggestionsViewController.swift b/catowseriOS/catowser/SearchSuggestions/SearchSuggestionsViewController.swift index dc383325..e092b90a 100644 --- a/catowseriOS/catowser/SearchSuggestions/SearchSuggestionsViewController.swift +++ b/catowseriOS/catowser/SearchSuggestions/SearchSuggestionsViewController.swift @@ -21,6 +21,7 @@ enum SuggestionType: Equatable { case looksLikeURL(String) } +@MainActor protocol SearchSuggestionsListDelegate: AnyObject { func searchSuggestionDidSelect(_ content: SuggestionType) async } @@ -48,7 +49,8 @@ final class SearchSuggestionsViewController: UITableViewController { } deinit { - taskHandler?.cancel() + #warning("AnyCancellable is not sendable, so, skipping cancel") + // taskHandler?.cancel() } required init?(coder: NSCoder) { diff --git a/catowseriOS/catowser/Utils/Features/FeatureManager+SpecificEnums.swift b/catowseriOS/catowser/Utils/Features/FeatureManager+SpecificEnums.swift index 7e0b4b7f..f46e7a8b 100644 --- a/catowseriOS/catowser/Utils/Features/FeatureManager+SpecificEnums.swift +++ b/catowseriOS/catowser/Utils/Features/FeatureManager+SpecificEnums.swift @@ -12,7 +12,7 @@ import FeaturesFlagsKit // MARK: - generic GETTER method extension FeatureManager.StateHolder { - func enumValue(_ enumCase: F) -> F? + func enumValue(_ enumCase: F) async -> F? where F.RawValue == Int { let keyStr: String switch enumCase.defaultValue { @@ -30,55 +30,55 @@ extension FeatureManager.StateHolder { assertionFailure("Attempt to search for not supported enum feature type") return nil } - return enumValue(enumCase, keyStr) + return await enumValue(enumCase, keyStr) } } // MARK: - GETTER methods specific to Enum features extension FeatureManager.StateHolder { - func tabAddPositionValue() -> AddedTabPosition { + func tabAddPositionValue() async -> AddedTabPosition { let feature: ApplicationEnumFeature = .tabAddPosition guard let source = source(for: feature) else { return feature.defaultEnumValue } - return source.currentEnumValue(of: feature) + return await source.currentEnumValue(of: feature) } - func tabDefaultContentValue() -> TabContentDefaultState { + func tabDefaultContentValue() async -> TabContentDefaultState { let feature: ApplicationEnumFeature = .tabDefaultContent guard let source = source(for: feature) else { return feature.defaultEnumValue } - return source.currentEnumValue(of: feature) + return await source.currentEnumValue(of: feature) } - func appAsyncApiTypeValue() -> AsyncApiType { + func appAsyncApiTypeValue() async -> AsyncApiType { let feature: ApplicationEnumFeature = .appDefaultAsyncApi #if DEBUG guard let source = source(for: feature) else { return feature.defaultEnumValue } - return source.currentEnumValue(of: feature) + return await source.currentEnumValue(of: feature) #else // We don't need to change this in Release builds return feature.defaultEnumValue #endif } - func webSearchAutoCompleteValue() -> WebAutoCompletionSource { + func webSearchAutoCompleteValue() async -> WebAutoCompletionSource { let feature: ApplicationEnumFeature = .webAutoCompletionSource guard let source = source(for: feature) else { return feature.defaultEnumValue } - return source.currentEnumValue(of: feature) + return await source.currentEnumValue(of: feature) } - func appUIFrameworkValue() -> UIFrameworkType { + func appUIFrameworkValue() async -> UIFrameworkType { let feature: ApplicationEnumFeature = .appDefaultUIFramework guard let source = source(for: feature) else { return feature.defaultEnumValue } - return source.currentEnumValue(of: feature) + return await source.currentEnumValue(of: feature) } } From c214c6ce769e16fde5d93f8f2f9222ed771c3ad8 Mon Sep 17 00:00:00 2001 From: Andrei Ermoshin Date: Sun, 11 Aug 2024 09:34:28 +0300 Subject: [PATCH 18/32] Fix swift 6 errors - part 8 --- catowseriOS/CoreBrowser/Models/Tab.swift | 2 +- .../SiteNavigationProtocols.swift | 4 +- .../WebViewModel/WebViewModel.swift | 3 +- catowseriOS/catowser/BaseViewController.swift | 2 +- .../BrowserContentViewModel.swift | 2 +- .../TopSites/TopSitesViewController.swift | 1 + .../WebView/WebViewAuthChallengeHandler.swift | 24 +++++----- .../WebView/WebViewController.swift | 45 +++++++++++-------- .../Coordinators/LinkTagsCoordinator.swift | 2 +- .../Coordinators/SearchBarCoordinator.swift | 4 +- .../Downloads/DownloadPanelPresenter.swift | 2 +- .../Downloads/FileDownloadViewModel.swift | 2 +- .../Downloads/LinkTagsViewController.swift | 1 + .../Extensions/ReloadWithCompletion.swift | 2 +- .../Search/SearchBarBaseViewController.swift | 2 +- 15 files changed, 57 insertions(+), 41 deletions(-) diff --git a/catowseriOS/CoreBrowser/Models/Tab.swift b/catowseriOS/CoreBrowser/Models/Tab.swift index 33fbfe8b..385d7f3b 100644 --- a/catowseriOS/CoreBrowser/Models/Tab.swift +++ b/catowseriOS/CoreBrowser/Models/Tab.swift @@ -10,7 +10,7 @@ import CottonBase /// Site is an obj-c type. /// https://github.com/apple/swift-evolution/blob/main/proposals/0302-concurrent-value-and-concurrent-closures.md -extension Site: @unchecked Sendable {} +extension Site: @unchecked @retroactive Sendable {} public extension Tab { enum ContentType: Sendable { diff --git a/catowseriOS/CottonData/WebViewModel/SiteNavigationProtocols.swift b/catowseriOS/CottonData/WebViewModel/SiteNavigationProtocols.swift index 7372d6f7..2975fa39 100644 --- a/catowseriOS/CottonData/WebViewModel/SiteNavigationProtocols.swift +++ b/catowseriOS/CottonData/WebViewModel/SiteNavigationProtocols.swift @@ -9,8 +9,10 @@ import Foundation import CoreBrowser +/// Interface for the site navigation for the external consumers, +/// can be sendable because it is a main actor. @MainActor -public protocol SiteExternalNavigationDelegate: AnyObject { +public protocol SiteExternalNavigationDelegate: AnyObject, Sendable { func provisionalNavigationDidStart() func didSiteOpen(appName: String) func loadingProgressdDidChange(_ progress: Float) diff --git a/catowseriOS/CottonData/WebViewModel/WebViewModel.swift b/catowseriOS/CottonData/WebViewModel/WebViewModel.swift index 089a6d7d..78a3b81c 100644 --- a/catowseriOS/CottonData/WebViewModel/WebViewModel.swift +++ b/catowseriOS/CottonData/WebViewModel/WebViewModel.swift @@ -27,8 +27,9 @@ public protocol NavigationActionable: AnyObject { var request: URLRequest { get } } +/// Web view model interface, can be sendable because it is an actor (main one) @MainActor -public protocol WebViewModel: ObservableObject { +public protocol WebViewModel: ObservableObject, Sendable { // MARK: - main public methods diff --git a/catowseriOS/catowser/BaseViewController.swift b/catowseriOS/catowser/BaseViewController.swift index 6ec566e6..1ecda333 100644 --- a/catowseriOS/catowser/BaseViewController.swift +++ b/catowseriOS/catowser/BaseViewController.swift @@ -14,7 +14,7 @@ extension UIViewController: UIIdiomable {} extension UIView: UIIdiomable {} -protocol UIIdiomable: AnyObject { +@MainActor protocol UIIdiomable: AnyObject { var isPad: Bool { get } } diff --git a/catowseriOS/catowser/BrowserContent/BrowserContentViewModel.swift b/catowseriOS/catowser/BrowserContent/BrowserContentViewModel.swift index 3b1a3906..44b70d2a 100644 --- a/catowseriOS/catowser/BrowserContent/BrowserContentViewModel.swift +++ b/catowseriOS/catowser/BrowserContent/BrowserContentViewModel.swift @@ -12,7 +12,7 @@ import CottonPlugins /// Content view model which observes for the currently selected tab content type. /// This reference type should be used to update the view if content changes. -final class BrowserContentViewModel: ObservableObject { +@MainActor final class BrowserContentViewModel: ObservableObject { /// View content type. https://stackoverflow.com/a/56724174 @Published var contentType: CoreBrowser.Tab.ContentType /// Tab's content loading diff --git a/catowseriOS/catowser/BrowserContent/TopSites/TopSitesViewController.swift b/catowseriOS/catowser/BrowserContent/TopSites/TopSitesViewController.swift index 1acfb1e7..cb46530c 100644 --- a/catowseriOS/catowser/BrowserContent/TopSites/TopSitesViewController.swift +++ b/catowseriOS/catowser/BrowserContent/TopSites/TopSitesViewController.swift @@ -9,6 +9,7 @@ import UIKit import CottonBase +@MainActor protocol TopSitesInterface: AnyObject { func reload(with sites: [Site]) } diff --git a/catowseriOS/catowser/BrowserContent/WebView/WebViewAuthChallengeHandler.swift b/catowseriOS/catowser/BrowserContent/WebView/WebViewAuthChallengeHandler.swift index 63fb1c57..a2d1b7f6 100644 --- a/catowseriOS/catowser/BrowserContent/WebView/WebViewAuthChallengeHandler.swift +++ b/catowseriOS/catowser/BrowserContent/WebView/WebViewAuthChallengeHandler.swift @@ -12,10 +12,9 @@ import Alamofire import Foundation import BrowserNetworking -private var logAuthChallenge = false - -final class WebViewAuthChallengeHandler { - typealias AuthHandler = (URLSession.AuthChallengeDisposition, URLCredential?) -> Void +/// Web view authentication challenge handler, should be on main actor because it uses web view +@MainActor final class WebViewAuthChallengeHandler: Sendable { + typealias AuthHandler = @Sendable (URLSession.AuthChallengeDisposition, URLCredential?) -> Void private let urlInfo: URLInfo private let challenge: URLAuthenticationChallenge @@ -23,6 +22,7 @@ final class WebViewAuthChallengeHandler { private let webView: WKWebView /// There is an Xcode warning about not calling that on main thread, so, using custom queue private let queue: DispatchQueue + private var logAuthChallenge = false init(_ urlInfo: URLInfo, _ webView: WKWebView, @@ -35,10 +35,12 @@ final class WebViewAuthChallengeHandler { queue = DispatchQueue(label: .queueNameWith(suffix: "webview.auth-challenge")) } - func solve(completion: @escaping (Bool?) -> Void) { + func solve(completion: @MainActor @Sendable @escaping (Bool?) -> Void) { guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust else { - queue.async { [weak self] in - self?.completionHandler(.performDefaultHandling, nil) + queue.async { + Task { [weak self] in + self?.completionHandler(.performDefaultHandling, nil) + } DispatchQueue.main.async { completion(nil) } @@ -95,7 +97,7 @@ private extension WebViewAuthChallengeHandler { func handleServerTrust(_ serverTrust: SecTrust, _ host: String, _ completionHandler: @escaping AuthHandler, - _ completion: @escaping (Bool?) -> Void) { + _ completion: @MainActor @escaping @Sendable (Bool?) -> Void) { do { let evaluator: DefaultTrustEvaluator = .ipHostEvaluator() @@ -143,8 +145,8 @@ private extension WebViewAuthChallengeHandler { } extension WebViewAuthChallengeHandler: Hashable { - static func == (lhs: WebViewAuthChallengeHandler, rhs: WebViewAuthChallengeHandler) -> Bool { - guard lhs.webView == rhs.webView else { + nonisolated static func == (lhs: WebViewAuthChallengeHandler, rhs: WebViewAuthChallengeHandler) -> Bool { + guard lhs.webView === rhs.webView else { return false } guard lhs.challenge == rhs.challenge else { @@ -156,7 +158,7 @@ extension WebViewAuthChallengeHandler: Hashable { return true } - public func hash(into hasher: inout Hasher) { + nonisolated public func hash(into hasher: inout Hasher) { hasher.combine(challenge) hasher.combine(urlInfo) } diff --git a/catowseriOS/catowser/BrowserContent/WebView/WebViewController.swift b/catowseriOS/catowser/BrowserContent/WebView/WebViewController.swift index 70edf122..31cdd3d2 100644 --- a/catowseriOS/catowser/BrowserContent/WebView/WebViewController.swift +++ b/catowseriOS/catowser/BrowserContent/WebView/WebViewController.swift @@ -169,7 +169,7 @@ final class WebViewController: BaseViewController, // MARK: - WKNavigationDelegate - func webView(_ webView: WKWebView, + private func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { if let domain = viewModel.nativeAppDomainNameString { @@ -223,19 +223,19 @@ final class WebViewController: BaseViewController, viewModel.siteNavigation?.showLoadingProgress(false) } - func webView(_ webView: WKWebView, - didReceive challenge: URLAuthenticationChallenge, - completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { + private func webView(_ webView: WKWebView, + didReceive challenge: URLAuthenticationChallenge, + completionHandler: @escaping @Sendable (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { let handler = WebViewAuthChallengeHandler(viewModel.urlInfo, webView, challenge, completionHandler) authHandlers.insert(handler) handler.solve { [weak self, weak handler] stopLoadingProgress in - guard let self = self else { + guard let self else { return } if stopLoadingProgress != nil { - self.viewModel.siteNavigation?.showLoadingProgress(false) + viewModel.siteNavigation?.showLoadingProgress(false) } - guard let handler = handler else { + guard let handler else { return } self.authHandlers.remove(handler) @@ -323,27 +323,36 @@ private extension WebViewController { loadingProgressObservation?.invalidate() loadingProgressObservation = webView?.observe(\.estimatedProgress, options: [.new]) { [weak self] (_, change) in - guard let self = self else { return } - guard let value = change.newValue else { return } - self.viewModel.siteNavigation?.loadingProgressdDidChange(Float(value)) + guard let self, let value = change.newValue else { + return + } + Task { + await viewModel.siteNavigation?.loadingProgressdDidChange(Float(value)) + } } } func addWebViewCanGoBackObserver() { canGoBackObservation?.invalidate() canGoBackObservation = webView?.observe(\.canGoBack, options: [.new]) { [weak self] (_, change) in - guard let self = self else { return } - guard let value = change.newValue else { return } - self.viewModel.siteNavigation?.didBackNavigationUpdate(to: value) + guard let self, let value = change.newValue else { + return + } + Task { + await viewModel.siteNavigation?.didBackNavigationUpdate(to: value) + } } } func addWebViewCanGoForwardObserver() { canGoForwardObservation?.invalidate() canGoForwardObservation = webView?.observe(\.canGoForward, options: [.new]) { [weak self] (_, change) in - guard let self = self else { return } - guard let value = change.newValue else { return } - self.viewModel.siteNavigation?.didForwardNavigationUpdate(to: value) + guard let self, let value = change.newValue else { + return + } + Task { + await viewModel.siteNavigation?.didForwardNavigationUpdate(to: value) + } } } @@ -397,7 +406,7 @@ private extension WebViewController { } } -extension WKNavigationType: CustomDebugStringConvertible { +extension WKNavigationType: @retroactive CustomDebugStringConvertible { public var debugDescription: String { switch self { case .linkActivated: @@ -418,4 +427,4 @@ extension WKNavigationType: CustomDebugStringConvertible { } } -extension WKNavigationAction: NavigationActionable {} +extension WKNavigationAction: @retroactive NavigationActionable {} diff --git a/catowseriOS/catowser/Coordinators/LinkTagsCoordinator.swift b/catowseriOS/catowser/Coordinators/LinkTagsCoordinator.swift index 151fe76e..1111bc63 100644 --- a/catowseriOS/catowser/Coordinators/LinkTagsCoordinator.swift +++ b/catowseriOS/catowser/Coordinators/LinkTagsCoordinator.swift @@ -10,7 +10,7 @@ import UIKit import CottonPlugins /// An interface only needed on Tablet layout, tablet's search bar implements it -protocol MediaLinksPresenter: AnyObject { +@MainActor protocol MediaLinksPresenter: AnyObject { func didReceiveMediaLinks() /// Returns source view and rectangle (could be a download arrow button) var downloadsPopoverStartInfo: (UIView, CGRect) { get } diff --git a/catowseriOS/catowser/Coordinators/SearchBarCoordinator.swift b/catowseriOS/catowser/Coordinators/SearchBarCoordinator.swift index e8ac9d2d..fd935f1e 100644 --- a/catowseriOS/catowser/Coordinators/SearchBarCoordinator.swift +++ b/catowseriOS/catowser/Coordinators/SearchBarCoordinator.swift @@ -312,8 +312,8 @@ extension SearchBarCoordinator: SearchSuggestionsListDelegate { } extension FeatureManager.StateHolder { - func searchPluginName() -> KnownSearchPluginName { - switch webSearchAutoCompleteValue() { + func searchPluginName() async -> KnownSearchPluginName { + switch await webSearchAutoCompleteValue() { case .google: return .google case .duckduckgo: diff --git a/catowseriOS/catowser/Downloads/DownloadPanelPresenter.swift b/catowseriOS/catowser/Downloads/DownloadPanelPresenter.swift index 10a17695..8cdd2693 100644 --- a/catowseriOS/catowser/Downloads/DownloadPanelPresenter.swift +++ b/catowseriOS/catowser/Downloads/DownloadPanelPresenter.swift @@ -9,7 +9,7 @@ import UIKit /// Used by tablet search bar on Tablet and by toolbar on Phone -protocol DownloadPanelPresenter: AnyObject { +@MainActor protocol DownloadPanelPresenter: AnyObject { func didPressDownloads(to hide: Bool) func didPressTabletLayoutDownloads(from sourceView: UIView, and sourceRect: CGRect) } diff --git a/catowseriOS/catowser/Downloads/FileDownloadViewModel.swift b/catowseriOS/catowser/Downloads/FileDownloadViewModel.swift index 94d9fe22..36d998e2 100644 --- a/catowseriOS/catowser/Downloads/FileDownloadViewModel.swift +++ b/catowseriOS/catowser/Downloads/FileDownloadViewModel.swift @@ -11,7 +11,7 @@ import Foundation // needed for `Downloadable` import BrowserNetworking -protocol FileDownloadDelegate: AnyObject { +@MainActor protocol FileDownloadDelegate: AnyObject { func didPressOpenFile(withLocal url: URL) } diff --git a/catowseriOS/catowser/Downloads/LinkTagsViewController.swift b/catowseriOS/catowser/Downloads/LinkTagsViewController.swift index 3a700569..a4d77745 100644 --- a/catowseriOS/catowser/Downloads/LinkTagsViewController.swift +++ b/catowseriOS/catowser/Downloads/LinkTagsViewController.swift @@ -36,6 +36,7 @@ protocol LinkTagsPresenter: AnyObject { func clearLinks() } +@MainActor protocol LinkTagsDelegate: AnyObject { func didSelect(type: LinksType, from sourceView: UIView) } diff --git a/catowseriOS/catowser/Extensions/ReloadWithCompletion.swift b/catowseriOS/catowser/Extensions/ReloadWithCompletion.swift index ac85bb97..982cc218 100644 --- a/catowseriOS/catowser/Extensions/ReloadWithCompletion.swift +++ b/catowseriOS/catowser/Extensions/ReloadWithCompletion.swift @@ -8,7 +8,7 @@ import UIKit -protocol ReloadableCollection { +@MainActor protocol ReloadableCollection { func reloadData() func reloadData(_ closure: @escaping (() -> Void)) } diff --git a/catowseriOS/catowser/Search/SearchBarBaseViewController.swift b/catowseriOS/catowser/Search/SearchBarBaseViewController.swift index dea5e66d..b64f2d9a 100644 --- a/catowseriOS/catowser/Search/SearchBarBaseViewController.swift +++ b/catowseriOS/catowser/Search/SearchBarBaseViewController.swift @@ -10,7 +10,7 @@ import UIKit import CoreBrowser import FeaturesFlagsKit -protocol SearchBarControllerInterface: AnyObject { +@MainActor protocol SearchBarControllerInterface: AnyObject { /* non optional */ func handleAction(_ action: SearchBarAction) } From bbcf0642726535631aaaac63bce29e00fef17d09 Mon Sep 17 00:00:00 2001 From: Andrei Ermoshin Date: Sun, 11 Aug 2024 20:01:05 +0300 Subject: [PATCH 19/32] Fix swift 6 errors - part 9 --- catowseriOS/CottonData/WebViewModel/WebViewModel.swift | 2 +- .../catowser/BrowserContent/TopSites/TopSitesView.swift | 5 ++--- .../catowser/BrowserContent/TopSites/TopSitesViewV2.swift | 6 ++---- catowseriOS/catowser/Extensions/String+Extension.swift | 2 +- .../catowser/Search/SearchBarBaseViewController.swift | 2 +- catowseriOS/catowser/Toolbar/BrowserToolbarView.swift | 2 +- catowseriOS/catowser/Toolbar/ToolbarViewV2.swift | 5 +++-- catowseriOS/catowser/WebTabs/TabView.swift | 4 ++-- .../catowser/WebTabs/TabsPreviewsViewController.swift | 2 +- 9 files changed, 14 insertions(+), 16 deletions(-) diff --git a/catowseriOS/CottonData/WebViewModel/WebViewModel.swift b/catowseriOS/CottonData/WebViewModel/WebViewModel.swift index 78a3b81c..26a33709 100644 --- a/catowseriOS/CottonData/WebViewModel/WebViewModel.swift +++ b/catowseriOS/CottonData/WebViewModel/WebViewModel.swift @@ -22,7 +22,7 @@ public enum WebPageLoadingAction: Equatable { } /// Interface for system's type `WKNavigationAction` from WebKit framework to be able to mock it -public protocol NavigationActionable: AnyObject { +@MainActor public protocol NavigationActionable: AnyObject { var navigationType: WKNavigationType { get } var request: URLRequest { get } } diff --git a/catowseriOS/catowser/BrowserContent/TopSites/TopSitesView.swift b/catowseriOS/catowser/BrowserContent/TopSites/TopSitesView.swift index 1fd217e9..270dd9d6 100644 --- a/catowseriOS/catowser/BrowserContent/TopSites/TopSitesView.swift +++ b/catowseriOS/catowser/BrowserContent/TopSites/TopSitesView.swift @@ -9,12 +9,11 @@ import SwiftUI struct TopSitesView: View { - let vm: TopSitesViewModel + @EnvironmentObject private var vm: TopSitesViewModel /// Selected swiftUI mode which is set at app start private let mode: SwiftUIMode - init(_ vm: TopSitesViewModel, _ mode: SwiftUIMode) { - self.vm = vm + init(_ mode: SwiftUIMode) { self.mode = mode } diff --git a/catowseriOS/catowser/BrowserContent/TopSites/TopSitesViewV2.swift b/catowseriOS/catowser/BrowserContent/TopSites/TopSitesViewV2.swift index 787a5e40..1df94ed1 100644 --- a/catowseriOS/catowser/BrowserContent/TopSites/TopSitesViewV2.swift +++ b/catowseriOS/catowser/BrowserContent/TopSites/TopSitesViewV2.swift @@ -12,12 +12,10 @@ import CoreBrowser @available(iOS 14.0, *) struct TopSitesViewV2: View { - private let vm: TopSitesViewModel + @EnvironmentObject private var vm: TopSitesViewModel @State private var selected: Site? - init(_ vm: TopSitesViewModel) { - self.vm = vm - } + init() { } /// Number of items which will be displayed in a row private let columns: [GridItem] = [ diff --git a/catowseriOS/catowser/Extensions/String+Extension.swift b/catowseriOS/catowser/Extensions/String+Extension.swift index e2a37161..4090f7aa 100644 --- a/catowseriOS/catowser/Extensions/String+Extension.swift +++ b/catowseriOS/catowser/Extensions/String+Extension.swift @@ -12,7 +12,7 @@ import Foundation // blob/592b76cce844f7474499831e6bd2c76ef485fed1/Queries/ContentView.swift /// Allows to use String in SwiftUI List view -extension String: Identifiable { +extension String: @retroactive Identifiable { public typealias ID = Int public var id: Int { return hash diff --git a/catowseriOS/catowser/Search/SearchBarBaseViewController.swift b/catowseriOS/catowser/Search/SearchBarBaseViewController.swift index b64f2d9a..49383f4b 100644 --- a/catowseriOS/catowser/Search/SearchBarBaseViewController.swift +++ b/catowseriOS/catowser/Search/SearchBarBaseViewController.swift @@ -42,7 +42,7 @@ final class SearchBarBaseViewController: BaseViewController { super.viewWillAppear(animated) Task { - await TabsDataService.shared.attach(self) + await TabsDataService.shared.attach(self, notify: false) } } diff --git a/catowseriOS/catowser/Toolbar/BrowserToolbarView.swift b/catowseriOS/catowser/Toolbar/BrowserToolbarView.swift index 3de46a91..0fdaa3ec 100644 --- a/catowseriOS/catowser/Toolbar/BrowserToolbarView.swift +++ b/catowseriOS/catowser/Toolbar/BrowserToolbarView.swift @@ -145,7 +145,7 @@ final class BrowserToolbarView: UIToolbar { /// Instead of `didMoveToSuperview` func attachToTabsListManager() async { - await TabsDataService.shared.attach(self) + await TabsDataService.shared.attach(self, notify: false) await TabsDataService.shared.attach(counterView, notify: true) } diff --git a/catowseriOS/catowser/Toolbar/ToolbarViewV2.swift b/catowseriOS/catowser/Toolbar/ToolbarViewV2.swift index e2d425e3..5f6a1479 100644 --- a/catowseriOS/catowser/Toolbar/ToolbarViewV2.swift +++ b/catowseriOS/catowser/Toolbar/ToolbarViewV2.swift @@ -9,7 +9,8 @@ import SwiftUI import CoreBrowser -struct ToolbarViewV2: ToolbarContent { +@MainActor @preconcurrency +struct ToolbarViewV2: @preconcurrency ToolbarContent { @ObservedObject var vm: BrowserToolbarViewModel private var tabsCount: Int @Binding private var showingMenu: Bool @@ -35,7 +36,7 @@ struct ToolbarViewV2: ToolbarContent { isRefreshDisabled = false } - var body: some ToolbarContent { + @MainActor @preconcurrency var body: some ToolbarContent { ToolbarItem(placement: .bottomBar) { DisableableButton("nav-back", vm.goBackDisabled, vm.goBack) } diff --git a/catowseriOS/catowser/WebTabs/TabView.swift b/catowseriOS/catowser/WebTabs/TabView.swift index 50843ed8..885aff7a 100644 --- a/catowseriOS/catowser/WebTabs/TabView.swift +++ b/catowseriOS/catowser/WebTabs/TabView.swift @@ -17,7 +17,7 @@ import Combine #endif import BrowserNetworking -protocol TabDelegate: AnyObject { +@MainActor protocol TabDelegate: AnyObject { func tabViewDidClose(_ tabView: TabView) async } @@ -88,7 +88,7 @@ final class TabView: UIView { super.willMove(toSuperview: newSuperview) Task { - await TabsDataService.shared.attach(viewModel) + await TabsDataService.shared.attach(viewModel, notify: false) } stateHandler?.cancel() stateHandler = viewModel.$state.sink(receiveValue: onStateChange) diff --git a/catowseriOS/catowser/WebTabs/TabsPreviewsViewController.swift b/catowseriOS/catowser/WebTabs/TabsPreviewsViewController.swift index d9cf3cb4..aa21e838 100644 --- a/catowseriOS/catowser/WebTabs/TabsPreviewsViewController.swift +++ b/catowseriOS/catowser/WebTabs/TabsPreviewsViewController.swift @@ -108,7 +108,7 @@ where C.R == TabsScreenRoute { super.viewWillAppear(animated) Task { - await TabsDataService.shared.attach(self) + await TabsDataService.shared.attach(self, notify: false) viewModel.load() } } From 5a39de4f6875f6189645401301e8499787bafaa1 Mon Sep 17 00:00:00 2001 From: Andrei Ermoshin Date: Sun, 11 Aug 2024 20:25:31 +0300 Subject: [PATCH 20/32] Add comments about @retroactive --- .../BrowserNetworking/SearchEngines/SearchEngine.swift | 5 +++-- .../TransportAdapters/AlamofireHTTPRxAdaptee.swift | 7 +++++-- catowseriOS/CoreBrowser/Models/Tab.swift | 2 +- .../CottonData/WebViewModel/WebViewModelState.swift | 5 +++++ catowseriOS/CottonPlugins/JSPluginsProgramImpl.swift | 2 ++ catowseriOS/CottonRestKit/RestClient.swift | 2 ++ .../BrowserContent/WebView/WebViewController.swift | 9 +++++++-- catowseriOS/catowser/Extensions/String+Extension.swift | 5 +++-- catowseriOS/catowser/Search/Phone/SearchFieldView.swift | 5 +++-- 9 files changed, 31 insertions(+), 11 deletions(-) diff --git a/catowseriOS/BrowserNetworking/SearchEngines/SearchEngine.swift b/catowseriOS/BrowserNetworking/SearchEngines/SearchEngine.swift index 773f454a..3ae39477 100644 --- a/catowseriOS/BrowserNetworking/SearchEngines/SearchEngine.swift +++ b/catowseriOS/BrowserNetworking/SearchEngines/SearchEngine.swift @@ -7,9 +7,10 @@ // import Foundation -@preconcurrency import CottonBase +import CottonBase -extension HTTPMethod: @unchecked @retroactive Sendable { } +/// Alamofire's http method type can't be retroactivly silenced because sendable is a system protocol. +extension HTTPMethod: @unchecked Sendable { } /// The model for OpenSearch format URL public struct SearchEngine: Sendable { diff --git a/catowseriOS/BrowserNetworking/TransportAdapters/AlamofireHTTPRxAdaptee.swift b/catowseriOS/BrowserNetworking/TransportAdapters/AlamofireHTTPRxAdaptee.swift index 262dd5c5..83639ee8 100644 --- a/catowseriOS/BrowserNetworking/TransportAdapters/AlamofireHTTPRxAdaptee.swift +++ b/catowseriOS/BrowserNetworking/TransportAdapters/AlamofireHTTPRxAdaptee.swift @@ -105,9 +105,12 @@ extension URLRequest /* : URLRequestCreatable */ { } } -/// Wrapper around Alamofire method -extension JSONEncoding: @unchecked @retroactive Sendable {} +/// Wrapper around Alamofire method. +/// Can't be retroactive because it is from 3rd party Alamofire lib. +extension JSONEncoding: @unchecked Sendable {} +/// Can be retroactivly Auto mockable because it is our own protocol which can't be known by Alamofire devs. extension JSONEncoding: @retroactive AutoMockable {} +/// Can be retroactivly conforming to JSONRequestEncodable because Alamofire devs won't know that protocol for sure. extension JSONEncoding: @retroactive JSONRequestEncodable { public func encodeRequest(_ urlRequest: URLRequestCreatable, with parameters: [String: Any]?) throws -> URLRequest { return try encode(urlRequest.convertToURLRequest(), with: parameters) diff --git a/catowseriOS/CoreBrowser/Models/Tab.swift b/catowseriOS/CoreBrowser/Models/Tab.swift index 385d7f3b..5ff86e8f 100644 --- a/catowseriOS/CoreBrowser/Models/Tab.swift +++ b/catowseriOS/CoreBrowser/Models/Tab.swift @@ -9,7 +9,7 @@ import CottonBase /// Site is an obj-c type. -/// https://github.com/apple/swift-evolution/blob/main/proposals/0302-concurrent-value-and-concurrent-closures.md +/// can mark with retroactive because Site type is from my CottonBase library extension Site: @unchecked @retroactive Sendable {} public extension Tab { diff --git a/catowseriOS/CottonData/WebViewModel/WebViewModelState.swift b/catowseriOS/CottonData/WebViewModel/WebViewModelState.swift index 49221ecc..40df6f0a 100644 --- a/catowseriOS/CottonData/WebViewModel/WebViewModelState.swift +++ b/catowseriOS/CottonData/WebViewModel/WebViewModelState.swift @@ -11,7 +11,12 @@ import CottonBase import CottonPlugins import CoreBrowser +/// Settings of the site can be sendable cause it is a model. +/// Can mark it as retroactive because it is from my CottonBase library. extension CottonBase.Site.Settings: @unchecked @retroactive Sendable {} + +/// URL info type can be sendable cause it is a model. +/// Can mark it as retroactive because it is from my CottonBase library. extension CottonBase.URLInfo: @unchecked @retroactive Sendable {} enum WebViewModelState: Sendable { diff --git a/catowseriOS/CottonPlugins/JSPluginsProgramImpl.swift b/catowseriOS/CottonPlugins/JSPluginsProgramImpl.swift index 6800cca0..255ce94b 100644 --- a/catowseriOS/CottonPlugins/JSPluginsProgramImpl.swift +++ b/catowseriOS/CottonPlugins/JSPluginsProgramImpl.swift @@ -10,6 +10,8 @@ import Foundation import WebKit import CottonBase +/// Host type is a model and can be sendable. +/// Can mark it as retroactive because it is from my CottonBase library. extension CottonBase.Host: @unchecked @retroactive Sendable {} /** diff --git a/catowseriOS/CottonRestKit/RestClient.swift b/catowseriOS/CottonRestKit/RestClient.swift index 3b2c793a..be109b24 100644 --- a/catowseriOS/CottonRestKit/RestClient.swift +++ b/catowseriOS/CottonRestKit/RestClient.swift @@ -19,6 +19,8 @@ fileprivate extension String { public typealias HttpTypedResult = Result public typealias TypedResponseClosure = (HttpTypedResult) -> Void +/// An interface of a server description can be sendable because it is a model. +/// Can mark it as retroactive because it is from my CottonBase library. extension CottonBase.ServerDescription: @unchecked @retroactive Sendable {} public final class RestClient Date: Mon, 12 Aug 2024 22:14:48 +0300 Subject: [PATCH 21/32] Fix ToolbarContent compilation on Swift 6.0 or Xcode 16 beta 4 --- catowseriOS/catowser/AppDelegate.swift | 2 +- .../catowser/Browser/View/PhoneView.swift | 83 +++++++++++++++++-- .../BrowserContent/BrowserContentView.swift | 2 +- .../TopSites/TopSitesLegacyView.swift | 6 +- .../TopSites/TopSitesView.swift | 4 +- .../catowser/Toolbar/DisableableButton.swift | 12 +-- .../catowser/Toolbar/ToolbarViewV2.swift | 9 +- 7 files changed, 93 insertions(+), 25 deletions(-) diff --git a/catowseriOS/catowser/AppDelegate.swift b/catowseriOS/catowser/AppDelegate.swift index c6685c4c..ed6c151a 100644 --- a/catowseriOS/catowser/AppDelegate.swift +++ b/catowseriOS/catowser/AppDelegate.swift @@ -12,7 +12,7 @@ import Alamofire import FeaturesFlagsKit import CoreBrowser -@UIApplicationMain +@main class AppDelegate: UIResponder, UIApplicationDelegate { /// Should be stored by strong reference, because it is the only owner of App coordinator diff --git a/catowseriOS/catowser/Browser/View/PhoneView.swift b/catowseriOS/catowser/Browser/View/PhoneView.swift index 8919c2a1..99c2009c 100644 --- a/catowseriOS/catowser/Browser/View/PhoneView.swift +++ b/catowseriOS/catowser/Browser/View/PhoneView.swift @@ -176,18 +176,30 @@ struct PhoneView: View { } else { let jsPlugins = browserContentVM.jsPluginsBuilder let siteNavigation: SiteExternalNavigationDelegate = toolbarVM - BrowserContentView(jsPlugins, - siteNavigation, - isLoading, - contentType, - $webViewNeedsUpdate, - mode, - webVM) + BrowserContentView( + jsPlugins, + siteNavigation, + isLoading, + contentType, + $webViewNeedsUpdate, + mode, + webVM + ) } } +#if swift(<6.0) .toolbar { - ToolbarViewV2(toolbarVM, tabsCount, $showingMenu, $showingTabs, $showSearchSuggestions) + ToolbarViewV2( + toolbarVM, + tabsCount, + $showingMenu, + $showingTabs, + $showSearchSuggestions + ) } +#else + .toolbar(content: toolBarContent) +#endif } .sheet(isPresented: $showingMenu) { BrowserMenuView(menuModel) @@ -230,4 +242,59 @@ struct PhoneView: View { webVM.siteNavigation = toolbarVM } } + +#if swift(>=6.0) + /// A workaround for swift 6.0 Xcode 16 beta 4 to compile the project + /// and use instead of `ToolbarViewV2` type + @ToolbarContentBuilder + func toolBarContent() -> some ToolbarContent { + ToolbarItem(placement: .bottomBar) { + DisableableButton( + "nav-back", + toolbarVM.goBackDisabled, + toolbarVM.goBack + ) + } + ToolbarItem(placement: .bottomBar) { + Spacer() + } + ToolbarItem(placement: .bottomBar) { + DisableableButton( + "nav-forward", + toolbarVM.goForwardDisabled, + toolbarVM.goForward + ) + } + ToolbarItem(placement: .bottomBar) { + Spacer() + } + ToolbarItem(placement: .bottomBar) { + DisableableButton( + "nav-refresh", + toolbarVM.reloadDisabled, + toolbarVM.reload + ) + } + ToolbarItem(placement: .bottomBar) { + Spacer() + } + ToolbarItem(placement: .bottomBar) { + Button { + showSearchSuggestions = false + withAnimation(.easeInOut(duration: 1)) { + showingTabs.toggle() + } + } label: { + Text(verbatim: "\(tabsCount)") + } + .foregroundColor(.black) + } + ToolbarItem(placement: .bottomBar) { + Spacer() + } + ToolbarItem(placement: .bottomBar) { + MenuButton($showSearchSuggestions, $showingMenu) + } + } +#endif } diff --git a/catowseriOS/catowser/BrowserContent/BrowserContentView.swift b/catowseriOS/catowser/BrowserContent/BrowserContentView.swift index 277c405e..8414ad12 100644 --- a/catowseriOS/catowser/BrowserContent/BrowserContentView.swift +++ b/catowseriOS/catowser/BrowserContent/BrowserContentView.swift @@ -63,7 +63,7 @@ struct BrowserContentView: View { case .blank: Spacer() case .topSites: - TopSitesView(topSitesVM, mode) + TopSitesView(mode) case .site(let site): WebView(webVM, site, webViewNeedsUpdate, mode) default: diff --git a/catowseriOS/catowser/BrowserContent/TopSites/TopSitesLegacyView.swift b/catowseriOS/catowser/BrowserContent/TopSites/TopSitesLegacyView.swift index dc709e49..c1927f2f 100644 --- a/catowseriOS/catowser/BrowserContent/TopSites/TopSitesLegacyView.swift +++ b/catowseriOS/catowser/BrowserContent/TopSites/TopSitesLegacyView.swift @@ -10,12 +10,10 @@ import SwiftUI import UIKit struct TopSitesLegacyView: CatowserUIVCRepresentable { - private let vm: TopSitesViewModel + @EnvironmentObject private var vm: TopSitesViewModel typealias UIViewControllerType = UIViewController - init(_ vm: TopSitesViewModel) { - self.vm = vm - } + init() { } func makeUIViewController(context: Context) -> UIViewControllerType { let interface = context.environment.browserContentCoordinators diff --git a/catowseriOS/catowser/BrowserContent/TopSites/TopSitesView.swift b/catowseriOS/catowser/BrowserContent/TopSites/TopSitesView.swift index 270dd9d6..9870f0ee 100644 --- a/catowseriOS/catowser/BrowserContent/TopSites/TopSitesView.swift +++ b/catowseriOS/catowser/BrowserContent/TopSites/TopSitesView.swift @@ -20,9 +20,9 @@ struct TopSitesView: View { var body: some View { switch mode { case .compatible: - TopSitesLegacyView(vm) + TopSitesLegacyView() case .full: - TopSitesViewV2(vm) + TopSitesViewV2() } } } diff --git a/catowseriOS/catowser/Toolbar/DisableableButton.swift b/catowseriOS/catowser/Toolbar/DisableableButton.swift index 0e5f4697..8dd23013 100644 --- a/catowseriOS/catowser/Toolbar/DisableableButton.swift +++ b/catowseriOS/catowser/Toolbar/DisableableButton.swift @@ -12,18 +12,20 @@ import SwiftUI struct DisableableButton: View { private let disabled: Bool private let imageName: String - private let onTap: () -> Void + private let onTap: @MainActor () -> Void - init(_ imageName: String, _ disabled: Bool, _ onTap: @MainActor @escaping () -> Void) { + init( + _ imageName: String, + _ disabled: Bool, + _ onTap: @escaping @MainActor () -> Void + ) { self.imageName = imageName self.disabled = disabled self.onTap = onTap } var body: some View { - Button { - onTap() - } label: { + Button(action: onTap) { Image(imageName) } .disabled(disabled) diff --git a/catowseriOS/catowser/Toolbar/ToolbarViewV2.swift b/catowseriOS/catowser/Toolbar/ToolbarViewV2.swift index 5f6a1479..711e6526 100644 --- a/catowseriOS/catowser/Toolbar/ToolbarViewV2.swift +++ b/catowseriOS/catowser/Toolbar/ToolbarViewV2.swift @@ -9,8 +9,8 @@ import SwiftUI import CoreBrowser -@MainActor @preconcurrency -struct ToolbarViewV2: @preconcurrency ToolbarContent { +#if swift(<6.0) +struct ToolbarViewV2: ToolbarContent { @ObservedObject var vm: BrowserToolbarViewModel private var tabsCount: Int @Binding private var showingMenu: Bool @@ -35,8 +35,8 @@ struct ToolbarViewV2: @preconcurrency ToolbarContent { isGoForwardDisabled = false isRefreshDisabled = false } - - @MainActor @preconcurrency var body: some ToolbarContent { + + var body: some ToolbarContent { ToolbarItem(placement: .bottomBar) { DisableableButton("nav-back", vm.goBackDisabled, vm.goBack) } @@ -74,3 +74,4 @@ struct ToolbarViewV2: @preconcurrency ToolbarContent { } } } +#endif From 439421a9b8d3bd26d8a31fd76ffc5b91c489467b Mon Sep 17 00:00:00 2001 From: Andrei Ermoshin Date: Sat, 17 Aug 2024 11:49:53 +0300 Subject: [PATCH 22/32] Remove TabContentDefaultState and replace it with existing Tab.ContentType --- .../CoreBrowser/History/DomainsHistory.swift | 4 +- .../InMemoryDomainSearchProvider.swift | 3 +- catowseriOS/CoreBrowser/Models/Tab.swift | 56 +++++++++++++++++-- .../CoreBrowser/Tabs/TabContentState.swift | 47 ---------------- .../catowser.xcodeproj/project.pbxproj | 4 -- catowseriOS/catowser/DB/TabsDBClient.swift | 4 +- .../catowser/Menu/BrowserMenuView.swift | 2 +- catowseriOS/catowser/Menu/MenuViewModel.swift | 4 +- .../TabDefaultContentViewPreview.swift | 2 +- .../Menu/TabDefaultContentModel.swift | 12 +--- .../catowser/Utils/DefaultTabProvider.swift | 2 +- .../BaseModels/SettingsEnumTypes.swift | 12 ++-- .../FeatureManager+SpecificEnums.swift | 4 +- .../SpecificModels/SpecificEnumFeatures.swift | 2 +- 14 files changed, 74 insertions(+), 84 deletions(-) delete mode 100644 catowseriOS/CoreBrowser/Tabs/TabContentState.swift diff --git a/catowseriOS/CoreBrowser/History/DomainsHistory.swift b/catowseriOS/CoreBrowser/History/DomainsHistory.swift index 6396b733..4ce98a0a 100644 --- a/catowseriOS/CoreBrowser/History/DomainsHistory.swift +++ b/catowseriOS/CoreBrowser/History/DomainsHistory.swift @@ -12,13 +12,13 @@ import AutoMockable /// Interface for known domain checks. Has to have async methods because actual class is a global actor. /// /// Can be sendable because implementation is a global actor. -public protocol KnownDomainsSource: AutoMockable, Sendable { +public protocol KnownDomainsSource: AutoMockable, Actor { func domainNames(whereURLContains filter: String) async -> [String] } /// Interface for domain checks. Has to have async methods because actual class is a global actor. /// /// Can be sendable because implementation is a global actor. -public protocol DomainsHistory: Sendable { +public protocol DomainsHistory: Actor { func remember(host: CottonBase.Host) async } diff --git a/catowseriOS/CoreBrowser/History/InMemoryDomainSearchProvider.swift b/catowseriOS/CoreBrowser/History/InMemoryDomainSearchProvider.swift index 61a4496d..4ce91f1e 100644 --- a/catowseriOS/CoreBrowser/History/InMemoryDomainSearchProvider.swift +++ b/catowseriOS/CoreBrowser/History/InMemoryDomainSearchProvider.swift @@ -39,7 +39,8 @@ public final class InMemoryDomainSearchProvider { } } -extension CottonBase.Host: @unchecked Sendable {} +/// Can be retroactive because it is own library in Kotlin +extension CottonBase.Host: @unchecked @retroactive Sendable {} extension InMemoryDomainSearchProvider.StateHolder: DomainsHistory { public func remember(host: CottonBase.Host) async { diff --git a/catowseriOS/CoreBrowser/Models/Tab.swift b/catowseriOS/CoreBrowser/Models/Tab.swift index 5ff86e8f..c11cc3ac 100644 --- a/catowseriOS/CoreBrowser/Models/Tab.swift +++ b/catowseriOS/CoreBrowser/Models/Tab.swift @@ -8,12 +8,29 @@ import CottonBase -/// Site is an obj-c type. -/// can mark with retroactive because Site type is from my CottonBase library +/// Site is an obj-c model type. +///Ccan mark with retroactive because Site type is from own CottonBase library extension Site: @unchecked @retroactive Sendable {} public extension Tab { - enum ContentType: Sendable { + enum ContentType: Sendable, RawRepresentable { + public init?(rawValue: Int) { + switch rawValue { + case 0: + self = .blank + case 1: + return nil + case 2: + self = .homepage + case 3: + self = .favorites + case 4: + self = .topSites + default: + return nil + } + } + case blank case site(Site) case homepage @@ -21,7 +38,7 @@ public extension Tab { case topSites /// Needed for database representation - public var rawValue: Int16 { + public var rawValue: Int { switch self { case .blank: return 0 @@ -235,3 +252,34 @@ fileprivate extension String { static let topSitesTitle = NSLocalizedString("ttl_tab_short_top_sites", comment: "Title for tab with list of favorite sites") } + +extension Tab.ContentType: Identifiable { + public var id: RawValue { + return self.rawValue + } + + public typealias ID = RawValue +} + +extension Tab.ContentType: CustomStringConvertible { + public var description: String { + let key: String + + switch self { + case .blank: + key = "txt_tab_content_blank" + case .homepage: + key = "txt_tab_content_homepage" + case .favorites: + key = "txt_tab_content_favorites" + case .topSites: + key = "txt_tab_content_top_sites" + case .site: + // site can't be used as a default content for now + key = "" + } + return NSLocalizedString(key, comment: "") + } +} + +extension Tab.ContentType: Hashable { } diff --git a/catowseriOS/CoreBrowser/Tabs/TabContentState.swift b/catowseriOS/CoreBrowser/Tabs/TabContentState.swift deleted file mode 100644 index 2856e57d..00000000 --- a/catowseriOS/CoreBrowser/Tabs/TabContentState.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// TabContentState.swift -// CoreBrowser -// -// Created by Andrei Ermoshin on 5/30/22. -// Copyright © 2022 Cotton/Catowser Andrei Ermoshin. All rights reserved. -// - -import Foundation - -/// Twin type for `CoreBrowser.Tab.ContentType` to have `rawValue` -/// and use it for settings. -public enum TabContentDefaultState: Int, CaseIterable, CustomStringConvertible, Sendable { - case blank - case homepage - case favorites - case topSites - - public var contentType: CoreBrowser.Tab.ContentType { - switch self { - case .blank: - return .blank - case .homepage: - return .homepage - case .favorites: - return .favorites - case .topSites: - return .topSites - } - } - - public var description: String { - let key: String - - switch self { - case .blank: - key = "txt_tab_content_blank" - case .homepage: - key = "txt_tab_content_homepage" - case .favorites: - key = "txt_tab_content_favorites" - case .topSites: - key = "txt_tab_content_top_sites" - } - return NSLocalizedString(key, comment: "") - } -} diff --git a/catowseriOS/catowser.xcodeproj/project.pbxproj b/catowseriOS/catowser.xcodeproj/project.pbxproj index b7460b55..c0e18a91 100644 --- a/catowseriOS/catowser.xcodeproj/project.pbxproj +++ b/catowseriOS/catowser.xcodeproj/project.pbxproj @@ -291,7 +291,6 @@ 8A98636B2822612500F679D7 /* EndpointMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A98636A2822612500F679D7 /* EndpointMocks.swift */; }; 8A99805E28453DE900E18762 /* NearbySelectionStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A99805D28453DE900E18762 /* NearbySelectionStrategy.swift */; }; 8A99806028453FFD00E18762 /* TabAddStates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A99805F28453FFD00E18762 /* TabAddStates.swift */; }; - 8A9980622845403600E18762 /* TabContentState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A9980612845403600E18762 /* TabContentState.swift */; }; 8A9980642845409100E18762 /* TabsSubject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A9980632845409100E18762 /* TabsSubject.swift */; }; 8A9980662845416500E18762 /* TabsStates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A9980652845416500E18762 /* TabsStates.swift */; }; 8A9980692845464000E18762 /* JavaScriptPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A9980682845464000E18762 /* JavaScriptPlugin.swift */; }; @@ -932,7 +931,6 @@ 8A98636A2822612500F679D7 /* EndpointMocks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EndpointMocks.swift; sourceTree = ""; }; 8A99805D28453DE900E18762 /* NearbySelectionStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearbySelectionStrategy.swift; sourceTree = ""; }; 8A99805F28453FFD00E18762 /* TabAddStates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabAddStates.swift; sourceTree = ""; }; - 8A9980612845403600E18762 /* TabContentState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabContentState.swift; sourceTree = ""; }; 8A9980632845409100E18762 /* TabsSubject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabsSubject.swift; sourceTree = ""; }; 8A9980652845416500E18762 /* TabsStates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabsStates.swift; sourceTree = ""; }; 8A9980682845464000E18762 /* JavaScriptPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JavaScriptPlugin.swift; sourceTree = ""; }; @@ -1290,7 +1288,6 @@ 608DA708222E6F5E009F75BD /* TabSelectionStrategy.swift */, 8A99805D28453DE900E18762 /* NearbySelectionStrategy.swift */, 8A99805F28453FFD00E18762 /* TabAddStates.swift */, - 8A9980612845403600E18762 /* TabContentState.swift */, 8A9980652845416500E18762 /* TabsStates.swift */, ); path = Tabs; @@ -3064,7 +3061,6 @@ 8A99806028453FFD00E18762 /* TabAddStates.swift in Sources */, 8A745C0F2480287700A379AD /* TabsRepository.swift in Sources */, 8AD18E102447580A00224702 /* ResourceReader.swift in Sources */, - 8A9980622845403600E18762 /* TabContentState.swift in Sources */, 6094E741221FE706004269A2 /* Tab.swift in Sources */, 6094E743221FE72C004269A2 /* TabsDataService.swift in Sources */, 8A90636924332376009D786E /* URL+CoreExtensions.swift in Sources */, diff --git a/catowseriOS/catowser/DB/TabsDBClient.swift b/catowseriOS/catowser/DB/TabsDBClient.swift index 770ac5e6..ab3bba00 100644 --- a/catowseriOS/catowser/DB/TabsDBClient.swift +++ b/catowseriOS/catowser/DB/TabsDBClient.swift @@ -53,7 +53,7 @@ final class TabsDBClient: @unchecked Sendable { do { let result = try managedContext.fetch(fetchRequest) if !result.isEmpty, let cdTab = result.first { - cdTab.contentType = tab.contentType.rawValue + cdTab.contentType = Int16(tab.contentType.rawValue) if let oldCdSite = cdTab.site { managedContext.delete(oldCdSite) } @@ -224,7 +224,7 @@ fileprivate extension CDTab { convenience init(context: NSManagedObjectContext, tab: CoreBrowser.Tab) { self.init(context: context) id = tab.id - contentType = tab.contentType.rawValue + contentType = Int16(tab.contentType.rawValue) addedTimestamp = tab.addedTimestamp if case .site(let siteContent) = tab.contentType { site = CDSite(context: context, site: siteContent) diff --git a/catowseriOS/catowser/Menu/BrowserMenuView.swift b/catowseriOS/catowser/Menu/BrowserMenuView.swift index 2bfdfae2..b7298a2c 100644 --- a/catowseriOS/catowser/Menu/BrowserMenuView.swift +++ b/catowseriOS/catowser/Menu/BrowserMenuView.swift @@ -80,7 +80,7 @@ struct BrowserMenuView: View { path = [] }) case .defaultTabContent: - BaseMenuView(viewModel: .init(model.tabContentRowValue) { selected in + BaseMenuView(viewModel: .init(model.tabContentRowValue) { selected in model.setTabContent(selected) path = [] }) diff --git a/catowseriOS/catowser/Menu/MenuViewModel.swift b/catowseriOS/catowser/Menu/MenuViewModel.swift index a98bca7b..7dd64f11 100644 --- a/catowseriOS/catowser/Menu/MenuViewModel.swift +++ b/catowseriOS/catowser/Menu/MenuViewModel.swift @@ -35,7 +35,7 @@ final class MenuViewModel: ObservableObject { // MARK: - Allow to update text view content dynamically - @Published var tabContentRowValue: TabContentDefaultState = .favorites + @Published var tabContentRowValue: CoreBrowser.Tab.ContentType = .favorites @Published var webAutocompleteRowValue: WebAutoCompletionSource = .google @Published var tabAddPositionRowValue: AddedTabPosition = .afterSelected @Published var asyncApiRowValue: AsyncApiType = .asyncAwait @@ -155,7 +155,7 @@ final class MenuViewModel: ObservableObject { tabAddPositionRowValue = selected } - func setTabContent(_ selected: TabContentDefaultState) { + func setTabContent(_ selected: CoreBrowser.Tab.ContentType) { Task { await FeatureManager.shared.setFeature(.tabDefaultContent, value: selected) } diff --git a/catowseriOS/catowser/Menu/Previews/TabDefaultContentViewPreview.swift b/catowseriOS/catowser/Menu/Previews/TabDefaultContentViewPreview.swift index 24fc25f1..e605d79e 100644 --- a/catowseriOS/catowser/Menu/Previews/TabDefaultContentViewPreview.swift +++ b/catowseriOS/catowser/Menu/Previews/TabDefaultContentViewPreview.swift @@ -17,7 +17,7 @@ struct TabDefaultContentView_Previews: PreviewProvider { let model: TabDefaultContentModel = .init(nil) { (_) in // } - return BaseMenuView(viewModel: model) + return BaseMenuView(viewModel: model) } } #endif diff --git a/catowseriOS/catowser/Menu/TabDefaultContentModel.swift b/catowseriOS/catowser/Menu/TabDefaultContentModel.swift index e119f75d..238ff1db 100644 --- a/catowseriOS/catowser/Menu/TabDefaultContentModel.swift +++ b/catowseriOS/catowser/Menu/TabDefaultContentModel.swift @@ -8,19 +8,11 @@ import CoreBrowser -typealias TabDefaultContentModel = BaseListViewModelImpl +typealias TabDefaultContentModel = BaseListViewModelImpl -extension BaseListViewModelImpl where EnumDataSourceType == TabContentDefaultState { +extension BaseListViewModelImpl where EnumDataSourceType == CoreBrowser.Tab.ContentType { init( _ selected: EnumDataSourceType?, _ completion: @escaping PopClosure) { let title = NSLocalizedString("ttl_tab_default_content", comment: "") self.init(title, completion, selected) } } - -extension TabContentDefaultState: Identifiable { - public var id: RawValue { - return self.rawValue - } - - public typealias ID = RawValue -} diff --git a/catowseriOS/catowser/Utils/DefaultTabProvider.swift b/catowseriOS/catowser/Utils/DefaultTabProvider.swift index 437429a0..8ec64cd8 100644 --- a/catowseriOS/catowser/Utils/DefaultTabProvider.swift +++ b/catowseriOS/catowser/Utils/DefaultTabProvider.swift @@ -52,7 +52,7 @@ final class DefaultTabProvider { var contentState: CoreBrowser.Tab.ContentType { get async { - await FeatureManager.shared.tabDefaultContentValue().contentType + await FeatureManager.shared.tabDefaultContentValue() } } diff --git a/catowseriOS/catowser/Utils/Features/BaseModels/SettingsEnumTypes.swift b/catowseriOS/catowser/Utils/Features/BaseModels/SettingsEnumTypes.swift index d82149e3..ac0d9deb 100644 --- a/catowseriOS/catowser/Utils/Features/BaseModels/SettingsEnumTypes.swift +++ b/catowseriOS/catowser/Utils/Features/BaseModels/SettingsEnumTypes.swift @@ -23,7 +23,7 @@ extension WebAutoCompletionSource: EnumDefaultValueSupportable { } } -extension AsyncApiType: EnumDefaultValueSupportable { +extension AsyncApiType: @retroactive EnumDefaultValueSupportable { public var defaultValue: AsyncApiType { return .combine } @@ -31,19 +31,19 @@ extension AsyncApiType: EnumDefaultValueSupportable { // MARK: - types from CoreBrowser -extension AddedTabPosition: EnumDefaultValueSupportable { +extension AddedTabPosition: @retroactive EnumDefaultValueSupportable { public var defaultValue: AddedTabPosition { return .listEnd } } -extension TabContentDefaultState: EnumDefaultValueSupportable { - public var defaultValue: TabContentDefaultState { +extension CoreBrowser.Tab.ContentType: @retroactive EnumDefaultValueSupportable { + public var defaultValue: CoreBrowser.Tab.ContentType { #if DEBUG - return TabContentDefaultState.topSites + return CoreBrowser.Tab.ContentType.topSites #else // In Release builds only User can decide which web sites to show by default - return TabContentDefaultState.favorites + return CoreBrowser.Tab.ContentType.favorites #endif } } diff --git a/catowseriOS/catowser/Utils/Features/FeatureManager+SpecificEnums.swift b/catowseriOS/catowser/Utils/Features/FeatureManager+SpecificEnums.swift index f46e7a8b..db2fd777 100644 --- a/catowseriOS/catowser/Utils/Features/FeatureManager+SpecificEnums.swift +++ b/catowseriOS/catowser/Utils/Features/FeatureManager+SpecificEnums.swift @@ -20,7 +20,7 @@ extension FeatureManager.StateHolder { keyStr = .autoCompletionKey case is AddedTabPosition: keyStr = .tabAddPositionKey - case is TabContentDefaultState: + case is CoreBrowser.Tab.ContentType: keyStr = .tabDefaultContentKey case is AsyncApiType: keyStr = .browserAsyncApiKey @@ -45,7 +45,7 @@ extension FeatureManager.StateHolder { return await source.currentEnumValue(of: feature) } - func tabDefaultContentValue() async -> TabContentDefaultState { + func tabDefaultContentValue() async -> CoreBrowser.Tab.ContentType { let feature: ApplicationEnumFeature = .tabDefaultContent guard let source = source(for: feature) else { return feature.defaultEnumValue diff --git a/catowseriOS/catowser/Utils/Features/SpecificModels/SpecificEnumFeatures.swift b/catowseriOS/catowser/Utils/Features/SpecificModels/SpecificEnumFeatures.swift index 6b9bd2ee..caa3ce65 100644 --- a/catowseriOS/catowser/Utils/Features/SpecificModels/SpecificEnumFeatures.swift +++ b/catowseriOS/catowser/Utils/Features/SpecificModels/SpecificEnumFeatures.swift @@ -20,7 +20,7 @@ extension String { typealias WebAutoCompletionFeature = GenericEnumFeature typealias TabAddPositionFeature = GenericEnumFeature -typealias TabContentFeature = GenericEnumFeature +typealias TabContentFeature = GenericEnumFeature typealias AppAsyncApiFeature = GenericEnumFeature typealias UIFrameworkFeature = GenericEnumFeature From e6b640c72ee6c2a7768d51cc89f7938c0d5a7475 Mon Sep 17 00:00:00 2001 From: Andrei Ermoshin Date: Sat, 17 Aug 2024 22:41:33 +0300 Subject: [PATCH 23/32] Better comments, Database class update --- .../CoreBrowser/Tabs/TabsDataService.swift | 20 +++++++++--- catowseriOS/catowser/DB/Database.swift | 32 ++++++++++--------- .../catowser/Utils/DefaultTabProvider.swift | 6 ++-- 3 files changed, 35 insertions(+), 23 deletions(-) diff --git a/catowseriOS/CoreBrowser/Tabs/TabsDataService.swift b/catowseriOS/CoreBrowser/Tabs/TabsDataService.swift index 59b15050..5c0a4dec 100644 --- a/catowseriOS/CoreBrowser/Tabs/TabsDataService.swift +++ b/catowseriOS/CoreBrowser/Tabs/TabsDataService.swift @@ -154,8 +154,14 @@ private extension TabsDataService { func handleCloseAllCommand() async -> TabsServiceDataOutput { let contentState = await positioning.contentState do { - // workaround https://forums.swift.org/t/why-does-sending-a-sendable-value-risk-causing-data-races/73074/4 - var tabsCopy = tabs + // because `tabs` field isolated to data service actor + // and observer is another actor (main) + // + // workaround at https://forums.swift.org/t/ + // why-does-sending-a-sendable-value-risk-causing-data-races/73074/4 + // + // need to create a local copy to unlink data from the actor + let tabsCopy = tabs _ = try await tabsRepository.remove(tabs: tabsCopy) tabs.removeAll() tabsCountInput.yield(0) @@ -265,8 +271,14 @@ extension TabsDataService: TabsSubject { return } await observer.updateTabsCount(with: tabs.count) - // workaround https://forums.swift.org/t/why-does-sending-a-sendable-value-risk-causing-data-races/73074/4 - var tabsCopy = tabs + // because `tabs` field isolated to data service actor + // and observer is another actor (main) + // + // workaround at https://forums.swift.org/t/ + // why-does-sending-a-sendable-value-risk-causing-data-races/73074/4 + // + // need to create a local copy to unlink data from the actor + let tabsCopy = tabs await observer.initializeObserver(with: tabsCopy) let defaultValue = positioning.defaultSelectedTabId guard selectedTabIdentifier != defaultValue else { diff --git a/catowseriOS/catowser/DB/Database.swift b/catowseriOS/catowser/DB/Database.swift index 3ebde2aa..a85c1beb 100644 --- a/catowseriOS/catowser/DB/Database.swift +++ b/catowseriOS/catowser/DB/Database.swift @@ -9,6 +9,12 @@ import Foundation import CoreData +/// A Database class, can't be an actor for now because, +/// private core data managed context must be initialised and used +/// on the same dispatch queue or thread and not clear how to +/// use it if it is created from an actor. +/// +/// Also, NSManagedObjectContext class is not sendable for an actor. final class Database: Sendable { /// The default directory for the persistent stores on the current platform. @@ -23,7 +29,7 @@ final class Database: Sendable { /// A read-only flag indicating if the persistent store is loaded. /// Can be nonisolated unsafe, because it is a Scalar bool type. - nonisolated(unsafe) private(set) var isStoreLoaded = false + private(set) nonisolated(unsafe) var isStoreLoaded = false /// The managed object context associated with the main queue (read-only). /// To perform tasks on a private background queue see @@ -59,14 +65,14 @@ final class Database: Sendable { /// Default is false. /// /// Can be nonisolated unsafe, because it is a Scalar bool type. - nonisolated(unsafe) private var isReadOnly = false + private nonisolated(unsafe) var isReadOnly = false /// A flag that indicates whether the store is added asynchronously. /// Set this value before loading the persistent store. /// Default is true. /// /// Can be nonisolated unsafe, because it is a Scalar bool type. - nonisolated(unsafe) private var shouldAddStoreAsynchronously = false + private nonisolated(unsafe) var shouldAddStoreAsynchronously = false /// A flag that indicates whether the store should be migrated /// automatically if the store model version does not match the @@ -75,7 +81,7 @@ final class Database: Sendable { /// Default is true. /// /// Can be nonisolated unsafe, because it is a Scalar bool type. - nonisolated(unsafe) private var shouldMigrateStoreAutomatically = true + private nonisolated(unsafe) var shouldMigrateStoreAutomatically = true /// A flag that indicates whether a mapping model should be inferred /// when migrating a store. @@ -83,7 +89,7 @@ final class Database: Sendable { /// Default is true. /// /// Can be nonisolated unsafe, because it is a Scalar bool type. - nonisolated(unsafe) private var shouldInferMappingModelAutomatically = true + private nonisolated(unsafe) var shouldInferMappingModelAutomatically = true /// Creates and returns a `CoreDataController` object. This is the designated /// initializer for the class. It creates the managed object model, @@ -197,21 +203,17 @@ final class Database: Sendable { continuation.resume(with: .failure(CottonError.zombieSelf)) return } - self.persistentContainer.loadPersistentStores { [weak self] (_, error) in - guard let self else { - continuation.resume(with: .failure(CottonError.zombieSelf)) - return - } - if let actualError = error { - continuation.resume(with: .failure(actualError)) + persistentContainer.loadPersistentStores { (_, error) in + if let error { + continuation.resume(with: .failure(error)) } else { - // Side effects! - self.isStoreLoaded = true - self.persistentContainer.viewContext.automaticallyMergesChangesFromParent = true continuation.resume(with: .success(())) } } } + // should be called only if no errors were thrown on libe above + isStoreLoaded = true + persistentContainer.viewContext.automaticallyMergesChangesFromParent = true return result } } diff --git a/catowseriOS/catowser/Utils/DefaultTabProvider.swift b/catowseriOS/catowser/Utils/DefaultTabProvider.swift index 8ec64cd8..ae77502c 100644 --- a/catowseriOS/catowser/Utils/DefaultTabProvider.swift +++ b/catowseriOS/catowser/Utils/DefaultTabProvider.swift @@ -17,10 +17,8 @@ final class DefaultTabProvider { static let shared = StateHolder() actor StateHolder: TabsStates { - var selected: Bool { - get async { - await UIDevice.current.userInterfaceIdiom == .pad - } + @MainActor var selected: Bool { + UIDevice.current.userInterfaceIdiom == .pad } let blockPopups: Bool = false From 8d8fe19317ed6f5d12dda2ed328ca442c14f497f Mon Sep 17 00:00:00 2001 From: Andrei Ermoshin Date: Sat, 17 Aug 2024 23:04:23 +0300 Subject: [PATCH 24/32] Another set of detailed comments about sendable --- catowseriOS/CottonData/RestClientContext.swift | 3 +++ .../Autocomplete/SearchAutocompleteStrategy.swift | 3 +++ catowseriOS/CottonData/SiteSettings+Extensions.swift | 1 + .../WebViewModel/DNSResolving/DNSResolvingStrategy.swift | 2 ++ catowseriOS/CottonData/WebViewModel/WebViewAction.swift | 2 +- 5 files changed, 10 insertions(+), 1 deletion(-) diff --git a/catowseriOS/CottonData/RestClientContext.swift b/catowseriOS/CottonData/RestClientContext.swift index 187a7af8..d4dd8651 100644 --- a/catowseriOS/CottonData/RestClientContext.swift +++ b/catowseriOS/CottonData/RestClientContext.swift @@ -17,6 +17,9 @@ import AutoMockable extension CottonBase.ServerDescription: @unchecked Sendable {} +/// Rest client context should be sendable, because it is used by the strategies +/// and a strategy is used by the use cases which shouldn't store any state (state-less). +/// It means that a use case must be sendable which requires the context to be sendable as well. // swiftlint:disable comment_spacing //sourcery: associatedtype = "R: ResponseType" //sourcery: associatedtype = "S: ServerDescription" diff --git a/catowseriOS/CottonData/SearchViewModel/Autocomplete/SearchAutocompleteStrategy.swift b/catowseriOS/CottonData/SearchViewModel/Autocomplete/SearchAutocompleteStrategy.swift index a0e55b7f..38bd43b3 100644 --- a/catowseriOS/CottonData/SearchViewModel/Autocomplete/SearchAutocompleteStrategy.swift +++ b/catowseriOS/CottonData/SearchViewModel/Autocomplete/SearchAutocompleteStrategy.swift @@ -12,6 +12,9 @@ import CottonRestKit import Combine import AutoMockable +/// Search auto complete strat is used by the use case which doesn't have any state +/// as any use case can't have any state, so, it should be sendable, and, this strategy +/// must be sendable to make the use case sendable as well... // swiftlint:disable comment_spacing //sourcery: associatedtype = "Context: RestClientContext" public protocol SearchAutocompleteStrategy: AnyObject, AutoMockable, Sendable { diff --git a/catowseriOS/CottonData/SiteSettings+Extensions.swift b/catowseriOS/CottonData/SiteSettings+Extensions.swift index f2e7c3d5..d549df38 100644 --- a/catowseriOS/CottonData/SiteSettings+Extensions.swift +++ b/catowseriOS/CottonData/SiteSettings+Extensions.swift @@ -11,6 +11,7 @@ import CottonBase extension Site.Settings { /// This will be ignored for old WebViews because it can't be changed for existing WebView without recration. + /// It has to be main actor because `WKWebViewConfiguration` uses it. @MainActor var webViewConfig: WKWebViewConfiguration { let configuration = WKWebViewConfiguration() if #available(iOS 14, *) { diff --git a/catowseriOS/CottonData/WebViewModel/DNSResolving/DNSResolvingStrategy.swift b/catowseriOS/CottonData/WebViewModel/DNSResolving/DNSResolvingStrategy.swift index 13eb9d4a..1ed5638f 100644 --- a/catowseriOS/CottonData/WebViewModel/DNSResolving/DNSResolvingStrategy.swift +++ b/catowseriOS/CottonData/WebViewModel/DNSResolving/DNSResolvingStrategy.swift @@ -12,6 +12,8 @@ import CottonRestKit import Combine import AutoMockable +/// DNS resolving strategy interface should be sendable, because it is used by the use cases, +/// and any use case has to be sendable because a use case shouldn't have any state. // swiftlint:disable comment_spacing //sourcery: associatedtype = "Context: RestClientContext" public protocol DNSResolvingStrategy: AnyObject, AutoMockable, Sendable { diff --git a/catowseriOS/CottonData/WebViewModel/WebViewAction.swift b/catowseriOS/CottonData/WebViewModel/WebViewAction.swift index a42d6aed..dfb80c12 100644 --- a/catowseriOS/CottonData/WebViewModel/WebViewAction.swift +++ b/catowseriOS/CottonData/WebViewModel/WebViewAction.swift @@ -7,7 +7,7 @@ // import Foundation -@preconcurrency import CottonBase +import CottonBase import CottonPlugins protocol Actionable { From 8d1eabc3d62b385cf6a4bbe7f2df94caa3881c1a Mon Sep 17 00:00:00 2001 From: Andrei Ermoshin Date: Sun, 25 Aug 2024 09:29:24 +0300 Subject: [PATCH 25/32] Fix service locator crash --- .../UseCases/LazyServiceLocator.swift | 8 +++++- .../CoreBrowser/UseCases/UseCaseLocator.swift | 25 ++++++------------- .../catowser/Environment/UseCaseFactory.swift | 6 ++--- 3 files changed, 18 insertions(+), 21 deletions(-) diff --git a/catowseriOS/CoreBrowser/UseCases/LazyServiceLocator.swift b/catowseriOS/CoreBrowser/UseCases/LazyServiceLocator.swift index 61230eef..001da006 100644 --- a/catowseriOS/CoreBrowser/UseCases/LazyServiceLocator.swift +++ b/catowseriOS/CoreBrowser/UseCases/LazyServiceLocator.swift @@ -33,13 +33,19 @@ public class LazyServiceLocator: ServiceLocator { } public func register(_ instance: T) { - let key = ObjectIdentifier(type(of: instance)) + let type = type(of: instance) + let key = ObjectIdentifier(type) idByRecord[key] = .instance(instance) } public func registerNamed(_ instance: T, _ key: String) { stringByRecord[key] = .instance(instance) } + + public func registerTyped(_ instance: T, of type: Any.Type) { + let key = ObjectIdentifier(type) + idByRecord[key] = .instance(instance) + } public func findService(_ type: T.Type, _ key: String? = nil) -> T? { let id = ObjectIdentifier(type) diff --git a/catowseriOS/CoreBrowser/UseCases/UseCaseLocator.swift b/catowseriOS/CoreBrowser/UseCases/UseCaseLocator.swift index a09e1841..10a5bf9d 100644 --- a/catowseriOS/CoreBrowser/UseCases/UseCaseLocator.swift +++ b/catowseriOS/CoreBrowser/UseCases/UseCaseLocator.swift @@ -13,29 +13,20 @@ import Foundation */ public final class UseCaseLocator: LazyServiceLocator { public override init() {} - - public override func register(_ recipe: @escaping () -> T) { - guard T.self is BaseUseCase.Type else { - return - } + + public func register(_ recipe: @escaping () -> T) { super.register(recipe) } - - public override func register(_ instance: T) { - guard instance is BaseUseCase else { - return - } + + public func register(_ instance: T) { super.register(instance) } - public override func registerNamed(_ instance: T, _ key: String) { - guard instance is BaseUseCase else { - return - } + public func registerNamed(_ instance: T, _ key: String) { super.registerNamed(instance, key) } - - public override func findService(_ type: T.Type, _ key: String? = nil) -> T? { - return super.findService(type, key) + + public func registerTyped(_ instance: T, of type: Any.Type) { + super.registerTyped(instance, of: type) } } diff --git a/catowseriOS/catowser/Environment/UseCaseFactory.swift b/catowseriOS/catowser/Environment/UseCaseFactory.swift index 53248511..fb25a498 100644 --- a/catowseriOS/catowser/Environment/UseCaseFactory.swift +++ b/catowseriOS/catowser/Environment/UseCaseFactory.swift @@ -44,11 +44,11 @@ final class UseCaseFactory { private func registerTabsUseCases() async { let dataService = await TabsDataService.shared let readUseCase: ReadTabsUseCase = ReadTabsUseCaseImpl(dataService, DefaultTabProvider.shared) - locator.register(readUseCase) + locator.registerTyped(readUseCase, of: ReadTabsUseCase.self) let writeUseCase: WriteTabsUseCase = WriteTabsUseCaseImpl(dataService) - locator.register(writeUseCase) + locator.registerTyped(writeUseCase, of: WriteTabsUseCase.self) let selectedTabUseCase: SelectedTabUseCase = SelectedTabUseCaseImpl(dataService) - locator.register(selectedTabUseCase) + locator.registerTyped(selectedTabUseCase, of: SelectedTabUseCase.self) } private func registerSearchAutocompleteUseCases() { From daaa4a49e0d22f7fe131f4923e4b83ee47d51929 Mon Sep 17 00:00:00 2001 From: Andrei Ermoshin Date: Sun, 25 Aug 2024 09:55:37 +0300 Subject: [PATCH 26/32] Another set of review fixes --- .../WebViewModel/WebViewContext.swift | 6 +++-- .../WebViewModel/WebViewModel.swift | 8 ++++-- .../WebViewModel/WebViewModelState.swift | 2 +- .../CottonPlugins/JSPluginsProgramImpl.swift | 26 ++++++++++++++++++- .../WebView/WebViewController.swift | 2 +- 5 files changed, 37 insertions(+), 7 deletions(-) diff --git a/catowseriOS/CottonData/WebViewModel/WebViewContext.swift b/catowseriOS/CottonData/WebViewModel/WebViewContext.swift index 8f5c6a91..84b82224 100644 --- a/catowseriOS/CottonData/WebViewModel/WebViewContext.swift +++ b/catowseriOS/CottonData/WebViewModel/WebViewContext.swift @@ -17,8 +17,10 @@ import CoreBrowser https://swiftrocks.com/whats-any-understanding-type-erasure-in-swift */ -/// web view context should carry some data or dependencies which can't be stored as a state and always are present. -/// protocol with async functions which do not belong to specific actor +/// Web view context should carry some data or dependencies which can't be stored as a state and always are present. +/// protocol with async functions which do not belong to specific actor. +/// +/// Can be sendable because doesn't store anything mutable. public protocol WebViewContext: Sendable { /// Plugins are optional because there is possibility that js files are not present or plugins delegates are not set var pluginsSource: any JSPluginsSource { get } diff --git a/catowseriOS/CottonData/WebViewModel/WebViewModel.swift b/catowseriOS/CottonData/WebViewModel/WebViewModel.swift index 26a33709..50eda30c 100644 --- a/catowseriOS/CottonData/WebViewModel/WebViewModel.swift +++ b/catowseriOS/CottonData/WebViewModel/WebViewModel.swift @@ -21,8 +21,12 @@ public enum WebPageLoadingAction: Equatable { case openApp(URL) } -/// Interface for system's type `WKNavigationAction` from WebKit framework to be able to mock it -@MainActor public protocol NavigationActionable: AnyObject { +/// Interface for system's type `WKNavigationAction` from WebKit framework to be able to mock it. +/// +/// Can be sendable because both fields are. +/// Also,`WKNavigationAction` which has these fields and confirms to this protocol, +/// it is marked as a main actor, so that, this protocol should be marked as main actor as well. +@MainActor public protocol NavigationActionable: AnyObject, Sendable { var navigationType: WKNavigationType { get } var request: URLRequest { get } } diff --git a/catowseriOS/CottonData/WebViewModel/WebViewModelState.swift b/catowseriOS/CottonData/WebViewModel/WebViewModelState.swift index 40df6f0a..1cd0944e 100644 --- a/catowseriOS/CottonData/WebViewModel/WebViewModelState.swift +++ b/catowseriOS/CottonData/WebViewModel/WebViewModelState.swift @@ -306,7 +306,7 @@ extension WebViewModelState: Equatable { return lData == rData && lSettings == rSettings case (.injectingPlugins(let lProgram, let lData, let lSettings), .injectingPlugins(let rProgram, let rData, let rSettings)): - if let lp = lProgram as? JSPluginsProgramImpl, let rp = rProgram as? JSPluginsProgramImpl /*, lp != rp */ { + if let lp = lProgram as? JSPluginsProgramImpl, let rp = rProgram as? JSPluginsProgramImpl, lp != rp { return false } return lData == rData && lSettings == rSettings diff --git a/catowseriOS/CottonPlugins/JSPluginsProgramImpl.swift b/catowseriOS/CottonPlugins/JSPluginsProgramImpl.swift index 255ce94b..8d73dbad 100644 --- a/catowseriOS/CottonPlugins/JSPluginsProgramImpl.swift +++ b/catowseriOS/CottonPlugins/JSPluginsProgramImpl.swift @@ -18,7 +18,7 @@ extension CottonBase.Host: @unchecked @retroactive Sendable {} An Object Structure (Program) from visitor desgin pattern. Could be a Composite */ @MainActor -public final class JSPluginsProgramImpl: JSPluginsProgram { +public final class JSPluginsProgramImpl: JSPluginsProgram, @preconcurrency Equatable { public let plugins: [(any JavaScriptPlugin, WKScriptMessageHandler)] public init( @@ -66,3 +66,27 @@ public final class JSPluginsProgramImpl: JSPluginsProgram { .forEach { webView.evaluate(jsScript: $0)} } } + +extension JSPluginsProgramImpl { + public static func == (lhs: JSPluginsProgramImpl, rhs: JSPluginsProgramImpl) -> Bool { + guard lhs.plugins.count == rhs.plugins.count else { + return false + } + var index = 0 + while index < lhs.plugins.count { + let lPair = lhs.plugins[index] + let rPair = rhs.plugins[index] + #warning("TODO: rework or remove, cause this code is hard to scale") + if let lInst = lPair.0 as? InstagramContentPlugin, let rInst = rPair.0 as? InstagramContentPlugin, lInst == rInst { + index += 1 + continue + } else if let lInst = lPair.0 as? BasePlugin, let rInst = rPair.0 as? BasePlugin, lInst == rInst { + index += 1 + continue + } else { + return false + } + } + return true + } +} diff --git a/catowseriOS/catowser/BrowserContent/WebView/WebViewController.swift b/catowseriOS/catowser/BrowserContent/WebView/WebViewController.swift index ca68a469..aed4fd4d 100644 --- a/catowseriOS/catowser/BrowserContent/WebView/WebViewController.swift +++ b/catowseriOS/catowser/BrowserContent/WebView/WebViewController.swift @@ -431,5 +431,5 @@ extension WKNavigationType: CustomDebugStringConvertible { } } -/// Can be retroactive because NavigationActionable is unknown for web view Apple devs +/// Can be retroactive because NavigationActionable is unknown for web view Apple devs. extension WKNavigationAction: @retroactive NavigationActionable {} From c064621baaaf2b9f27074bfdba1a9349fc4813cc Mon Sep 17 00:00:00 2001 From: Andrei Ermoshin Date: Sun, 25 Aug 2024 13:39:29 +0300 Subject: [PATCH 27/32] More doc comments --- .../UseCases/LazyServiceLocator.swift | 22 ++++++++++++++++++- .../CoreBrowser/UseCases/ServiceLocator.swift | 1 + .../CoreBrowser/UseCases/UseCaseLocator.swift | 6 +++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/catowseriOS/CoreBrowser/UseCases/LazyServiceLocator.swift b/catowseriOS/CoreBrowser/UseCases/LazyServiceLocator.swift index 61230eef..0fc9b72b 100644 --- a/catowseriOS/CoreBrowser/UseCases/LazyServiceLocator.swift +++ b/catowseriOS/CoreBrowser/UseCases/LazyServiceLocator.swift @@ -23,24 +23,44 @@ enum ServiceRecord { } } -public class LazyServiceLocator: ServiceLocator { +public class LazyServiceLocator { private lazy var idByRecord: [ObjectIdentifier: ServiceRecord] = [:] private lazy var stringByRecord: [String: ServiceRecord] = [:] + /// Register a closure which could create an instance of a certain type + /// - Parameter instance: an instance (without generic parameters) which is stored by the specific metatype id public func register(_ recipe: @escaping () -> T) { let key = ObjectIdentifier(type(of: T.self)) idByRecord[key] = .fromClosure(recipe) } + /// Register an instance of a certain type + /// - Parameter instance: an instance (without generic parameters) which is stored by the specific metatype id public func register(_ instance: T) { let key = ObjectIdentifier(type(of: instance)) idByRecord[key] = .instance(instance) } + /// Register an instance using a string constant + /// it is for the types with the generic parameters which are not + /// convinient to store by specific metatype + /// + /// - Parameter instance: an object instance stored in a service locator + /// - Parameter key: a string key to store an object instance public func registerNamed(_ instance: T, _ key: String) { stringByRecord[key] = .instance(instance) } +} +extension LazyServiceLocator: ServiceLocator { + /// Searches for the service instance based on the object identifier which can be + /// computed automatically and if it is not possible to compute it, + /// (e.g. complex protocol types with primary associated type) + /// any consumer can try to register or find the instance using simple string key + /// + /// @param type of the service which instance need to find + /// @param key A string key if the service instance was registered not using type object id + /// @return a reference to already stored object instance found by the key or type or nil if it wasn't registered public func findService(_ type: T.Type, _ key: String? = nil) -> T? { let id = ObjectIdentifier(type) var instance: T? diff --git a/catowseriOS/CoreBrowser/UseCases/ServiceLocator.swift b/catowseriOS/CoreBrowser/UseCases/ServiceLocator.swift index 9ce54693..0186015f 100644 --- a/catowseriOS/CoreBrowser/UseCases/ServiceLocator.swift +++ b/catowseriOS/CoreBrowser/UseCases/ServiceLocator.swift @@ -16,5 +16,6 @@ protocol ServiceLocator: AnyObject { /// /// @param type of the service which instance need to find /// @param key A string key if the service instance was registered not using type object id + /// @return a reference to already stored object instance found by the key or type or nil if it wasn't registered func findService(_ type: T.Type, _ key: String?) -> T? } diff --git a/catowseriOS/CoreBrowser/UseCases/UseCaseLocator.swift b/catowseriOS/CoreBrowser/UseCases/UseCaseLocator.swift index a09e1841..98fae049 100644 --- a/catowseriOS/CoreBrowser/UseCases/UseCaseLocator.swift +++ b/catowseriOS/CoreBrowser/UseCases/UseCaseLocator.swift @@ -28,6 +28,12 @@ public final class UseCaseLocator: LazyServiceLocator { super.register(instance) } + /// Register an instance of a certain type using a string constant + /// it is for the types with a generic parameter which are not + /// convinient to store by specific metatype + /// + /// - Parameter instance: an object instance stored in a service locator + /// - Parameter key: a string key to store an object instance public override func registerNamed(_ instance: T, _ key: String) { guard instance is BaseUseCase else { return From d4df746d45cfddaf35de28328840e236906cc386 Mon Sep 17 00:00:00 2001 From: Andrei Ermoshin Date: Tue, 27 Aug 2024 20:25:16 +0300 Subject: [PATCH 28/32] Disable CottonData unit tests for now because Swiftymocky doesn't work with Swift 6 projects yet --- Makefile | 60 ++++++++++++------- .../SearchEngines/SearchEngine.swift | 2 +- .../Servers/DuckDuckGoServer.swift | 2 +- .../Servers/GoogleDnsServer.swift | 2 +- .../Servers/GoogleServer.swift | 2 +- .../TabsDataServiceTests.swift | 2 +- .../CottonRestKit/RestClientError.swift | 2 +- .../CottonRestKitTests/EndpointMocks.swift | 2 +- .../ReactiveHttpKit/RxObserverWrapper.swift | 6 +- 9 files changed, 48 insertions(+), 32 deletions(-) diff --git a/Makefile b/Makefile index 09922c48..ffbf6954 100644 --- a/Makefile +++ b/Makefile @@ -152,11 +152,21 @@ ios-tests-core-browser: build-cotton-base-ios-release -sdk macosx | $(XCPRETTY) --test \ cd ..; \ +.PHONY: ios-tests-cotton-data +ios-tests-cotton-data: build-cotton-base-ios-release + cd catowseriOS; \ + $(SWIFTYMOCKY) doctor ; \ + $(SWIFTYMOCKY) generate ; \ + xcodebuild -scheme "CottonData Unit Tests" test \ + -workspace catowser.xcworkspace \ + -run-tests-until-failure \ + -destination platform=macOS, arch=x86_64 \ + -sdk macosx | $(XCPRETTY) --test \ + cd ..; \ + .PHONY: ios-unit-tests ios-unit-tests: build-cotton-base-ios-release cd catowseriOS; \ - $(SWIFTYMOCKY) doctor ; \ - $(SWIFTYMOCKY) generate ; \ xcodebuild -scheme "CoreBrowser Unit Tests" test \ -workspace catowser.xcworkspace \ -run-tests-until-failure \ @@ -172,20 +182,23 @@ ios-unit-tests: build-cotton-base-ios-release -run-tests-until-failure \ -destination platform=macOS, arch=x86_64 \ -sdk macosx | $(XCPRETTY) --test; \ - xcodebuild -scheme "CottonData Unit Tests" test \ - -workspace catowser.xcworkspace \ - -run-tests-until-failure \ - -destination platform=macOS, arch=x86_64 \ - -sdk macosx | $(XCPRETTY) --test; \ cd ..; \ +# $(SWIFTYMOCKY) doctor ; \ +# $(SWIFTYMOCKY) generate ; \ +# xcodebuild -scheme "CottonData Unit Tests" test \ +# -workspace catowser.xcworkspace \ +# -run-tests-until-failure \ +# -destination platform=macOS, arch=x86_64 \ +# -sdk macosx | $(XCPRETTY) --test; \ + # Github workflow unit tests (specific macOS runners) .PHONY: github-ios-unit-tests github-ios-unit-tests: build-cotton-base-ios-release brew bundle install --file=./brew_configs/Brewfile; \ export PATH="${PATH}:${HOME}/.mint/bin" ; \ - mint install MakeAWishFoundation/SwiftyMocky; \ + @echo "mint install MakeAWishFoundation/SwiftyMocky;" ; \ sourcery --config "catowseriOS/CoreBrowserTests/.sourcery.yml" cd catowseriOS; \ xcodebuild -scheme "CoreBrowser Unit Tests" test \ @@ -209,15 +222,17 @@ github-ios-unit-tests: build-cotton-base-ios-release -sdk $(MACOSSDK_VERSION) | $(XCPRETTY) --test && exit ${PIPESTATUS[0]}; \ cd ..; \ cd catowseriOS; \ - swiftymocky doctor ; \ - swiftymocky generate ; \ - xcodebuild -scheme "CottonData Unit Tests" test \ - -workspace catowser.xcworkspace \ - -run-tests-until-failure \ - -destination platform=macOS, arch=x86_64 \ - -sdk $(MACOSSDK_VERSION) | $(XCPRETTY) --test; \ cd ..; \ +# mint install MakeAWishFoundation/SwiftyMocky; \ +# swiftymocky doctor ; \ +# swiftymocky generate ; \ +# xcodebuild -scheme "CottonData Unit Tests" test \ +# -workspace catowser.xcworkspace \ +# -run-tests-until-failure \ +# -destination platform=macOS, arch=x86_64 \ +# -sdk $(MACOSSDK_VERSION) | $(XCPRETTY) --test; \ + # Help define HELP_CONTENT @@ -227,23 +242,24 @@ Local and CI targets \t\t* make clean\t\t: Cleans all build artifacts for both platforms. \tiOS build -\t\t* make build-ios-dev-release\t\t: Build Release version of Kotlin multiplatform & Xcode project. -\t\t* make github-workflow-ios\t\t: GitHub workflow for iOS. -\t\t* make ios-lint\t\t: Only run linter on Swift files. +\t\t* make build-ios-dev-release\t: Build Release version of Kotlin multiplatform & Xcode project. +\t\t* make github-workflow-ios\t: GitHub workflow for iOS. +\t\t* make ios-lint\t\t\t: Only run linter on Swift files. \tAndroid build -\t\t* make build-android-dev-release\t\t: Build Release version of Kotlin multiplatform & Android project. -\t\t* make github-workflow-android\t\t: GitHub workflow for Android. +\t\t* make build-android-dev-release: Build Release version of Kotlin multiplatform & Android project. +\t\t* make github-workflow-android\t: GitHub workflow for Android. \t\t* make android-lint\t\t: CLI kotlin lint. \tCotton-Base build \t\t* make build-cotton-base-ios-release\t\t: Build cotton-base XCFramework for iOS. -\t\t* make build-cotton-base-android-release\t\t: Build & publish cotton-base to local Maven for Android. +\t\t* make build-cotton-base-android-release\t: Build & publish cotton-base to local Maven for Android. \t\t* make build-cotton-base-release\t\t: Build cotton-base together for iOS & Android. \tUnit tests -\t\t* make ios-unit-tests\t\t: Build and run iOS unit tests. +\t\t* make ios-unit-tests\t\t\t: Build and run iOS unit tests. \t\t* make ios-tests-core-browser\t\t: Build and run Cotton-base Kotlin unit tests. +\t\t* make ios-tests-cotton-data\t\t: run Swiftymocky based tests endef export HELP_CONTENT diff --git a/catowseriOS/BrowserNetworking/SearchEngines/SearchEngine.swift b/catowseriOS/BrowserNetworking/SearchEngines/SearchEngine.swift index 3ae39477..bd7b8e96 100644 --- a/catowseriOS/BrowserNetworking/SearchEngines/SearchEngine.swift +++ b/catowseriOS/BrowserNetworking/SearchEngines/SearchEngine.swift @@ -10,7 +10,7 @@ import Foundation import CottonBase /// Alamofire's http method type can't be retroactivly silenced because sendable is a system protocol. -extension HTTPMethod: @unchecked Sendable { } +extension CottonBase.HTTPMethod: @unchecked Sendable { } /// The model for OpenSearch format URL public struct SearchEngine: Sendable { diff --git a/catowseriOS/BrowserNetworking/Servers/DuckDuckGoServer.swift b/catowseriOS/BrowserNetworking/Servers/DuckDuckGoServer.swift index e2c8ff52..900b9e03 100644 --- a/catowseriOS/BrowserNetworking/Servers/DuckDuckGoServer.swift +++ b/catowseriOS/BrowserNetworking/Servers/DuckDuckGoServer.swift @@ -8,7 +8,7 @@ import CottonBase -public class DuckDuckGoServer: ServerDescription { +public class DuckDuckGoServer: ServerDescription, @unchecked Sendable { public convenience init() { // swiftlint:disable:next force_try let host = try! Host(input: "ac.duckduckgo.com") diff --git a/catowseriOS/BrowserNetworking/Servers/GoogleDnsServer.swift b/catowseriOS/BrowserNetworking/Servers/GoogleDnsServer.swift index 26491ddd..29dc3db7 100644 --- a/catowseriOS/BrowserNetworking/Servers/GoogleDnsServer.swift +++ b/catowseriOS/BrowserNetworking/Servers/GoogleDnsServer.swift @@ -8,7 +8,7 @@ import CottonBase -public class GoogleDnsServer: ServerDescription { +public class GoogleDnsServer: ServerDescription, @unchecked Sendable { public convenience init() { // swiftlint:disable:next force_try let host = try! Host(input: "dns.google") diff --git a/catowseriOS/BrowserNetworking/Servers/GoogleServer.swift b/catowseriOS/BrowserNetworking/Servers/GoogleServer.swift index b8e836b0..d52d67e9 100644 --- a/catowseriOS/BrowserNetworking/Servers/GoogleServer.swift +++ b/catowseriOS/BrowserNetworking/Servers/GoogleServer.swift @@ -8,7 +8,7 @@ import CottonBase -public class GoogleServer: ServerDescription { +public class GoogleServer: ServerDescription, @unchecked Sendable { public convenience init() { // swiftlint:disable:next force_try let host = try! Host(input: "www.google.com") diff --git a/catowseriOS/CoreBrowserTests/TabsDataServiceTests.swift b/catowseriOS/CoreBrowserTests/TabsDataServiceTests.swift index 20f0b7dd..3e277cde 100644 --- a/catowseriOS/CoreBrowserTests/TabsDataServiceTests.swift +++ b/catowseriOS/CoreBrowserTests/TabsDataServiceTests.swift @@ -18,7 +18,7 @@ extension UUID { class TabsDataServiceTests: XCTestCase { - let tabsStorageMock: TabsStoragableMock = .init() + let tabsStorageMock: TabsRepositoryMock = .init() let tabsStates: TabsStatesMock = .init() diff --git a/catowseriOS/CottonRestKit/RestClientError.swift b/catowseriOS/CottonRestKit/RestClientError.swift index 7e14c281..94e92ffa 100644 --- a/catowseriOS/CottonRestKit/RestClientError.swift +++ b/catowseriOS/CottonRestKit/RestClientError.swift @@ -9,7 +9,7 @@ import Foundation import CottonBase -extension DomainName.Error: @unchecked Sendable {} +extension DomainName.Error: @unchecked @retroactive Sendable {} public enum HttpError: LocalizedError, Equatable { /* Comon errors related to http client */ diff --git a/catowseriOS/CottonRestKitTests/EndpointMocks.swift b/catowseriOS/CottonRestKitTests/EndpointMocks.swift index 9a23d3ec..74f7ca6f 100644 --- a/catowseriOS/CottonRestKitTests/EndpointMocks.swift +++ b/catowseriOS/CottonRestKitTests/EndpointMocks.swift @@ -15,7 +15,7 @@ struct MockedGoodEndpointResponse: ResponseType { } } -class MockedGoodServer: ServerDescription { +class MockedGoodServer: ServerDescription, @unchecked Sendable { convenience init() { // swiftlint:disable:next force_try let host = try! Host(input: "www.example.com") diff --git a/catowseriOS/ReactiveHttpKit/RxObserverWrapper.swift b/catowseriOS/ReactiveHttpKit/RxObserverWrapper.swift index d10067b7..0baae0ef 100644 --- a/catowseriOS/ReactiveHttpKit/RxObserverWrapper.swift +++ b/catowseriOS/ReactiveHttpKit/RxObserverWrapper.swift @@ -10,7 +10,7 @@ import CottonRestKit @preconcurrency import ReactiveSwift import CottonBase -extension Signal.Observer: RxAnyObserver where Value: ResponseType, Error == HttpError { +extension Signal.Observer: @retroactive RxAnyObserver where Value: ResponseType, Error == HttpError { public typealias Response = Value public func newSend(value: Response) { @@ -25,7 +25,7 @@ extension Signal.Observer: RxAnyObserver where Value: ResponseType, Error == Htt } } -extension Signal.Observer: RxAnyVoidObserver where Value == Void, Error == HttpError { +extension Signal.Observer: @retroactive RxAnyVoidObserver where Value == Void, Error == HttpError { public func newSend(value: Value) { send(value: value) } @@ -38,7 +38,7 @@ extension Signal.Observer: RxAnyVoidObserver where Value == Void, Error == HttpE } } -extension Lifetime: RxAnyLifetime { +extension Lifetime: @retroactive RxAnyLifetime { public func newObserveEnded(_ action: @escaping () -> Void) { observeEnded(action) } From 16f01a5aefc3110917b0ff6243e681ab7fd5ebb1 Mon Sep 17 00:00:00 2001 From: Andrei Ermoshin Date: Sun, 1 Sep 2024 08:39:16 +0300 Subject: [PATCH 29/32] Fix plugins comment --- .../CottonPlugins/JSPluginsBuilder.swift | 14 +++++++-- .../CottonPlugins/JSPluginsProgram.swift | 21 +++++++++++-- .../CottonPlugins/JSPluginsProgramImpl.swift | 30 ++++++++++++------- 3 files changed, 49 insertions(+), 16 deletions(-) diff --git a/catowseriOS/CottonPlugins/JSPluginsBuilder.swift b/catowseriOS/CottonPlugins/JSPluginsBuilder.swift index 166eb196..ba492638 100644 --- a/catowseriOS/CottonPlugins/JSPluginsBuilder.swift +++ b/catowseriOS/CottonPlugins/JSPluginsBuilder.swift @@ -13,7 +13,7 @@ import WebKit */ public final class JSPluginsBuilder: JSPluginsSource { public typealias Program = JSPluginsProgramImpl - private var plugins: [(any JavaScriptPlugin, WKScriptMessageHandler)] + private var plugins: [HandlablePlugin] public var jsProgram: Program { JSPluginsProgramImpl(plugins) @@ -25,13 +25,21 @@ public final class JSPluginsBuilder: JSPluginsSource { public func setBase(_ baseDelegate: BasePluginContentDelegate) -> Self { let basePlugin = BasePlugin() - plugins.append((basePlugin, BaseJSHandler(baseDelegate))) + let value = HandlablePlugin( + plugin: basePlugin, + handler: BaseJSHandler(baseDelegate) + ) + plugins.append(value) return self } public func setInstagram(_ instagramDelegate: InstagramContentDelegate) -> Self { let igPlugin = InstagramContentPlugin() - plugins.append((igPlugin, InstagramHandler(instagramDelegate))) + let value = HandlablePlugin( + plugin: igPlugin, + handler: InstagramHandler(instagramDelegate) + ) + plugins.append(value) return self } } diff --git a/catowseriOS/CottonPlugins/JSPluginsProgram.swift b/catowseriOS/CottonPlugins/JSPluginsProgram.swift index 1b0566b5..e1fdf475 100644 --- a/catowseriOS/CottonPlugins/JSPluginsProgram.swift +++ b/catowseriOS/CottonPlugins/JSPluginsProgram.swift @@ -10,9 +10,24 @@ import Foundation import WebKit import CottonBase -@MainActor -public protocol JSPluginsProgram: AnyObject, Sendable { - var plugins: [(any JavaScriptPlugin, WKScriptMessageHandler)] { get } +/// Can be sendable, but it is not required now +public final class HandlablePlugin { + public let plugin: any JavaScriptPlugin + public let handler: WKScriptMessageHandler + + public init( + plugin: any JavaScriptPlugin, + handler: WKScriptMessageHandler + ) { + self.plugin = plugin + self.handler = handler + } +} + +/// Should be main actor because it stores wk handlers which are marked as +/// main actors in the sdk. +@MainActor public protocol JSPluginsProgram: AnyObject, Sendable { + var plugins: [HandlablePlugin] { get } func inject(to visitor: WKUserContentController, context: CottonBase.Host, canInject: Bool) func enable(on webView: JavaScriptEvaluateble, context: CottonBase.Host, jsEnabled: Bool) diff --git a/catowseriOS/CottonPlugins/JSPluginsProgramImpl.swift b/catowseriOS/CottonPlugins/JSPluginsProgramImpl.swift index 8d73dbad..12a6806f 100644 --- a/catowseriOS/CottonPlugins/JSPluginsProgramImpl.swift +++ b/catowseriOS/CottonPlugins/JSPluginsProgramImpl.swift @@ -15,14 +15,16 @@ import CottonBase extension CottonBase.Host: @unchecked @retroactive Sendable {} /** - An Object Structure (Program) from visitor desgin pattern. Could be a Composite + An Object Structure (Program) from visitor desgin pattern. Could be a Composite. + Should be main actor because it stores wk handlers which are marked as + main actors in the sdk. */ @MainActor public final class JSPluginsProgramImpl: JSPluginsProgram, @preconcurrency Equatable { - public let plugins: [(any JavaScriptPlugin, WKScriptMessageHandler)] + public let plugins: [HandlablePlugin] public init( - _ plugins: [(any JavaScriptPlugin, WKScriptMessageHandler)] + _ plugins: [HandlablePlugin] ) { guard !plugins.isEmpty else { fatalError("Plugins program was initialized with 0 JS plugins") @@ -30,7 +32,11 @@ public final class JSPluginsProgramImpl: JSPluginsProgram, @preconcurrency Equat self.plugins = plugins } - public func inject(to visitor: WKUserContentController, context: CottonBase.Host, canInject: Bool) { + public func inject( + to visitor: WKUserContentController, + context: CottonBase.Host, + canInject: Bool + ) { guard !plugins.isEmpty else { return } @@ -41,20 +47,24 @@ public final class JSPluginsProgramImpl: JSPluginsProgram, @preconcurrency Equat visitor.removeAllUserScripts() // reset old state plugins.forEach { pair in do { - try pair.0.accept(visitor, context, canInject, pair.1) + try pair.plugin.accept(visitor, context, canInject, pair.handler) } catch { print("\(#function) failed to load plugin: \(error.localizedDescription)") } } } - public func enable(on webView: JavaScriptEvaluateble, context: CottonBase.Host, jsEnabled: Bool) { + public func enable( + on webView: JavaScriptEvaluateble, + context: CottonBase.Host, + jsEnabled: Bool + ) { guard !plugins.isEmpty else { return } plugins .filter { pair in - guard let pluginHostName = pair.0.hostKeyword else { + guard let pluginHostName = pair.plugin.hostKeyword else { return true } guard context.isSimilar(name: pluginHostName) else { @@ -62,7 +72,7 @@ public final class JSPluginsProgramImpl: JSPluginsProgram, @preconcurrency Equat } return true } - .compactMap { $0.0.scriptString(jsEnabled) } + .compactMap { $0.plugin.scriptString(jsEnabled) } .forEach { webView.evaluate(jsScript: $0)} } } @@ -77,10 +87,10 @@ extension JSPluginsProgramImpl { let lPair = lhs.plugins[index] let rPair = rhs.plugins[index] #warning("TODO: rework or remove, cause this code is hard to scale") - if let lInst = lPair.0 as? InstagramContentPlugin, let rInst = rPair.0 as? InstagramContentPlugin, lInst == rInst { + if let lInst = lPair.plugin as? InstagramContentPlugin, let rInst = rPair.plugin as? InstagramContentPlugin, lInst == rInst { index += 1 continue - } else if let lInst = lPair.0 as? BasePlugin, let rInst = rPair.0 as? BasePlugin, lInst == rInst { + } else if let lInst = lPair.plugin as? BasePlugin, let rInst = rPair.plugin as? BasePlugin, lInst == rInst { index += 1 continue } else { From 05d26b407b281de6d5f221798b92b92a8ad899ba Mon Sep 17 00:00:00 2001 From: Andrei Ermoshin Date: Mon, 2 Sep 2024 21:39:23 +0300 Subject: [PATCH 30/32] Add atomics for the Database --- .../Handlers/BaseJSHandler.swift | 8 +++++-- .../Handlers/InstagramHandler.swift | 8 +++++-- .../JavaScriptPluginVisitor.swift | 6 +++--- catowseriOS/CottonRestKit/RestClient.swift | 4 ++++ .../catowser.xcodeproj/project.pbxproj | 17 +++++++++++++++ .../xcshareddata/swiftpm/Package.resolved | 11 +++++++++- catowseriOS/catowser/DB/Database.swift | 21 ++++++++++--------- .../catowser/Utils/FaviconImageViewable.swift | 2 +- 8 files changed, 58 insertions(+), 19 deletions(-) diff --git a/catowseriOS/CottonPlugins/Handlers/BaseJSHandler.swift b/catowseriOS/CottonPlugins/Handlers/BaseJSHandler.swift index 2e759b08..4ac36716 100644 --- a/catowseriOS/CottonPlugins/Handlers/BaseJSHandler.swift +++ b/catowseriOS/CottonPlugins/Handlers/BaseJSHandler.swift @@ -8,8 +8,12 @@ import WebKit -@MainActor -final class BaseJSHandler: NSObject { +/// Handler for the base java script based web site, +/// +/// should be main actor because it confirms to `WKScriptMessageHandler`. +/// Also, it should subclass from NSObject because confirmation to `WKScriptMessageHandler` +/// is based on `NSObjectProtocol`. +@MainActor final class BaseJSHandler: NSObject { private weak var delegate: BasePluginContentDelegate? init(_ delegate: BasePluginContentDelegate) { diff --git a/catowseriOS/CottonPlugins/Handlers/InstagramHandler.swift b/catowseriOS/CottonPlugins/Handlers/InstagramHandler.swift index bcb6fc5f..cf9f982d 100644 --- a/catowseriOS/CottonPlugins/Handlers/InstagramHandler.swift +++ b/catowseriOS/CottonPlugins/Handlers/InstagramHandler.swift @@ -9,8 +9,12 @@ import Foundation import WebKit -@MainActor -final class InstagramHandler: NSObject { +/// Handler for the instagram web site, +/// +/// should be main actor because it confirms to `WKScriptMessageHandler`. +/// Also, it should subclass from NSObject because confirmation to `WKScriptMessageHandler` +/// is based on `NSObjectProtocol`. +@MainActor final class InstagramHandler: NSObject { private weak var delegate: InstagramContentDelegate? init(_ delegate: InstagramContentDelegate) { self.delegate = delegate diff --git a/catowseriOS/CottonPlugins/PluginsVisitor/JavaScriptPluginVisitor.swift b/catowseriOS/CottonPlugins/PluginsVisitor/JavaScriptPluginVisitor.swift index 803de755..17ae42a0 100644 --- a/catowseriOS/CottonPlugins/PluginsVisitor/JavaScriptPluginVisitor.swift +++ b/catowseriOS/CottonPlugins/PluginsVisitor/JavaScriptPluginVisitor.swift @@ -10,9 +10,9 @@ import Foundation import CottonBase import WebKit -/// Will be used for `WKUserContentController` because it is actually the only possible visitor -@MainActor -public protocol JavaScriptPluginVisitor: AnyObject { +/// Will be used for `WKUserContentController` because it is actually the only possible visitor. +/// Should be main actor because both functions have main actor specific parameters. +@MainActor public protocol JavaScriptPluginVisitor: AnyObject { /** Determines if specific plugin can be used on specific host diff --git a/catowseriOS/CottonRestKit/RestClient.swift b/catowseriOS/CottonRestKit/RestClient.swift index be109b24..1c1923c0 100644 --- a/catowseriOS/CottonRestKit/RestClient.swift +++ b/catowseriOS/CottonRestKit/RestClient.swift @@ -23,6 +23,10 @@ public typealias TypedResponseClosure = (HttpTypedResult) -> Void /// Can mark it as retroactive because it is from my CottonBase library. extension CottonBase.ServerDescription: @unchecked @retroactive Sendable {} +/// Rest client. +/// +/// It should be sendable because is used in the context for the strategy +/// and strategy is used by the use cases which can't store any mutable state. public final class RestClient: @unchecked Sendable, RestInterface where R.Server == S { diff --git a/catowseriOS/catowser.xcodeproj/project.pbxproj b/catowseriOS/catowser.xcodeproj/project.pbxproj index c0e18a91..eec738c8 100644 --- a/catowseriOS/catowser.xcodeproj/project.pbxproj +++ b/catowseriOS/catowser.xcodeproj/project.pbxproj @@ -121,6 +121,7 @@ 755E10A527CA3FC300D90AD0 /* SpecificEnumFeatures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755E10A427CA3FC300D90AD0 /* SpecificEnumFeatures.swift */; }; 755E10B727CA81B600D90AD0 /* SpecificApplicationEnumFeatures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755E10B627CA81B600D90AD0 /* SpecificApplicationEnumFeatures.swift */; }; 7570F7462B5C07E90066ABF1 /* UseCaseFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7570F7452B5C07E90066ABF1 /* UseCaseFactory.swift */; }; + 759E3E4D2C863B3D00245B44 /* Atomics in Frameworks */ = {isa = PBXBuildFile; productRef = 759E3E4C2C863B3D00245B44 /* Atomics */; }; 75A764302B64BBB100D9D158 /* SiteNavigationProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A510F622424ADC30070503C /* SiteNavigationProtocols.swift */; }; 75A764312B64BBDF00D9D158 /* WebViewNavigatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A8F038A293FA96200237DFF /* WebViewNavigatable.swift */; }; 75A764322B64BC3800D9D158 /* SiteNavigationComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A8F0390293FABD000237DFF /* SiteNavigationComponent.swift */; }; @@ -1105,6 +1106,7 @@ 8A5E6C83290B0E6C0034A78B /* AutoMockable.framework in Frameworks */, 8AD4F51C28F49219009C4BDC /* ReactiveSwift in Frameworks */, 8ABF504427CC1A64007C6427 /* FeaturesFlagsKit.framework in Frameworks */, + 759E3E4D2C863B3D00245B44 /* Atomics in Frameworks */, 8A27715A268E467500D8878C /* BrowserNetworking.framework in Frameworks */, 8AD4F52028F49303009C4BDC /* Alamofire in Frameworks */, 8AF4337C248EBB2A0047452B /* CssParser.framework in Frameworks */, @@ -2454,6 +2456,7 @@ 8AD4F51B28F49219009C4BDC /* ReactiveSwift */, 8AD4F51D28F492DC009C4BDC /* AlamofireImage */, 8AD4F51F28F49303009C4BDC /* Alamofire */, + 759E3E4C2C863B3D00245B44 /* Atomics */, ); productName = catowser; productReference = 8435A4971EED5E700020102F /* Cotton Dev.app */; @@ -2793,6 +2796,7 @@ 8A03F44028F4769B00E4A296 /* XCRemoteSwiftPackageReference "ReactiveSwift" */, 8A03F45828F47ACB00E4A296 /* XCRemoteSwiftPackageReference "SwiftSoup" */, 8A0E4D5829133E77008F7964 /* XCRemoteSwiftPackageReference "SwiftyMocky" */, + 759E3E4B2C863B3D00245B44 /* XCRemoteSwiftPackageReference "swift-atomics" */, ); productRefGroup = 8435A4981EED5E700020102F /* Products */; projectDirPath = ""; @@ -5020,6 +5024,14 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + 759E3E4B2C863B3D00245B44 /* XCRemoteSwiftPackageReference "swift-atomics" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "git@github.com:apple/swift-atomics.git"; + requirement = { + kind = exactVersion; + version = 1.2.0; + }; + }; 8A03F44028F4769B00E4A296 /* XCRemoteSwiftPackageReference "ReactiveSwift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/ReactiveCocoa/ReactiveSwift"; @@ -5071,6 +5083,11 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 759E3E4C2C863B3D00245B44 /* Atomics */ = { + isa = XCSwiftPackageProductDependency; + package = 759E3E4B2C863B3D00245B44 /* XCRemoteSwiftPackageReference "swift-atomics" */; + productName = Atomics; + }; 8A03F45928F47ACB00E4A296 /* SwiftSoup */ = { isa = XCSwiftPackageProductDependency; package = 8A03F45828F47ACB00E4A296 /* XCRemoteSwiftPackageReference "SwiftSoup" */; diff --git a/catowseriOS/catowser.xcworkspace/xcshareddata/swiftpm/Package.resolved b/catowseriOS/catowser.xcworkspace/xcshareddata/swiftpm/Package.resolved index 65a0b8c6..7c8d6f0d 100644 --- a/catowseriOS/catowser.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/catowseriOS/catowser.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "29574935b175a0ed7b4cd37b499db86e4bbe4391b57214b44fdfa51e5b2ad0b7", + "originHash" : "3a477979511422683063c0d7fbd3d91a7eaf00ba30f60e08688793f78eed17d9", "pins" : [ { "identity" : "aexml", @@ -91,6 +91,15 @@ "version" : "0.10.1" } }, + { + "identity" : "swift-atomics", + "kind" : "remoteSourceControl", + "location" : "git@github.com:apple/swift-atomics.git", + "state" : { + "revision" : "cd142fd2f64be2100422d658e7411e39489da985", + "version" : "1.2.0" + } + }, { "identity" : "swiftsoup", "kind" : "remoteSourceControl", diff --git a/catowseriOS/catowser/DB/Database.swift b/catowseriOS/catowser/DB/Database.swift index a85c1beb..062d323c 100644 --- a/catowseriOS/catowser/DB/Database.swift +++ b/catowseriOS/catowser/DB/Database.swift @@ -6,6 +6,7 @@ // Copyright © 2020 Cotton/Catowser Andrei Ermoshin. All rights reserved. // +import Atomics import Foundation import CoreData @@ -29,7 +30,7 @@ final class Database: Sendable { /// A read-only flag indicating if the persistent store is loaded. /// Can be nonisolated unsafe, because it is a Scalar bool type. - private(set) nonisolated(unsafe) var isStoreLoaded = false + let isStoreLoaded = ManagedAtomic(false) /// The managed object context associated with the main queue (read-only). /// To perform tasks on a private background queue see @@ -65,14 +66,14 @@ final class Database: Sendable { /// Default is false. /// /// Can be nonisolated unsafe, because it is a Scalar bool type. - private nonisolated(unsafe) var isReadOnly = false + private let isReadOnly = ManagedAtomic(false) /// A flag that indicates whether the store is added asynchronously. /// Set this value before loading the persistent store. /// Default is true. /// /// Can be nonisolated unsafe, because it is a Scalar bool type. - private nonisolated(unsafe) var shouldAddStoreAsynchronously = false + private let shouldAddStoreAsynchronously = ManagedAtomic(false) /// A flag that indicates whether the store should be migrated /// automatically if the store model version does not match the @@ -81,7 +82,7 @@ final class Database: Sendable { /// Default is true. /// /// Can be nonisolated unsafe, because it is a Scalar bool type. - private nonisolated(unsafe) var shouldMigrateStoreAutomatically = true + private let shouldMigrateStoreAutomatically = ManagedAtomic(true) /// A flag that indicates whether a mapping model should be inferred /// when migrating a store. @@ -89,7 +90,7 @@ final class Database: Sendable { /// Default is true. /// /// Can be nonisolated unsafe, because it is a Scalar bool type. - private nonisolated(unsafe) var shouldInferMappingModelAutomatically = true + private let shouldInferMappingModelAutomatically = ManagedAtomic(true) /// Creates and returns a `CoreDataController` object. This is the designated /// initializer for the class. It creates the managed object model, @@ -184,10 +185,10 @@ final class Database: Sendable { private func storeDescription(with url: URL) -> NSPersistentStoreDescription { let description = NSPersistentStoreDescription(url: url) - description.shouldMigrateStoreAutomatically = shouldMigrateStoreAutomatically - description.shouldInferMappingModelAutomatically = shouldInferMappingModelAutomatically - description.shouldAddStoreAsynchronously = shouldAddStoreAsynchronously - description.isReadOnly = isReadOnly + description.shouldMigrateStoreAutomatically = shouldMigrateStoreAutomatically.load(ordering: .relaxed) + description.shouldInferMappingModelAutomatically = shouldInferMappingModelAutomatically.load(ordering: .relaxed) + description.shouldAddStoreAsynchronously = shouldAddStoreAsynchronously.load(ordering: .relaxed) + description.isReadOnly = isReadOnly.load(ordering: .relaxed) return description } @@ -212,7 +213,7 @@ final class Database: Sendable { } } // should be called only if no errors were thrown on libe above - isStoreLoaded = true + isStoreLoaded.store(true, ordering: .relaxed) persistentContainer.viewContext.automaticallyMergesChangesFromParent = true return result } diff --git a/catowseriOS/catowser/Utils/FaviconImageViewable.swift b/catowseriOS/catowser/Utils/FaviconImageViewable.swift index 86c5d6b4..8c84ecb3 100644 --- a/catowseriOS/catowser/Utils/FaviconImageViewable.swift +++ b/catowseriOS/catowser/Utils/FaviconImageViewable.swift @@ -10,7 +10,7 @@ import UIKit import CottonBase import FeaturesFlagsKit -protocol FaviconImageViewable: AnyObject { +@MainActor protocol FaviconImageViewable: AnyObject { var faviconImageView: UIImageView { get } func reloadImageWith(_ site: Site, _ useDoH: Bool) async From fb2c72589f8f1d2a169646f6ea82905c15bf2249 Mon Sep 17 00:00:00 2001 From: Andrei Ermoshin Date: Tue, 3 Sep 2024 18:47:01 +0300 Subject: [PATCH 31/32] Improve database class --- .../catowser/BrowserContent/WebView/WebViewController.swift | 3 ++- catowseriOS/catowser/DB/Database.swift | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/catowseriOS/catowser/BrowserContent/WebView/WebViewController.swift b/catowseriOS/catowser/BrowserContent/WebView/WebViewController.swift index aed4fd4d..57b7989e 100644 --- a/catowseriOS/catowser/BrowserContent/WebView/WebViewController.swift +++ b/catowseriOS/catowser/BrowserContent/WebView/WebViewController.swift @@ -19,8 +19,9 @@ import ReactiveSwift #endif import CottonData -/// Can't retroactivly mark web view as sendable, it is a system protocol. +/// Can't retroactivly mark web view as sendable, it is a system type and protocol. extension WKWebView: @unchecked Sendable { } + /// Can be retroactive because Web view Apple devs don't know about JavaScriptEvaluateble protocol. extension WKWebView: @retroactive JavaScriptEvaluateble { public func evaluateJavaScriptV2( diff --git a/catowseriOS/catowser/DB/Database.swift b/catowseriOS/catowser/DB/Database.swift index 062d323c..4ee74d4a 100644 --- a/catowseriOS/catowser/DB/Database.swift +++ b/catowseriOS/catowser/DB/Database.swift @@ -204,16 +204,15 @@ final class Database: Sendable { continuation.resume(with: .failure(CottonError.zombieSelf)) return } - persistentContainer.loadPersistentStores { (_, error) in + persistentContainer.loadPersistentStores { [weak self] (_, error) in if let error { continuation.resume(with: .failure(error)) } else { + self?.isStoreLoaded.store(true, ordering: .relaxed) continuation.resume(with: .success(())) } } } - // should be called only if no errors were thrown on libe above - isStoreLoaded.store(true, ordering: .relaxed) persistentContainer.viewContext.automaticallyMergesChangesFromParent = true return result } From 5899128dd76a103cfaaf67079ff116792a01f696 Mon Sep 17 00:00:00 2001 From: Andrei Ermoshin Date: Wed, 11 Sep 2024 07:38:08 +0300 Subject: [PATCH 32/32] Xcode 16 RC fixes --- .../SearchEngines/SearchEngine.swift | 3 +-- .../catowser/Downloads/FilesGridViewController.swift | 4 ++-- catowseriOS/catowser/Extensions/Site+Extension.swift | 2 +- catowseriOS/catowser/Menu/AppAsyncApiTypeModel.swift | 4 ++-- catowseriOS/catowser/Menu/TabAddPositionsModel.swift | 4 ++-- .../catowser/Utils/FaviconImageViewable.swift | 2 +- .../WebTabs/TabsPreviewsViewController.swift | 12 +++++------- 7 files changed, 14 insertions(+), 17 deletions(-) diff --git a/catowseriOS/BrowserNetworking/SearchEngines/SearchEngine.swift b/catowseriOS/BrowserNetworking/SearchEngines/SearchEngine.swift index bd7b8e96..3040c55e 100644 --- a/catowseriOS/BrowserNetworking/SearchEngines/SearchEngine.swift +++ b/catowseriOS/BrowserNetworking/SearchEngines/SearchEngine.swift @@ -6,11 +6,10 @@ // Copyright © 2019 Cotton/Catowser Andrei Ermoshin. All rights reserved. // -import Foundation import CottonBase /// Alamofire's http method type can't be retroactivly silenced because sendable is a system protocol. -extension CottonBase.HTTPMethod: @unchecked Sendable { } +extension CottonBase.HTTPMethod: @unchecked @retroactive Sendable { } /// The model for OpenSearch format URL public struct SearchEngine: Sendable { diff --git a/catowseriOS/catowser/Downloads/FilesGridViewController.swift b/catowseriOS/catowser/Downloads/FilesGridViewController.swift index 08caba5f..a0eb0a13 100644 --- a/catowseriOS/catowser/Downloads/FilesGridViewController.swift +++ b/catowseriOS/catowser/Downloads/FilesGridViewController.swift @@ -150,7 +150,7 @@ extension FilesGridViewController: FileDownloadViewDelegate { /// Declaring following properties here, because type and protocol are from different frameworks. /// So, this place is neutral. -extension InstagramVideoNode: Downloadable { +extension InstagramVideoNode: @retroactive Downloadable { public var url: URL { return videoUrl } @@ -160,7 +160,7 @@ extension InstagramVideoNode: Downloadable { } } -extension HTMLVideoTag: Downloadable { +extension HTMLVideoTag: @retroactive Downloadable { public var url: URL { return src } diff --git a/catowseriOS/catowser/Extensions/Site+Extension.swift b/catowseriOS/catowser/Extensions/Site+Extension.swift index 9d43dca6..fd464343 100644 --- a/catowseriOS/catowser/Extensions/Site+Extension.swift +++ b/catowseriOS/catowser/Extensions/Site+Extension.swift @@ -72,7 +72,7 @@ extension Site { /// `The purpose of Identifiable is to distinguish the identity of an entity from the state of an entity.` /// https://github.com/apple/swift-evolution/blob/main/proposals/0261-identifiable.md#concrete-conformances /// So, this shouldn't be similar to Hashable impl and it doesn't require to combine all the properites in one id value. -extension Site: Identifiable { +extension Site: @retroactive Identifiable { public var id: String { // So, it is partly depencs on a state urlInfo.platformURL.absoluteString diff --git a/catowseriOS/catowser/Menu/AppAsyncApiTypeModel.swift b/catowseriOS/catowser/Menu/AppAsyncApiTypeModel.swift index b1befc16..f47496cd 100644 --- a/catowseriOS/catowser/Menu/AppAsyncApiTypeModel.swift +++ b/catowseriOS/catowser/Menu/AppAsyncApiTypeModel.swift @@ -19,7 +19,7 @@ extension BaseListViewModelImpl where EnumDataSourceType == AsyncApiType { } } -extension AsyncApiType: CustomStringConvertible { +extension AsyncApiType: @retroactive CustomStringConvertible { public var description: String { let key: String @@ -35,7 +35,7 @@ extension AsyncApiType: CustomStringConvertible { } } -extension AsyncApiType: Identifiable { +extension AsyncApiType: @retroactive Identifiable { public var id: RawValue { return self.rawValue } diff --git a/catowseriOS/catowser/Menu/TabAddPositionsModel.swift b/catowseriOS/catowser/Menu/TabAddPositionsModel.swift index 37377644..d91cf906 100644 --- a/catowseriOS/catowser/Menu/TabAddPositionsModel.swift +++ b/catowseriOS/catowser/Menu/TabAddPositionsModel.swift @@ -20,7 +20,7 @@ extension BaseListViewModelImpl where EnumDataSourceType == AddedTabPosition { /// Declare string representation for CoreBrowser enum /// in host app to use localized strings. -extension AddedTabPosition: CustomStringConvertible { +extension AddedTabPosition: @retroactive CustomStringConvertible { public var description: String { let key: String @@ -34,7 +34,7 @@ extension AddedTabPosition: CustomStringConvertible { } } -extension AddedTabPosition: Identifiable { +extension AddedTabPosition: @retroactive Identifiable { public var id: RawValue { return self.rawValue } diff --git a/catowseriOS/catowser/Utils/FaviconImageViewable.swift b/catowseriOS/catowser/Utils/FaviconImageViewable.swift index 8c84ecb3..a9ade84d 100644 --- a/catowseriOS/catowser/Utils/FaviconImageViewable.swift +++ b/catowseriOS/catowser/Utils/FaviconImageViewable.swift @@ -37,6 +37,6 @@ extension FaviconImageViewable { default: return } - await faviconImageView.updateImage(from: source) + faviconImageView.updateImage(from: source) } } diff --git a/catowseriOS/catowser/WebTabs/TabsPreviewsViewController.swift b/catowseriOS/catowser/WebTabs/TabsPreviewsViewController.swift index aa21e838..fe76909b 100644 --- a/catowseriOS/catowser/WebTabs/TabsPreviewsViewController.swift +++ b/catowseriOS/catowser/WebTabs/TabsPreviewsViewController.swift @@ -205,13 +205,11 @@ where C.R == TabsScreenRoute { // MARK: - private functions @objc func addTabPressed() { - Task { - coordinator?.showNext(.addTab) - // on previews screen will make new added tab always selected - // same behaviour has Safari and Firefox - if await DefaultTabProvider.shared.selected { - coordinator?.stop() - } + coordinator?.showNext(.addTab) + // on previews screen will make new added tab always selected + // same behaviour has Safari and Firefox + if DefaultTabProvider.shared.selected { + coordinator?.stop() } } }