[M120 Migration]Fix for crash during chrome exit
[platform/framework/web/chromium-efl.git] / chrome / browser / shell_integration_mac.mm
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.
4
5 #include "chrome/browser/shell_integration.h"
6
7 #include <AppKit/AppKit.h>
8 #include <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
9
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"
20
21 namespace shell_integration {
22
23 namespace {
24
25 // Returns the bundle id of the default client application for the given
26 // scheme or nil on failure.
27 NSString* GetBundleIdForDefaultAppForScheme(NSString* scheme) {
28   NSURL* scheme_url =
29       [NSURL URLWithString:[scheme stringByAppendingString:@":"]];
30
31   NSURL* default_app_url =
32       [NSWorkspace.sharedWorkspace URLForApplicationToOpenURL:scheme_url];
33   if (!default_app_url) {
34     return nil;
35   }
36
37   NSBundle* default_app_bundle = [NSBundle bundleWithURL:default_app_url];
38   return default_app_bundle.bundleIdentifier;
39 }
40
41 }  // namespace
42
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();
48     if (!app_bundle) {
49       return false;
50     }
51
52     [NSWorkspace.sharedWorkspace setDefaultApplicationAtURL:app_bundle
53                                        toOpenURLsWithScheme:@"http"
54                                           completionHandler:^(NSError*){
55                                           }];
56     [NSWorkspace.sharedWorkspace setDefaultApplicationAtURL:app_bundle
57                                        toOpenURLsWithScheme:@"https"
58                                           completionHandler:^(NSError*){
59                                           }];
60     [NSWorkspace.sharedWorkspace setDefaultApplicationAtURL:app_bundle
61                                           toOpenContentType:UTTypeHTML
62                                           completionHandler:^(NSError*){
63                                           }];
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.
67   } else {
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);
72     if (!identifier) {
73       return false;
74     }
75
76     if (LSSetDefaultHandlerForURLScheme(CFSTR("http"), identifier) != noErr) {
77       return false;
78     }
79     if (LSSetDefaultHandlerForURLScheme(CFSTR("https"), identifier) != noErr) {
80       return false;
81     }
82     if (LSSetDefaultRoleHandlerForContentType(kUTTypeHTML, kLSRolesViewer,
83                                               identifier) != noErr) {
84       return false;
85     }
86   }
87
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";
95
96   for (NSRunningApplication* application in NSWorkspace.sharedWorkspace
97            .runningApplications) {
98     if ([application.bundleIdentifier
99             isEqualToString:kCoreServicesUIAgentBundleID]) {
100       [application activateWithOptions:NSApplicationActivateAllWindows];
101       break;
102     }
103   }
104
105   return true;
106 }
107
108 bool SetAsDefaultClientForScheme(const std::string& scheme) {
109   if (scheme.empty()) {
110     return false;
111   }
112
113   if (GetDefaultSchemeClientSetPermission() != SET_DEFAULT_UNATTENDED) {
114     return false;
115   }
116
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();
121     if (!app_bundle) {
122       return false;
123     }
124
125     [NSWorkspace.sharedWorkspace
126         setDefaultApplicationAtURL:app_bundle
127               toOpenURLsWithScheme:base::SysUTF8ToNSString(scheme)
128                  completionHandler:^(NSError*){
129                  }];
130
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.
134     return true;
135   } else {
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;
139     if (!identifier) {
140       return false;
141     }
142
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;
148   }
149 }
150
151 std::u16string GetApplicationNameForScheme(const GURL& url) {
152   NSURL* ns_url = net::NSURLWithGURL(url);
153   if (!ns_url) {
154     return {};
155   }
156
157   NSURL* app_url =
158       [NSWorkspace.sharedWorkspace URLForApplicationToOpenURL:ns_url];
159   if (!app_url) {
160     return std::u16string();
161   }
162
163   NSString* app_display_name =
164       [NSFileManager.defaultManager displayNameAtPath:app_url.path];
165   return base::SysNSStringToUTF16(app_display_name);
166 }
167
168 std::vector<base::FilePath> GetAllApplicationPathsForURL(const GURL& url) {
169   NSURL* ns_url = net::NSURLWithGURL(url);
170   if (!ns_url) {
171     return {};
172   }
173
174   NSArray* app_urls = nil;
175   if (@available(macos 12.0, *)) {
176     app_urls =
177         [NSWorkspace.sharedWorkspace URLsForApplicationsToOpenURL:ns_url];
178   } else {
179     app_urls = base::apple::CFToNSOwnershipCast(LSCopyApplicationURLsForURL(
180         base::apple::NSToCFPtrCast(ns_url), kLSRolesAll));
181   }
182
183   if (app_urls.count == 0) {
184     return {};
185   }
186
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));
191   }
192   return app_paths;
193 }
194
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);
202   return result;
203 }
204
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;
215   }
216
217   NSString* default_browser = GetBundleIdForDefaultAppForScheme(@"http");
218   if ([default_browser isEqualToString:my_identifier]) {
219     return IS_DEFAULT;
220   }
221
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];
231     }
232     return [parts componentsJoinedByString:@"."];
233   };
234
235   NSString* my_identifier_lopped = three_components_only_lopper(my_identifier);
236   NSString* default_browser_lopped =
237       three_components_only_lopper(default_browser);
238
239   if ([my_identifier_lopped isEqualToString:default_browser_lopped]) {
240     return OTHER_MODE_IS_DEFAULT;
241   }
242 #endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
243   return NOT_DEFAULT;
244 }
245
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"];
250 }
251
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;
257   }
258
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;
264   }
265
266   NSString* default_browser =
267       GetBundleIdForDefaultAppForScheme(base::SysUTF8ToNSString(scheme));
268   return [default_browser isEqualToString:my_identifier] ? IS_DEFAULT
269                                                          : NOT_DEFAULT;
270 }
271
272 namespace internal {
273
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;
282 }
283
284 }  // namespace internal
285
286 }  // namespace shell_integration