chromium/chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac_cocoa.mm

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

#include "chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac_cocoa.h"
#include "base/memory/raw_ptr.h"

#include <utility>

#include "base/compiler_specific.h"
#include "base/mac/mac_util.h"
#import "base/mac/scoped_sending_event.h"
#import "base/message_loop/message_pump_apple.h"
#include "base/strings/sys_string_conversions.h"
#include "base/task/current_thread.h"
#include "chrome/browser/headless/headless_mode_util.h"
#import "components/remote_cocoa/app_shim/menu_controller_cocoa_delegate_impl.h"
#include "content/public/browser/web_contents.h"
#import "ui/base/cocoa/menu_controller.h"
#include "ui/base/cocoa/menu_utils.h"
#include "ui/base/interaction/element_tracker_mac.h"
#include "ui/color/color_provider.h"
#include "ui/views/controls/menu/menu_controller_cocoa_delegate_params.h"
#include "ui/views/interaction/element_tracker_views.h"
#include "ui/views/widget/widget.h"

namespace {

// Retrieves an NSMenuItem which has the specified command_id. This function
// traverses the given |model| in the depth-first order. When this function
// finds an item whose command_id is the same as the given |command_id|, it
// returns the NSMenuItem associated with the item. This function emulates
// views::MenuItemViews::GetMenuItemByID() for Mac.
NSMenuItem* GetMenuItemByID(ui::MenuModel* model,
                            NSMenu* menu,
                            int command_id) {
  for (size_t i = 0; i < model->GetItemCount(); ++i) {
    NSMenuItem* item = [menu itemAtIndex:i];
    if (model->GetCommandIdAt(i) == command_id)
      return item;

    ui::MenuModel* submenu = model->GetSubmenuModelAt(i);
    if (submenu && [item hasSubmenu]) {
      NSMenuItem* subitem =
          GetMenuItemByID(submenu, [item submenu], command_id);
      if (subitem)
        return subitem;
    }
  }
  return nil;
}

}  // namespace

// Obj-C bridge class that is the target of all items in the context menu.
// Relies on the tag being set to the command id.
RenderViewContextMenuMacCocoa::RenderViewContextMenuMacCocoa(
    content::RenderFrameHost& render_frame_host,
    const content::ContextMenuParams& params,
    NSView* parent_view)
    : RenderViewContextMenuMac(render_frame_host, params),
      parent_view_(parent_view) {
}

RenderViewContextMenuMacCocoa::~RenderViewContextMenuMacCocoa() {
  if (menu_controller_)
    [menu_controller_ cancel];
}

void RenderViewContextMenuMacCocoa::Show() {
  views::Widget* widget = views::Widget::GetTopLevelWidgetForNativeView(
      source_web_contents_->GetNativeView());

  if (!widget || headless::IsHeadlessMode()) {
    return;
  }

  menu_controller_delegate_ = [[MenuControllerCocoaDelegateImpl alloc]
      initWithParams:MenuControllerParamsForWidget(widget)];
  menu_controller_ =
      [[MenuControllerCocoa alloc] initWithModel:&menu_model_
                                        delegate:menu_controller_delegate_
                          useWithPopUpButtonCell:NO];

  NSPoint position =
      NSMakePoint(params_.x, NSHeight(parent_view_.bounds) - params_.y);
  position = [parent_view_ convertPoint:position toView:nil];
  NSEvent* clickEvent = ui::EventForPositioningContextMenuRelativeToWindow(
      position, [parent_view_ window]);

  ui::ShowContextMenu(menu_controller_.menu, clickEvent, parent_view_,
                      /*allow_nested_tasks=*/true,
                      views::ElementTrackerViews::GetContextForWidget(widget));
}

void RenderViewContextMenuMacCocoa::CancelToolkitMenu() {
  [menu_controller_ cancel];
}

void RenderViewContextMenuMacCocoa::UpdateToolkitMenuItem(
    int command_id,
    bool enabled,
    bool hidden,
    const std::u16string& title) {
  NSMenuItem* item =
      GetMenuItemByID(&menu_model_, [menu_controller_ menu], command_id);
  if (!item)
    return;

  // Update the returned NSMenuItem directly so we can update it immediately.
  item.enabled = enabled;
  item.title = base::SysUTF16ToNSString(title);
  item.hidden = hidden;
}