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

// Copyright 2015 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/browser_window_command_handler.h"

#import "base/apple/foundation_util.h"
#include "base/check.h"
#include "base/notreached.h"
#include "base/strings/sys_string_conversions.h"
#include "chrome/app/chrome_command_ids.h"
#import "chrome/browser/app_controller_mac.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window.h"
#include "components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h"
#include "components/remote_cocoa/common/native_widget_ns_window_host.mojom.h"
#include "content/public/browser/web_contents.h"
#import "ui/base/cocoa/cocoa_base_utils.h"

namespace {

void SetToggleState(bool toggled, id item) {
  NSMenuItem* menuItem = base::apple::ObjCCast<NSMenuItem>(item);
  NSButton* buttonItem = base::apple::ObjCCast<NSButton>(item);
  if (menuItem) {
    NSControlStateValue old_state = [menuItem state];
    NSControlStateValue new_state =
        toggled ? NSControlStateValueOn : NSControlStateValueOff;
    if (old_state != new_state)
      [menuItem setState:new_state];
  } else if (buttonItem) {
    NSControlStateValue old_state = [buttonItem state];
    NSControlStateValue new_state =
        toggled ? NSControlStateValueOn : NSControlStateValueOff;
    if (old_state != new_state)
      [buttonItem setState:new_state];
  }
}

// Identify the actual Browser to which the command should be dispatched. It
// might belong to a background window, yet another dispatcher gets it because
// it is the foreground window's dispatcher and thus in the responder chain.
// Some senders don't have this problem (for example, menus only operate on the
// foreground window), so this is only an issue for senders that are part of
// windows.
remote_cocoa::NativeWidgetNSWindowBridge* FindBridgeForSender(
    id sender,
    NSWindow* window) {
  NSWindow* targetWindow = window;
  if ([sender respondsToSelector:@selector(window)])
    targetWindow = [sender window];
  auto* bridge = remote_cocoa::NativeWidgetNSWindowBridge::GetFromNativeWindow(
      targetWindow);
  DCHECK(bridge);
  return bridge;
}

}  // namespace

@implementation BrowserWindowCommandHandler

- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item
                           window:(NSWindow*)window {
  if ([item action] != @selector(commandDispatch:) &&
      [item action] != @selector(commandDispatchUsingKeyModifiers:)) {
    // NSWindow should only forward the above selectors here. All other
    // selectors must be handled by the default -[NSWindow
    // validateUserInterfaceItem:window:].
    NOTREACHED_IN_MIGRATION();
    // By default, interface items are enabled if the object in the responder
    // chain that implements the action does not implement
    // -validateUserInterfaceItem. Since we only care about -commandDispatch,
    // return YES for all other actions.
    return YES;
  }

  auto* bridge =
      remote_cocoa::NativeWidgetNSWindowBridge::GetFromNativeWindow(window);
  DCHECK(bridge);

  remote_cocoa::mojom::ValidateUserInterfaceItemResultPtr result;
  if (!bridge->host()->ValidateUserInterfaceItem([item tag], &result))
    return NO;

  if (result->set_toggle_state)
    SetToggleState(result->new_toggle_state, item);

  if (NSMenuItem* menuItem = base::apple::ObjCCast<NSMenuItem>(item)) {
    if (result->disable_if_has_no_key_equivalent)
      result->enable &= !![[menuItem keyEquivalent] length];

    if (result->set_hidden_state)
      [menuItem setHidden:result->new_hidden_state];

    if (result->new_title)
      [menuItem setTitle:base::SysUTF16ToNSString(*result->new_title)];
  }

  return result->enable;
}

- (void)commandDispatch:(id)sender window:(NSWindow*)window {
  DCHECK(sender);
  int command = [sender tag];
  bool was_executed = false;
  FindBridgeForSender(sender, window)
      ->host()
      ->ExecuteCommand(command, WindowOpenDisposition::CURRENT_TAB,
                       /*is_before_first_responder=*/false, &was_executed);
  DCHECK(was_executed);
}

- (void)commandDispatchUsingKeyModifiers:(id)sender window:(NSWindow*)window {
  DCHECK(sender);

  if (![sender isEnabled]) {
    // This code is reachable e.g. if the user mashes the back button, queuing
    // up a bunch of events before the button's enabled state is updated:
    // http://crbug.com/63254
    return;
  }

  NSInteger command = [sender tag];
  NSUInteger modifierFlags = [[NSApp currentEvent] modifierFlags];
  if ((command == IDC_RELOAD) &&
      (modifierFlags &
       (NSEventModifierFlagShift | NSEventModifierFlagControl))) {
    command = IDC_RELOAD_BYPASSING_CACHE;
    // Mask off Shift and Control so they don't affect the disposition below.
    modifierFlags &= ~(NSEventModifierFlagShift | NSEventModifierFlagControl);
  }
  if (![[sender window] isMainWindow]) {
    // Remove the command key from the flags, it means "keep the window in
    // the background" in this case.
    modifierFlags &= ~NSEventModifierFlagCommand;
  }

  bool was_executed = false;
  FindBridgeForSender(sender, window)
      ->host()
      ->ExecuteCommand(command,
                       ui::WindowOpenDispositionFromNSEventWithFlags(
                           [NSApp currentEvent], modifierFlags),
                       /*is_before_first_responder=*/false, &was_executed);
  DCHECK(was_executed);
}

@end