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 #import "chrome/browser/ui/cocoa/first_run_dialog.h"
8 #include "base/mac/bundle_locations.h"
9 #include "base/mac/mac_util.h"
10 #import "base/mac/scoped_nsobject.h"
11 #include "base/memory/ref_counted.h"
12 #include "base/message_loop/message_loop.h"
13 #include "base/strings/sys_string_conversions.h"
14 #include "chrome/browser/first_run/first_run.h"
15 #include "chrome/browser/first_run/first_run_dialog.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/browser/search_engines/template_url_service_factory.h"
18 #include "chrome/browser/shell_integration.h"
19 #include "chrome/common/chrome_version_info.h"
20 #include "chrome/common/url_constants.h"
21 #include "components/search_engines/template_url_service.h"
22 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
23 #include "ui/base/l10n/l10n_util_mac.h"
26 #if defined(GOOGLE_CHROME_BUILD)
27 #include "base/prefs/pref_service.h"
28 #include "chrome/browser/browser_process.h"
29 #include "chrome/common/pref_names.h"
30 #include "chrome/installer/util/google_update_settings.h"
31 #import "components/breakpad/app/breakpad_mac.h"
34 @interface FirstRunDialogController (PrivateMethods)
41 // Compare function for -[NSArray sortedArrayUsingFunction:context:] that
42 // sorts the views in Y order bottom up.
43 NSInteger CompareFrameY(id view1, id view2, void* context) {
44 CGFloat y1 = NSMinY([view1 frame]);
45 CGFloat y2 = NSMinY([view2 frame]);
47 return NSOrderedAscending;
49 return NSOrderedDescending;
54 class FirstRunShowBridge : public base::RefCounted<FirstRunShowBridge> {
56 FirstRunShowBridge(FirstRunDialogController* controller);
61 friend class base::RefCounted<FirstRunShowBridge>;
63 ~FirstRunShowBridge();
65 FirstRunDialogController* controller_;
68 FirstRunShowBridge::FirstRunShowBridge(
69 FirstRunDialogController* controller) : controller_(controller) {
72 void FirstRunShowBridge::ShowDialog() {
74 base::MessageLoop::current()->QuitNow();
77 FirstRunShowBridge::~FirstRunShowBridge() {}
79 // Show the first run UI.
80 // Returns true if the first run dialog was shown.
81 bool ShowFirstRun(Profile* profile) {
82 bool dialog_shown = false;
83 #if defined(GOOGLE_CHROME_BUILD)
84 // The purpose of the dialog is to ask the user to enable stats and crash
85 // reporting. This setting may be controlled through configuration management
86 // in enterprise scenarios. If that is the case, skip the dialog entirely, as
87 // it's not worth bothering the user for only the default browser question
88 // (which is likely to be forced in enterprise deployments anyway).
89 const PrefService::Preference* metrics_reporting_pref =
90 g_browser_process->local_state()->FindPreference(
91 prefs::kMetricsReportingEnabled);
92 if (!metrics_reporting_pref || !metrics_reporting_pref->IsManaged()) {
93 base::scoped_nsobject<FirstRunDialogController> dialog(
94 [[FirstRunDialogController alloc] init]);
96 [dialog.get() showWindow:nil];
99 // If the dialog asked the user to opt-in for stats and crash reporting,
100 // record the decision and enable the crash reporter if appropriate.
101 bool stats_enabled = [dialog.get() statsEnabled];
102 GoogleUpdateSettings::SetCollectStatsConsent(stats_enabled);
104 // Breakpad is normally enabled very early in the startup process. However,
105 // on the first run it may not have been enabled due to the missing opt-in
106 // from the user. If the user agreed now, enable breakpad if necessary.
107 if (!breakpad::IsCrashReporterEnabled() && stats_enabled) {
108 breakpad::InitCrashReporter(std::string());
109 breakpad::InitCrashProcessInfo(std::string());
112 // If selected set as default browser.
113 BOOL make_default_browser = [dialog.get() makeDefaultBrowser];
114 if (make_default_browser) {
115 bool success = ShellIntegration::SetAsDefaultBrowser();
119 #else // GOOGLE_CHROME_BUILD
120 // We don't show the dialog in Chromium.
121 #endif // GOOGLE_CHROME_BUILD
123 // Set preference to show first run bubble and welcome page.
124 // Only display the bubble if there is a default search provider.
125 TemplateURLService* search_engines_model =
126 TemplateURLServiceFactory::GetForProfile(profile);
127 if (search_engines_model &&
128 search_engines_model->GetDefaultSearchProvider()) {
129 first_run::SetShowFirstRunBubblePref(first_run::FIRST_RUN_BUBBLE_SHOW);
131 first_run::SetShouldShowWelcomePage();
136 // True when the stats checkbox should be checked by default. This is only
137 // the case when the canary is running.
138 bool StatsCheckboxDefault() {
139 return chrome::VersionInfo::GetChannel() ==
140 chrome::VersionInfo::CHANNEL_CANARY;
145 namespace first_run {
147 bool ShowFirstRunDialog(Profile* profile) {
148 return ShowFirstRun(profile);
151 } // namespace first_run
153 @implementation FirstRunDialogController
155 @synthesize statsEnabled = statsEnabled_;
156 @synthesize makeDefaultBrowser = makeDefaultBrowser_;
160 [base::mac::FrameworkBundle() pathForResource:@"FirstRunDialog"
162 if ((self = [super initWithWindowNibPath:nibpath owner:self])) {
163 // Bound to the dialog checkboxes.
164 makeDefaultBrowser_ = ShellIntegration::CanSetAsDefaultBrowser() !=
165 ShellIntegration::SET_DEFAULT_NOT_ALLOWED;
166 statsEnabled_ = StatsCheckboxDefault();
175 - (IBAction)showWindow:(id)sender {
176 // The main MessageLoop has not yet run, but has been spun. If we call
177 // -[NSApplication runModalForWindow:] we will hang <http://crbug.com/54248>.
178 // Therefore the main MessageLoop is run so things work.
180 scoped_refptr<FirstRunShowBridge> bridge(new FirstRunShowBridge(self));
181 base::MessageLoop::current()->PostTask(FROM_HERE,
182 base::Bind(&FirstRunShowBridge::ShowDialog, bridge.get()));
183 base::MessageLoop::current()->Run();
187 NSWindow* win = [self window];
189 if (!ShellIntegration::CanSetAsDefaultBrowser()) {
190 [setAsDefaultCheckbox_ setHidden:YES];
193 // Only support the sizing the window once.
194 DCHECK(!beenSized_) << "ShowWindow was called twice?";
197 DCHECK_GT([objectsToSize_ count], 0U);
199 // Size everything to fit, collecting the widest growth needed (XIB provides
200 // the min size, i.e.-never shrink, just grow).
201 CGFloat largestWidthChange = 0.0;
202 for (NSView* view in objectsToSize_) {
203 DCHECK_NE(statsCheckbox_, view) << "Stats checkbox shouldn't be in list";
204 if (![view isHidden]) {
205 NSSize delta = [GTMUILocalizerAndLayoutTweaker sizeToFitView:view];
206 DCHECK_EQ(delta.height, 0.0)
207 << "Didn't expect anything to change heights";
208 if (largestWidthChange < delta.width)
209 largestWidthChange = delta.width;
213 // Make the window wide enough to fit everything.
214 if (largestWidthChange > 0.0) {
215 NSView* contentView = [win contentView];
216 NSRect windowFrame = [contentView convertRect:[win frame] fromView:nil];
217 windowFrame.size.width += largestWidthChange;
218 windowFrame = [contentView convertRect:windowFrame toView:nil];
219 [win setFrame:windowFrame display:NO];
222 // The stats checkbox gets some really long text, so it gets word wrapped
224 DCHECK(statsCheckbox_);
225 CGFloat statsCheckboxHeightChange = 0.0;
226 [GTMUILocalizerAndLayoutTweaker wrapButtonTitleForWidth:statsCheckbox_];
227 statsCheckboxHeightChange =
228 [GTMUILocalizerAndLayoutTweaker sizeToFitView:statsCheckbox_].height;
230 // Walk bottom up shuffling for all the hidden views.
232 [[[win contentView] subviews] sortedArrayUsingFunction:CompareFrameY
234 CGFloat moveDown = 0.0;
235 NSUInteger numSubViews = [subViews count];
236 for (NSUInteger idx = 0 ; idx < numSubViews ; ++idx) {
237 NSView* view = [subViews objectAtIndex:idx];
239 // If the view is hidden, collect the amount to move everything above it
240 // down, if it's not hidden, apply any shift down.
241 if ([view isHidden]) {
242 DCHECK_GT((numSubViews - 1), idx)
243 << "Don't support top view being hidden";
244 NSView* nextView = [subViews objectAtIndex:(idx + 1)];
245 CGFloat viewBottom = [view frame].origin.y;
246 CGFloat nextViewBottom = [nextView frame].origin.y;
247 moveDown += nextViewBottom - viewBottom;
249 if (moveDown != 0.0) {
250 NSPoint origin = [view frame].origin;
251 origin.y -= moveDown;
252 [view setFrameOrigin:origin];
255 // Special case, if this is the stats checkbox, everything above it needs
256 // to get moved up by the amount it changed height.
257 if (view == statsCheckbox_) {
258 moveDown -= statsCheckboxHeightChange;
262 // Resize the window for any height change from hidden views, etc.
263 if (moveDown != 0.0) {
264 NSView* contentView = [win contentView];
265 [contentView setAutoresizesSubviews:NO];
266 NSRect windowFrame = [contentView convertRect:[win frame] fromView:nil];
267 windowFrame.size.height -= moveDown;
268 windowFrame = [contentView convertRect:windowFrame toView:nil];
269 [win setFrame:windowFrame display:NO];
270 [contentView setAutoresizesSubviews:YES];
275 // Neat weirdness in the below code - the Application menu stays enabled
276 // while the window is open but selecting items from it (e.g. Quit) has
277 // no effect. I'm guessing that this is an artifact of us being a
278 // background-only application at this stage and displaying a modal
283 [NSApp runModalForWindow:win];
286 - (IBAction)ok:(id)sender {
287 [[self window] close];
291 - (IBAction)learnMore:(id)sender {
292 NSString* urlStr = base::SysUTF8ToNSString(chrome::kLearnMoreReportingURL);
293 NSURL* learnMoreUrl = [NSURL URLWithString:urlStr];
294 [[NSWorkspace sharedWorkspace] openURL:learnMoreUrl];