// Copyright (c) 2012 The Chromium Embedded Framework Authors.
// Portions copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "libcef/browser/menu_model_impl.h"

#include <vector>

#include "libcef/browser/thread_util.h"
#include "libcef/common/task_runner_impl.h"

#include "base/bind.h"
#include "base/logging.h"
#include "third_party/blink/public/mojom/context_menu/context_menu.mojom.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/models/image_model.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/image/image.h"

namespace {

const int kSeparatorId = -1;
const int kInvalidGroupId = -1;
const int kInvalidCommandId = -1;
const int kDefaultIndex = -1;
const int kInvalidIndex = -2;

// A simple MenuModel implementation that delegates to CefMenuModelImpl.
class CefSimpleMenuModel : public ui::MenuModel {
 public:
  // The Delegate can be NULL, though if it is items can't be checked or
  // disabled.
  explicit CefSimpleMenuModel(CefMenuModelImpl* impl) : impl_(impl) {}

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

  // MenuModel methods.
  bool HasIcons() const override { return false; }

  size_t GetItemCount() const override { return impl_->GetCount(); }

  ItemType GetTypeAt(size_t index) const override {
    switch (impl_->GetTypeAt(index)) {
      case MENUITEMTYPE_COMMAND:
        return TYPE_COMMAND;
      case MENUITEMTYPE_CHECK:
        return TYPE_CHECK;
      case MENUITEMTYPE_RADIO:
        return TYPE_RADIO;
      case MENUITEMTYPE_SEPARATOR:
        return TYPE_SEPARATOR;
      case MENUITEMTYPE_SUBMENU:
        return TYPE_SUBMENU;
      default:
        NOTREACHED();
        return TYPE_COMMAND;
    }
  }

  ui::MenuSeparatorType GetSeparatorTypeAt(size_t index) const override {
    return ui::NORMAL_SEPARATOR;
  }

  int GetCommandIdAt(size_t index) const override {
    return impl_->GetCommandIdAt(index);
  }

  std::u16string GetLabelAt(size_t index) const override {
    return impl_->GetFormattedLabelAt(index);
  }

  bool IsItemDynamicAt(size_t index) const override { return false; }

  const gfx::FontList* GetLabelFontListAt(size_t index) const override {
    return impl_->GetLabelFontListAt(index);
  }

  bool GetAcceleratorAt(size_t index,
                        ui::Accelerator* accelerator) const override {
    int key_code = 0;
    bool shift_pressed = false;
    bool ctrl_pressed = false;
    bool alt_pressed = false;
    if (impl_->GetAcceleratorAt(index, key_code, shift_pressed, ctrl_pressed,
                                alt_pressed)) {
      int modifiers = 0;
      if (shift_pressed)
        modifiers |= ui::EF_SHIFT_DOWN;
      if (ctrl_pressed)
        modifiers |= ui::EF_CONTROL_DOWN;
      if (alt_pressed)
        modifiers |= ui::EF_ALT_DOWN;

      *accelerator =
          ui::Accelerator(static_cast<ui::KeyboardCode>(key_code), modifiers);
      return true;
    }
    return false;
  }

  bool IsItemCheckedAt(size_t index) const override {
    return impl_->IsCheckedAt(index);
  }

  int GetGroupIdAt(size_t index) const override {
    return impl_->GetGroupIdAt(index);
  }

  ui::ImageModel GetIconAt(size_t index) const override {
    return ui::ImageModel();
  }

  ui::ButtonMenuItemModel* GetButtonMenuItemAt(size_t index) const override {
    return nullptr;
  }

  bool IsEnabledAt(size_t index) const override {
    return impl_->IsEnabledAt(index);
  }

  bool IsVisibleAt(size_t index) const override {
    return impl_->IsVisibleAt(index);
  }

  void ActivatedAt(size_t index) override { ActivatedAt(index, 0); }

  void ActivatedAt(size_t index, int event_flags) override {
    impl_->ActivatedAt(index, static_cast<cef_event_flags_t>(event_flags));
  }

  MenuModel* GetSubmenuModelAt(size_t index) const override {
    CefRefPtr<CefMenuModel> submenu = impl_->GetSubMenuAt(index);
    if (submenu.get())
      return static_cast<CefMenuModelImpl*>(submenu.get())->model();
    return nullptr;
  }

  void MouseOutsideMenu(const gfx::Point& screen_point) override {
    impl_->MouseOutsideMenu(screen_point);
  }

  void UnhandledOpenSubmenu(bool is_rtl) override {
    impl_->UnhandledOpenSubmenu(is_rtl);
  }

  void UnhandledCloseSubmenu(bool is_rtl) override {
    impl_->UnhandledCloseSubmenu(is_rtl);
  }

  bool GetTextColor(size_t index,
                    bool is_minor,
                    bool is_hovered,
                    SkColor* override_color) const override {
    return impl_->GetTextColor(index, is_minor, is_hovered, override_color);
  }

  bool GetBackgroundColor(size_t index,
                          bool is_hovered,
                          SkColor* override_color) const override {
    return impl_->GetBackgroundColor(index, is_hovered, override_color);
  }

  void MenuWillShow() override { impl_->MenuWillShow(); }

  void MenuWillClose() override { impl_->MenuWillClose(); }

 private:
  CefMenuModelImpl* impl_;
};

cef_menu_color_type_t GetMenuColorType(bool is_text,
                                       bool is_accelerator,
                                       bool is_hovered) {
  if (is_text) {
    if (is_accelerator) {
      return is_hovered ? CEF_MENU_COLOR_TEXT_ACCELERATOR_HOVERED
                        : CEF_MENU_COLOR_TEXT_ACCELERATOR;
    }
    return is_hovered ? CEF_MENU_COLOR_TEXT_HOVERED : CEF_MENU_COLOR_TEXT;
  }

  DCHECK(!is_accelerator);
  return is_hovered ? CEF_MENU_COLOR_BACKGROUND_HOVERED
                    : CEF_MENU_COLOR_BACKGROUND;
}

}  // namespace

// static
CefRefPtr<CefMenuModel> CefMenuModel::CreateMenuModel(
    CefRefPtr<CefMenuModelDelegate> delegate) {
  CEF_REQUIRE_UIT_RETURN(nullptr);
  DCHECK(delegate);
  if (!delegate)
    return nullptr;

  CefRefPtr<CefMenuModelImpl> menu_model =
      new CefMenuModelImpl(nullptr, delegate, false);
  return menu_model;
}

struct CefMenuModelImpl::Item {
  Item(cef_menu_item_type_t type,
       int command_id,
       const CefString& label,
       int group_id)
      : type_(type),
        command_id_(command_id),
        label_(label),
        group_id_(group_id) {}

  // Basic information.
  cef_menu_item_type_t type_;
  int command_id_;
  CefString label_;
  int group_id_;
  CefRefPtr<CefMenuModelImpl> submenu_;

  // State information.
  bool enabled_ = true;
  bool visible_ = true;
  bool checked_ = false;

  // Accelerator information.
  bool has_accelerator_ = false;
  int key_code_ = 0;
  bool shift_pressed_ = false;
  bool ctrl_pressed_ = false;
  bool alt_pressed_ = false;

  cef_color_t colors_[CEF_MENU_COLOR_COUNT] = {0};
  gfx::FontList font_list_;
  bool has_font_list_ = false;
};

CefMenuModelImpl::CefMenuModelImpl(
    Delegate* delegate,
    CefRefPtr<CefMenuModelDelegate> menu_model_delegate,
    bool is_submenu)
    : supported_thread_id_(base::PlatformThread::CurrentId()),
      delegate_(delegate),
      menu_model_delegate_(menu_model_delegate),
      is_submenu_(is_submenu) {
  DCHECK(delegate_ || menu_model_delegate_);
  model_.reset(new CefSimpleMenuModel(this));
}

CefMenuModelImpl::~CefMenuModelImpl() {}

bool CefMenuModelImpl::IsSubMenu() {
  if (!VerifyContext())
    return false;
  return is_submenu_;
}

bool CefMenuModelImpl::Clear() {
  if (!VerifyContext())
    return false;

  items_.clear();
  return true;
}

size_t CefMenuModelImpl::GetCount() {
  if (!VerifyContext())
    return 0;

  return items_.size();
}

bool CefMenuModelImpl::AddSeparator() {
  if (!VerifyContext())
    return false;

  AppendItem(
      Item(MENUITEMTYPE_SEPARATOR, kSeparatorId, CefString(), kInvalidGroupId));
  return true;
}

bool CefMenuModelImpl::AddItem(int command_id, const CefString& label) {
  if (!VerifyContext())
    return false;

  AppendItem(Item(MENUITEMTYPE_COMMAND, command_id, label, kInvalidGroupId));
  return true;
}

bool CefMenuModelImpl::AddCheckItem(int command_id, const CefString& label) {
  if (!VerifyContext())
    return false;

  AppendItem(Item(MENUITEMTYPE_CHECK, command_id, label, kInvalidGroupId));
  return true;
}

bool CefMenuModelImpl::AddRadioItem(int command_id,
                                    const CefString& label,
                                    int group_id) {
  if (!VerifyContext())
    return false;

  AppendItem(Item(MENUITEMTYPE_RADIO, command_id, label, group_id));
  return true;
}

CefRefPtr<CefMenuModel> CefMenuModelImpl::AddSubMenu(int command_id,
                                                     const CefString& label) {
  if (!VerifyContext())
    return nullptr;

  Item item(MENUITEMTYPE_SUBMENU, command_id, label, kInvalidGroupId);
  item.submenu_ = new CefMenuModelImpl(delegate_, menu_model_delegate_, true);
  AppendItem(item);
  return item.submenu_.get();
}

bool CefMenuModelImpl::InsertSeparatorAt(size_t index) {
  if (!VerifyContext())
    return false;

  InsertItemAt(
      Item(MENUITEMTYPE_SEPARATOR, kSeparatorId, CefString(), kInvalidGroupId),
      index);
  return true;
}

bool CefMenuModelImpl::InsertItemAt(size_t index,
                                    int command_id,
                                    const CefString& label) {
  if (!VerifyContext())
    return false;

  InsertItemAt(Item(MENUITEMTYPE_COMMAND, command_id, label, kInvalidGroupId),
               index);
  return true;
}

bool CefMenuModelImpl::InsertCheckItemAt(size_t index,
                                         int command_id,
                                         const CefString& label) {
  if (!VerifyContext())
    return false;

  InsertItemAt(Item(MENUITEMTYPE_CHECK, command_id, label, kInvalidGroupId),
               index);
  return true;
}

bool CefMenuModelImpl::InsertRadioItemAt(size_t index,
                                         int command_id,
                                         const CefString& label,
                                         int group_id) {
  if (!VerifyContext())
    return false;

  InsertItemAt(Item(MENUITEMTYPE_RADIO, command_id, label, group_id), index);
  return true;
}

CefRefPtr<CefMenuModel> CefMenuModelImpl::InsertSubMenuAt(
    size_t index,
    int command_id,
    const CefString& label) {
  if (!VerifyContext())
    return nullptr;

  Item item(MENUITEMTYPE_SUBMENU, command_id, label, kInvalidGroupId);
  item.submenu_ = new CefMenuModelImpl(delegate_, menu_model_delegate_, true);
  InsertItemAt(item, index);
  return item.submenu_.get();
}

bool CefMenuModelImpl::Remove(int command_id) {
  return RemoveAt(GetIndexOf(command_id));
}

bool CefMenuModelImpl::RemoveAt(size_t index) {
  if (!VerifyContext())
    return false;

  if (index < items_.size()) {
    items_.erase(items_.begin() + index);
    return true;
  }
  return false;
}

int CefMenuModelImpl::GetIndexOf(int command_id) {
  if (!VerifyContext())
    return kInvalidIndex;

  for (ItemVector::iterator i = items_.begin(); i != items_.end(); ++i) {
    if ((*i).command_id_ == command_id) {
      return static_cast<int>(std::distance(items_.begin(), i));
    }
  }
  return kInvalidIndex;
}

int CefMenuModelImpl::GetCommandIdAt(size_t index) {
  if (!VerifyContext())
    return kInvalidCommandId;

  if (index < items_.size())
    return items_[index].command_id_;
  return kInvalidCommandId;
}

bool CefMenuModelImpl::SetCommandIdAt(size_t index, int command_id) {
  if (!VerifyContext())
    return false;

  if (index < items_.size()) {
    items_[index].command_id_ = command_id;
    return true;
  }
  return false;
}

CefString CefMenuModelImpl::GetLabel(int command_id) {
  return GetLabelAt(GetIndexOf(command_id));
}

CefString CefMenuModelImpl::GetLabelAt(size_t index) {
  if (!VerifyContext())
    return CefString();

  if (index < items_.size())
    return items_[index].label_;
  return CefString();
}

bool CefMenuModelImpl::SetLabel(int command_id, const CefString& label) {
  return SetLabelAt(GetIndexOf(command_id), label);
}

bool CefMenuModelImpl::SetLabelAt(size_t index, const CefString& label) {
  if (!VerifyContext())
    return false;

  if (index < items_.size()) {
    items_[index].label_ = label;
    return true;
  }
  return false;
}

CefMenuModelImpl::MenuItemType CefMenuModelImpl::GetType(int command_id) {
  return GetTypeAt(GetIndexOf(command_id));
}

CefMenuModelImpl::MenuItemType CefMenuModelImpl::GetTypeAt(size_t index) {
  if (!VerifyContext())
    return MENUITEMTYPE_NONE;

  if (index < items_.size())
    return items_[index].type_;
  return MENUITEMTYPE_NONE;
}

int CefMenuModelImpl::GetGroupId(int command_id) {
  return GetGroupIdAt(GetIndexOf(command_id));
}

int CefMenuModelImpl::GetGroupIdAt(size_t index) {
  if (!VerifyContext())
    return kInvalidGroupId;

  if (index < items_.size())
    return items_[index].group_id_;
  return kInvalidGroupId;
}

bool CefMenuModelImpl::SetGroupId(int command_id, int group_id) {
  return SetGroupIdAt(GetIndexOf(command_id), group_id);
}

bool CefMenuModelImpl::SetGroupIdAt(size_t index, int group_id) {
  if (!VerifyContext())
    return false;

  if (index < items_.size()) {
    items_[index].group_id_ = group_id;
    return true;
  }
  return false;
}

CefRefPtr<CefMenuModel> CefMenuModelImpl::GetSubMenu(int command_id) {
  return GetSubMenuAt(GetIndexOf(command_id));
}

CefRefPtr<CefMenuModel> CefMenuModelImpl::GetSubMenuAt(size_t index) {
  if (!VerifyContext())
    return nullptr;

  if (index < items_.size())
    return items_[index].submenu_.get();
  return nullptr;
}

bool CefMenuModelImpl::IsVisible(int command_id) {
  return IsVisibleAt(GetIndexOf(command_id));
}

bool CefMenuModelImpl::IsVisibleAt(size_t index) {
  if (!VerifyContext())
    return false;

  if (index < items_.size())
    return items_[index].visible_;
  return false;
}

bool CefMenuModelImpl::SetVisible(int command_id, bool visible) {
  return SetVisibleAt(GetIndexOf(command_id), visible);
}

bool CefMenuModelImpl::SetVisibleAt(size_t index, bool visible) {
  if (!VerifyContext())
    return false;

  if (index < items_.size()) {
    items_[index].visible_ = visible;
    return true;
  }
  return false;
}

bool CefMenuModelImpl::IsEnabled(int command_id) {
  return IsEnabledAt(GetIndexOf(command_id));
}

bool CefMenuModelImpl::IsEnabledAt(size_t index) {
  if (!VerifyContext())
    return false;

  if (index < items_.size())
    return items_[index].enabled_;
  return false;
}

bool CefMenuModelImpl::SetEnabled(int command_id, bool enabled) {
  return SetEnabledAt(GetIndexOf(command_id), enabled);
}

bool CefMenuModelImpl::SetEnabledAt(size_t index, bool enabled) {
  if (!VerifyContext())
    return false;

  if (index < items_.size()) {
    items_[index].enabled_ = enabled;
    return true;
  }
  return false;
}

bool CefMenuModelImpl::IsChecked(int command_id) {
  return IsCheckedAt(GetIndexOf(command_id));
}

bool CefMenuModelImpl::IsCheckedAt(size_t index) {
  if (!VerifyContext())
    return false;

  if (index < items_.size())
    return items_[index].checked_;
  return false;
}

bool CefMenuModelImpl::SetChecked(int command_id, bool checked) {
  return SetCheckedAt(GetIndexOf(command_id), checked);
}

bool CefMenuModelImpl::SetCheckedAt(size_t index, bool checked) {
  if (!VerifyContext())
    return false;

  if (index < items_.size()) {
    items_[index].checked_ = checked;
    return true;
  }
  return false;
}

bool CefMenuModelImpl::HasAccelerator(int command_id) {
  return HasAcceleratorAt(GetIndexOf(command_id));
}

bool CefMenuModelImpl::HasAcceleratorAt(size_t index) {
  if (!VerifyContext())
    return false;

  if (index < items_.size())
    return items_[index].has_accelerator_;
  return false;
}

bool CefMenuModelImpl::SetAccelerator(int command_id,
                                      int key_code,
                                      bool shift_pressed,
                                      bool ctrl_pressed,
                                      bool alt_pressed) {
  return SetAcceleratorAt(GetIndexOf(command_id), key_code, shift_pressed,
                          ctrl_pressed, alt_pressed);
}

bool CefMenuModelImpl::SetAcceleratorAt(size_t index,
                                        int key_code,
                                        bool shift_pressed,
                                        bool ctrl_pressed,
                                        bool alt_pressed) {
  if (!VerifyContext())
    return false;

  if (index < items_.size()) {
    Item& item = items_[index];
    item.has_accelerator_ = true;
    item.key_code_ = key_code;
    item.shift_pressed_ = shift_pressed;
    item.ctrl_pressed_ = ctrl_pressed;
    item.alt_pressed_ = alt_pressed;
    return true;
  }
  return false;
}

bool CefMenuModelImpl::RemoveAccelerator(int command_id) {
  return RemoveAcceleratorAt(GetIndexOf(command_id));
}

bool CefMenuModelImpl::RemoveAcceleratorAt(size_t index) {
  if (!VerifyContext())
    return false;

  if (index < items_.size()) {
    Item& item = items_[index];
    if (item.has_accelerator_) {
      item.has_accelerator_ = false;
      item.key_code_ = 0;
      item.shift_pressed_ = false;
      item.ctrl_pressed_ = false;
      item.alt_pressed_ = false;
    }
    return true;
  }
  return false;
}

bool CefMenuModelImpl::GetAccelerator(int command_id,
                                      int& key_code,
                                      bool& shift_pressed,
                                      bool& ctrl_pressed,
                                      bool& alt_pressed) {
  return GetAcceleratorAt(GetIndexOf(command_id), key_code, shift_pressed,
                          ctrl_pressed, alt_pressed);
}

bool CefMenuModelImpl::GetAcceleratorAt(size_t index,
                                        int& key_code,
                                        bool& shift_pressed,
                                        bool& ctrl_pressed,
                                        bool& alt_pressed) {
  if (!VerifyContext())
    return false;

  if (index < items_.size()) {
    const Item& item = items_[index];
    if (item.has_accelerator_) {
      key_code = item.key_code_;
      shift_pressed = item.shift_pressed_;
      ctrl_pressed = item.ctrl_pressed_;
      alt_pressed = item.alt_pressed_;
      return true;
    }
  }
  return false;
}

bool CefMenuModelImpl::SetColor(int command_id,
                                cef_menu_color_type_t color_type,
                                cef_color_t color) {
  return SetColorAt(GetIndexOf(command_id), color_type, color);
}

bool CefMenuModelImpl::SetColorAt(int index,
                                  cef_menu_color_type_t color_type,
                                  cef_color_t color) {
  if (!VerifyContext())
    return false;

  if (color_type < 0 || color_type >= CEF_MENU_COLOR_COUNT)
    return false;

  if (index == kDefaultIndex) {
    default_colors_[color_type] = color;
    return true;
  }

  if (index >= 0 && index < static_cast<int>(items_.size())) {
    Item& item = items_[index];
    item.colors_[color_type] = color;
    return true;
  }

  return false;
}

bool CefMenuModelImpl::GetColor(int command_id,
                                cef_menu_color_type_t color_type,
                                cef_color_t& color) {
  return GetColorAt(GetIndexOf(command_id), color_type, color);
}

bool CefMenuModelImpl::GetColorAt(int index,
                                  cef_menu_color_type_t color_type,
                                  cef_color_t& color) {
  if (!VerifyContext())
    return false;

  if (color_type < 0 || color_type >= CEF_MENU_COLOR_COUNT)
    return false;

  if (index == kDefaultIndex) {
    color = default_colors_[color_type];
    return true;
  }

  if (index >= 0 && index < static_cast<int>(items_.size())) {
    Item& item = items_[index];
    color = item.colors_[color_type];
    return true;
  }

  return false;
}

bool CefMenuModelImpl::SetFontList(int command_id, const CefString& font_list) {
  return SetFontListAt(GetIndexOf(command_id), font_list);
}

bool CefMenuModelImpl::SetFontListAt(int index, const CefString& font_list) {
  if (!VerifyContext())
    return false;

  if (index == kDefaultIndex) {
    if (font_list.empty()) {
      has_default_font_list_ = false;
    } else {
      default_font_list_ = gfx::FontList(font_list);
      has_default_font_list_ = true;
    }
    return true;
  }

  if (index >= 0 && index < static_cast<int>(items_.size())) {
    Item& item = items_[index];
    if (font_list.empty()) {
      item.has_font_list_ = false;
    } else {
      item.font_list_ = gfx::FontList(font_list);
      item.has_font_list_ = true;
    }
    return true;
  }
  return false;
}

void CefMenuModelImpl::ActivatedAt(size_t index,
                                   cef_event_flags_t event_flags) {
  if (!VerifyContext())
    return;

  const int command_id = GetCommandIdAt(index);
  if (delegate_)
    delegate_->ExecuteCommand(this, command_id, event_flags);
  if (menu_model_delegate_)
    menu_model_delegate_->ExecuteCommand(this, command_id, event_flags);
}

void CefMenuModelImpl::MouseOutsideMenu(const gfx::Point& screen_point) {
  if (!VerifyContext())
    return;

  // Allow the callstack to unwind before notifying the delegate since it may
  // result in the menu being destroyed.
  CefTaskRunnerImpl::GetCurrentTaskRunner()->PostTask(
      FROM_HERE, base::BindOnce(&CefMenuModelImpl::OnMouseOutsideMenu, this,
                                screen_point));
}

void CefMenuModelImpl::UnhandledOpenSubmenu(bool is_rtl) {
  if (!VerifyContext())
    return;

  // Allow the callstack to unwind before notifying the delegate since it may
  // result in the menu being destroyed.
  CefTaskRunnerImpl::GetCurrentTaskRunner()->PostTask(
      FROM_HERE,
      base::BindOnce(&CefMenuModelImpl::OnUnhandledOpenSubmenu, this, is_rtl));
}

void CefMenuModelImpl::UnhandledCloseSubmenu(bool is_rtl) {
  if (!VerifyContext())
    return;

  // Allow the callstack to unwind before notifying the delegate since it may
  // result in the menu being destroyed.
  CefTaskRunnerImpl::GetCurrentTaskRunner()->PostTask(
      FROM_HERE,
      base::BindOnce(&CefMenuModelImpl::OnUnhandledCloseSubmenu, this, is_rtl));
}

bool CefMenuModelImpl::GetTextColor(size_t index,
                                    bool is_accelerator,
                                    bool is_hovered,
                                    SkColor* override_color) const {
  if (index < items_.size()) {
    const Item& item = items_[index];
    if (!item.enabled_) {
      // Use accelerator color for disabled item text.
      is_accelerator = true;
    }

    const cef_menu_color_type_t color_type =
        GetMenuColorType(true, is_accelerator, is_hovered);
    if (item.colors_[color_type] != 0) {
      *override_color = item.colors_[color_type];
      return true;
    }
  }

  const cef_menu_color_type_t color_type =
      GetMenuColorType(true, is_accelerator, is_hovered);
  if (default_colors_[color_type] != 0) {
    *override_color = default_colors_[color_type];
    return true;
  }

  return false;
}

bool CefMenuModelImpl::GetBackgroundColor(size_t index,
                                          bool is_hovered,
                                          SkColor* override_color) const {
  const cef_menu_color_type_t color_type =
      GetMenuColorType(false, false, is_hovered);

  if (index < items_.size()) {
    const Item& item = items_[index];
    if (item.colors_[color_type] != 0) {
      *override_color = item.colors_[color_type];
      return true;
    }
  }

  if (default_colors_[color_type] != 0) {
    *override_color = default_colors_[color_type];
    return true;
  }

  return false;
}

void CefMenuModelImpl::MenuWillShow() {
  if (!VerifyContext())
    return;

  if (delegate_)
    delegate_->MenuWillShow(this);
  if (menu_model_delegate_)
    menu_model_delegate_->MenuWillShow(this);
}

void CefMenuModelImpl::MenuWillClose() {
  if (!VerifyContext())
    return;

  if (!auto_notify_menu_closed_)
    return;

  // Due to how menus work on the different platforms, ActivatedAt will be
  // called after this.  It's more convenient for the delegate to be called
  // afterwards, though, so post a task.
  CefTaskRunnerImpl::GetCurrentTaskRunner()->PostTask(
      FROM_HERE, base::BindOnce(&CefMenuModelImpl::OnMenuClosed, this));
}

std::u16string CefMenuModelImpl::GetFormattedLabelAt(size_t index) {
  std::u16string label = GetLabelAt(index).ToString16();
  if (delegate_)
    delegate_->FormatLabel(this, label);
  if (menu_model_delegate_) {
    CefString new_label = label;
    if (menu_model_delegate_->FormatLabel(this, new_label))
      label = new_label;
  }
  return label;
}

const gfx::FontList* CefMenuModelImpl::GetLabelFontListAt(size_t index) const {
  if (index < items_.size()) {
    const Item& item = items_[index];
    if (item.has_font_list_)
      return &item.font_list_;
  }

  if (has_default_font_list_)
    return &default_font_list_;
  return nullptr;
}

bool CefMenuModelImpl::VerifyRefCount() {
  if (!VerifyContext())
    return false;

  if (!HasOneRef())
    return false;

  for (ItemVector::iterator i = items_.begin(); i != items_.end(); ++i) {
    if ((*i).submenu_.get()) {
      if (!(*i).submenu_->VerifyRefCount())
        return false;
    }
  }

  return true;
}

void CefMenuModelImpl::AddMenuItem(
    const blink::mojom::CustomContextMenuItem& menu_item) {
  const int command_id = static_cast<int>(menu_item.action);

  switch (menu_item.type) {
    case blink::mojom::CustomContextMenuItemType::kOption:
      AddItem(command_id, menu_item.label);
      break;
    case blink::mojom::CustomContextMenuItemType::kCheckableOption:
      AddCheckItem(command_id, menu_item.label);
      break;
    case blink::mojom::CustomContextMenuItemType::kGroup:
      AddRadioItem(command_id, menu_item.label, 0);
      break;
    case blink::mojom::CustomContextMenuItemType::kSeparator:
      AddSeparator();
      break;
    case blink::mojom::CustomContextMenuItemType::kSubMenu: {
      CefRefPtr<CefMenuModelImpl> sub_menu = static_cast<CefMenuModelImpl*>(
          AddSubMenu(command_id, menu_item.label).get());
      for (size_t i = 0; i < menu_item.submenu.size(); ++i)
        sub_menu->AddMenuItem(*menu_item.submenu[i]);
      break;
    }
  }

  if (!menu_item.enabled &&
      menu_item.type != blink::mojom::CustomContextMenuItemType::kSeparator)
    SetEnabled(command_id, false);

  if (menu_item.checked &&
      (menu_item.type ==
           blink::mojom::CustomContextMenuItemType::kCheckableOption ||
       menu_item.type == blink::mojom::CustomContextMenuItemType::kGroup)) {
    SetChecked(command_id, true);
  }
}

void CefMenuModelImpl::NotifyMenuClosed() {
  DCHECK(!auto_notify_menu_closed_);
  OnMenuClosed();
}

void CefMenuModelImpl::AppendItem(const Item& item) {
  ValidateItem(item);
  items_.push_back(item);
}

void CefMenuModelImpl::InsertItemAt(const Item& item, size_t index) {
  // Sanitize the index.
  if (index > items_.size())
    index = items_.size();

  ValidateItem(item);
  items_.insert(items_.begin() + index, item);
}

void CefMenuModelImpl::ValidateItem(const Item& item) {
#if DCHECK_IS_ON()
  if (item.type_ == MENUITEMTYPE_SEPARATOR) {
    DCHECK_EQ(item.command_id_, kSeparatorId);
  } else {
    DCHECK_GE(item.command_id_, 0);
  }
#endif
}

void CefMenuModelImpl::OnMouseOutsideMenu(const gfx::Point& screen_point) {
  if (delegate_)
    delegate_->MouseOutsideMenu(this, screen_point);
  if (menu_model_delegate_) {
    menu_model_delegate_->MouseOutsideMenu(
        this, CefPoint(screen_point.x(), screen_point.y()));
  }
}

void CefMenuModelImpl::OnUnhandledOpenSubmenu(bool is_rtl) {
  if (delegate_)
    delegate_->UnhandledOpenSubmenu(this, is_rtl);
  if (menu_model_delegate_)
    menu_model_delegate_->UnhandledOpenSubmenu(this, is_rtl);
}

void CefMenuModelImpl::OnUnhandledCloseSubmenu(bool is_rtl) {
  if (delegate_)
    delegate_->UnhandledCloseSubmenu(this, is_rtl);
  if (menu_model_delegate_)
    menu_model_delegate_->UnhandledCloseSubmenu(this, is_rtl);
}

void CefMenuModelImpl::OnMenuClosed() {
  if (delegate_)
    delegate_->MenuClosed(this);
  if (menu_model_delegate_)
    menu_model_delegate_->MenuClosed(this);
}

bool CefMenuModelImpl::VerifyContext() {
  if (base::PlatformThread::CurrentId() != supported_thread_id_) {
    // This object should only be accessed from the thread that created it.
    NOTREACHED();
    return false;
  }

  return true;
}