• 3 min read
How to get the list of installed browsers on macOS
To get the list of browsers using Swift on macOS we will use the following NSWorkspace
APIs:
func urlsForApplications(toOpen url: URL) -> [URL]
func urlsForApplications(toOpen contentType: UTType) -> [URL]
It’s worth noting that these APIs are only available on macOS 12.0 onwards. To support earlier versions of macOS, we will use the following APIs:
func LSCopyApplicationURLsForURL(
_ inURL: CFURL,
_ inRoleMask: LSRolesMask
) -> Unmanaged<CFArray>?
func LSCopyAllHandlersForURLScheme(_ inURLScheme: CFString) -> Unmanaged<CFArray>?
So, to actually get the list of installed browsers, let’s implement a general solution that intersects the result of the APIs:
extension NSWorkspace {
var urlsForBrowsersApplications: [URL] {
guard let schemeURL = URL(string: "https:") else {
return []
}
let htmlHandlerAppsURLs: [URL]
let httpsHandlerAppsURLs: [URL]
if #available(macOS 12.0, *) {
htmlHandlerAppsURLs = urlsForApplications(toOpen: .html)
httpsHandlerAppsURLs = urlsForApplications(toOpen: schemeURL)
} else {
let htmlHandlerAppsIds = LSCopyAllRoleHandlersForContentType(kUTTypeHTML, .viewer)?.takeRetainedValue() as! [String]
htmlHandlerAppsURLs = htmlHandlerAppsIds.compactMap(urlForApplication(withBundleIdentifier:))
httpsHandlerAppsURLs = LSCopyApplicationURLsForURL(schemeURL as CFURL, .viewer)?.takeRetainedValue() as! [URL]
}
return Array(Set(htmlHandlerAppsURLs).intersection(httpsHandlerAppsURLs))
}
}
From my own experience, just using an API to get the list of browsers is not enough, as some apps can handle HTTP/HTTPS even though they are not browsers. So making the intersection between the list of apps that handle HTTP/HTTPS and apps that handle HTML proved to be more accurate.
Getting the default browser
To get the URL of the default browser, there is also more than one API. However, as the most recent and recommended API requires at least macOS 10.6, I think it’s fine to just use it.
The API we’re going to use is in the NSWorkspace
class:
func urlForApplication(toOpen url: URL) -> URL?
And to get the URL of the default browser, this API can be used as follows:
extension NSWorkspace {
var urlForDefaultBrowserApplication: URL? {
guard let schemeURL = URL(string: "https:"),
let defaultBrowserURL = urlForApplication(toOpen: schemeURL) else {
return nil
}
return defaultBrowserURL
}
}
Bonus: changing the default browser
It wasn’t part of the scope of this post to show how to change the default browser, but I don’t see the point in writing a post just for that. So let’s take advantage of the context and add this point.
There are also two APIs here and we’re going to use both to support older versions of macOS while implementing a newer API.
For macOS 12.0 onwards, we have the following API from the NSWorkspace
class:
func setDefaultApplication(
at applicationURL: URL,
toOpenURLsWithScheme urlScheme: String,
completion completionHandler: ((Error?) -> Void)? = nil
)
For older versions of macOS, we have the following API:
func LSSetDefaultHandlerForURLScheme(
_ inURLScheme: CFString,
_ inHandlerBundleID: CFString
) -> OSStatus
Using both APIs, we can implement a general solution to change the default browser as follows:
extension NSWorkspace {
func setDefaultBrowserApplication(at url: URL) {
if #available(macOS 12.0, *) {
setDefaultApplication(at: url, toOpenURLsWithScheme: "http")
} else if let bundleId = Bundle(url: url)?.bundleIdentifier {
LSSetDefaultHandlerForURLScheme("http" as CFString, bundleId as CFString)
}
}
}
Note that we are using the string “http” and not “https”. I don’t know why, but using “https” has no effect.