Skip to content

Instantly share code, notes, and snippets.

@xmollv
Created April 29, 2026 16:08
Show Gist options
  • Select an option

  • Save xmollv/df94f2bef40c1e98fb2efc177727aae5 to your computer and use it in GitHub Desktop.

Select an option

Save xmollv/df94f2bef40c1e98fb2efc177727aae5 to your computer and use it in GitHub Desktop.
concurrency.swift
import SafariServices
// TODO: Remove these conformances if Apple ships their own.
extension SFSafariWindow: @retroactive @unchecked Sendable {}
extension SFSafariPage: @retroactive @unchecked Sendable {}
class SafariExtensionHandler: SFSafariExtensionHandler {
// MARK: Toolbar
override func validateToolbarItem(in window: SFSafariWindow, validationHandler: @escaping (Bool, String) -> Void) {
nonisolated(unsafe) let handler = validationHandler
Task { @MainActor in
let url = await window.getURL()
handler(url != nil, "")
}
}
override func toolbarItemClicked(in window: SFSafariWindow) {
Task { @MainActor in
guard let url = await window.getURL() else { return }
Self.handle(url: url)
}
}
// MARK: Context menu
override func contextMenuItemSelected(withCommand command: String, in page: SFSafariPage, userInfo: [String : Any]? = nil) {
guard command == "io.xmollv.abyss.safari.contextmenu" else { return }
if let userInfo,
let link = userInfo["link"] as? String,
let url = URL(string: link),
url.scheme == "http" || url.scheme == "https" {
Task { @MainActor in
Self.handle(url: url)
}
} else {
Task { @MainActor in
guard let properties = await page.properties(), let url = properties.url else { return }
Self.handle(url: url)
}
}
}
// MARK: Private helpers
@MainActor
private static func handle(url: URL) {
guard let urlToOpen = URL(string: "later://x-callback-url/add?url=\(url.absoluteString)") else { return }
NSWorkspace.shared.open(urlToOpen)
}
}
private extension SFSafariWindow {
func getURL() async -> URL? {
guard let activeTab = await activeTab() else { return nil }
guard let page = await activeTab.activePage() else { return nil }
guard let properties = await page.properties() else { return nil }
return properties.url
}
}
@mattmassicotte
Copy link
Copy Markdown

you should be able to remove those to retroactives and instead do:

@preconcurrency import SafariServices

This is only marginally-better, though. Ultimately, the problem is that SFSafariExtensionHandler isn't correctly annotated. So, we'd have to do pretty deep investigations into how it actually works here. Needing to subclass makes it particularly hard. But I would not be surprised to learn that it is actually a MainActor type. And if that's the case, this will become easy to fix later on.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment