From 02c9d7258af0819574d1f1ff788d088e27de9bc6 Mon Sep 17 00:00:00 2001 From: Carson Ramsden Date: Wed, 7 Jan 2026 13:34:16 -0700 Subject: [PATCH 1/3] Remove @Sendable from closures marked as @MainActor --- .../Utilities/DispatchQueueHelper.swift | 4 +-- .../Utilities/DispatchQueueInterface.swift | 4 +-- BrowserKit/Sources/MenuKit/MenuElement.swift | 4 +-- BrowserKit/Sources/Redux/Reducer.swift | 2 +- .../Sources/Shared/UIDeviceDetails.swift | 2 +- .../WebEngine/WKWebview/WKEngineWebView.swift | 4 +-- .../Mock/MockWKEngineWebView.swift | 2 +- firefox-ios/Client/AccountSyncHandler.swift | 2 +- .../Client/Application/WebServer.swift | 2 +- .../CreditCardInputViewModel.swift | 2 +- .../WebContextMenuActionsProvider.swift | 6 ++--- ...owserViewController+WebViewDelegates.swift | 26 +++++++++---------- .../BrowserWebUIDelegate.swift | 8 +++--- .../DownloadHelper/DownloadHelper.swift | 2 +- .../Browser/MainMenuActionHelper.swift | 6 ++--- .../Frontend/Browser/RelayController.swift | 2 +- .../SearchEngines/SearchEngineProvider.swift | 2 +- .../TabProviderAdapter.swift | 4 +-- .../SectionHeader/LabelButtonHeaderView.swift | 2 +- .../Library/ClearHistorySheetProvider.swift | 6 ++--- .../HistoryPanel/HistoryPanelViewModel.swift | 2 +- .../PasswordManagerViewModel.swift | 4 +-- .../PasswordDetailViewController.swift | 2 +- .../SingleActionViewModel.swift | 12 ++++----- firefox-ios/Client/TabManagement/Tab.swift | 4 +-- .../Client/Utils/MainThreadThrottler.swift | 4 +-- .../ShareTo/InitialViewController.swift | 2 +- .../FirefoxAccountSignInViewController.swift | 2 +- .../RustFxA/FxAWebViewController.swift | 2 +- firefox-ios/Storage/Queue.swift | 2 +- firefox-ios/Storage/SQL/SQLiteQueue.swift | 2 +- .../Homepage/Mock/MockThrottler.swift | 2 +- .../Tests/ClientTests/Mocks/MockProfile.swift | 2 +- .../Mocks/MockTabProviderProtocol.swift | 2 +- .../ClientTests/Mocks/MockTabWebView.swift | 2 +- 35 files changed, 69 insertions(+), 69 deletions(-) diff --git a/BrowserKit/Sources/Common/Utilities/DispatchQueueHelper.swift b/BrowserKit/Sources/Common/Utilities/DispatchQueueHelper.swift index ae8e72f3fcc05..125f856b83db2 100644 --- a/BrowserKit/Sources/Common/Utilities/DispatchQueueHelper.swift +++ b/BrowserKit/Sources/Common/Utilities/DispatchQueueHelper.swift @@ -7,7 +7,7 @@ import Foundation // Only push the task async if we are not already on the main thread. // Unless you want another event to fire before your work happens. // This is better than using DispatchQueue.main.async to ensure main thread -public func ensureMainThread(execute work: @escaping @MainActor @Sendable @convention(block) () -> Swift.Void) { +public func ensureMainThread(execute work: @escaping @MainActor @convention(block) () -> Swift.Void) { if Thread.isMainThread { MainActor.assumeIsolated { work() @@ -19,7 +19,7 @@ public func ensureMainThread(execute work: @escaping @MainActor @Sendable @conve } } -public func ensureMainThread(execute work: @escaping @MainActor @Sendable () -> T) { +public func ensureMainThread(execute work: @escaping @MainActor () -> T) { if Thread.isMainThread { MainActor.assumeIsolated { _ = work() diff --git a/BrowserKit/Sources/Common/Utilities/DispatchQueueInterface.swift b/BrowserKit/Sources/Common/Utilities/DispatchQueueInterface.swift index d6f7377aa6359..7a4861a3ca876 100644 --- a/BrowserKit/Sources/Common/Utilities/DispatchQueueInterface.swift +++ b/BrowserKit/Sources/Common/Utilities/DispatchQueueInterface.swift @@ -10,7 +10,7 @@ public protocol DispatchQueueInterface: Sendable { flags: DispatchWorkItemFlags, execute work: @escaping @Sendable @convention(block) () -> Void) - func ensureMainThread(execute work: @escaping @MainActor @Sendable @convention(block) () -> Swift.Void) + func ensureMainThread(execute work: @escaping @MainActor @convention(block) () -> Swift.Void) func asyncAfter(deadline: DispatchTime, execute: DispatchWorkItem) @@ -35,7 +35,7 @@ extension DispatchQueueInterface { asyncAfter(deadline: deadline, qos: qos, flags: flags, execute: work) } - public func ensureMainThread(execute work: @escaping @MainActor @Sendable @convention(block) () -> Swift.Void) { + public func ensureMainThread(execute work: @escaping @MainActor @convention(block) () -> Swift.Void) { if Thread.isMainThread { MainActor.assumeIsolated { work() diff --git a/BrowserKit/Sources/MenuKit/MenuElement.swift b/BrowserKit/Sources/MenuKit/MenuElement.swift index 47d5ae448cc1c..15bf639bcdd00 100644 --- a/BrowserKit/Sources/MenuKit/MenuElement.swift +++ b/BrowserKit/Sources/MenuKit/MenuElement.swift @@ -17,7 +17,7 @@ public struct MenuElement: Equatable, Sendable { let a11yId: String let isOptional: Bool let infoTitle: String? - public let action: (@MainActor @Sendable () -> Void)? + public let action: (@MainActor () -> Void)? // We need this init as by default the init generated by the compiler // for the struct will be internal and can not be used outside of MenuKit @@ -34,7 +34,7 @@ public struct MenuElement: Equatable, Sendable { a11yId: String, isOptional: Bool = false, infoTitle: String? = nil, - action: (@MainActor @Sendable () -> Void)? + action: (@MainActor () -> Void)? ) { self.title = title self.description = description diff --git a/BrowserKit/Sources/Redux/Reducer.swift b/BrowserKit/Sources/Redux/Reducer.swift index bd3e74351c20f..1ae42a1a6efdf 100644 --- a/BrowserKit/Sources/Redux/Reducer.swift +++ b/BrowserKit/Sources/Redux/Reducer.swift @@ -6,4 +6,4 @@ import Foundation /// Provide pure functions, that based on the current `Action` and the current app `State`, /// create a new app state. `Reducers` are the only place in which the application state should be modified. -public typealias Reducer = @Sendable @MainActor (State, Action) -> State +public typealias Reducer = @MainActor (State, Action) -> State diff --git a/BrowserKit/Sources/Shared/UIDeviceDetails.swift b/BrowserKit/Sources/Shared/UIDeviceDetails.swift index fcc96df866374..a06fbf88f4b0a 100644 --- a/BrowserKit/Sources/Shared/UIDeviceDetails.swift +++ b/BrowserKit/Sources/Shared/UIDeviceDetails.swift @@ -36,7 +36,7 @@ public struct UIDeviceDetails { /// **DO NOT USE THIS METHOD ELSEWHERE IN THE CODE BASE.** /// This is a workaround to access unchanging `UIDevice.current` values that Apple has needlessly main actor-isolated. private static func getMainThreadDataSynchronously( - work: @MainActor @Sendable () -> (T) + work: @MainActor () -> (T) ) -> T { if Thread.isMainThread { MainActor.assumeIsolated { diff --git a/BrowserKit/Sources/WebEngine/WKWebview/WKEngineWebView.swift b/BrowserKit/Sources/WebEngine/WKWebview/WKEngineWebView.swift index 41e7839cf6b61..f7347b6d9b202 100644 --- a/BrowserKit/Sources/WebEngine/WKWebview/WKEngineWebView.swift +++ b/BrowserKit/Sources/WebEngine/WKWebview/WKEngineWebView.swift @@ -75,7 +75,7 @@ protocol WKEngineWebView: UIView { _ javaScript: String, in frame: WKFrameInfo?, in contentWorld: WKContentWorld, - completionHandler: (@MainActor @Sendable (Result) -> Void)? + completionHandler: (@MainActor (Result) -> Void)? ) func close() @@ -86,7 +86,7 @@ extension WKEngineWebView { _ javaScript: String, in frame: WKFrameInfo? = nil, in contentWorld: WKContentWorld, - completionHandler: (@MainActor @Sendable (Result) -> Void)? = nil + completionHandler: (@MainActor (Result) -> Void)? = nil ) { evaluateJavaScript(javaScript, in: frame, diff --git a/BrowserKit/Tests/WebEngineTests/Mock/MockWKEngineWebView.swift b/BrowserKit/Tests/WebEngineTests/Mock/MockWKEngineWebView.swift index 561b564489d46..c846fe8ad7835 100644 --- a/BrowserKit/Tests/WebEngineTests/Mock/MockWKEngineWebView.swift +++ b/BrowserKit/Tests/WebEngineTests/Mock/MockWKEngineWebView.swift @@ -136,7 +136,7 @@ class MockWKEngineWebView: UIView, WKEngineWebView { func evaluateJavaScript(_ javaScript: String, in frame: WKFrameInfo?, in contentWorld: WKContentWorld, - completionHandler: (@MainActor @Sendable (Result) -> Void)?) { + completionHandler: (@MainActor (Result) -> Void)?) { evaluateJavaScriptCalled += 1 savedJavaScript = javaScript diff --git a/firefox-ios/Client/AccountSyncHandler.swift b/firefox-ios/Client/AccountSyncHandler.swift index 383d54b49b562..689abcaa23b3f 100644 --- a/firefox-ios/Client/AccountSyncHandler.swift +++ b/firefox-ios/Client/AccountSyncHandler.swift @@ -16,7 +16,7 @@ final class Debouncer { self.delay = delay } - func call(action: @escaping @MainActor @Sendable () -> Void) { + func call(action: @escaping @MainActor () -> Void) { task?.cancel() let nanos = UInt64(delay) * nanosecondsPerSecond diff --git a/firefox-ios/Client/Application/WebServer.swift b/firefox-ios/Client/Application/WebServer.swift index 5f52269ea6153..3f0118c3662ac 100644 --- a/firefox-ios/Client/Application/WebServer.swift +++ b/firefox-ios/Client/Application/WebServer.swift @@ -56,7 +56,7 @@ final class WebServer: WebServerProtocol, @unchecked Sendable { _ method: String, module: String, resource: String, - handler: @escaping @Sendable @MainActor ( + handler: @escaping @MainActor ( _ request: GCDWebServerRequest?, _ responseCompletion: @escaping @Sendable (GCDWebServerResponse?) -> Void ) -> Void diff --git a/firefox-ios/Client/Frontend/Autofill/CreditCard/CreditCardSettingsView/CreditCardInputViewModel.swift b/firefox-ios/Client/Frontend/Autofill/CreditCard/CreditCardSettingsView/CreditCardInputViewModel.swift index 469dbd45cd391..b816eae822057 100644 --- a/firefox-ios/Client/Frontend/Autofill/CreditCard/CreditCardSettingsView/CreditCardInputViewModel.swift +++ b/firefox-ios/Client/Frontend/Autofill/CreditCard/CreditCardSettingsView/CreditCardInputViewModel.swift @@ -256,7 +256,7 @@ final class CreditCardInputViewModel: ObservableObject, @unchecked Sendable { } func removeCreditCard(creditCard: CreditCard?, - completion: @escaping @MainActor @Sendable (CreditCardModifiedStatus, Bool) -> Void) { + completion: @escaping @MainActor (CreditCardModifiedStatus, Bool) -> Void) { guard let currentCreditCard = creditCard, !currentCreditCard.guid.isEmpty else { ensureMainThread { diff --git a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Actions/WebContextMenuActionsProvider.swift b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Actions/WebContextMenuActionsProvider.swift index a4b3a9f14391f..3c7bf72b58451 100644 --- a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Actions/WebContextMenuActionsProvider.swift +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Actions/WebContextMenuActionsProvider.swift @@ -28,7 +28,7 @@ class WebContextMenuActionsProvider { } @MainActor - func addOpenInNewTab(url: URL, currentTab: Tab, addTab: @escaping @MainActor @Sendable (URL, Bool, Tab) -> Void) { + func addOpenInNewTab(url: URL, currentTab: Tab, addTab: @escaping @MainActor (URL, Bool, Tab) -> Void) { actions.append( UIAction( title: .ContextMenuOpenInNewTab, @@ -41,7 +41,7 @@ class WebContextMenuActionsProvider { } @MainActor - func addOpenInNewPrivateTab(url: URL, currentTab: Tab, addTab: @escaping @MainActor @Sendable (URL, Bool, Tab) -> Void) { + func addOpenInNewPrivateTab(url: URL, currentTab: Tab, addTab: @escaping @MainActor (URL, Bool, Tab) -> Void) { actions.append( UIAction( title: .ContextMenuOpenInNewPrivateTab, @@ -161,7 +161,7 @@ class WebContextMenuActionsProvider { @MainActor func addSaveImage(url: URL, getImageData: @escaping (URL, @Sendable @escaping (Data) -> Void) -> Void, - writeToPhotoAlbum: @escaping @Sendable @MainActor (UIImage) -> Void) { + writeToPhotoAlbum: @escaping @MainActor (UIImage) -> Void) { actions.append(UIAction( title: .ContextMenuSaveImage, identifier: UIAction.Identifier("linkContextMenu.saveImage") diff --git a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+WebViewDelegates.swift b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+WebViewDelegates.swift index f40e9497877d0..cf5e568db42a1 100644 --- a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+WebViewDelegates.swift +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+WebViewDelegates.swift @@ -76,7 +76,7 @@ extension BrowserViewController: WKUIDelegate { private func handleJavaScriptAlert( _ alert: T, for webView: WKWebView, - spamCallback: @escaping @MainActor @Sendable () -> Void + spamCallback: @escaping @MainActor () -> Void ) { if jsAlertExceedsSpamLimits(webView) { handleSpammedJSAlert(spamCallback) @@ -95,7 +95,7 @@ extension BrowserViewController: WKUIDelegate { _ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, - completionHandler: @escaping @MainActor @Sendable () -> Void + completionHandler: @escaping @MainActor () -> Void ) { let messageAlert = MessageAlert(message: message, frame: frame, @@ -110,7 +110,7 @@ extension BrowserViewController: WKUIDelegate { _ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, - completionHandler: @escaping @MainActor @Sendable (Bool) -> Void + completionHandler: @escaping @MainActor (Bool) -> Void ) { let confirmAlert = ConfirmPanelAlert(message: message, frame: frame) { confirm in self.logger.log("JavaScript confirm panel was completed with result: \(confirm)", level: .info, category: .webview) @@ -127,7 +127,7 @@ extension BrowserViewController: WKUIDelegate { runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, - completionHandler: @escaping @MainActor @Sendable (String?) -> Void + completionHandler: @escaping @MainActor (String?) -> Void ) { let textInputAlert = TextInputAlert(message: prompt, frame: frame, defaultText: defaultText) { input in self.logger.log("JavaScript text input panel was completed with input", level: .info, category: .webview) @@ -153,7 +153,7 @@ extension BrowserViewController: WKUIDelegate { func webView( _ webView: WKWebView, contextMenuConfigurationForElement elementInfo: WKContextMenuElementInfo, - completionHandler: @escaping @MainActor @Sendable (UIContextMenuConfiguration?) -> Void + completionHandler: @escaping @MainActor (UIContextMenuConfiguration?) -> Void ) { guard let url = elementInfo.linkURL, let currentTab = tabManager.selectedTab, @@ -184,7 +184,7 @@ extension BrowserViewController: WKUIDelegate { requestMediaCapturePermissionFor origin: WKSecurityOrigin, initiatedByFrame frame: WKFrameInfo, type: WKMediaCaptureType, - decisionHandler: @escaping @MainActor @Sendable (WKPermissionDecision) -> Void + decisionHandler: @escaping @MainActor (WKPermissionDecision) -> Void ) { // If the tab isn't the selected one or we're on the homepage, do not show the media capture prompt guard tabManager.selectedTab?.webView === webView, !contentContainer.hasAnyHomepage else { @@ -197,7 +197,7 @@ extension BrowserViewController: WKUIDelegate { // MARK: - Helpers - private func handleSpammedJSAlert(_ callback: @escaping @MainActor @Sendable () -> Void) { + private func handleSpammedJSAlert(_ callback: @escaping @MainActor () -> Void) { // User is being spammed. Squelch alert. Note that we have to do this after // a delay to avoid JS that could spin the CPU endlessly. DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) { callback() } @@ -312,7 +312,7 @@ extension BrowserViewController: WKUIDelegate { func createActions(isPrivate: Bool, url: URL, - addTab: @escaping @MainActor @Sendable (URL, Bool, Tab) -> Void, + addTab: @escaping @MainActor (URL, Bool, Tab) -> Void, title: String?, image: URL?, currentTab: Tab, @@ -705,7 +705,7 @@ extension BrowserViewController: WKNavigationDelegate { func webView( _ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, - decisionHandler: @escaping @MainActor @Sendable (WKNavigationResponsePolicy) -> Void + decisionHandler: @escaping @MainActor (WKNavigationResponsePolicy) -> Void ) { let response = navigationResponse.response let responseURL = response.url @@ -928,7 +928,7 @@ extension BrowserViewController: WKNavigationDelegate { // web view don't invoke another download. pendingDownloadWebView = nil - let downloadAction: @Sendable @MainActor (HTTPDownload) -> Void = { [weak self] download in + let downloadAction: @MainActor (HTTPDownload) -> Void = { [weak self] download in self?.downloadQueue.enqueue(download) } @@ -1053,7 +1053,7 @@ extension BrowserViewController: WKNavigationDelegate { func webView( _ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, - completionHandler: @escaping @MainActor @Sendable (URLSession.AuthChallengeDisposition, URLCredential?) -> Void + completionHandler: @escaping @MainActor (URLSession.AuthChallengeDisposition, URLCredential?) -> Void ) { guard challenge.protectionSpace.authenticationMethod != NSURLAuthenticationMethodServerTrust else { handleServerTrust( @@ -1232,7 +1232,7 @@ private extension BrowserViewController { // Use for sms and mailto, which do not show a confirmation before opening. func showExternalAlert(withText text: String, - completion: @escaping @MainActor @Sendable (UIAlertAction) -> Void) { + completion: @escaping @MainActor (UIAlertAction) -> Void) { let alert = UIAlertController(title: nil, message: text, preferredStyle: .alert) @@ -1329,7 +1329,7 @@ private extension BrowserViewController { func handleServerTrust( challenge: URLAuthenticationChallenge, dispatchQueue: DispatchQueueInterface, - completionHandler: @escaping @MainActor @Sendable (URLSession.AuthChallengeDisposition, URLCredential?) -> Void + completionHandler: @escaping @MainActor (URLSession.AuthChallengeDisposition, URLCredential?) -> Void ) { dispatchQueue.async { // If this is a certificate challenge, see if the certificate has previously been diff --git a/firefox-ios/Client/Frontend/Browser/BrowserViewController/WebEngineIntegration/BrowserWebUIDelegate.swift b/firefox-ios/Client/Frontend/Browser/BrowserViewController/WebEngineIntegration/BrowserWebUIDelegate.swift index 8e8e3730fdc20..dccdafd4bae06 100644 --- a/firefox-ios/Client/Frontend/Browser/BrowserViewController/WebEngineIntegration/BrowserWebUIDelegate.swift +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/WebEngineIntegration/BrowserWebUIDelegate.swift @@ -53,7 +53,7 @@ class BrowserWebUIDelegate: NSObject, WKUIDelegate { _ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, - completionHandler: @escaping @MainActor @Sendable (Bool) -> Void + completionHandler: @escaping @MainActor (Bool) -> Void ) { legacyResponder?.webView?( webView, @@ -68,7 +68,7 @@ class BrowserWebUIDelegate: NSObject, WKUIDelegate { runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, - completionHandler: @escaping @MainActor @Sendable (String?) -> Void + completionHandler: @escaping @MainActor (String?) -> Void ) { legacyResponder?.webView?( webView, @@ -86,7 +86,7 @@ class BrowserWebUIDelegate: NSObject, WKUIDelegate { func webView( _ webView: WKWebView, contextMenuConfigurationForElement elementInfo: WKContextMenuElementInfo, - completionHandler: @escaping @MainActor @Sendable (UIContextMenuConfiguration?) -> Void + completionHandler: @escaping @MainActor (UIContextMenuConfiguration?) -> Void ) { legacyResponder?.webView?( webView, @@ -104,7 +104,7 @@ class BrowserWebUIDelegate: NSObject, WKUIDelegate { requestMediaCapturePermissionFor origin: WKSecurityOrigin, initiatedByFrame frame: WKFrameInfo, type: WKMediaCaptureType, - decisionHandler: @escaping @MainActor @Sendable (WKPermissionDecision) -> Void + decisionHandler: @escaping @MainActor (WKPermissionDecision) -> Void ) { legacyResponder?.webView?( webView, diff --git a/firefox-ios/Client/Frontend/Browser/DownloadHelper/DownloadHelper.swift b/firefox-ios/Client/Frontend/Browser/DownloadHelper/DownloadHelper.swift index 67290fa9e0d31..6c9f93e6bf341 100644 --- a/firefox-ios/Client/Frontend/Browser/DownloadHelper/DownloadHelper.swift +++ b/firefox-ios/Client/Frontend/Browser/DownloadHelper/DownloadHelper.swift @@ -62,7 +62,7 @@ class DownloadHelper: NSObject { @MainActor func downloadViewModel(windowUUID: WindowUUID, - okAction: @Sendable @MainActor @escaping (HTTPDownload) -> Void) -> PhotonActionSheetViewModel? { + okAction: @MainActor @escaping (HTTPDownload) -> Void) -> PhotonActionSheetViewModel? { var requestUrl = request.url if let url = requestUrl, url.scheme == "blob" { requestUrl = url.removeBlobFromUrl() diff --git a/firefox-ios/Client/Frontend/Browser/MainMenuActionHelper.swift b/firefox-ios/Client/Frontend/Browser/MainMenuActionHelper.swift index 5ba6a17470f49..30c544081fb4f 100644 --- a/firefox-ios/Client/Frontend/Browser/MainMenuActionHelper.swift +++ b/firefox-ios/Client/Frontend/Browser/MainMenuActionHelper.swift @@ -123,7 +123,7 @@ final class MainMenuActionHelper: PhotonActionSheetProtocol, } func getToolbarActions(navigationController: UINavigationController?, - completion: @escaping @Sendable @MainActor ([[PhotonRowActions]]) -> Void) { + completion: @escaping @MainActor ([[PhotonRowActions]]) -> Void) { var actions: [[PhotonRowActions]] = [] let firstMiscSection = getFirstMiscSection(navigationController) @@ -163,7 +163,7 @@ final class MainMenuActionHelper: PhotonActionSheetProtocol, /// Update data to show the proper menus related to the page /// - Parameter dataLoadingCompletion: Complete when the loading of data from the profile is done - private func updateData(dataLoadingCompletion: (@MainActor @Sendable () -> Void)? = nil) { + private func updateData(dataLoadingCompletion: (@MainActor () -> Void)? = nil) { var url: String? if let tabUrl = tabUrl, tabUrl.isReaderModeURL, let tabUrlDecoded = tabUrl.decodeReaderModeURL { @@ -541,7 +541,7 @@ final class MainMenuActionHelper: PhotonActionSheetProtocol, } private func syncMenuButton() -> PhotonRowActions? { - let action: @Sendable @MainActor (SingleActionViewModel) -> Void = { [weak self] action in + let action: @MainActor (SingleActionViewModel) -> Void = { [weak self] action in let fxaParams = FxALaunchParams(entrypoint: .browserMenu, query: [:]) let parameters = FxASignInViewParameters(launchParameters: fxaParams, flowType: .emailLoginFlow, diff --git a/firefox-ios/Client/Frontend/Browser/RelayController.swift b/firefox-ios/Client/Frontend/Browser/RelayController.swift index 157ff006c8aa7..5f9c01a597db1 100644 --- a/firefox-ios/Client/Frontend/Browser/RelayController.swift +++ b/firefox-ios/Client/Frontend/Browser/RelayController.swift @@ -7,7 +7,7 @@ import MozillaAppServices import Account import Shared -typealias RelayPopulateCompletion = @MainActor @Sendable (RelayMaskGenerationResult) -> Void +typealias RelayPopulateCompletion = @MainActor (RelayMaskGenerationResult) -> Void /// Describes public protocol for Relay component to track state and facilitate /// messaging between the BVC, keyboard accessory, and A~S Relay APIs. diff --git a/firefox-ios/Client/Frontend/Browser/SearchEngines/SearchEngineProvider.swift b/firefox-ios/Client/Frontend/Browser/SearchEngines/SearchEngineProvider.swift index 8d828a1c633fc..29a87a3944ed9 100644 --- a/firefox-ios/Client/Frontend/Browser/SearchEngines/SearchEngineProvider.swift +++ b/firefox-ios/Client/Frontend/Browser/SearchEngines/SearchEngineProvider.swift @@ -5,7 +5,7 @@ import UIKit import Common -typealias SearchEngineCompletion = @MainActor @Sendable (SearchEnginePrefs, [OpenSearchEngine]) -> Void +typealias SearchEngineCompletion = @MainActor (SearchEnginePrefs, [OpenSearchEngine]) -> Void protocol SearchEngineProvider: Sendable { /// Takes a list of custom search engines (added by the user) along with an ordered diff --git a/firefox-ios/Client/Frontend/Browser/TabScrollController/TabProviderAdapter.swift b/firefox-ios/Client/Frontend/Browser/TabScrollController/TabProviderAdapter.swift index f51439f047c46..f36d0b0a6ae8d 100644 --- a/firefox-ios/Client/Frontend/Browser/TabScrollController/TabProviderAdapter.swift +++ b/firefox-ios/Client/Frontend/Browser/TabScrollController/TabProviderAdapter.swift @@ -11,7 +11,7 @@ protocol TabProviderProtocol: AnyObject { var isFindInPageMode: Bool { get } var isLoading: Bool { get } // Pull to refresh related - var onLoadingStateChanged: (@MainActor @Sendable () -> Void)? { get set } + var onLoadingStateChanged: (@MainActor () -> Void)? { get set } func removePullToRefresh() func addPullToRefresh(onReload: @escaping () -> Void) func reloadPage() @@ -29,7 +29,7 @@ final class TabProviderAdapter: TabProviderProtocol { var isLoading: Bool { tab.isLoading } var scrollView: UIScrollView? { tab.webView?.scrollView } - var onLoadingStateChanged: (@MainActor @Sendable () -> Void)? { + var onLoadingStateChanged: (@MainActor () -> Void)? { get { tab.onWebViewLoadingStateChanged } set { tab.onWebViewLoadingStateChanged = newValue } } diff --git a/firefox-ios/Client/Frontend/Home/Homepage/SectionHeader/LabelButtonHeaderView.swift b/firefox-ios/Client/Frontend/Home/Homepage/SectionHeader/LabelButtonHeaderView.swift index a63ae2e22e285..d0e5040687434 100644 --- a/firefox-ios/Client/Frontend/Home/Homepage/SectionHeader/LabelButtonHeaderView.swift +++ b/firefox-ios/Client/Frontend/Home/Homepage/SectionHeader/LabelButtonHeaderView.swift @@ -94,7 +94,7 @@ class LabelButtonHeaderView: UICollectionReusableView, func configure( state: SectionHeaderConfiguration, - moreButtonAction: (@Sendable @MainActor (UIButton) -> Void)? = nil, + moreButtonAction: (@MainActor (UIButton) -> Void)? = nil, textColor: UIColor?, theme: Theme ) { diff --git a/firefox-ios/Client/Frontend/Library/ClearHistorySheetProvider.swift b/firefox-ios/Client/Frontend/Library/ClearHistorySheetProvider.swift index a3e1ec110cd50..20ebb79269648 100644 --- a/firefox-ios/Client/Frontend/Library/ClearHistorySheetProvider.swift +++ b/firefox-ios/Client/Frontend/Library/ClearHistorySheetProvider.swift @@ -51,7 +51,7 @@ class ClearHistorySheetProvider { func setupActions( for alert: UIAlertController, - didComplete: @MainActor @Sendable @escaping (HistoryDeletionUtilityDateOptions) -> Void + didComplete: @MainActor @escaping (HistoryDeletionUtilityDateOptions) -> Void ) { addDeleteSomeData(to: alert, didComplete: didComplete) addDeleteEverythingOption(to: alert, didComplete: didComplete) @@ -60,7 +60,7 @@ class ClearHistorySheetProvider { func addDeleteSomeData( to alert: UIAlertController, - didComplete: @MainActor @Sendable @escaping (HistoryDeletionUtilityDateOptions) -> Void + didComplete: @MainActor @escaping (HistoryDeletionUtilityDateOptions) -> Void ) { typealias DateOptions = HistoryDeletionUtilityDateOptions [ @@ -85,7 +85,7 @@ class ClearHistorySheetProvider { func addDeleteEverythingOption( to alert: UIAlertController, - didComplete: @MainActor @Sendable @escaping (HistoryDeletionUtilityDateOptions) -> Void + didComplete: @MainActor @escaping (HistoryDeletionUtilityDateOptions) -> Void ) { alert.addAction(UIAlertAction(title: .LibraryPanel.History.ClearHistorySheet.AllTimeOption, style: .destructive) { _ in diff --git a/firefox-ios/Client/Frontend/Library/HistoryPanel/HistoryPanelViewModel.swift b/firefox-ios/Client/Frontend/Library/HistoryPanel/HistoryPanelViewModel.swift index eda2dbff3d36f..86fba28a8f418 100644 --- a/firefox-ios/Client/Frontend/Library/HistoryPanel/HistoryPanelViewModel.swift +++ b/firefox-ios/Client/Frontend/Library/HistoryPanel/HistoryPanelViewModel.swift @@ -127,7 +127,7 @@ final class HistoryPanelViewModel: FeatureFlaggable, @unchecked Sendable { } } - func performSearch(term: String, completion: @MainActor @Sendable @escaping (Bool) -> Void) { + func performSearch(term: String, completion: @MainActor @escaping (Bool) -> Void) { isFetchInProgress = true profile.places.interruptReader() diff --git a/firefox-ios/Client/Frontend/PasswordManagement/PasswordManagerViewModel.swift b/firefox-ios/Client/Frontend/PasswordManagement/PasswordManagerViewModel.swift index b93acde1d8c5e..51c9bce3c1cdf 100644 --- a/firefox-ios/Client/Frontend/PasswordManagement/PasswordManagerViewModel.swift +++ b/firefox-ios/Client/Frontend/PasswordManagement/PasswordManagerViewModel.swift @@ -80,7 +80,7 @@ final class PasswordManagerViewModel { /// Searches SQLite database for logins that match query. /// Wraps the SQLiteLogins method to allow us to cancel it from our end. - func queryLogins(_ query: String, completion: @escaping @MainActor @Sendable ([Login]) -> Void) { + func queryLogins(_ query: String, completion: @escaping @MainActor ([Login]) -> Void) { loginProvider.searchLoginsWithQuery(query) { result in ensureMainThread { switch result { @@ -159,7 +159,7 @@ final class PasswordManagerViewModel { } } - public func save(loginRecord: LoginEntry, completion: @escaping @MainActor @Sendable (String?) -> Void) { + public func save(loginRecord: LoginEntry, completion: @escaping @MainActor (String?) -> Void) { loginProvider.addLogin(login: loginRecord, completionHandler: { result in ensureMainThread { switch result { diff --git a/firefox-ios/Client/Frontend/Settings/PasswordDetailViewController.swift b/firefox-ios/Client/Frontend/Settings/PasswordDetailViewController.swift index 1369038e62dfd..7a62aab332414 100644 --- a/firefox-ios/Client/Frontend/Settings/PasswordDetailViewController.swift +++ b/firefox-ios/Client/Frontend/Settings/PasswordDetailViewController.swift @@ -399,7 +399,7 @@ extension PasswordDetailViewController { } } - func onProfileDidFinishSyncing(completion: @escaping @MainActor @Sendable () -> Void) { + func onProfileDidFinishSyncing(completion: @escaping @MainActor () -> Void) { // Reload details after syncing. viewModel.profile.logins.getLogin(id: viewModel.login.id) { [weak self] result in DispatchQueue.main.async { [weak self] in diff --git a/firefox-ios/Client/Frontend/Widgets/PhotonActionSheet/SingleActionViewModel.swift b/firefox-ios/Client/Frontend/Widgets/PhotonActionSheet/SingleActionViewModel.swift index 3e82389e40b1b..ffe017b33614e 100644 --- a/firefox-ios/Client/Frontend/Widgets/PhotonActionSheet/SingleActionViewModel.swift +++ b/firefox-ios/Client/Frontend/Widgets/PhotonActionSheet/SingleActionViewModel.swift @@ -41,7 +41,7 @@ struct SingleActionViewModel: Sendable { let bold: Bool let tabCount: String? - let tapHandler: (@Sendable @MainActor (SingleActionViewModel) -> Void)? + let tapHandler: (@MainActor (SingleActionViewModel) -> Void)? let isEnabled: Bool // Used by toggles like night mode to switch tint color // Flip the cells for the main menu (hamburger menu) since content needs to appear at the bottom @@ -50,10 +50,10 @@ struct SingleActionViewModel: Sendable { let isFlipped: Bool // Enable title customization beyond what the interface provides, - let customRender: (@Sendable @MainActor (_ title: UILabel, _ contentView: UIView) -> Void)? + let customRender: (@MainActor (_ title: UILabel, _ contentView: UIView) -> Void)? // Enable height customization - let customHeight: (@Sendable @MainActor (SingleActionViewModel) -> CGFloat)? + let customHeight: (@MainActor (SingleActionViewModel) -> CGFloat)? // Normally the icon name is used, but if there is no icon, this is used. let accessibilityId: String? @@ -71,9 +71,9 @@ struct SingleActionViewModel: Sendable { bold: Bool? = false, tabCount: String? = nil, isFlipped: Bool = false, - tapHandler: (@Sendable @MainActor (SingleActionViewModel) -> Void)? = nil, - customRender: (@Sendable @MainActor (_ title: UILabel, _ contentView: UIView) -> Void)? = nil, - customHeight: (@Sendable @MainActor (SingleActionViewModel) -> CGFloat)? = nil, + tapHandler: (@MainActor (SingleActionViewModel) -> Void)? = nil, + customRender: (@MainActor (_ title: UILabel, _ contentView: UIView) -> Void)? = nil, + customHeight: (@MainActor (SingleActionViewModel) -> CGFloat)? = nil, accessibilityId: String? = nil ) { self.title = title diff --git a/firefox-ios/Client/TabManagement/Tab.swift b/firefox-ios/Client/TabManagement/Tab.swift index 6159237343ca3..ad0a4c67b7eb1 100644 --- a/firefox-ios/Client/TabManagement/Tab.swift +++ b/firefox-ios/Client/TabManagement/Tab.swift @@ -855,7 +855,7 @@ class Tab: NSObject, ThemeApplicable, FeatureFlaggable, ShareTab { // MARK: - Temporary Document handling - PDF Refactor /// Retrieves the session cookies attached to the current `WKWebView` managed by the `Tab` - func getSessionCookies(_ completion: @Sendable @MainActor @escaping ([HTTPCookie]) -> Void) { + func getSessionCookies(_ completion: @MainActor @escaping ([HTTPCookie]) -> Void) { webView?.configuration.websiteDataStore.httpCookieStore.getAllCookies(completion) } @@ -1210,7 +1210,7 @@ class TabWebView: WKWebView, MenuHelperWebViewInterface, ThemeApplicable, Featur override func evaluateJavaScript( _ javaScriptString: String, completionHandler: ( - @MainActor @Sendable (Any?, (any Error)?) -> Void + @MainActor (Any?, (any Error)?) -> Void )? = nil ) { super.evaluateJavaScript(javaScriptString, completionHandler: completionHandler) diff --git a/firefox-ios/Client/Utils/MainThreadThrottler.swift b/firefox-ios/Client/Utils/MainThreadThrottler.swift index 5d176f59215ef..7802049a70584 100644 --- a/firefox-ios/Client/Utils/MainThreadThrottler.swift +++ b/firefox-ios/Client/Utils/MainThreadThrottler.swift @@ -6,7 +6,7 @@ import Foundation import Common protocol MainThreadThrottlerProtocol { - func throttle(completion: @escaping @MainActor @Sendable () -> Void) + func throttle(completion: @escaping @MainActor () -> Void) } /// For any work that needs to be delayed, you can wrap it inside a throttler @@ -22,7 +22,7 @@ class MainThreadThrottler: MainThreadThrottlerProtocol { } // This debounces; the task will not happen unless a duration of delay passes since the function was called - func throttle(completion: @escaping @MainActor @Sendable () -> Void) { + func throttle(completion: @escaping @MainActor () -> Void) { guard threshold <= 0 || lastExecutionTime.timeIntervalSinceNow < -threshold else { return } lastExecutionTime = Date() DispatchQueue.main.async(execute: completion) diff --git a/firefox-ios/Extensions/ShareTo/InitialViewController.swift b/firefox-ios/Extensions/ShareTo/InitialViewController.swift index 00e889e6e8db3..e1eddcd58ba95 100644 --- a/firefox-ios/Extensions/ShareTo/InitialViewController.swift +++ b/firefox-ios/Extensions/ShareTo/InitialViewController.swift @@ -95,7 +95,7 @@ class InitialViewController: UIViewController { } } - func getShareItem(completion: @MainActor @Sendable @escaping (ExtensionUtils.ExtractedShareItem?) -> Void) { + func getShareItem(completion: @MainActor @escaping (ExtensionUtils.ExtractedShareItem?) -> Void) { ExtensionUtils.extractSharedItem(fromExtensionContext: extensionContext) { item, error in DispatchQueue.main.async { if let item = item, error == nil { diff --git a/firefox-ios/RustFxA/FirefoxAccountSignInViewController.swift b/firefox-ios/RustFxA/FirefoxAccountSignInViewController.swift index 68ddd0ede0796..53167f1b70aee 100644 --- a/firefox-ios/RustFxA/FirefoxAccountSignInViewController.swift +++ b/firefox-ios/RustFxA/FirefoxAccountSignInViewController.swift @@ -302,7 +302,7 @@ class FirefoxAccountSignInViewController: UIViewController, Themeable { navigationController?.pushViewController(fxaWebVC, animated: true) } - private func showFxAWebViewController(_ url: URL, completion: @escaping @MainActor @Sendable (URL) -> Void) { + private func showFxAWebViewController(_ url: URL, completion: @escaping @MainActor (URL) -> Void) { if let accountManager = profile.rustFxA.accountManager { let entrypoint = self.deepLinkParams.entrypoint.rawValue accountManager.getManageAccountURL(entrypoint: "ios_settings_\(entrypoint)") { [weak self] result in diff --git a/firefox-ios/RustFxA/FxAWebViewController.swift b/firefox-ios/RustFxA/FxAWebViewController.swift index 2e99c53cfaa86..7ff997e70cef4 100755 --- a/firefox-ios/RustFxA/FxAWebViewController.swift +++ b/firefox-ios/RustFxA/FxAWebViewController.swift @@ -158,7 +158,7 @@ extension FxAWebViewController: WKNavigationDelegate { func webView( _ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, - decisionHandler: @escaping @MainActor @Sendable (WKNavigationActionPolicy) -> Void + decisionHandler: @escaping @MainActor (WKNavigationActionPolicy) -> Void ) { if let blobURL = navigationAction.request.url, viewModel.isMozillaAccountPDF(blobURL: blobURL, webViewURL: webView.url) { diff --git a/firefox-ios/Storage/Queue.swift b/firefox-ios/Storage/Queue.swift index f973806dd8762..afd1348ded564 100644 --- a/firefox-ios/Storage/Queue.swift +++ b/firefox-ios/Storage/Queue.swift @@ -7,7 +7,7 @@ import Shared public protocol TabQueue: Sendable { func addToQueue(_ tab: ShareItem) -> Success - func getQueuedTabs(completion: @MainActor @Sendable @escaping ([ShareItem]) -> Void) + func getQueuedTabs(completion: @MainActor @escaping ([ShareItem]) -> Void) @discardableResult func clearQueuedTabs() -> Success } diff --git a/firefox-ios/Storage/SQL/SQLiteQueue.swift b/firefox-ios/Storage/SQL/SQLiteQueue.swift index a3f945ec89127..7b8c1712d401c 100644 --- a/firefox-ios/Storage/SQL/SQLiteQueue.swift +++ b/firefox-ios/Storage/SQL/SQLiteQueue.swift @@ -21,7 +21,7 @@ public final class SQLiteQueue: TabQueue { return ShareItem(url: url, title: "") } - public func getQueuedTabs(completion: @MainActor @Sendable @escaping ([ShareItem]) -> Void) { + public func getQueuedTabs(completion: @MainActor @escaping ([ShareItem]) -> Void) { let sql = "SELECT url FROM queue" db.runQuery(sql, args: nil, factory: self.factory) .uponQueue(.main) { result in diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/Mock/MockThrottler.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/Mock/MockThrottler.swift index 72c93b2143304..936ee836523da 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/Mock/MockThrottler.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/Mock/MockThrottler.swift @@ -10,7 +10,7 @@ class MockThrottler: MainThreadThrottlerProtocol { init() {} - func throttle(completion: @escaping @MainActor @Sendable () -> Void) { + func throttle(completion: @escaping @MainActor () -> Void) { didCallThrottle = true ensureMainThread { completion() diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Mocks/MockProfile.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Mocks/MockProfile.swift index c9182099a3944..6fe4bc24aabaa 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Mocks/MockProfile.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Mocks/MockProfile.swift @@ -90,7 +90,7 @@ final class MockTabQueue: TabQueue, @unchecked Sendable { return succeed() } - func getQueuedTabs(completion: @MainActor @Sendable @escaping ([ShareItem]) -> Void) { + func getQueuedTabs(completion: @MainActor @escaping ([ShareItem]) -> Void) { Task { @MainActor in completion(queuedTabs) getQueuedTabsCalled += 1 diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Mocks/MockTabProviderProtocol.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Mocks/MockTabProviderProtocol.swift index 4d76005b77bf6..affffe733bae9 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Mocks/MockTabProviderProtocol.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Mocks/MockTabProviderProtocol.swift @@ -12,7 +12,7 @@ final class MockTabProviderProtocol: TabProviderProtocol { var isFindInPageMode = false var isLoading = false - var onLoadingStateChanged: (@MainActor @Sendable () -> Void)? + var onLoadingStateChanged: (@MainActor () -> Void)? var scrollView: UIScrollView? var pullToRefreshAddCount = 0 diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Mocks/MockTabWebView.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Mocks/MockTabWebView.swift index 2b8a1804a23e6..13f1d25a74b71 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Mocks/MockTabWebView.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Mocks/MockTabWebView.swift @@ -109,7 +109,7 @@ class MockTab: Tab { return isHomePage } - override func getSessionCookies(_ completion: @escaping @MainActor @Sendable ([HTTPCookie]) -> Void) { + override func getSessionCookies(_ completion: @escaping @MainActor ([HTTPCookie]) -> Void) { completion([]) } From 6e44238a4bb2e5c065c95fa7e382b48e52bfa113 Mon Sep 17 00:00:00 2001 From: Carson Ramsden Date: Wed, 7 Jan 2026 13:49:04 -0700 Subject: [PATCH 2/3] remove remaining @Sendable that are unneeded --- .../Frontend/AuthenticationManager/AppAuthenticator.swift | 8 ++++---- .../Browser/SearchEngines/SearchEnginesManager.swift | 2 +- .../Bookmarks/Legacy/BookmarksPanelViewModel.swift | 2 +- .../Frontend/Library/ClearHistorySheetProvider.swift | 2 +- .../Frontend/Onboarding/Models/UpdateViewModel.swift | 2 +- .../Tests/ClientTests/Mocks/MockAppAuthenticator.swift | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/firefox-ios/Client/Frontend/AuthenticationManager/AppAuthenticator.swift b/firefox-ios/Client/Frontend/AuthenticationManager/AppAuthenticator.swift index c4e1c61a960c5..6a65b7d326e65 100644 --- a/firefox-ios/Client/Frontend/AuthenticationManager/AppAuthenticator.swift +++ b/firefox-ios/Client/Frontend/AuthenticationManager/AppAuthenticator.swift @@ -19,14 +19,14 @@ enum AuthenticationState { protocol AppAuthenticationProtocol { var canAuthenticateDeviceOwner: Bool { get } - func getAuthenticationState(completion: @MainActor @escaping @Sendable (AuthenticationState) -> Void) + func getAuthenticationState(completion: @MainActor @escaping (AuthenticationState) -> Void) func authenticateWithDeviceOwnerAuthentication( - _ completion: @MainActor @escaping @Sendable (Result) -> Void + _ completion: @MainActor @escaping (Result) -> Void ) } class AppAuthenticator: AppAuthenticationProtocol { - func getAuthenticationState(completion: @MainActor @escaping @Sendable (AuthenticationState) -> Void) { + func getAuthenticationState(completion: @MainActor @escaping (AuthenticationState) -> Void) { if canAuthenticateDeviceOwner { authenticateWithDeviceOwnerAuthentication { result in DispatchQueue.main.async { @@ -46,7 +46,7 @@ class AppAuthenticator: AppAuthenticationProtocol { } func authenticateWithDeviceOwnerAuthentication( - _ completion: @MainActor @escaping @Sendable (Result) -> Void + _ completion: @MainActor @escaping (Result) -> Void ) { // Get a fresh context for each login. If you use the same context on multiple attempts // (by commenting out the next line), then a previously successful authentication diff --git a/firefox-ios/Client/Frontend/Browser/SearchEngines/SearchEnginesManager.swift b/firefox-ios/Client/Frontend/Browser/SearchEngines/SearchEnginesManager.swift index 892949b71a3ac..53885bc58bd38 100644 --- a/firefox-ios/Client/Frontend/Browser/SearchEngines/SearchEnginesManager.swift +++ b/firefox-ios/Client/Frontend/Browser/SearchEngines/SearchEnginesManager.swift @@ -275,7 +275,7 @@ class SearchEnginesManager: SearchEnginesManagerProvider { } } - func deleteCustomEngine(_ engine: OpenSearchEngine, completion: @MainActor @escaping @Sendable () -> Void) { + func deleteCustomEngine(_ engine: OpenSearchEngine, completion: @MainActor @escaping () -> Void) { // We can't delete a preinstalled engine or an engine that is currently the default. guard engine.isCustomEngine && !isEngineDefault(engine) else { return } diff --git a/firefox-ios/Client/Frontend/Library/Bookmarks/Legacy/BookmarksPanelViewModel.swift b/firefox-ios/Client/Frontend/Library/Bookmarks/Legacy/BookmarksPanelViewModel.swift index 3fb2c0c4fbfd6..d28e823cc56c6 100644 --- a/firefox-ios/Client/Frontend/Library/Bookmarks/Legacy/BookmarksPanelViewModel.swift +++ b/firefox-ios/Client/Frontend/Library/Bookmarks/Legacy/BookmarksPanelViewModel.swift @@ -118,7 +118,7 @@ final class BookmarksPanelViewModel: @unchecked Sendable { func createPinUnpinAction( for site: Site, isPinned: Bool, - successHandler: @MainActor @escaping @Sendable (String) -> Void + successHandler: @MainActor @escaping (String) -> Void ) -> PhotonRowActions { return SingleActionViewModel( title: isPinned ? .Bookmarks.Menu.RemoveFromShortcutsTitle : .AddToShortcutsActionTitle, diff --git a/firefox-ios/Client/Frontend/Library/ClearHistorySheetProvider.swift b/firefox-ios/Client/Frontend/Library/ClearHistorySheetProvider.swift index 20ebb79269648..0795864fd0440 100644 --- a/firefox-ios/Client/Frontend/Library/ClearHistorySheetProvider.swift +++ b/firefox-ios/Client/Frontend/Library/ClearHistorySheetProvider.swift @@ -22,7 +22,7 @@ class ClearHistorySheetProvider { /// - didComplete: Did complete a recent history clear up action func showClearRecentHistory( onViewController viewController: UIViewController, - didComplete: @MainActor @escaping @Sendable (HistoryDeletionUtilityDateOptions) -> Void + didComplete: @MainActor @escaping (HistoryDeletionUtilityDateOptions) -> Void ) { let alert = createAlertAndConfigureWithArrowIfNeeded(from: viewController) setupActions(for: alert, didComplete: didComplete) diff --git a/firefox-ios/Client/Frontend/Onboarding/Models/UpdateViewModel.swift b/firefox-ios/Client/Frontend/Onboarding/Models/UpdateViewModel.swift index 7da6c00e83289..e8e6a18c414bd 100644 --- a/firefox-ios/Client/Frontend/Onboarding/Models/UpdateViewModel.swift +++ b/firefox-ios/Client/Frontend/Onboarding/Models/UpdateViewModel.swift @@ -73,7 +73,7 @@ class UpdateViewModel: OnboardingViewModelProtocol, // Function added to wait for AccountManager initialization to get // if the user is Sign in with Sync Account to decide which cards to show - func hasSyncableAccount(completion: @MainActor @escaping @Sendable () -> Void) { + func hasSyncableAccount(completion: @MainActor @escaping () -> Void) { hasSyncableAccount = profile.hasAccount() ensureMainThread { completion() diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Mocks/MockAppAuthenticator.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Mocks/MockAppAuthenticator.swift index 670c94f9e5c71..89e004d3c8af8 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Mocks/MockAppAuthenticator.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Mocks/MockAppAuthenticator.swift @@ -11,7 +11,7 @@ class MockAppAuthenticator: AppAuthenticationProtocol, @unchecked Sendable { var shouldAuthenticateDeviceOwner = true var shouldSucceed = true - func getAuthenticationState(completion: @MainActor @escaping @Sendable (AuthenticationState) -> Void) { + func getAuthenticationState(completion: @MainActor @escaping (AuthenticationState) -> Void) { ensureMainThread { completion(self.authenticationState) } @@ -22,7 +22,7 @@ class MockAppAuthenticator: AppAuthenticationProtocol, @unchecked Sendable { } func authenticateWithDeviceOwnerAuthentication( - _ completion: @MainActor @escaping @Sendable (Result) -> Void + _ completion: @MainActor @escaping (Result) -> Void ) { ensureMainThread { if self.shouldSucceed { From 5183c2719f603b478c75f587b27bd093f817b665 Mon Sep 17 00:00:00 2001 From: tomerqodo Date: Sun, 25 Jan 2026 12:07:56 +0200 Subject: [PATCH 3/3] update pr --- .../Sources/Common/Utilities/DispatchQueueHelper.swift | 7 ++++--- firefox-ios/Client/Application/WebServer.swift | 2 +- .../BrowserViewController+WebViewDelegates.swift | 4 +--- firefox-ios/Client/Utils/MainThreadThrottler.swift | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/BrowserKit/Sources/Common/Utilities/DispatchQueueHelper.swift b/BrowserKit/Sources/Common/Utilities/DispatchQueueHelper.swift index 125f856b83db2..6e864c474a986 100644 --- a/BrowserKit/Sources/Common/Utilities/DispatchQueueHelper.swift +++ b/BrowserKit/Sources/Common/Utilities/DispatchQueueHelper.swift @@ -19,14 +19,15 @@ public func ensureMainThread(execute work: @escaping @MainActor @convention(bloc } } -public func ensureMainThread(execute work: @escaping @MainActor () -> T) { +public func ensureMainThread(execute work: @escaping @MainActor () -> T) -> T? { if Thread.isMainThread { - MainActor.assumeIsolated { - _ = work() + return MainActor.assumeIsolated { + return work() } } else { DispatchQueue.main.async { _ = work() } + return nil } } diff --git a/firefox-ios/Client/Application/WebServer.swift b/firefox-ios/Client/Application/WebServer.swift index 3f0118c3662ac..6e7d7372171d5 100644 --- a/firefox-ios/Client/Application/WebServer.swift +++ b/firefox-ios/Client/Application/WebServer.swift @@ -15,7 +15,7 @@ protocol WebServerProtocol { /// FIXME: FXIOS-13989 Make truly thread safe /// NOTE: FXIOS-14560 -- Be careful; `@MainActor` will cause crashes with GCDWebServer dependency. -final class WebServer: WebServerProtocol, @unchecked Sendable { +class WebServer: WebServerProtocol, @unchecked Sendable { static let sharedInstance = WebServer() private let logger: Logger diff --git a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+WebViewDelegates.swift b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+WebViewDelegates.swift index cf5e568db42a1..a4081f5972938 100644 --- a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+WebViewDelegates.swift +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+WebViewDelegates.swift @@ -1340,9 +1340,7 @@ private extension BrowserViewController { let cert = SecTrustCopyCertificateChain(trust) as? [SecCertificate], self.profile.certStore.containsCertificate(cert[0], forOrigin: origin) else { - ensureMainThread { - completionHandler(.performDefaultHandling, nil) - } + completionHandler(.performDefaultHandling, nil) return } diff --git a/firefox-ios/Client/Utils/MainThreadThrottler.swift b/firefox-ios/Client/Utils/MainThreadThrottler.swift index 7802049a70584..9a1a1f9b8091d 100644 --- a/firefox-ios/Client/Utils/MainThreadThrottler.swift +++ b/firefox-ios/Client/Utils/MainThreadThrottler.swift @@ -23,8 +23,8 @@ class MainThreadThrottler: MainThreadThrottlerProtocol { // This debounces; the task will not happen unless a duration of delay passes since the function was called func throttle(completion: @escaping @MainActor () -> Void) { - guard threshold <= 0 || lastExecutionTime.timeIntervalSinceNow < -threshold else { return } lastExecutionTime = Date() + guard threshold <= 0 || lastExecutionTime.timeIntervalSinceNow < -threshold else { return } DispatchQueue.main.async(execute: completion) } }