// 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 "ash/app_menu/app_menu_model_adapter.h"
#include <memory>
#include <utility>
#include "ash/app_menu/notification_menu_controller.h"
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/app_menu_constants.h"
#include "base/metrics/histogram_functions.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/base/models/simple_menu_model.h"
#include "ui/views/controls/menu/menu_item_view.h"
#include "ui/views/controls/menu/menu_model_adapter.h"
#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/controls/menu/submenu_view.h"
#include "ui/views/widget/widget.h"
namespace ash {
namespace {
// The UMA histogram that logs the commands which are executed on non-app
// context menus.
constexpr char kNonAppContextMenuExecuteCommand[] =
"Apps.ContextMenuExecuteCommand.NotFromApp";
constexpr char kNonAppContextMenuExecuteCommandInTablet[] =
"Apps.ContextMenuExecuteCommand.NotFromApp.TabletMode";
constexpr char kNonAppContextMenuExecuteCommandInClamshell[] =
"Apps.ContextMenuExecuteCommand.NotFromApp.ClamshellMode";
// The UMA histogram that logs the commands which are executed on app context
// menus.
constexpr char kAppContextMenuExecuteCommand[] =
"Apps.ContextMenuExecuteCommand.FromApp";
constexpr char kAppContextMenuExecuteCommandInTablet[] =
"Apps.ContextMenuExecuteCommand.FromApp.TabletMode";
constexpr char kAppContextMenuExecuteCommandInClamshell[] =
"Apps.ContextMenuExecuteCommand.FromApp.ClamshellMode";
} // namespace
AppMenuModelAdapter::AppMenuModelAdapter(
const std::string& app_id,
std::unique_ptr<ui::SimpleMenuModel> model,
views::Widget* widget_owner,
ui::MenuSourceType source_type,
base::OnceClosure on_menu_closed_callback,
bool is_tablet_mode)
: views::MenuModelAdapter(model.get()),
app_id_(app_id),
model_(std::move(model)),
widget_owner_(widget_owner),
source_type_(source_type),
on_menu_closed_callback_(std::move(on_menu_closed_callback)),
is_tablet_mode_(is_tablet_mode) {}
AppMenuModelAdapter::~AppMenuModelAdapter() = default;
void AppMenuModelAdapter::Run(const gfx::Rect& menu_anchor_rect,
views::MenuAnchorPosition menu_anchor_position,
int run_types) {
DCHECK(!root_);
DCHECK(model_);
menu_open_time_ = base::TimeTicks::Now();
std::unique_ptr<views::MenuItemView> root = CreateMenu();
root_ = root.get();
if (ash::features::IsNotificationsInContextMenuEnabled()) {
notification_menu_controller_ =
std::make_unique<NotificationMenuController>(app_id_, root_, this);
}
menu_runner_ =
std::make_unique<views::MenuRunner>(std::move(root), run_types);
menu_runner_->RunMenuAt(widget_owner_, nullptr /* MenuButtonController */,
menu_anchor_rect, menu_anchor_position, source_type_);
}
bool AppMenuModelAdapter::IsShowingMenu() const {
return menu_runner_ && menu_runner_->IsRunning();
}
void AppMenuModelAdapter::Cancel() {
if (!IsShowingMenu())
return;
menu_runner_->Cancel();
}
int AppMenuModelAdapter::GetCommandIdForHistograms(int command_id) {
return command_id;
}
base::TimeTicks AppMenuModelAdapter::GetClosingEventTime() {
DCHECK(menu_runner_);
return menu_runner_->closing_event_time();
}
views::Widget* AppMenuModelAdapter::GetSubmenuWidget() {
if (root_ && root_->GetSubmenu())
return root_->GetSubmenu()->GetWidget();
return nullptr;
}
void AppMenuModelAdapter::ExecuteCommand(int id, int mouse_event_flags) {
// Note that the command execution may cause this to get deleted - for
// example, for search result menus, the command could open an app window
// causing the app list search to get cleared, destroying non-zero state
// search results.
auto weak_self = weak_ptr_factory_.GetWeakPtr();
RecordExecuteCommandHistogram(GetCommandIdForHistograms(id));
views::MenuModelAdapter::ExecuteCommand(id, mouse_event_flags);
if (!weak_self)
return;
if (id >= USE_LAUNCH_TYPE_COMMAND_START &&
id <= USE_LAUNCH_TYPE_COMMAND_END) {
// Rebuild the menu to ensure that the `LAUNCH_NEW` menu item is refreshed
// after changing the app launch type. Note: this closes the submenu with
// `USE_LAUNCH_TYPE_*` commands.
BuildMenu(root_);
}
}
void AppMenuModelAdapter::OnMenuClosed(views::MenuItemView* menu) {
DCHECK_NE(base::TimeTicks(), menu_open_time_);
RecordHistogramOnMenuClosed();
// No |widget_owner_| in tests.
if (widget_owner_ && widget_owner_->GetRootView()) {
widget_owner_->GetRootView()->NotifyAccessibilityEvent(
ax::mojom::Event::kMenuEnd,
/*send_native_event=*/true);
}
if (on_menu_closed_callback_)
std::move(on_menu_closed_callback_).Run();
}
bool AppMenuModelAdapter::ShouldExecuteCommandWithoutClosingMenu(
int id,
const ui::Event& event) {
return id >= USE_LAUNCH_TYPE_COMMAND_START &&
id <= USE_LAUNCH_TYPE_COMMAND_END;
}
void AppMenuModelAdapter::RecordExecuteCommandHistogram(int command_id) {
const bool is_not_from_app = app_id().empty();
base::UmaHistogramSparse(is_not_from_app ? kNonAppContextMenuExecuteCommand
: kAppContextMenuExecuteCommand,
command_id);
if (is_tablet_mode_) {
base::UmaHistogramSparse(is_not_from_app
? kNonAppContextMenuExecuteCommandInTablet
: kAppContextMenuExecuteCommandInTablet,
command_id);
} else {
base::UmaHistogramSparse(is_not_from_app
? kNonAppContextMenuExecuteCommandInClamshell
: kAppContextMenuExecuteCommandInClamshell,
command_id);
}
}
} // namespace ash