chromium/ui/views_content_client/views_content_client_main_parts_mac.mm

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

#import <Cocoa/Cocoa.h>

#include <utility>

#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/path_service.h"
#include "content/public/browser/context_factory.h"
#include "content/public/common/content_paths.h"
#include "content/public/common/result_codes.h"
#include "content/shell/browser/shell_application_mac.h"
#include "content/shell/browser/shell_browser_context.h"
#include "ui/views/test/test_views_delegate.h"
#include "ui/views_content_client/views_content_client.h"
#include "ui/views_content_client/views_content_client_main_parts.h"

// A simple NSApplicationDelegate that provides a basic mainMenu and can
// activate a task when the application has finished loading.
@interface ViewsContentClientAppController : NSObject <NSApplicationDelegate> {
 @private
  base::OnceClosure _onApplicationDidFinishLaunching;
}

// Set the task to run after receiving -applicationDidFinishLaunching:.
- (void)setOnApplicationDidFinishLaunching:(base::OnceClosure)task;

@end

namespace ui {

namespace {

class ViewsContentClientMainPartsMac : public ViewsContentClientMainParts {
 public:
  explicit ViewsContentClientMainPartsMac(
      ViewsContentClient* views_content_client);

  ViewsContentClientMainPartsMac(const ViewsContentClientMainPartsMac&) =
      delete;
  ViewsContentClientMainPartsMac& operator=(
      const ViewsContentClientMainPartsMac&) = delete;

  ~ViewsContentClientMainPartsMac() override;

  // content::BrowserMainParts:
  int PreMainMessageLoopRun() override;

 private:
  ViewsContentClientAppController* __strong app_controller_;
};

ViewsContentClientMainPartsMac::ViewsContentClientMainPartsMac(
    ViewsContentClient* views_content_client)
    : ViewsContentClientMainParts(views_content_client) {
  // Cache the child process path to avoid triggering an AssertIOAllowed.
  base::FilePath child_process_exe;
  base::PathService::Get(content::CHILD_PROCESS_EXE, &child_process_exe);

  app_controller_ = [[ViewsContentClientAppController alloc] init];
  NSApplication.sharedApplication.delegate = app_controller_;
}

int ViewsContentClientMainPartsMac::PreMainMessageLoopRun() {
  ViewsContentClientMainParts::PreMainMessageLoopRun();

  views_delegate()->set_context_factory(content::GetContextFactory());

  // On Mac, the task must be deferred to applicationDidFinishLaunching. If not,
  // the widget can activate, but (even if configured) the mainMenu won't be
  // ready to switch over in the OSX UI, so it will look strange.
  NSWindow* window_context = nil;
  [app_controller_
      setOnApplicationDidFinishLaunching:
          base::BindOnce(&ViewsContentClient::OnPreMainMessageLoopRun,
                         base::Unretained(views_content_client()),
                         base::Unretained(browser_context()),
                         base::Unretained(window_context))];

  return content::RESULT_CODE_NORMAL_EXIT;
}

ViewsContentClientMainPartsMac::~ViewsContentClientMainPartsMac() {
  NSApplication.sharedApplication.delegate = nil;
}

}  // namespace

// static
std::unique_ptr<ViewsContentClientMainParts>
ViewsContentClientMainParts::Create(ViewsContentClient* views_content_client) {
  return std::make_unique<ViewsContentClientMainPartsMac>(views_content_client);
}

// static
void ViewsContentClientMainParts::PreBrowserMain() {
  // Simply instantiating an instance of ShellCrApplication serves to register
  // it as the application class. Do make sure that no other code has done this
  // first, though.
  CHECK_EQ(NSApp, nil);
  [ShellCrApplication sharedApplication];
}

}  // namespace ui

@implementation ViewsContentClientAppController

- (void)setOnApplicationDidFinishLaunching:(base::OnceClosure)task {
  _onApplicationDidFinishLaunching = std::move(task);
}

- (void)applicationDidFinishLaunching:(NSNotification*)aNotification {
  // To get key events, the application needs to have an activation policy.
  // Unbundled apps (i.e. those without an Info.plist) default to
  // NSApplicationActivationPolicyProhibited.
  [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];

  // Create a basic mainMenu object using the executable filename.
  NSMenu* mainMenu = [[NSMenu alloc] initWithTitle:@""];
  NSMenuItem* appMenuItem = [mainMenu addItemWithTitle:@""
                                                action:nullptr
                                         keyEquivalent:@""];
  [NSApp setMainMenu:mainMenu];

  NSMenu* appMenu = [[NSMenu alloc] initWithTitle:@""];
  NSString* appName = NSProcessInfo.processInfo.processName;
  // TODO(tapted): Localize "Quit" if this is ever used for a released binary.
  // At the time of writing, ui_strings.grd has "Close" but not "Quit".
  NSString* quitTitle = [@"Quit " stringByAppendingString:appName];
  [appMenu addItemWithTitle:quitTitle
                     action:@selector(terminate:)
              keyEquivalent:@"q"];
  [appMenuItem setSubmenu:appMenu];

  CHECK([NSApp isKindOfClass:[ShellCrApplication class]]);

  std::move(_onApplicationDidFinishLaunching).Run();
}

@end