102 lines
3.3 KiB
Swift
102 lines
3.3 KiB
Swift
#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, Error>) -> Void
|
|
private var hasCompleted: Bool = false
|
|
private var timeoutWorkItem: DispatchWorkItem?
|
|
private var postCommitWorkItem: DispatchWorkItem?
|
|
static var associationKey: UInt8 = 0
|
|
nonisolated static let postCommitSuccessDelay: TimeInterval = 0.75
|
|
|
|
init(completion: @escaping (Result<Void, Error>) -> Void) {
|
|
self.completion = completion
|
|
}
|
|
|
|
func armTimeout(seconds: TimeInterval) {
|
|
self.timeoutWorkItem?.cancel()
|
|
let delay = max(seconds, 0)
|
|
let workItem = DispatchWorkItem { [weak self] in
|
|
MainActor.assumeIsolated {
|
|
self?.completeOnce(.failure(URLError(.timedOut)))
|
|
}
|
|
}
|
|
self.timeoutWorkItem = workItem
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: workItem)
|
|
}
|
|
|
|
func cancel() {
|
|
self.completeOnce(.failure(CancellationError()))
|
|
}
|
|
|
|
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
|
self.completeOnce(.success(()))
|
|
}
|
|
|
|
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
|
|
guard !self.hasCompleted else { return }
|
|
self.postCommitWorkItem?.cancel()
|
|
let workItem = DispatchWorkItem { [weak self] in
|
|
MainActor.assumeIsolated {
|
|
self?.completeOnce(.success(()))
|
|
}
|
|
}
|
|
self.postCommitWorkItem = workItem
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + Self.postCommitSuccessDelay, execute: workItem)
|
|
}
|
|
|
|
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<Void, Error>) {
|
|
guard !self.hasCompleted else { return }
|
|
self.hasCompleted = true
|
|
self.timeoutWorkItem?.cancel()
|
|
self.timeoutWorkItem = nil
|
|
self.postCommitWorkItem?.cancel()
|
|
self.postCommitWorkItem = 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
|