1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/shell_integration.h"
7 #include <AppKit/AppKit.h>
8 #include <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
10 #include "base/apple/bridging.h"
11 #include "base/apple/bundle_locations.h"
12 #include "base/apple/foundation_util.h"
13 #include "base/apple/scoped_cftyperef.h"
14 #include "base/mac/mac_util.h"
15 #include "base/strings/sys_string_conversions.h"
16 #include "build/branding_buildflags.h"
17 #include "chrome/common/channel_info.h"
18 #include "components/version_info/version_info.h"
19 #import "net/base/mac/url_conversions.h"
21 namespace shell_integration {
25 // Returns the bundle id of the default client application for the given
26 // scheme or nil on failure.
27 NSString* GetBundleIdForDefaultAppForScheme(NSString* scheme) {
29 [NSURL URLWithString:[scheme stringByAppendingString:@":"]];
31 NSURL* default_app_url =
32 [NSWorkspace.sharedWorkspace URLForApplicationToOpenURL:scheme_url];
33 if (!default_app_url) {
37 NSBundle* default_app_bundle = [NSBundle bundleWithURL:default_app_url];
38 return default_app_bundle.bundleIdentifier;
43 bool SetAsDefaultBrowser() {
44 if (@available(macOS 12, *)) {
45 // We really do want the outer bundle here, not the main bundle since
46 // setting a shortcut to Chrome as the default browser doesn't make sense.
47 NSURL* app_bundle = base::apple::OuterBundleURL();
52 [NSWorkspace.sharedWorkspace setDefaultApplicationAtURL:app_bundle
53 toOpenURLsWithScheme:@"http"
54 completionHandler:^(NSError*){
56 [NSWorkspace.sharedWorkspace setDefaultApplicationAtURL:app_bundle
57 toOpenURLsWithScheme:@"https"
58 completionHandler:^(NSError*){
60 [NSWorkspace.sharedWorkspace setDefaultApplicationAtURL:app_bundle
61 toOpenContentType:UTTypeHTML
62 completionHandler:^(NSError*){
64 // TODO(https://crbug.com/1393452): Passing empty completion handlers,
65 // above, is kinda broken, but given that this API is synchronous, nothing
66 // better can be done. This entire API should be rebuilt.
68 // We really do want the outer bundle here, not the main bundle since
69 // setting a shortcut to Chrome as the default browser doesn't make sense.
70 CFStringRef identifier =
71 base::apple::NSToCFPtrCast(base::apple::OuterBundle().bundleIdentifier);
76 if (LSSetDefaultHandlerForURLScheme(CFSTR("http"), identifier) != noErr) {
79 if (LSSetDefaultHandlerForURLScheme(CFSTR("https"), identifier) != noErr) {
82 if (LSSetDefaultRoleHandlerForContentType(kUTTypeHTML, kLSRolesViewer,
83 identifier) != noErr) {
88 // The CoreServicesUIAgent presents a dialog asking the user to confirm their
89 // new default browser choice, but the agent sometimes orders the dialog
90 // behind the Chrome window. The user never sees the dialog, and therefore
91 // never confirms the change. Make the CoreServicesUIAgent active so the
92 // confirmation dialog comes to the front.
93 NSString* const kCoreServicesUIAgentBundleID =
94 @"com.apple.coreservices.uiagent";
96 for (NSRunningApplication* application in NSWorkspace.sharedWorkspace
97 .runningApplications) {
98 if ([application.bundleIdentifier
99 isEqualToString:kCoreServicesUIAgentBundleID]) {
100 [application activateWithOptions:NSApplicationActivateAllWindows];
108 bool SetAsDefaultClientForScheme(const std::string& scheme) {
109 if (scheme.empty()) {
113 if (GetDefaultSchemeClientSetPermission() != SET_DEFAULT_UNATTENDED) {
117 if (@available(macOS 12, *)) {
118 // We really do want the main bundle here since it makes sense to set an
119 // app shortcut as a default scheme handler.
120 NSURL* app_bundle = base::apple::MainBundleURL();
125 [NSWorkspace.sharedWorkspace
126 setDefaultApplicationAtURL:app_bundle
127 toOpenURLsWithScheme:base::SysUTF8ToNSString(scheme)
128 completionHandler:^(NSError*){
131 // TODO(https://crbug.com/1393452): Passing empty completion handlers,
132 // above, is kinda broken, but given that this API is synchronous, nothing
133 // better can be done. This entire API should be rebuilt.
136 // We really do want the main bundle here since it makes sense to set an
137 // app shortcut as a default scheme handler.
138 NSString* identifier = base::apple::MainBundle().bundleIdentifier;
143 NSString* scheme_ns = base::SysUTF8ToNSString(scheme);
144 OSStatus return_code =
145 LSSetDefaultHandlerForURLScheme(base::apple::NSToCFPtrCast(scheme_ns),
146 base::apple::NSToCFPtrCast(identifier));
147 return return_code == noErr;
151 std::u16string GetApplicationNameForScheme(const GURL& url) {
152 NSURL* ns_url = net::NSURLWithGURL(url);
158 [NSWorkspace.sharedWorkspace URLForApplicationToOpenURL:ns_url];
160 return std::u16string();
163 NSString* app_display_name =
164 [NSFileManager.defaultManager displayNameAtPath:app_url.path];
165 return base::SysNSStringToUTF16(app_display_name);
168 std::vector<base::FilePath> GetAllApplicationPathsForURL(const GURL& url) {
169 NSURL* ns_url = net::NSURLWithGURL(url);
174 NSArray* app_urls = nil;
175 if (@available(macos 12.0, *)) {
177 [NSWorkspace.sharedWorkspace URLsForApplicationsToOpenURL:ns_url];
179 app_urls = base::apple::CFToNSOwnershipCast(LSCopyApplicationURLsForURL(
180 base::apple::NSToCFPtrCast(ns_url), kLSRolesAll));
183 if (app_urls.count == 0) {
187 std::vector<base::FilePath> app_paths;
188 app_paths.reserve(app_urls.count);
189 for (NSURL* app_url in app_urls) {
190 app_paths.push_back(base::apple::NSURLToFilePath(app_url));
195 bool CanApplicationHandleURL(const base::FilePath& app_path, const GURL& url) {
196 NSURL* ns_item_url = net::NSURLWithGURL(url);
197 NSURL* ns_app_url = base::apple::FilePathToNSURL(app_path);
198 Boolean result = FALSE;
199 LSCanURLAcceptURL(base::apple::NSToCFPtrCast(ns_item_url),
200 base::apple::NSToCFPtrCast(ns_app_url), kLSRolesAll,
201 kLSAcceptDefault, &result);
205 // Attempt to determine if this instance of Chrome is the default browser and
206 // return the appropriate state. (Defined as being the handler for HTTP/HTTPS
207 // schemes; we don't want to report "no" here if the user has simply chosen
208 // to open HTML files in a text editor and FTP links with an FTP client.)
209 DefaultWebClientState GetDefaultBrowser() {
210 // We really do want the outer bundle here, since this we want to know the
211 // status of the main Chrome bundle and not a shortcut.
212 NSString* my_identifier = base::apple::OuterBundle().bundleIdentifier;
213 if (!my_identifier) {
214 return UNKNOWN_DEFAULT;
217 NSString* default_browser = GetBundleIdForDefaultAppForScheme(@"http");
218 if ([default_browser isEqualToString:my_identifier]) {
222 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
223 // Flavors of Chrome are of the constructions "com.google.Chrome" and
224 // "com.google.Chrome.beta". If the first three components match, then these
225 // are variant flavors.
226 auto three_components_only_lopper = [](NSString* bundle_id) {
227 NSMutableArray<NSString*>* parts =
228 [[bundle_id componentsSeparatedByString:@"."] mutableCopy];
229 while (parts.count > 3) {
230 [parts removeLastObject];
232 return [parts componentsJoinedByString:@"."];
235 NSString* my_identifier_lopped = three_components_only_lopper(my_identifier);
236 NSString* default_browser_lopped =
237 three_components_only_lopper(default_browser);
239 if ([my_identifier_lopped isEqualToString:default_browser_lopped]) {
240 return OTHER_MODE_IS_DEFAULT;
242 #endif // BUILDFLAG(GOOGLE_CHROME_BRANDING)
246 // Returns true if Firefox is the default browser for the current user.
247 bool IsFirefoxDefaultBrowser() {
248 return [GetBundleIdForDefaultAppForScheme(@"http")
249 isEqualToString:@"org.mozilla.firefox"];
252 // Attempt to determine if this instance of Chrome is the default client
253 // application for the given scheme and return the appropriate state.
254 DefaultWebClientState IsDefaultClientForScheme(const std::string& scheme) {
255 if (scheme.empty()) {
256 return UNKNOWN_DEFAULT;
259 // We really do want the main bundle here since it makes sense to set an
260 // app shortcut as a default scheme handler.
261 NSString* my_identifier = base::apple::MainBundle().bundleIdentifier;
262 if (!my_identifier) {
263 return UNKNOWN_DEFAULT;
266 NSString* default_browser =
267 GetBundleIdForDefaultAppForScheme(base::SysUTF8ToNSString(scheme));
268 return [default_browser isEqualToString:my_identifier] ? IS_DEFAULT
274 DefaultWebClientSetPermission GetPlatformSpecificDefaultWebClientSetPermission(
275 WebClientSetMethod method) {
276 // This should be `SET_DEFAULT_INTERACTIVE`, but that changes how
277 // `DefaultBrowserWorker` and `DefaultSchemeClientWorker` work.
278 // TODO(https://crbug.com/1393452): Migrate all callers to the new API,
279 // migrate all the Mac code to integrate with it, and change this to return
280 // the correct value.
281 return SET_DEFAULT_UNATTENDED;
284 } // namespace internal
286 } // namespace shell_integration