1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
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/chrome_browser_main_mac.h"
7 #import <Cocoa/Cocoa.h>
10 #include "base/command_line.h"
11 #include "base/mac/bundle_locations.h"
12 #import "base/mac/foundation_util.h"
13 #include "base/mac/mac_util.h"
14 #include "base/mac/scoped_nsobject.h"
15 #include "base/metrics/histogram_functions.h"
16 #include "base/path_service.h"
17 #include "base/strings/sys_string_conversions.h"
18 #include "build/branding_buildflags.h"
19 #import "chrome/browser/app_controller_mac.h"
20 #include "chrome/browser/apps/app_shim/app_shim_listener.h"
21 #include "chrome/browser/browser_process.h"
22 #include "chrome/browser/browser_process_platform_part.h"
23 #import "chrome/browser/chrome_browser_application_mac.h"
24 #include "chrome/browser/first_run/first_run.h"
25 #include "chrome/browser/mac/install_from_dmg.h"
26 #import "chrome/browser/mac/keystone_glue.h"
27 #include "chrome/browser/mac/mac_startup_profiler.h"
28 #include "chrome/browser/ui/cocoa/main_menu_builder.h"
29 #include "chrome/common/channel_info.h"
30 #include "chrome/common/chrome_paths.h"
31 #include "chrome/common/chrome_switches.h"
32 #include "chrome/common/pref_names.h"
33 #include "chrome/grit/chromium_strings.h"
34 #include "components/crash/core/app/crashpad.h"
35 #include "components/metrics/metrics_service.h"
36 #include "components/os_crypt/os_crypt.h"
37 #include "components/version_info/channel.h"
38 #include "content/public/common/main_function_params.h"
39 #include "content/public/common/result_codes.h"
40 #include "ui/base/l10n/l10n_util.h"
41 #include "ui/base/resource/resource_bundle.h"
42 #include "ui/base/resource/resource_handle.h"
43 #include "ui/native_theme/native_theme_mac.h"
47 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
49 // These values are persisted to logs as OSXOtherChromeInstancesResult.
50 // Entries should not be renumbered and numeric values should never be reused.
51 enum class OtherInstancesResult {
52 kFailureDontKnowWhenOtherChromeUsed,
55 kOneOtherChromeAndLastUsedWithinWeek,
56 kOneOtherChromeAndLastUsedWithinMonth,
57 kOneOtherChromeAndLastUsedMoreThanAMonthAgo,
58 kMoreThanOneOtherChromeAndLastUsedWithinWeek,
59 kMoreThanOneOtherChromeAndLastUsedWithinMonth,
60 kMoreThanOneOtherChromeAndLastUsedMoreThanAMonthAgo,
61 kMaxValue = kMoreThanOneOtherChromeAndLastUsedMoreThanAMonthAgo,
65 int within_last_week = 0;
66 int within_last_month = 0;
67 int before_last_month = 0;
70 OtherInstancesResult OtherInstancesResultForWhenLastUsed(
71 const WhenLastUsed& used) {
72 if (used.within_last_week + used.within_last_month + used.before_last_month ==
74 return OtherInstancesResult::kNoOtherChrome;
77 if (used.within_last_week + used.within_last_month + used.before_last_month ==
79 if (used.within_last_week)
80 return OtherInstancesResult::kOneOtherChromeAndLastUsedWithinWeek;
82 if (used.within_last_month)
83 return OtherInstancesResult::kOneOtherChromeAndLastUsedWithinMonth;
85 return OtherInstancesResult::kOneOtherChromeAndLastUsedMoreThanAMonthAgo;
88 if (used.within_last_week)
89 return OtherInstancesResult::kMoreThanOneOtherChromeAndLastUsedWithinWeek;
91 if (used.within_last_month)
92 return OtherInstancesResult::kMoreThanOneOtherChromeAndLastUsedWithinMonth;
94 return OtherInstancesResult::
95 kMoreThanOneOtherChromeAndLastUsedMoreThanAMonthAgo;
98 void RecordChromeQueryResults(NSMetadataQuery* query) {
99 __block bool other_chrome_last_used_unknown = false;
100 __block bool failed_to_read_plist = false;
101 __block WhenLastUsed same_channel;
102 __block WhenLastUsed different_channel;
104 NSURL* this_url = NSBundle.mainBundle.bundleURL;
105 std::string this_channel = chrome::GetChannelName();
106 NSDate* about_a_week_ago =
107 [[NSDate date] dateByAddingTimeInterval:-7 * 24 * 60 * 60];
108 NSDate* about_a_month_ago =
109 [[NSDate date] dateByAddingTimeInterval:-30 * 24 * 60 * 60];
111 [query enumerateResultsUsingBlock:^(id result, NSUInteger idx, BOOL* stop) {
112 // Skip this copy of Chrome. Note that NSMetadataItemURLKey is not used as
113 // it always returns nil while NSMetadataItemPathKey returns a legit path.
114 // Filed as FB7689234.
115 NSString* app_path = base::mac::ObjCCast<NSString>(
116 [result valueForAttribute:NSMetadataItemPathKey]);
118 // It seems implausible, but there are Macs in the field for which
119 // Spotlight will find results for the query of locating Chrome but cannot
120 // actually return a path to the result. https://crbug.com/1086555
121 failed_to_read_plist = true;
126 NSURL* app_url = [NSURL fileURLWithPath:app_path isDirectory:YES];
127 if ([app_url isEqual:this_url])
130 NSURL* plist_url = [[app_url URLByAppendingPathComponent:@"Contents"
132 URLByAppendingPathComponent:@"Info.plist"
134 NSDictionary* plist = [NSDictionary dictionaryWithContentsOfURL:plist_url];
136 failed_to_read_plist = true;
141 // Skip any SxS-capable copies of Chrome.
142 if (plist[@"CrProductDirName"])
145 WhenLastUsed* when_last_used = &different_channel;
146 if (this_channel == base::SysNSStringToUTF8(plist[@"KSChannelID"]))
147 when_last_used = &same_channel;
149 NSDate* last_used = base::mac::ObjCCast<NSDate>(
150 [result valueForAttribute:NSMetadataItemLastUsedDateKey]);
152 other_chrome_last_used_unknown = true;
157 if ([last_used compare:about_a_week_ago] == NSOrderedDescending)
158 ++when_last_used->within_last_week;
159 else if ([last_used compare:about_a_month_ago] == NSOrderedDescending)
160 ++when_last_used->within_last_month;
162 ++when_last_used->before_last_month;
165 if (other_chrome_last_used_unknown) {
166 base::UmaHistogramEnumeration(
167 "OSX.Installation.OtherChromeInstances.SameChannel",
168 OtherInstancesResult::kFailureDontKnowWhenOtherChromeUsed);
169 base::UmaHistogramEnumeration(
170 "OSX.Installation.OtherChromeInstances.DifferentChannel",
171 OtherInstancesResult::kFailureDontKnowWhenOtherChromeUsed);
175 if (failed_to_read_plist) {
176 base::UmaHistogramEnumeration(
177 "OSX.Installation.OtherChromeInstances.SameChannel",
178 OtherInstancesResult::kFailureToReadPlist);
179 base::UmaHistogramEnumeration(
180 "OSX.Installation.OtherChromeInstances.DifferentChannel",
181 OtherInstancesResult::kFailureToReadPlist);
185 base::UmaHistogramEnumeration(
186 "OSX.Installation.OtherChromeInstances.SameChannel",
187 OtherInstancesResultForWhenLastUsed(same_channel));
188 base::UmaHistogramEnumeration(
189 "OSX.Installation.OtherChromeInstances.DifferentChannel",
190 OtherInstancesResultForWhenLastUsed(different_channel));
193 void ExecuteChromeQuery() {
194 __block NSMetadataQuery* query = [[NSMetadataQuery alloc] init];
196 __block id token = [[NSNotificationCenter defaultCenter]
197 addObserverForName:NSMetadataQueryDidFinishGatheringNotification
199 queue:[NSOperationQueue mainQueue]
200 usingBlock:^(NSNotification* note) {
202 RecordChromeQueryResults(query);
204 [[NSNotificationCenter defaultCenter] removeObserver:token];
208 [NSPredicate predicateWithFormat:
209 @"kMDItemContentType == 'com.apple.application-bundle'"
210 @"AND kMDItemCFBundleIdentifier == 'com.google.Chrome'"];
215 // Records statistics about this install of Chromium if it is a Google Chrome
216 // Beta or Google Chrome Dev instance. This is to allow for decisions to be made
217 // about the migration of user data directories.
218 void RecordBetaAndDevStats() {
219 version_info::Channel channel = chrome::GetChannel();
220 if (channel != version_info::Channel::BETA &&
221 channel != version_info::Channel::DEV) {
225 ExecuteChromeQuery();
228 #endif // GOOGLE_CHROME_BRANDING
232 // ChromeBrowserMainPartsMac ---------------------------------------------------
234 ChromeBrowserMainPartsMac::ChromeBrowserMainPartsMac(
235 const content::MainFunctionParams& parameters,
236 StartupData* startup_data)
237 : ChromeBrowserMainPartsPosix(parameters, startup_data) {}
239 ChromeBrowserMainPartsMac::~ChromeBrowserMainPartsMac() {
242 int ChromeBrowserMainPartsMac::PreEarlyInitialization() {
243 if (base::mac::WasLaunchedAsLoginItemRestoreState()) {
244 base::CommandLine* singleton_command_line =
245 base::CommandLine::ForCurrentProcess();
246 singleton_command_line->AppendSwitch(switches::kRestoreLastSession);
247 } else if (base::mac::WasLaunchedAsHiddenLoginItem()) {
248 base::CommandLine* singleton_command_line =
249 base::CommandLine::ForCurrentProcess();
250 singleton_command_line->AppendSwitch(switches::kNoStartupWindow);
253 return ChromeBrowserMainPartsPosix::PreEarlyInitialization();
256 void ChromeBrowserMainPartsMac::PreMainMessageLoopStart() {
257 MacStartupProfiler::GetInstance()->Profile(
258 MacStartupProfiler::PRE_MAIN_MESSAGE_LOOP_START);
259 ChromeBrowserMainPartsPosix::PreMainMessageLoopStart();
261 // ChromeBrowserMainParts should have loaded the resource bundle by this
262 // point (needed to load the nib).
263 CHECK(ui::ResourceBundle::HasSharedInstance());
265 // This is a no-op if the KeystoneRegistration framework is not present.
266 // The framework is only distributed with branded Google Chrome builds.
267 [[KeystoneGlue defaultKeystoneGlue] registerWithKeystone];
269 // Disk image installation is sort of a first-run task, so it shares the
270 // no first run switches.
272 // This needs to be done after the resource bundle is initialized (for
273 // access to localizations in the UI) and after Keystone is initialized
274 // (because the installation may need to promote Keystone) but before the
275 // app controller is set up (and thus before MainMenu.nib is loaded, because
276 // the app controller assumes that a browser has been set up and will crash
277 // upon receipt of certain notifications if no browser exists), before
278 // anyone tries doing anything silly like firing off an import job, and
279 // before anything creating preferences like Local State in order for the
280 // relaunched installed application to still consider itself as first-run.
281 if (!first_run::IsFirstRunSuppressed(parsed_command_line())) {
282 if (MaybeInstallFromDiskImage()) {
283 // The application was installed and the installed copy has been
284 // launched. This process is now obsolete. Exit.
289 // Create the app delegate. This object is intentionally leaked as a global
290 // singleton. It is accessed through -[NSApp delegate].
291 AppController* app_controller = [[AppController alloc] init];
292 [NSApp setDelegate:app_controller];
294 chrome::BuildMainMenu(NSApp, app_controller,
295 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME), false);
296 [app_controller mainMenuCreated];
298 PrefService* local_state = g_browser_process->local_state();
301 // AppKit only restores windows to their original spaces when relaunching
302 // apps after a restart, and puts them all on the current space when an app
303 // is manually quit and relaunched. If Chrome restarted itself, ask AppKit to
304 // treat this launch like a system restart and restore everything.
305 if (local_state->GetBoolean(prefs::kWasRestarted)) {
306 [NSUserDefaults.standardUserDefaults registerDefaults:@{
307 @"NSWindowRestoresWorkspaceAtLaunch" : @YES
312 void ChromeBrowserMainPartsMac::PostMainMessageLoopStart() {
313 MacStartupProfiler::GetInstance()->Profile(
314 MacStartupProfiler::POST_MAIN_MESSAGE_LOOP_START);
315 ChromeBrowserMainPartsPosix::PostMainMessageLoopStart();
317 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
318 RecordBetaAndDevStats();
319 #endif // GOOGLE_CHROME_BRANDING
322 void ChromeBrowserMainPartsMac::PreProfileInit() {
323 MacStartupProfiler::GetInstance()->Profile(
324 MacStartupProfiler::PRE_PROFILE_INIT);
325 ChromeBrowserMainPartsPosix::PreProfileInit();
327 // This is called here so that the app shim socket is only created after
328 // taking the singleton lock.
329 g_browser_process->platform_part()->app_shim_listener()->Init();
332 void ChromeBrowserMainPartsMac::PostProfileInit() {
333 MacStartupProfiler::GetInstance()->Profile(
334 MacStartupProfiler::POST_PROFILE_INIT);
335 ChromeBrowserMainPartsPosix::PostProfileInit();
337 g_browser_process->metrics_service()->RecordBreakpadRegistration(
338 crash_reporter::GetUploadsEnabled());
340 // Activation of Keystone is not automatic but done in response to the
341 // counting and reporting of profiles.
342 KeystoneGlue* glue = [KeystoneGlue defaultKeystoneGlue];
343 if (glue && ![glue isRegisteredAndActive]) {
344 // If profile loading has failed, we still need to handle other tasks
345 // like marking of the product as active.
346 [glue setRegistrationActive];
350 void ChromeBrowserMainPartsMac::DidEndMainMessageLoop() {
351 AppController* appController =
352 base::mac::ObjCCastStrict<AppController>([NSApp delegate]);
353 [appController didEndMainMessageLoop];