Files
brave-core/browser/brave_app_controller_mac.mm

383 lines
12 KiB
Plaintext

/* Copyright (c) 2023 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */
#import "brave/browser/brave_app_controller_mac.h"
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#include "base/check.h"
#include "brave/app/brave_command_ids.h"
#include "brave/browser/brave_browser_features.h"
#include "brave/browser/ui/browser_commands.h"
#include "brave/components/brave_origin/buildflags/buildflags.h"
#include "brave/components/tor/buildflags/buildflags.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/grit/generated_resources.h"
#include "components/policy/core/common/policy_pref_names.h"
#include "components/prefs/pref_member.h"
#include "ui/base/l10n/l10n_util_mac.h"
#if BUILDFLAG(IS_BRAVE_ORIGIN_BRANDED)
#include "brave/browser/ui/views/brave_origin/brave_origin_startup_view.h"
#endif
#if BUILDFLAG(ENABLE_TOR)
#include "brave/browser/tor/tor_profile_service_factory.h"
#include "brave/components/tor/pref_names.h"
#endif
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
#if BUILDFLAG(ENABLE_TOR)
class TorPrefObserver : public BooleanPrefMember {
public:
TorPrefObserver(BraveAppController* controller, NSMenu* menu)
: controller_(controller), menu_(menu) {
Profile* profile = [controller_ lastProfileIfLoaded];
CHECK(profile);
// The incognito profile will be deleted before app termination. So we
// should original profile in order to avoid crash.
// https://github.com/brave/brave-core/pull/21892#issuecomment-1928944343
profile = profile->GetOriginalProfile();
incognito_pref_observer_.Init(
policy::policy_prefs::kIncognitoModeAvailability, profile->GetPrefs(),
base::BindRepeating(&TorPrefObserver::UpdateMenu,
base::Unretained(this)));
CHECK(g_browser_process);
CHECK(g_browser_process->local_state());
tor_disabled_pref_observer_.Init(
tor::prefs::kTorDisabled, g_browser_process->local_state(),
base::BindRepeating(&TorPrefObserver::UpdateMenu,
base::Unretained(this)));
}
private:
void UpdateMenu() { [controller_ menuNeedsUpdate:menu_]; }
BraveAppController* controller_; // Owner of this
NSMenu* menu_;
IntegerPrefMember incognito_pref_observer_;
BooleanPrefMember tor_disabled_pref_observer_;
};
#endif // BUILDFLAG(ENABLE_TOR)
} // namespace
@interface AppController (Brave)
// Expose methods in chrome/..app_controller_mac.mm
- (BOOL)canOpenNewBrowser;
- (void)commandDispatchUsingKeyModifiers:(id)sender;
@end
@interface BraveAppController () {
NSMenuItem* _copyMenuItem;
NSMenuItem* _copyCleanLinkMenuItem;
#if BUILDFLAG(IS_BRAVE_ORIGIN_BRANDED)
NSMenuItem* _profileMenuItem; // Stashed while startup dialog is showing
NSInteger _profileMenuIndex; // Original position in the menu bar
#endif
#if BUILDFLAG(ENABLE_TOR)
NSMenuItem* _torMenuItem; // For dock menu
NSMenuItem* _torMainMenuItem; // For main menu
std::unique_ptr<TorPrefObserver> tor_pref_observer_;
std::unique_ptr<TorPrefObserver> tor_main_pref_observer_;
#endif // BUILDFLAG(ENABLE_TOR)
}
@end
#if BUILDFLAG(IS_BRAVE_ORIGIN_BRANDED)
void BraveRestoreProfileMenu() {
auto* controller =
static_cast<BraveAppController*>(AppController.sharedController);
[controller restoreProfileMenu];
}
#endif
@implementation BraveAppController
#if BUILDFLAG(IS_BRAVE_ORIGIN_BRANDED)
- (BOOL)canOpenNewBrowser {
if (BraveOriginStartupView::IsShowing()) {
return NO;
}
return [super canOpenNewBrowser];
}
- (void)commandDispatch:(id)sender {
if (BraveOriginStartupView::IsShowing()) {
// Allow Quit through so the user can exit from the startup dialog.
if ([sender tag] == IDC_EXIT) {
BraveOriginStartupView::Hide();
}
return;
}
[super commandDispatch:sender];
}
- (void)commandDispatchUsingKeyModifiers:(id)sender {
if (BraveOriginStartupView::IsShowing()) {
return;
}
[super commandDispatchUsingKeyModifiers:sender];
}
- (BOOL)applicationShouldHandleReopen:(NSApplication*)theApplication
hasVisibleWindows:(BOOL)hasVisibleWindows {
if (BraveOriginStartupView::IsShowing()) {
return NO;
}
return [super applicationShouldHandleReopen:theApplication
hasVisibleWindows:hasVisibleWindows];
}
- (void)application:(NSApplication*)sender openURLs:(NSArray<NSURL*>*)urls {
if (BraveOriginStartupView::IsShowing()) {
return;
}
[super application:sender openURLs:urls];
}
- (void)restoreProfileMenu {
if (_profileMenuItem) {
[[NSApp mainMenu] insertItem:_profileMenuItem atIndex:_profileMenuIndex];
_profileMenuItem = nil;
}
}
#endif // BUILDFLAG(IS_BRAVE_ORIGIN_BRANDED)
- (void)mainMenuCreated {
[super mainMenuCreated];
#if BUILDFLAG(IS_BRAVE_ORIGIN_BRANDED)
// Stash and remove the Profiles top-level menu. Profile switching, "Manage",
// and "Add Profile" all open browser windows that bypass the startup dialog.
// restoreProfileMenu re-inserts it. It is called from the startup completion
// callback, or immediately if no dialog is needed.
{
NSMenu* mainMenu = [NSApp mainMenu];
NSMenuItem* profileMenu = [mainMenu itemWithTag:IDC_PROFILE_MAIN_MENU];
if (profileMenu) {
_profileMenuIndex = [mainMenu indexOfItem:profileMenu];
_profileMenuItem = profileMenu;
[mainMenu removeItem:profileMenu];
}
}
#endif // BUILDFLAG(IS_BRAVE_ORIGIN_BRANDED)
NSMenu* editMenu = [[[NSApp mainMenu] itemWithTag:IDC_EDIT_MENU] submenu];
_copyMenuItem = [editMenu itemWithTag:IDC_CONTENT_CONTEXT_COPY];
DCHECK(_copyMenuItem);
[[_copyMenuItem menu] setDelegate:self];
_copyCleanLinkMenuItem = [editMenu itemWithTag:IDC_COPY_CLEAN_LINK];
DCHECK(_copyCleanLinkMenuItem);
[[_copyCleanLinkMenuItem menu] setDelegate:self];
#if BUILDFLAG(ENABLE_TOR)
// Find and set up the main menu Tor item
NSMenu* fileMenu = [[[NSApp mainMenu] itemWithTag:IDC_FILE_MENU] submenu];
_torMainMenuItem = [fileMenu itemWithTag:IDC_NEW_OFFTHERECORD_WINDOW_TOR];
CHECK(_torMainMenuItem);
[[_torMainMenuItem menu] setDelegate:self];
// Don't create observer yet - profile may not be loaded during startup
// Observer will be created when first needed in torMainMenuNeedsUpdate
[self torMainMenuNeedsUpdate]; // Initial state update
#endif // BUILDFLAG(ENABLE_TOR)
}
- (void)dealloc {
[[_copyMenuItem menu] setDelegate:nil];
[[_copyCleanLinkMenuItem menu] setDelegate:nil];
#if BUILDFLAG(ENABLE_TOR)
CHECK(_torMainMenuItem);
[[_torMainMenuItem menu] setDelegate:nil];
#endif // BUILDFLAG(ENABLE_TOR)
}
- (void)applicationWillTerminate:(NSNotification*)notification {
#if BUILDFLAG(ENABLE_TOR)
tor_pref_observer_.reset();
tor_main_pref_observer_.reset();
_torMenuItem = nil;
_torMainMenuItem = nil;
#endif
[super applicationWillTerminate:notification];
}
- (Browser*)getBrowser {
return chrome::FindBrowserWithProfile([self lastProfileIfLoaded]);
}
- (BOOL)shouldShowCleanLinkItem {
return brave::HasSelectedURL([self getBrowser]);
}
- (void)setKeyEquivalentToItem:(NSMenuItem*)item {
auto* hotkeyItem =
item == _copyMenuItem ? _copyMenuItem : _copyCleanLinkMenuItem;
auto* noHotkeyItem =
item == _copyMenuItem ? _copyCleanLinkMenuItem : _copyMenuItem;
[hotkeyItem setKeyEquivalent:@"c"];
[hotkeyItem setKeyEquivalentModifierMask:NSEventModifierFlagCommand];
[noHotkeyItem setKeyEquivalent:@""];
[noHotkeyItem setKeyEquivalentModifierMask:0];
}
- (void)menuNeedsUpdate:(NSMenu*)menu {
if (menu == [_copyMenuItem menu] || menu == [_copyCleanLinkMenuItem menu]) {
[self copyLinkMenuNeedsUpdate];
return;
}
#if BUILDFLAG(ENABLE_TOR)
if (menu == [_torMenuItem menu]) {
[self torMenuNeedsUpdate];
return;
}
if (menu == [_torMainMenuItem menu]) {
[self torMainMenuNeedsUpdate];
return;
}
#endif // BUILDFLAG(ENABLE_TOR)
[super menuNeedsUpdate:menu];
}
- (void)copyLinkMenuNeedsUpdate {
if ([self shouldShowCleanLinkItem]) {
[_copyCleanLinkMenuItem setHidden:NO];
if (base::FeatureList::IsEnabled(features::kBraveCopyCleanLinkByDefault)) {
[self setKeyEquivalentToItem:_copyCleanLinkMenuItem];
} else {
[self setKeyEquivalentToItem:_copyMenuItem];
}
} else {
[_copyCleanLinkMenuItem setHidden:YES];
[self setKeyEquivalentToItem:_copyMenuItem];
}
}
#if BUILDFLAG(ENABLE_TOR)
- (void)torMenuNeedsUpdate {
_torMenuItem.enabled = [self validateUserInterfaceItem:_torMenuItem];
_torMenuItem.hidden = !_torMenuItem.enabled;
}
- (void)torMainMenuNeedsUpdate {
if (_torMainMenuItem) {
auto* profile = [self lastProfileIfLoaded];
if (profile) {
// Create observer lazily once profile is loaded
if (!tor_main_pref_observer_) {
tor_main_pref_observer_ =
std::make_unique<TorPrefObserver>(self, [_torMainMenuItem menu]);
}
bool tor_enabled = !TorProfileServiceFactory::IsTorDisabled(profile);
_torMainMenuItem.enabled = tor_enabled;
_torMainMenuItem.hidden = !tor_enabled;
} else {
// Profile not loaded yet, hide the item
_torMainMenuItem.enabled = NO;
_torMainMenuItem.hidden = YES;
}
}
}
#endif // BUILDFLAG(ENABLE_TOR)
- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
NSInteger tag = [item tag];
if (tag == IDC_COPY_CLEAN_LINK) {
return [self shouldShowCleanLinkItem];
}
#if BUILDFLAG(ENABLE_TOR)
SEL action = [item action];
if (action == @selector(commandFromDock:) &&
tag == IDC_NEW_OFFTHERECORD_WINDOW_TOR) {
auto* profile = [self lastProfileIfLoaded];
return profile && !TorProfileServiceFactory::IsTorDisabled(profile) &&
[self canOpenNewBrowser];
}
#endif // BUILDFLAG(ENABLE_TOR)
return [super validateUserInterfaceItem:item];
}
- (void)executeCommand:(id)sender withProfile:(Profile*)profile {
if (!profile) {
// Couldn't load the Profile. RunInSafeProfileHelper will show the
// ProfilePicker instead.
return;
}
NSInteger tag = [sender tag];
if (tag == IDC_COPY_CLEAN_LINK) {
brave::CleanAndCopySelectedURL([self getBrowser]);
return;
}
#if BUILDFLAG(ENABLE_TOR)
if (tag == IDC_NEW_OFFTHERECORD_WINDOW_TOR) {
brave::NewOffTheRecordWindowTor(profile);
return;
}
#endif // BUILDFLAG(ENABLE_TOR)
[super executeCommand:sender withProfile:profile];
}
- (NSMenu*)applicationDockMenu:(NSApplication*)sender {
#if BUILDFLAG(IS_BRAVE_ORIGIN_BRANDED)
if (BraveOriginStartupView::IsShowing()) {
// Return an empty dock menu while the startup dialog is showing.
// The system always adds "Quit" automatically.
return [[NSMenu alloc] initWithTitle:@""];
}
#endif // BUILDFLAG(IS_BRAVE_ORIGIN_BRANDED)
auto* menu = [super applicationDockMenu:sender];
#if BUILDFLAG(ENABLE_TOR)
// Add "New Private Window with Tor" only if the "New Private Window" item
// exists. "New Private Window" could be missing when policy is set to
// disable it.
auto* open_new_private_window = [menu itemWithTag:IDC_NEW_INCOGNITO_WINDOW];
if (open_new_private_window) {
auto index = [menu indexOfItem:open_new_private_window];
_torMenuItem =
[[NSMenuItem alloc] initWithTitle:l10n_util::GetNSStringWithFixup(
IDS_NEW_OFFTHERECORD_WINDOW_TOR)
action:@selector(commandFromDock:)
keyEquivalent:@""];
[menu insertItem:_torMenuItem atIndex:index + 1];
_torMenuItem.target = self;
_torMenuItem.tag = IDC_NEW_OFFTHERECORD_WINDOW_TOR;
[self torMenuNeedsUpdate];
tor_pref_observer_ =
std::make_unique<TorPrefObserver>(self, [_torMenuItem menu]);
}
#endif
return menu;
}
@end // @implementation BraveAppController