diff --git a/BrowserKit/Package.swift b/BrowserKit/Package.swift index 708bbc72187d3..cf3baa3642625 100644 --- a/BrowserKit/Package.swift +++ b/BrowserKit/Package.swift @@ -151,7 +151,8 @@ let package = Package( name: "WebEngine", dependencies: ["Common", .product(name: "GCDWebServers", package: "GCDWebServer")], - swiftSettings: [.unsafeFlags(["-enable-testing"])]), + swiftSettings: [.unsafeFlags(["-enable-testing"]), + .enableExperimentalFeature("StrictConcurrency")]), .testTarget( name: "WebEngineTests", dependencies: ["WebEngine"]), diff --git a/BrowserKit/Sources/Common/Utilities/MenuHelper/MenuHelperWebViewInterface.swift b/BrowserKit/Sources/Common/Utilities/MenuHelper/MenuHelperWebViewInterface.swift index 456a1f1812cbd..fe8b58fc9067a 100644 --- a/BrowserKit/Sources/Common/Utilities/MenuHelper/MenuHelperWebViewInterface.swift +++ b/BrowserKit/Sources/Common/Utilities/MenuHelper/MenuHelperWebViewInterface.swift @@ -8,11 +8,11 @@ import Foundation public protocol MenuHelperWebViewInterface { /// Used to add a find in page menu option on webview textfields @objc - optional func menuHelperFindInPage() + optional nonisolated func menuHelperFindInPage() /// Used to add a search with "client" menu option on the webview textfields @objc - optional func menuHelperSearchWith() + optional nonisolated func menuHelperSearchWith() } /// Used to pass in the Client strings for the webview textfields menu options diff --git a/BrowserKit/Sources/WebEngine/Engine.swift b/BrowserKit/Sources/WebEngine/Engine.swift index 245384af7ee83..72bb44fa4de50 100644 --- a/BrowserKit/Sources/WebEngine/Engine.swift +++ b/BrowserKit/Sources/WebEngine/Engine.swift @@ -6,20 +6,18 @@ import Foundation /// The engine used to create an `EngineView` and `EngineSession`. /// There is only when engine view to be created, but multiple sessions can exists. +@MainActor public protocol Engine { /// Creates a new view for rendering web content. /// - Returns: The created `EngineView` - @MainActor func createView() -> EngineView /// Creates a new engine session. /// - Parameter dependencies: Pass in the required session dependencies on creation /// - Returns: The created `EngineSession` - @MainActor func createSession(dependencies: EngineSessionDependencies) throws -> EngineSession /// Warm the `Engine` whenever we move the application to foreground - @MainActor func warmEngine() /// Idle the `Engine` whenever we move the application to background diff --git a/BrowserKit/Sources/WebEngine/EngineSession.swift b/BrowserKit/Sources/WebEngine/EngineSession.swift index 1520a01b9a633..6f5915594ea5f 100644 --- a/BrowserKit/Sources/WebEngine/EngineSession.swift +++ b/BrowserKit/Sources/WebEngine/EngineSession.swift @@ -6,12 +6,13 @@ import Foundation import UIKit /// Protocol representing a single engine session. In browsers usually a session corresponds to a tab. +@MainActor public protocol EngineSession: NSObject { /// Engine session delegate var delegate: EngineSessionDelegate? { get set } /// Proxy object for handling telemetry events. - var telemetryProxy: EngineTelemetryProxy? { get set } + nonisolated var telemetryProxy: EngineTelemetryProxy? { get set } /// Whether the engine session is currently being rendered var isActive: Bool { get set } @@ -34,7 +35,6 @@ public protocol EngineSession: NSObject { func goForward() /// Scroll the session to the top. - @MainActor func scrollToTop() /// Show the web view's built-in find interaction. @@ -63,7 +63,6 @@ public protocol EngineSession: NSObject { func restore(state: Data) /// Close the session. This may free underlying objects. Call this when you are finished using this session. - @MainActor func close() /// Switch to standard tracking protection mode. diff --git a/BrowserKit/Sources/WebEngine/FullscreenDelegate.swift b/BrowserKit/Sources/WebEngine/FullscreenDelegate.swift index 4736622e6e3cb..5741268bfce1f 100644 --- a/BrowserKit/Sources/WebEngine/FullscreenDelegate.swift +++ b/BrowserKit/Sources/WebEngine/FullscreenDelegate.swift @@ -12,6 +12,7 @@ import Foundation /// /// Due to limited documentation on this behavior, the following methods handle /// fullscreen transitions based on trial and error. +@MainActor public protocol FullscreenDelegate: AnyObject { /// Called when the web view enters fullscreen mode. /// diff --git a/BrowserKit/Sources/WebEngine/WKWebview/Scripts/Metadata/MetadataFetcherHelper.swift b/BrowserKit/Sources/WebEngine/WKWebview/Scripts/Metadata/MetadataFetcherHelper.swift index be854e679253d..d37fed63b5a25 100644 --- a/BrowserKit/Sources/WebEngine/WKWebview/Scripts/Metadata/MetadataFetcherHelper.swift +++ b/BrowserKit/Sources/WebEngine/WKWebview/Scripts/Metadata/MetadataFetcherHelper.swift @@ -6,11 +6,13 @@ import Common import Foundation /// Helper to get the metadata fetched out of a `WKEngineSession` +@MainActor protocol MetadataFetcherHelper { var delegate: MetadataFetcherDelegate? { get set } func fetch(fromSession session: WKEngineSession, url: URL) } +@MainActor protocol MetadataFetcherDelegate: AnyObject { func didLoad(pageMetadata: EnginePageMetadata) } diff --git a/BrowserKit/Sources/WebEngine/WKWebview/Scripts/ReaderMode/WKReadabilityOperation.swift b/BrowserKit/Sources/WebEngine/WKWebview/Scripts/ReaderMode/WKReadabilityOperation.swift index e598815dd6a14..e578995837c30 100644 --- a/BrowserKit/Sources/WebEngine/WKWebview/Scripts/ReaderMode/WKReadabilityOperation.swift +++ b/BrowserKit/Sources/WebEngine/WKWebview/Scripts/ReaderMode/WKReadabilityOperation.swift @@ -11,18 +11,22 @@ public enum ReadabilityOperationResult { case timeout } +@MainActor protocol ReaderModeNavigationDelegate: AnyObject { func didFailWithError(error: Error) func didFinish() } // TODO: FXIOS-11373 - finish handling reader mode in WebEngine - this class is to be tested +@MainActor final class WKReadabilityOperation: Operation, @unchecked Sendable, ReaderModeNavigationDelegate, WKReaderModeDelegate { var url: URL - var semaphore: DispatchSemaphore +// TODO: FXIOS-11373 - The original code had a semaphore, but it's removed here since with @MainActor we +// don't want to suspense the main thread. So this class needs to be retought if this project is picked back again. +// var semaphore: DispatchSemaphore var result: ReadabilityOperationResult? var session: WKEngineSession? var readerModeCache: ReaderModeCache @@ -36,7 +40,6 @@ final class WKReadabilityOperation: Operation, logger: Logger = DefaultLogger.shared ) { self.url = url - self.semaphore = DispatchSemaphore(value: 0) self.readerModeCache = readerModeCache self.mainQueue = mainQueue self.logger = logger @@ -46,34 +49,30 @@ final class WKReadabilityOperation: Operation, if self.isCancelled { return } + // TODO: FXIOS-11373 - finish handling reader mode in WebEngine - This isn't concurrency safe // Setup a new session and kick all this off on the main thread since UIKit // and WebKit are not safe from other threads. - Task { @MainActor in - let configProvider = DefaultWKEngineConfigurationProvider() - let parameters = WKWebViewParameters() - let dependencies = EngineSessionDependencies(webviewParameters: parameters) - let session = WKEngineSession.sessionFactory(userScriptManager: DefaultUserScriptManager(), - dependencies: dependencies, - configurationProvider: configProvider, - readerModeDelegate: self) - session?.navigationHandler.readerModeNavigationDelegate = self - self.session = session - - // Load the page in the session. This either fails with a navigation error, or we - // get a readability callback. Or it takes too long, in which case the semaphore - // times out. The script on the page will retry every 500ms for 10 seconds. - let context = BrowsingContext(type: .internalNavigation, url: self.url) - guard let browserURL = BrowserURL(browsingContext: context) else { return } - session?.load(browserURL: browserURL) - } - - let timeout = DispatchTime.now() + .seconds(10) - if semaphore.wait(timeout: timeout) == .timedOut { - result = ReadabilityOperationResult.timeout - } - - processResult() +// Task { @MainActor in +// let configProvider = DefaultWKEngineConfigurationProvider() +// let parameters = WKWebViewParameters() +// let dependencies = EngineSessionDependencies(webviewParameters: parameters) +// let session = WKEngineSession.sessionFactory(userScriptManager: DefaultUserScriptManager(), +// dependencies: dependencies, +// configurationProvider: configProvider, +// readerModeDelegate: self) +// session?.navigationHandler.readerModeNavigationDelegate = self +// self.session = session +// +// // Load the page in the session. This either fails with a navigation error, or we +// // get a readability callback. Or it takes too long, in which case the semaphore +// // times out. The script on the page will retry every 500ms for 10 seconds. +// let context = BrowsingContext(type: .internalNavigation, url: self.url) +// guard let browserURL = BrowserURL(browsingContext: context) else { return } +// session?.load(browserURL: browserURL) +// } +// +// processResult() } private func processResult() { @@ -106,7 +105,7 @@ final class WKReadabilityOperation: Operation, func didFailWithError(error: Error) { result = ReadabilityOperationResult.error(error as NSError) - semaphore.signal() +// semaphore.signal() } func didFinish() { @@ -140,6 +139,6 @@ final class WKReadabilityOperation: Operation, guard session == self.session else { return } result = ReadabilityOperationResult.success(readabilityResult) - semaphore.signal() +// semaphore.signal() } } diff --git a/BrowserKit/Sources/WebEngine/WKWebview/Scripts/ReaderMode/WKReadabilityService.swift b/BrowserKit/Sources/WebEngine/WKWebview/Scripts/ReaderMode/WKReadabilityService.swift index af1a3f9bdaff3..630507c03cccb 100644 --- a/BrowserKit/Sources/WebEngine/WKWebview/Scripts/ReaderMode/WKReadabilityService.swift +++ b/BrowserKit/Sources/WebEngine/WKWebview/Scripts/ReaderMode/WKReadabilityService.swift @@ -5,6 +5,7 @@ import Foundation // TODO: FXIOS-11373 - finish handling reader mode in WebEngine - this class is to be tested +@MainActor final class WKReadabilityService { private let ReadabilityServiceDefaultConcurrency = 1 var queue: OperationQueue diff --git a/BrowserKit/Sources/WebEngine/WKWebview/Scripts/ReaderMode/WKReaderModeDelegate.swift b/BrowserKit/Sources/WebEngine/WKWebview/Scripts/ReaderMode/WKReaderModeDelegate.swift index 7cfde7c63c37a..ae7dfd465536d 100644 --- a/BrowserKit/Sources/WebEngine/WKWebview/Scripts/ReaderMode/WKReaderModeDelegate.swift +++ b/BrowserKit/Sources/WebEngine/WKWebview/Scripts/ReaderMode/WKReaderModeDelegate.swift @@ -6,6 +6,7 @@ import Common import Foundation /// Delegate that contains callbacks that we have added on top of the built-in WKWebViewDelegate +@MainActor public protocol WKReaderModeDelegate: AnyObject { func readerMode( _ readerMode: ReaderModeStyleSetter, diff --git a/BrowserKit/Sources/WebEngine/WKWebview/Scripts/ReaderMode/WKReaderModeHandlers.swift b/BrowserKit/Sources/WebEngine/WKWebview/Scripts/ReaderMode/WKReaderModeHandlers.swift index 52e761ff50501..03bd3984a20d7 100644 --- a/BrowserKit/Sources/WebEngine/WKWebview/Scripts/ReaderMode/WKReaderModeHandlers.swift +++ b/BrowserKit/Sources/WebEngine/WKWebview/Scripts/ReaderMode/WKReaderModeHandlers.swift @@ -124,6 +124,7 @@ class WKReaderModeHandlers: WKReaderModeHandlersProtocol, Notifiable { } } + @MainActor private func handleReaderModeError(url: URL, readerModeConfiguration: ReaderModeConfiguration) -> GCDWebServerResponse? { WKReadabilityService().process(url, cache: readerModeCache) diff --git a/BrowserKit/Sources/WebEngine/WKWebview/WKEngine.swift b/BrowserKit/Sources/WebEngine/WKWebview/WKEngine.swift index 3f8746620afde..264cef03c8f2a 100644 --- a/BrowserKit/Sources/WebEngine/WKWebview/WKEngine.swift +++ b/BrowserKit/Sources/WebEngine/WKWebview/WKEngine.swift @@ -6,6 +6,7 @@ import Common import Foundation import WebKit +@MainActor public class WKEngine: Engine { private let sourceTimerFactory: DispatchSourceTimerFactory private var shutdownWebServerTimer: DispatchSourceInterface? @@ -15,7 +16,6 @@ public class WKEngine: Engine { private let configProvider: WKEngineConfigurationProvider // TODO: FXIOS-13670 With Swift 6 we can use default params in the init - @MainActor public static func factory(engineDependencies: EngineDependencies) -> WKEngine { let configProvider = DefaultWKEngineConfigurationProvider() let userScriptManager = DefaultUserScriptManager() @@ -29,7 +29,6 @@ public class WKEngine: Engine { ) } - @MainActor init(userScriptManager: WKUserScriptManager, webServerUtil: WKWebServerUtil, sourceTimerFactory: DispatchSourceTimerFactory, @@ -48,7 +47,6 @@ public class WKEngine: Engine { return WKEngineView.factory(frame: .zero) } - @MainActor public func createSession(dependencies: EngineSessionDependencies) throws -> EngineSession { guard let session = WKEngineSession.sessionFactory(userScriptManager: userScriptManager, dependencies: dependencies, @@ -59,7 +57,6 @@ public class WKEngine: Engine { return session } - @MainActor public func warmEngine() { shutdownWebServerTimer?.cancel() shutdownWebServerTimer = nil diff --git a/BrowserKit/Sources/WebEngine/WKWebview/WKEngineSession.swift b/BrowserKit/Sources/WebEngine/WKWebview/WKEngineSession.swift index 0eca67c3d1ed2..c94faa9da1026 100644 --- a/BrowserKit/Sources/WebEngine/WKWebview/WKEngineSession.swift +++ b/BrowserKit/Sources/WebEngine/WKWebview/WKEngineSession.swift @@ -6,12 +6,14 @@ import Common import Foundation @preconcurrency import WebKit +@MainActor protocol SessionHandler: AnyObject { func commitURLChange() func fetchMetadata(withURL url: URL) func received(error: NSError, forURL url: URL) } +@MainActor protocol WKJavascriptInterface: AnyObject { /// Calls a javascript method. /// - Parameter method: The method signature to be called in javascript world. @@ -19,6 +21,7 @@ protocol WKJavascriptInterface: AnyObject { func callJavascriptMethod(_ method: String, scope: String?) } +@MainActor class WKEngineSession: NSObject, EngineSession, WKEngineWebViewDelegate, @@ -30,7 +33,7 @@ class WKEngineSession: NSObject, uiHandler.delegate = delegate } } - weak var telemetryProxy: EngineTelemetryProxy? + nonisolated(unsafe) weak var telemetryProxy: EngineTelemetryProxy? weak var fullscreenDelegate: FullscreenDelegate? private(set) var webView: WKEngineWebView diff --git a/BrowserKit/Sources/WebEngine/WKWebview/WKEngineWebView.swift b/BrowserKit/Sources/WebEngine/WKWebview/WKEngineWebView.swift index 540fb38de1dea..89498a38051ea 100644 --- a/BrowserKit/Sources/WebEngine/WKWebview/WKEngineWebView.swift +++ b/BrowserKit/Sources/WebEngine/WKWebview/WKEngineWebView.swift @@ -6,6 +6,7 @@ import Common import Foundation import WebKit +@MainActor protocol WKEngineWebViewDelegate: AnyObject { func tabWebView(_ webView: WKEngineWebView, findInPageSelection: String) func tabWebView(_ webView: WKEngineWebView, searchSelection: String) @@ -16,6 +17,7 @@ protocol WKEngineWebViewDelegate: AnyObject { } /// Abstraction on top of the `WKWebView` +@MainActor protocol WKEngineWebView: UIView { var navigationDelegate: WKNavigationDelegate? { get set } var uiDelegate: WKUIDelegate? { get set } @@ -177,7 +179,15 @@ final class DefaultWKEngineWebView: WKWebView, } deinit { - close() + // TODO: FXIOS-13097 This is a work around until we can leverage isolated deinits + guard Thread.isMainThread else { + assertionFailure("DevicePickerViewController was not deallocated on the main thread. Observer was not removed") + return + } + + MainActor.assumeIsolated { + close() + } } func close() { @@ -192,46 +202,62 @@ final class DefaultWKEngineWebView: WKWebView, private func setupObservers() { let loadingToken = observe(\.isLoading, options: [.new]) { [weak self] _, change in - guard let isLoading = change.newValue else { return } - self?.handleWebsiteLoadingChanged(isLoading) - self?.delegate?.webViewPropertyChanged(.loading(isLoading)) + guard let isLoading = change.newValue, let self else { return } + ensureMainThread { + self.handleWebsiteLoadingChanged(isLoading) + self.delegate?.webViewPropertyChanged(.loading(isLoading)) + } } let progressObserver = observe(\.estimatedProgress, options: [.new]) { [weak self] _, change in - guard let progress = change.newValue else { return } - self?.delegate?.webViewPropertyChanged(.estimatedProgress(progress)) + guard let progress = change.newValue, let self else { return } + ensureMainThread { + self.delegate?.webViewPropertyChanged(.estimatedProgress(progress)) + } } let urlObserver = observe(\.url, options: [.new]) { [weak self] _, change in - guard let url = change.newValue else { return } - self?.delegate?.webViewPropertyChanged(.URL(url)) + guard let url = change.newValue, let self else { return } + ensureMainThread { + self.delegate?.webViewPropertyChanged(.URL(url)) + } } let titleObserver = observe(\.title, options: [.new]) { [weak self] _, change in - guard let title = change.newValue as? String else { return } - self?.delegate?.webViewPropertyChanged(.title(title)) + guard let title = change.newValue as? String, let self else { return } + ensureMainThread { + self.delegate?.webViewPropertyChanged(.title(title)) + } } let canGoBackObserver = observe(\.canGoBack, options: [.new]) { [weak self] _, change in - guard let canGoBack = change.newValue else { return } - self?.delegate?.webViewPropertyChanged(.canGoBack(canGoBack)) + guard let canGoBack = change.newValue, let self else { return } + ensureMainThread { + self.delegate?.webViewPropertyChanged(.canGoBack(canGoBack)) + } } let canGoForwardObserver = observe(\.canGoForward, options: [.new]) { [weak self] _, change in - guard let canGoForward = change.newValue else { return } - self?.delegate?.webViewPropertyChanged(.canGoForward(canGoForward)) + guard let canGoForward = change.newValue, let self else { return } + ensureMainThread { + self.delegate?.webViewPropertyChanged(.canGoForward(canGoForward)) + } } let hasOnlySecureBrowserObserver = observe(\.hasOnlySecureContent, options: [.new]) { [weak self] _, change in - guard let hasOnlySecureContent = change.newValue else { return } - self?.delegate?.webViewPropertyChanged(.hasOnlySecureContent(hasOnlySecureContent)) + guard let hasOnlySecureContent = change.newValue, let self else { return } + ensureMainThread { + self.delegate?.webViewPropertyChanged(.hasOnlySecureContent(hasOnlySecureContent)) + } } let contentSizeObserver = scrollView.observe(\.contentSize, options: [.new]) { [weak self] _, change in - guard let newSize = change.newValue else { return } - self?.pullRefreshViewHeightConstraint?.constant = newSize.height - self?.pullRefreshViewWidthConstraint?.constant = newSize.width - self?.delegate?.webViewPropertyChanged(.contentSize(newSize)) + guard let newSize = change.newValue, let self else { return } + ensureMainThread { + self.pullRefreshViewHeightConstraint?.constant = newSize.height + self.pullRefreshViewWidthConstraint?.constant = newSize.width + self.delegate?.webViewPropertyChanged(.contentSize(newSize)) + } } observedTokens.append( @@ -251,11 +277,14 @@ final class DefaultWKEngineWebView: WKWebView, // and `.exitingFullscreen` only. When the view is on fullscreen is removed from the view hierarchy // so we add it back for `.exitingFullscreen` if #available(iOS 16.0, *) { - let fullscreenObserver = observe(\.fullscreenState, options: [.new]) { [weak self] object, change in - guard object.fullscreenState == .enteringFullscreen || - object.fullscreenState == .exitingFullscreen else { return } - - self?.delegate?.webViewPropertyChanged(.isFullScreen(object.fullscreenState == .enteringFullscreen)) + let fullscreenObserver = observe(\.fullscreenState, options: [.new]) { [weak self] object, change in + guard let self else { return } + ensureMainThread { + guard object.fullscreenState == .enteringFullscreen || + object.fullscreenState == .exitingFullscreen else { return } + + self.delegate?.webViewPropertyChanged(.isFullScreen(object.fullscreenState == .enteringFullscreen)) + } } observedTokens.append(fullscreenObserver) } @@ -317,17 +346,23 @@ final class DefaultWKEngineWebView: WKWebView, configuration.userContentController.removeAllScriptMessageHandlers() } - func menuHelperFindInPage() { - evaluateJavascriptInDefaultContentWorld("getSelection().toString()") { result, _ in - let selection = result as? String ?? "" - self.delegate?.tabWebView(self, findInPageSelection: selection) + @objc + nonisolated func menuHelperFindInPage() { + ensureMainThread { + self.evaluateJavascriptInDefaultContentWorld("getSelection().toString()") { result, _ in + let selection = result as? String ?? "" + self.delegate?.tabWebView(self, findInPageSelection: selection) + } } } - func menuHelperSearchWith() { - evaluateJavascriptInDefaultContentWorld("getSelection().toString()") { result, _ in - let selection = result as? String ?? "" - self.delegate?.tabWebView(self, searchSelection: selection) + @objc + nonisolated func menuHelperSearchWith() { + ensureMainThread { + self.evaluateJavascriptInDefaultContentWorld("getSelection().toString()") { result, _ in + let selection = result as? String ?? "" + self.delegate?.tabWebView(self, searchSelection: selection) + } } } diff --git a/BrowserKit/Sources/WebEngine/WKWebview/WKUIHandler.swift b/BrowserKit/Sources/WebEngine/WKWebview/WKUIHandler.swift index 26d4ab602f5de..0a5a19ea4535e 100644 --- a/BrowserKit/Sources/WebEngine/WKWebview/WKUIHandler.swift +++ b/BrowserKit/Sources/WebEngine/WKWebview/WKUIHandler.swift @@ -6,6 +6,7 @@ @preconcurrency import WebKit import Common +@MainActor public protocol WKUIHandler: WKUIDelegate { var delegate: EngineSessionDelegate? { get set } var isActive: Bool {get set} diff --git a/BrowserKit/Tests/WebEngineTests/MetadataFetcherHelperTests.swift b/BrowserKit/Tests/WebEngineTests/MetadataFetcherHelperTests.swift index 84c003c54d216..6bacdc6b5685a 100644 --- a/BrowserKit/Tests/WebEngineTests/MetadataFetcherHelperTests.swift +++ b/BrowserKit/Tests/WebEngineTests/MetadataFetcherHelperTests.swift @@ -5,6 +5,7 @@ import XCTest @testable import WebEngine +@MainActor @available(iOS 16.0, *) final class MetadataFetcherHelperTests: XCTestCase { var metadataDelegate: MockMetadataFetcherDelegate! @@ -63,7 +64,7 @@ final class MetadataFetcherHelperTests: XCTestCase { subject.fetch(fromSession: session, url: url) - let savedJavaScript = await session.webviewProvider.webView.savedJavaScript + let savedJavaScript = session.webviewProvider.webView.savedJavaScript XCTAssertEqual(savedJavaScript, expectedJavascript) XCTAssertEqual(metadataDelegate.didLoadPageMetadataCalled, 0) } diff --git a/BrowserKit/Tests/WebEngineTests/WKEngineTests.swift b/BrowserKit/Tests/WebEngineTests/WKEngineTests.swift index 1ca93691edbf3..6c7ba9291f2da 100644 --- a/BrowserKit/Tests/WebEngineTests/WKEngineTests.swift +++ b/BrowserKit/Tests/WebEngineTests/WKEngineTests.swift @@ -55,6 +55,7 @@ final class WKEngineTests: XCTestCase { XCTAssertEqual(webServerUtil.setUpWebServerCalled, 1) } + @MainActor func testIdleEngineCallsStopEngine() async { let subject = await createSubject() subject.idleEngine() diff --git a/BrowserKit/Tests/WebEngineTests/WKEngineWebViewTests.swift b/BrowserKit/Tests/WebEngineTests/WKEngineWebViewTests.swift index f076abdeac107..cbe04e753ebca 100644 --- a/BrowserKit/Tests/WebEngineTests/WKEngineWebViewTests.swift +++ b/BrowserKit/Tests/WebEngineTests/WKEngineWebViewTests.swift @@ -6,6 +6,7 @@ import XCTest @testable import WebEngine import WebKit +@MainActor final class WKEngineWebViewTests: XCTestCase { private var delegate: MockWKEngineWebViewDelegate! private let testURL = URL(string: "https://www.example.com/")! @@ -20,7 +21,6 @@ final class WKEngineWebViewTests: XCTestCase { super.tearDown() } - @MainActor func testNoLeaks() { let subject = createSubject() subject.close() @@ -29,7 +29,6 @@ final class WKEngineWebViewTests: XCTestCase { RunLoop.current.run(until: Date().addingTimeInterval(0.1)) } - @MainActor func testLoad_callsObservers() { let subject = createSubject() let loadingExpectation = expectation(that: \WKWebView.isLoading, on: subject) { _, change in @@ -85,7 +84,6 @@ final class WKEngineWebViewTests: XCTestCase { RunLoop.current.run(until: Date().addingTimeInterval(0.1)) } - @MainActor func testLoad_callsBeginRefreshing_onUIRefreshControl() throws { let subject = createSubject(pullRefreshViewType: MockUIRefreshControl.self) let pullRefresh = try XCTUnwrap(subject.scrollView.refreshControl as? MockUIRefreshControl) @@ -97,7 +95,6 @@ final class WKEngineWebViewTests: XCTestCase { RunLoop.current.run(until: Date().addingTimeInterval(0.1)) } - @MainActor func testInit_setupsPullRefresh() throws { let subject = createSubject() @@ -111,7 +108,6 @@ final class WKEngineWebViewTests: XCTestCase { RunLoop.current.run(until: Date().addingTimeInterval(0.1)) } - @MainActor func testInit_withUIRefreshControl_setupsCorrectlyPullRefresh() { let subject = createSubject(pullRefreshViewType: UIRefreshControl.self) @@ -119,7 +115,6 @@ final class WKEngineWebViewTests: XCTestCase { RunLoop.current.run(until: Date().addingTimeInterval(0.1)) } - @MainActor func testScrollWillBeginZooming_removesPullRefresh() { let subject = createSubject() @@ -130,7 +125,6 @@ final class WKEngineWebViewTests: XCTestCase { RunLoop.current.run(until: Date().addingTimeInterval(0.1)) } - @MainActor func testScrollDidEndZooming_setupsPullRefresh() { let subject = createSubject() @@ -144,7 +138,6 @@ final class WKEngineWebViewTests: XCTestCase { RunLoop.current.run(until: Date().addingTimeInterval(0.1)) } - @MainActor func testScrollDidEndZooming_doesntSetupPullRefreshAgain() { let subject = createSubject() @@ -157,7 +150,6 @@ final class WKEngineWebViewTests: XCTestCase { RunLoop.current.run(until: Date().addingTimeInterval(0.1)) } - @MainActor func testTriggerPullRefresh_callsDelegate() throws { let subject = createSubject() let pullRefresh = try XCTUnwrap(subject.scrollView.subviews.first { $0 is EnginePullRefreshView } @@ -169,7 +161,6 @@ final class WKEngineWebViewTests: XCTestCase { RunLoop.current.run(until: Date().addingTimeInterval(0.1)) } - @MainActor func createSubject(pullRefreshViewType: EnginePullRefreshViewType = MockEnginePullRefreshView.self, file: StaticString = #filePath, line: UInt = #line) -> DefaultWKEngineWebView { diff --git a/firefox-ios/Client/Configuration/version.xcconfig b/firefox-ios/Client/Configuration/version.xcconfig index 55d16bcbd4bfc..6d486625a7ae1 100644 --- a/firefox-ios/Client/Configuration/version.xcconfig +++ b/firefox-ios/Client/Configuration/version.xcconfig @@ -1 +1 @@ -APP_VERSION = 143.2 +APP_VERSION = 144.0 diff --git a/firefox-ios/Client/TabManagement/Tab.swift b/firefox-ios/Client/TabManagement/Tab.swift index 77de476542828..635e56cec9f0c 100644 --- a/firefox-ios/Client/TabManagement/Tab.swift +++ b/firefox-ios/Client/TabManagement/Tab.swift @@ -1235,16 +1235,20 @@ class TabWebView: WKWebView, MenuHelperWebViewInterface, ThemeApplicable, Featur } func menuHelperFindInPage() { - evaluateJavascriptInDefaultContentWorld("getSelection().toString()") { result, _ in - let selection = result as? String ?? "" - self.delegate?.tabWebView(self, didSelectFindInPageForSelection: selection) + ensureMainThread { + self.evaluateJavascriptInDefaultContentWorld("getSelection().toString()") { result, _ in + let selection = result as? String ?? "" + self.delegate?.tabWebView(self, didSelectFindInPageForSelection: selection) + } } } func menuHelperSearchWith() { - evaluateJavascriptInDefaultContentWorld("getSelection().toString()") { result, _ in - let selection = result as? String ?? "" - self.delegate?.tabWebViewSearchWithFirefox(self, didSelectSearchWithFirefoxForSelection: selection) + ensureMainThread { + self.evaluateJavascriptInDefaultContentWorld("getSelection().toString()") { result, _ in + let selection = result as? String ?? "" + self.delegate?.tabWebViewSearchWithFirefox(self, didSelectSearchWithFirefoxForSelection: selection) + } } }