#include "ui/views/controls/menu/menu_runner_impl.h"
#include <memory>
#include <utility>
#include "build/build_config.h"
#include "ui/accessibility/platform/ax_platform_node_base.h"
#include "ui/native_theme/native_theme.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/button/menu_button_controller.h"
#include "ui/views/controls/menu/menu_controller.h"
#include "ui/views/controls/menu/menu_delegate.h"
#include "ui/views/controls/menu/menu_item_view.h"
#include "ui/views/controls/menu/menu_runner_impl_adapter.h"
#include "ui/views/widget/widget.h"
#if BUILDFLAG(IS_WIN)
#include "ui/events/win/system_event_state_lookup.h"
#endif
#if BUILDFLAG(IS_OZONE)
#include "ui/base/ui_base_features.h"
#include "ui/events/event_constants.h"
#include "ui/ozone/public/ozone_platform.h"
#include "ui/ozone/public/platform_menu_utils.h"
#endif
namespace views {
namespace {
void FireFocusAfterMenuClose(base::WeakPtr<Widget> widget) {
if (widget) {
FocusManager* focus_manager = widget->GetFocusManager();
if (focus_manager && focus_manager->GetFocusedView()) {
focus_manager->GetFocusedView()
->GetViewAccessibility()
.FireFocusAfterMenuClose();
}
}
}
#if BUILDFLAG(IS_OZONE)
bool IsAltPressed() {
if (const auto* const platorm_menu_utils =
ui::OzonePlatform::GetInstance()->GetPlatformMenuUtils()) {
return (platorm_menu_utils->GetCurrentKeyModifiers() & ui::EF_ALT_DOWN) !=
0;
}
return false;
}
#endif
}
namespace internal {
#if !BUILDFLAG(IS_MAC)
MenuRunnerImplInterface* MenuRunnerImplInterface::Create(
ui::MenuModel* menu_model,
int32_t run_types,
base::RepeatingClosure on_menu_closed_callback) {
return new MenuRunnerImplAdapter(menu_model,
std::move(on_menu_closed_callback));
}
#endif
MenuRunnerImpl::MenuRunnerImpl(std::unique_ptr<MenuItemView> menu)
: menu_(std::move(menu)) {}
bool MenuRunnerImpl::IsRunning() const {
return running_;
}
void MenuRunnerImpl::Release() {
if (running_) {
if (delete_after_run_) {
return;
}
delete_after_run_ = true;
if (!empty_delegate_.get()) {
empty_delegate_ = std::make_unique<MenuDelegate>();
}
menu_->set_delegate(empty_delegate_.get());
if (controller_) {
controller_->Cancel(MenuController::ExitType::kDestroyed);
return;
}
}
delete this;
}
void MenuRunnerImpl::RunMenuAt(
Widget* parent,
MenuButtonController* button_controller,
const gfx::Rect& bounds,
MenuAnchorPosition anchor,
ui::mojom::MenuSourceType source_type,
int32_t run_types,
gfx::NativeView native_view_for_gestures,
gfx::AcceleratedWidget parent_widget,
std::optional<gfx::RoundedCornersF> corners,
std::optional<std::string> show_menu_host_duration_histogram) {
closing_event_time_ = base::TimeTicks();
if (running_) {
return;
}
MenuController* controller = MenuController::GetActiveInstance();
if (controller) {
controller->SetMenuRoundedCorners(corners);
if ((run_types & MenuRunner::IS_NESTED) != 0) {
if (controller->for_drop()) {
controller->Cancel(MenuController::ExitType::kAll);
controller = nullptr;
} else {
controller->AddNestedDelegate(this);
}
} else {
controller->Cancel(MenuController::ExitType::kAll);
if ((run_types & MenuRunner::FOR_DROP) == 0) {
return;
}
controller = nullptr;
}
}
running_ = true;
for_drop_ = (run_types & MenuRunner::FOR_DROP) != 0;
bool has_mnemonics = (run_types & MenuRunner::HAS_MNEMONICS) != 0;
owns_controller_ = false;
if (!controller) {
controller = new MenuController(for_drop_, this);
owns_controller_ = true;
controller->SetMenuRoundedCorners(corners);
}
DCHECK((run_types & MenuRunner::COMBOBOX) == 0 ||
(run_types & MenuRunner::EDITABLE_COMBOBOX) == 0);
using ComboboxType = MenuController::ComboboxType;
if (run_types & MenuRunner::COMBOBOX) {
controller->set_combobox_type(ComboboxType::kReadonly);
} else if (run_types & MenuRunner::EDITABLE_COMBOBOX) {
controller->set_combobox_type(ComboboxType::kEditable);
} else {
controller->set_combobox_type(ComboboxType::kNone);
}
controller->set_send_gesture_events_to_owner(
(run_types & MenuRunner::SEND_GESTURE_EVENTS_TO_OWNER) != 0);
controller->set_use_ash_system_ui_layout(
(run_types & MenuRunner::USE_ASH_SYS_UI_LAYOUT) != 0);
controller_ = controller->AsWeakPtr();
menu_->set_controller(controller_.get());
menu_->PrepareForRun(has_mnemonics,
!for_drop_ && ShouldShowMnemonics(run_types));
if (show_menu_host_duration_histogram.has_value() &&
!show_menu_host_duration_histogram.value().empty()) {
controller->SetShowMenuHostDurationHistogram(
std::move(show_menu_host_duration_histogram));
}
MenuController::MenuType menu_type = MenuController::MenuType::kNormal;
if ((run_types & MenuRunner::MENU_ITEM_CONTEXT_MENU) != 0) {
menu_type = MenuController::MenuType::kMenuItemContextMenu;
} else if ((run_types & MenuRunner::CONTEXT_MENU) != 0) {
menu_type = MenuController::MenuType::kContextMenu;
}
if (source_type == ui::mojom::MenuSourceType::kNone &&
(run_types & MenuRunner::INVOKED_FROM_KEYBOARD)) {
source_type = ui::mojom::MenuSourceType::kKeyboard;
}
controller->Run(parent, button_controller, menu_.get(), bounds, anchor,
source_type, menu_type,
(run_types & MenuRunner::NESTED_DRAG) != 0,
native_view_for_gestures, parent_widget);
}
void MenuRunnerImpl::Cancel() {
if (running_) {
controller_->Cancel(MenuController::ExitType::kAll);
}
}
base::TimeTicks MenuRunnerImpl::GetClosingEventTime() const {
return closing_event_time_;
}
void MenuRunnerImpl::OnMenuClosed(NotifyType type,
MenuItemView* menu,
int mouse_event_flags) {
base::WeakPtr<Widget> parent_widget;
if (controller_) {
closing_event_time_ = controller_->closing_event_time();
if (controller_->owner()) {
parent_widget = controller_->owner()->GetWeakPtr();
}
}
menu_->set_controller(nullptr);
if (owns_controller_ && controller_) {
delete controller_.get();
owns_controller_ = false;
}
controller_ = nullptr;
menu_->DestroyAllMenuHosts();
if (delete_after_run_) {
FireFocusAfterMenuClose(parent_widget);
delete this;
return;
}
running_ = false;
if (menu_->GetDelegate()) {
base::WeakPtr<MenuRunnerImpl> ref(weak_factory_.GetWeakPtr());
if (menu && !for_drop_) {
menu_->GetDelegate()->ExecuteCommand(menu->GetCommand(),
mouse_event_flags);
}
if (ref && type == NOTIFY_DELEGATE) {
menu_->GetDelegate()->OnMenuClosed(menu);
}
}
FireFocusAfterMenuClose(parent_widget);
}
void MenuRunnerImpl::SiblingMenuCreated(MenuItemView* menu) {
if (menu != menu_.get() && sibling_menus_.count(menu) == 0) {
sibling_menus_.insert(menu);
}
}
MenuRunnerImpl::~MenuRunnerImpl() {
for (MenuItemView* sibling_menu : sibling_menus_) {
delete sibling_menu;
}
}
bool MenuRunnerImpl::ShouldShowMnemonics(int32_t run_types) {
bool show_mnemonics = run_types & MenuRunner::SHOULD_SHOW_MNEMONICS;
#if BUILDFLAG(IS_WIN)
show_mnemonics |= ui::win::IsAltPressed();
#elif BUILDFLAG(IS_OZONE)
show_mnemonics |= IsAltPressed();
#elif BUILDFLAG(IS_MAC)
show_mnemonics = false;
#endif
return show_mnemonics;
}
}
}