#if os(macOS) import Foundation import WebKit // MARK: - Navigation helper (revived from the old credits scraper) @MainActor final class NavigationDelegate: NSObject, WKNavigationDelegate { private let completion: (Result) -> Void private var hasCompleted: Bool = false private var timeoutTask: Task? private var postCommitTask: Task? static var associationKey: UInt8 = 0 nonisolated static let postCommitSuccessDelay: TimeInterval = 0.75 init(completion: @escaping (Result) -> Void) { self.completion = completion } func armTimeout(seconds: TimeInterval) { self.timeoutTask?.cancel() self.timeoutTask = Task { @MainActor [weak self] in guard let self else { return } let nanoseconds = UInt64(max(seconds, 0) * 1_000_000_000) try? await Task.sleep(nanoseconds: nanoseconds) self.completeOnce(.failure(URLError(.timedOut))) } } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { self.completeOnce(.success(())) } func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) { guard !self.hasCompleted else { return } self.postCommitTask?.cancel() self.postCommitTask = Task { @MainActor [weak self] in guard let self else { return } let nanoseconds = UInt64(Self.postCommitSuccessDelay * 1_000_000_000) try? await Task.sleep(nanoseconds: nanoseconds) self.completeOnce(.success(())) } } func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { if Self.shouldIgnoreNavigationError(error) { return } self.completeOnce(.failure(error)) } func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { if Self.shouldIgnoreNavigationError(error) { return } self.completeOnce(.failure(error)) } nonisolated static func shouldIgnoreNavigationError(_ error: Error) -> Bool { let nsError = error as NSError if nsError.domain == NSURLErrorDomain, nsError.code == NSURLErrorCancelled { return true } if nsError.domain == "WebKitErrorDomain", nsError.code == 102 { return true } return false } private func completeOnce(_ result: Result) { guard !self.hasCompleted else { return } self.hasCompleted = true self.timeoutTask?.cancel() self.timeoutTask = nil self.postCommitTask?.cancel() self.postCommitTask = nil self.completion(result) } } extension WKWebView { var codexNavigationDelegate: NavigationDelegate? { get { objc_getAssociatedObject(self, &NavigationDelegate.associationKey) as? NavigationDelegate } set { objc_setAssociatedObject( self, &NavigationDelegate.associationKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } } #endif