chromium/chrome/browser/ui/cocoa/first_run_dialog_cocoa.mm

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "chrome/browser/ui/cocoa/first_run_dialog_cocoa.h"

#include "base/apple/bundle_locations.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "base/strings/sys_string_conversions.h"
#include "base/task/current_thread.h"
#import "base/task/single_thread_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "chrome/browser/browser_process_impl.h"
#include "chrome/browser/first_run/first_run.h"
#include "chrome/browser/first_run/first_run_dialog.h"
#include "chrome/browser/metrics/metrics_reporting_state.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/shell_integration.h"
#include "chrome/browser/ui/cocoa/first_run_dialog_controller.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/url_constants.h"
#include "content/public/common/content_switches.h"
#include "ui/base/l10n/l10n_util_mac.h"
#include "url/gurl.h"

@interface FirstRunDialogController (PrivateMethods)
// Show the dialog.
- (void)show;
@end

namespace {

class FirstRunShowBridge : public base::RefCounted<FirstRunShowBridge> {
 public:
  explicit FirstRunShowBridge(FirstRunDialogController* controller);

  void ShowDialog(base::OnceClosure quit_closure);

 private:
  friend class base::RefCounted<FirstRunShowBridge>;

  ~FirstRunShowBridge();

  FirstRunDialogController* controller_;
};

FirstRunShowBridge::FirstRunShowBridge(FirstRunDialogController* controller)
    : controller_(controller) {}

void FirstRunShowBridge::ShowDialog(base::OnceClosure quit_closure) {
  // Proceeding past the modal dialog requires user interaction. Allow nested
  // tasks to run so that signal handlers operate correctly.
  base::CurrentThread::ScopedAllowApplicationTasksInNativeNestedLoop allow;
  [controller_ show];
  std::move(quit_closure).Run();
}

FirstRunShowBridge::~FirstRunShowBridge() = default;

void ShowFirstRunModal() {
  FirstRunDialogController* dialog = [[FirstRunDialogController alloc] init];

  [dialog showWindow:nil];

  // If the dialog asked the user to opt-in for stats and crash reporting,
  // record the decision and enable the crash reporter if appropriate.
  bool consent_given = [dialog isStatsReportingEnabled];
  ChangeMetricsReportingState(consent_given);

  // If selected, set as default browser. Skip in automated tests so that an OS
  // dialog confirming the default browser choice isn't left on screen.
  BOOL make_default_browser =
      [dialog isMakeDefaultBrowserEnabled] &&
      !base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType);
  if (make_default_browser) {
    bool success = shell_integration::SetAsDefaultBrowser();
    DCHECK(success);
  }
}

}  // namespace

namespace first_run {

void ShowFirstRunDialogCocoa() {
  ShowFirstRunModal();
}

}  // namespace first_run

@implementation FirstRunDialogController {
  FirstRunDialogViewController* __strong _viewController;
}

- (instancetype)init {
  _viewController = [[FirstRunDialogViewController alloc] init];

  // Create the content view controller (and the content view) *before* the
  // window, so that we can find out what the content view's frame is supposed
  // to be for use here.
  NSWindow* window =
      [[NSWindow alloc] initWithContentRect:_viewController.view.frame
                                  styleMask:NSWindowStyleMaskTitled
                                    backing:NSBackingStoreBuffered
                                      defer:YES];
  window.contentView = _viewController.view;
  window.title = [_viewController windowTitle];

  self = [super initWithWindow:window];

  return self;
}

- (IBAction)showWindow:(id)sender {
  // The main MessageLoop has not yet run, but has been spun. If we call
  // -[NSApplication runModalForWindow:] we will hang <http://crbug.com/54248>.
  // Therefore the main MessageLoop is run so things work.

  scoped_refptr<FirstRunShowBridge> bridge(new FirstRunShowBridge(self));
  base::RunLoop run_loop;

  // At this point during startup, ChromeBrowserMain has yet to start the main
  // message loop. Consequently, this run loop will effectively be the main
  // message loop for the duration of the dialog's lifetime. Tell the
  // BrowserProcessImpl how to quit the loop if any of the shutdown signal
  // handlers is received. (The ShutdownDetector posts a task to the UI thread's
  // TaskRunner to begin shutdown upon receiving a SIGTERM.)
  static_cast<BrowserProcessImpl*>(g_browser_process)
      ->SetQuitClosure(base::BindOnce(
          [](base::RunLoop* run_loop) {
            [NSApp abortModal];
            run_loop->Quit();
          },
          &run_loop));

  // Barring a shutdown signal, the run loop will quit when the user closes the
  // first run dialog.
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&FirstRunShowBridge::ShowDialog, bridge.get(),
                                run_loop.QuitClosure()));
  run_loop.Run();

  static_cast<BrowserProcessImpl*>(g_browser_process)->ClearQuitClosure();
}

- (void)show {
  NSWindow* win = [self window];

  // Neat weirdness in the below code - the Application menu stays enabled
  // while the window is open but selecting items from it (e.g. Quit) has
  // no effect.  I'm guessing that this is an artifact of us being a
  // background-only application at this stage and displaying a modal
  // window.

  // Display dialog.
  [win center];
  [NSApp runModalForWindow:win];
}

- (BOOL)isStatsReportingEnabled {
  return [_viewController isStatsReportingEnabled];
}

- (BOOL)isMakeDefaultBrowserEnabled {
  return [_viewController isMakeDefaultBrowserEnabled];
}

@end