chromium/chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu_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.

#include "chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac.h"

#include <utility>

#include "base/mac/mac_util.h"
#include "base/strings/sys_string_conversions.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/grit/generated_resources.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "third_party/blink/public/common/context_menu_data/context_menu_data.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/resources/grit/ui_resources.h"
#include "ui/strings/grit/ui_strings.h"

// macOS implementation of the ToolkitDelegate.
// This simply (re)delegates calls to RVContextMenuMac.
class ToolkitDelegateMacCocoa : public RenderViewContextMenu::ToolkitDelegate {
 public:
  explicit ToolkitDelegateMacCocoa(RenderViewContextMenuMac* context_menu)
      : context_menu_(context_menu) {}

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

  ~ToolkitDelegateMacCocoa() override {}

 private:
  // ToolkitDelegate:
  void Init(ui::SimpleMenuModel* menu_model) override {
    context_menu_->InitToolkitMenu();
  }

  void Cancel() override { context_menu_->CancelToolkitMenu(); }

  void UpdateMenuItem(int command_id,
                      bool enabled,
                      bool hidden,
                      const std::u16string& title) override {
    context_menu_->UpdateToolkitMenuItem(command_id, enabled, hidden, title);
  }

  raw_ptr<RenderViewContextMenuMac> context_menu_;
};

RenderViewContextMenuMac::RenderViewContextMenuMac(
    content::RenderFrameHost& render_frame_host,
    const content::ContextMenuParams& params)
    : RenderViewContextMenu(render_frame_host, params),
      text_services_context_menu_(this) {
  auto delegate = std::make_unique<ToolkitDelegateMacCocoa>(this);
  set_toolkit_delegate(std::move(delegate));
}

RenderViewContextMenuMac::~RenderViewContextMenuMac() {
}

void RenderViewContextMenuMac::ExecuteCommand(int command_id, int event_flags) {
  if (command_id == IDC_CONTENT_CONTEXT_LOOK_UP)
    LookUpInDictionary();
  else
    RenderViewContextMenu::ExecuteCommand(command_id, event_flags);
}

bool RenderViewContextMenuMac::IsCommandIdChecked(int command_id) const {
  if (text_services_context_menu_.SupportsCommand(command_id))
    return text_services_context_menu_.IsCommandIdChecked(command_id);

  if (command_id == IDC_CONTENT_CONTEXT_LOOK_UP)
    return false;

  return RenderViewContextMenu::IsCommandIdChecked(command_id);
}

bool RenderViewContextMenuMac::IsCommandIdEnabled(int command_id) const {
  if (text_services_context_menu_.SupportsCommand(command_id))
    return text_services_context_menu_.IsCommandIdEnabled(command_id);

  if (command_id == IDC_CONTENT_CONTEXT_LOOK_UP)
    return true;

  return RenderViewContextMenu::IsCommandIdEnabled(command_id);
}

std::u16string RenderViewContextMenuMac::GetSelectedText() const {
  return params_.selection_text;
}

bool RenderViewContextMenuMac::IsTextDirectionEnabled(
    base::i18n::TextDirection direction) const {
  return ParamsForTextDirection(direction) &
         blink::ContextMenuData::kCheckableMenuItemEnabled;
}

bool RenderViewContextMenuMac::IsTextDirectionChecked(
    base::i18n::TextDirection direction) const {
  return ParamsForTextDirection(direction) &
         blink::ContextMenuData::kCheckableMenuItemChecked;
}

void RenderViewContextMenuMac::UpdateTextDirection(
    base::i18n::TextDirection direction) {
  DCHECK_NE(direction, base::i18n::UNKNOWN_DIRECTION);

  int command_id = IDC_WRITING_DIRECTION_LTR;
  if (direction == base::i18n::RIGHT_TO_LEFT)
    command_id = IDC_WRITING_DIRECTION_RTL;

  // Note: we get the local render frame host so that the writing mode settings
  // changes apply to the correct frame. See crbug.com/1129073 for a
  // description of what happens if we use the outermost frame.
  content::RenderFrameHost* rfh = GetRenderFrameHost();
  // It's possible that the frame drops out from under us while the context
  // menu is open. In this case, we'll not perform the action, but still record
  // metrics.
  if (rfh) {
    rfh->GetRenderWidgetHost()->UpdateTextDirection(direction);
    rfh->GetRenderWidgetHost()->NotifyTextDirection();
  }

  RenderViewContextMenu::RecordUsedItem(command_id);
}

void RenderViewContextMenuMac::AppendPlatformEditableItems() {
  text_services_context_menu_.AppendEditableItems(&menu_model_);
}

void RenderViewContextMenuMac::InitToolkitMenu() {
  if (params_.form_control_type ==
      blink::mojom::FormControlType::kInputPassword) {
    return;
  }

  if (!params_.selection_text.empty() && params_.link_url.is_empty()) {
    // In case the user has selected a word that triggers spelling suggestions,
    // show the dictionary lookup under the group that contains the command to
    // “Add to Dictionary.”
    const std::optional<size_t> index_opt =
        menu_model_.GetIndexOfCommandId(IDC_SPELLCHECK_ADD_TO_DICTIONARY);
    size_t index = index_opt.value_or(0);
    if (index_opt.has_value()) {
      while (menu_model_.GetTypeAt(index) != ui::MenuModel::TYPE_SEPARATOR) {
        index++;
      }
      ++index;  // Place it below the separator.
    }

    std::u16string printable_selection_text = PrintableSelectionText();
    EscapeAmpersands(&printable_selection_text);
    menu_model_.InsertItemAt(
        index++, IDC_CONTENT_CONTEXT_LOOK_UP,
        l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_LOOK_UP,
                                   printable_selection_text));
    menu_model_.InsertSeparatorAt(index++, ui::NORMAL_SEPARATOR);
  }

  if (!params_.selection_text.empty())
    text_services_context_menu_.AppendToContextMenu(&menu_model_);
}

void RenderViewContextMenuMac::LookUpInDictionary() {
  content::RenderWidgetHostView* view =
      GetRenderFrameHost()->GetRenderWidgetHost()->GetView();
  if (view)
    view->ShowDefinitionForSelection();
}

int RenderViewContextMenuMac::ParamsForTextDirection(
    base::i18n::TextDirection direction) const {
  switch (direction) {
    case base::i18n::UNKNOWN_DIRECTION:
      return params_.writing_direction_default;
    case base::i18n::RIGHT_TO_LEFT:
      return params_.writing_direction_right_to_left;
    case base::i18n::LEFT_TO_RIGHT:
      return params_.writing_direction_left_to_right;
  }
}