#include "ui/views/controls/menu/menu_runner.h"
#include <stdint.h>
#include <memory>
#include <utility>
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/run_until.h"
#include "base/test/simple_test_tick_clock.h"
#include "build/build_config.h"
#include "ui/base/mojom/menu_source_type.mojom-shared.h"
#include "ui/compositor/compositor.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/native_ui_types.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/button/label_button.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.h"
#include "ui/views/controls/menu/menu_types.h"
#include "ui/views/controls/menu/submenu_view.h"
#include "ui/views/controls/menu/test_menu_item_view.h"
#include "ui/views/style/platform_style.h"
#include "ui/views/test/menu_test_utils.h"
#include "ui/views/test/test_views.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/widget/native_widget_private.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
#include "ui/views/widget/widget_utils.h"
#if BUILDFLAG(IS_MAC)
#include "ui/views/controls/menu/menu_cocoa_watcher_mac.h"
#endif
namespace views::test {
enum TestCommandIds {
kItem1 = TestMenuDelegate::kInvalidExecuteCommandId + 1,
kItem2,
kMaxValue = kItem2,
};
class MenuRunnerTest : public ViewsTestBase {
public:
MenuRunnerTest()
: ViewsTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
MenuRunnerTest(const MenuRunnerTest&) = delete;
MenuRunnerTest& operator=(const MenuRunnerTest&) = delete;
~MenuRunnerTest() override = default;
std::unique_ptr<MenuItemView> CreateMenuItemView() {
auto menu_item_view =
std::make_unique<TestMenuItemView>(menu_delegate_.get());
menu_item_view->AppendMenuItem(TestCommandIds::kItem1, u"One");
menu_item_view->AppendMenuItem(TestCommandIds::kItem2, u"\x062f\x0648");
menu_item_view_ = menu_item_view.get();
return menu_item_view;
}
void ResetMenuItemView() { menu_item_view_ = nullptr; }
void InitMenuRunner(int32_t run_types) {
menu_runner_ =
std::make_unique<MenuRunner>(CreateMenuItemView(), run_types);
}
views::TestMenuItemView* menu_item_view() { return menu_item_view_; }
TestMenuDelegate* menu_delegate() { return menu_delegate_.get(); }
MenuRunner* menu_runner() { return menu_runner_.get(); }
Widget* owner() { return owner_.get(); }
void SetUp() override {
ViewsTestBase::SetUp();
#if BUILDFLAG(IS_MAC)
MenuCocoaWatcherMac::SetNotificationFilterForTesting(
MacNotificationFilter::IgnoreWorkspaceNotifications);
#endif
menu_delegate_ = std::make_unique<TestMenuDelegate>();
Widget::InitParams params = CreateParams(
Widget::InitParams::CLIENT_OWNS_WIDGET, Widget::InitParams::TYPE_POPUP);
owner_ = std::make_unique<Widget>();
owner_->Init(std::move(params));
owner_->Show();
}
void TearDown() override {
ResetMenuItemView();
if (owner_) {
owner_->CloseNow();
}
#if BUILDFLAG(IS_MAC)
MenuCocoaWatcherMac::SetNotificationFilterForTesting(
MacNotificationFilter::DontIgnoreNotifications);
#endif
ViewsTestBase::TearDown();
}
bool IsItemSelected(int command_id) {
MenuItemView* item = menu_item_view()->GetMenuItemByID(command_id);
return item ? item->IsSelected() : false;
}
bool MenuSupportsMnemonics() {
return !MenuConfig::instance().all_menus_use_prefix_selection;
}
void OpenMenuAsyncWithRightClick(gfx::Point anchor_position,
ui::test::EventGenerator& event_generator) {
event_generator.MoveMouseTo(anchor_position);
event_generator.PressRightButton();
ASSERT_FALSE(menu_runner()->IsRunning());
base::RepeatingClosure quit_closure = task_environment()->QuitClosure();
task_environment()->GetMainThreadTaskRunner()->PostTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
menu_runner()->RunMenuAt(
owner(), nullptr, gfx::Rect(anchor_position, gfx::Size()),
MenuAnchorPosition::kTopLeft, ui::mojom::MenuSourceType::kMouse);
quit_closure.Run();
}));
task_environment()->RunUntilQuit();
}
private:
raw_ptr<views::TestMenuItemView> menu_item_view_ = nullptr;
std::unique_ptr<TestMenuDelegate> menu_delegate_;
std::unique_ptr<MenuRunner> menu_runner_;
std::unique_ptr<Widget> owner_;
};
TEST_F(MenuRunnerTest, AsynchronousRun) {
InitMenuRunner(0);
MenuRunner* runner = menu_runner();
runner->RunMenuAt(owner(), nullptr, gfx::Rect(), MenuAnchorPosition::kTopLeft,
ui::mojom::MenuSourceType::kNone);
EXPECT_TRUE(runner->IsRunning());
runner->Cancel();
EXPECT_FALSE(runner->IsRunning());
TestMenuDelegate* delegate = menu_delegate();
EXPECT_EQ(1, delegate->on_menu_closed_called());
EXPECT_EQ(nullptr, delegate->on_menu_closed_menu());
}
TEST_F(MenuRunnerTest, AsynchronousKeyEventHandling) {
InitMenuRunner(0);
MenuRunner* runner = menu_runner();
runner->RunMenuAt(owner(), nullptr, gfx::Rect(), MenuAnchorPosition::kTopLeft,
ui::mojom::MenuSourceType::kNone);
EXPECT_TRUE(runner->IsRunning());
ui::test::EventGenerator generator(GetContext(), owner()->GetNativeWindow());
generator.PressKey(ui::VKEY_ESCAPE, 0);
EXPECT_FALSE(runner->IsRunning());
TestMenuDelegate* delegate = menu_delegate();
EXPECT_EQ(1, delegate->on_menu_closed_called());
EXPECT_EQ(nullptr, delegate->on_menu_closed_menu());
}
#if BUILDFLAG(IS_OZONE)
#define MAYBE_LatinMnemonic DISABLED_LatinMnemonic
#else
#define MAYBE_LatinMnemonic LatinMnemonic
#endif
TEST_F(MenuRunnerTest, MAYBE_LatinMnemonic) {
if (!MenuSupportsMnemonics()) {
return;
}
views::test::DisableMenuClosureAnimations();
InitMenuRunner(0);
MenuRunner* runner = menu_runner();
runner->RunMenuAt(owner(), nullptr, gfx::Rect(), MenuAnchorPosition::kTopLeft,
ui::mojom::MenuSourceType::kNone);
EXPECT_TRUE(runner->IsRunning());
ui::test::EventGenerator generator(GetContext(), owner()->GetNativeWindow());
generator.PressKey(ui::VKEY_O, 0);
views::test::WaitForMenuClosureAnimation();
EXPECT_FALSE(runner->IsRunning());
TestMenuDelegate* delegate = menu_delegate();
EXPECT_EQ(TestCommandIds::kItem1, delegate->execute_command_id());
EXPECT_EQ(1, delegate->on_menu_closed_called());
EXPECT_NE(nullptr, delegate->on_menu_closed_menu());
}
#if !BUILDFLAG(IS_WIN)
TEST_F(MenuRunnerTest, NonLatinMnemonic) {
if (!MenuSupportsMnemonics()) {
return;
}
views::test::DisableMenuClosureAnimations();
InitMenuRunner(0);
MenuRunner* runner = menu_runner();
runner->RunMenuAt(owner(), nullptr, gfx::Rect(), MenuAnchorPosition::kTopLeft,
ui::mojom::MenuSourceType::kNone);
EXPECT_TRUE(runner->IsRunning());
ui::test::EventGenerator generator(GetContext(), owner()->GetNativeWindow());
ui::KeyEvent key_press =
ui::KeyEvent::FromCharacter(0x062f, ui::VKEY_N, ui::DomCode::NONE, 0);
generator.Dispatch(&key_press);
views::test::WaitForMenuClosureAnimation();
EXPECT_FALSE(runner->IsRunning());
TestMenuDelegate* delegate = menu_delegate();
EXPECT_EQ(TestCommandIds::kItem2, delegate->execute_command_id());
EXPECT_EQ(1, delegate->on_menu_closed_called());
EXPECT_NE(nullptr, delegate->on_menu_closed_menu());
}
#endif
TEST_F(MenuRunnerTest, MenuItemViewShowsMnemonics) {
if (!MenuSupportsMnemonics()) {
return;
}
InitMenuRunner(MenuRunner::HAS_MNEMONICS | MenuRunner::SHOULD_SHOW_MNEMONICS);
menu_runner()->RunMenuAt(owner(), nullptr, gfx::Rect(),
MenuAnchorPosition::kTopLeft,
ui::mojom::MenuSourceType::kNone);
EXPECT_TRUE(menu_item_view()->show_mnemonics());
}
TEST_F(MenuRunnerTest, MenuItemViewDoesNotShowMnemonics) {
if (!MenuSupportsMnemonics()) {
return;
}
InitMenuRunner(MenuRunner::HAS_MNEMONICS);
menu_runner()->RunMenuAt(owner(), nullptr, gfx::Rect(),
MenuAnchorPosition::kTopLeft,
ui::mojom::MenuSourceType::kNone);
EXPECT_FALSE(menu_item_view()->show_mnemonics());
}
TEST_F(MenuRunnerTest, PrefixSelect) {
if (!MenuConfig::instance().all_menus_use_prefix_selection) {
return;
}
base::SimpleTestTickClock clock;
views::test::DisableMenuClosureAnimations();
InitMenuRunner(0);
menu_item_view()->AppendMenuItem(3, u"One Two");
MenuRunner* runner = menu_runner();
runner->RunMenuAt(owner(), nullptr, gfx::Rect(), MenuAnchorPosition::kTopLeft,
ui::mojom::MenuSourceType::kNone);
EXPECT_TRUE(runner->IsRunning());
menu_item_view()
->GetSubmenu()
->GetPrefixSelector()
->set_tick_clock_for_testing(&clock);
ui::test::EventGenerator generator(GetContext(), owner()->GetNativeWindow());
generator.PressKey(ui::VKEY_O, 0);
EXPECT_TRUE(IsItemSelected(TestCommandIds::kItem1));
generator.PressKey(ui::VKEY_N, 0);
generator.PressKey(ui::VKEY_E, 0);
EXPECT_TRUE(IsItemSelected(TestCommandIds::kItem1));
generator.PressKey(ui::VKEY_SPACE, 0);
EXPECT_TRUE(IsItemSelected(3));
clock.Advance(base::Seconds(10));
generator.PressKey(ui::VKEY_SPACE, 0);
views::test::WaitForMenuClosureAnimation();
EXPECT_FALSE(runner->IsRunning());
TestMenuDelegate* delegate = menu_delegate();
EXPECT_EQ(3, delegate->execute_command_id());
EXPECT_EQ(1, delegate->on_menu_closed_called());
EXPECT_NE(nullptr, delegate->on_menu_closed_menu());
}
#if BUILDFLAG(IS_MAC)
TEST_F(MenuRunnerTest, SpaceActivatesItem) {
if (!MenuConfig::instance().all_menus_use_prefix_selection) {
return;
}
views::test::DisableMenuClosureAnimations();
InitMenuRunner(0);
MenuRunner* runner = menu_runner();
runner->RunMenuAt(owner(), nullptr, gfx::Rect(), MenuAnchorPosition::kTopLeft,
ui::mojom::MenuSourceType::kNone);
EXPECT_TRUE(runner->IsRunning());
ui::test::EventGenerator generator(GetContext(), owner()->GetNativeWindow());
generator.PressKey(ui::VKEY_DOWN, 0);
EXPECT_TRUE(IsItemSelected(TestCommandIds::kItem1));
generator.PressKey(ui::VKEY_SPACE, 0);
views::test::WaitForMenuClosureAnimation();
EXPECT_FALSE(runner->IsRunning());
TestMenuDelegate* delegate = menu_delegate();
EXPECT_EQ(TestCommandIds::kItem1, delegate->execute_command_id());
EXPECT_EQ(1, delegate->on_menu_closed_called());
EXPECT_NE(nullptr, delegate->on_menu_closed_menu());
}
#endif
TEST_F(MenuRunnerTest, NestingDuringDrag) {
InitMenuRunner(MenuRunner::FOR_DROP);
MenuRunner* runner = menu_runner();
runner->RunMenuAt(owner(), nullptr, gfx::Rect(), MenuAnchorPosition::kTopLeft,
ui::mojom::MenuSourceType::kNone);
EXPECT_TRUE(runner->IsRunning());
auto nested_delegate = std::make_unique<TestMenuDelegate>();
MenuRunner nested_runner(
MenuRunner(std::make_unique<MenuItemView>(nested_delegate.get()),
MenuRunner::IS_NESTED));
nested_runner.RunMenuAt(owner(), nullptr, gfx::Rect(),
MenuAnchorPosition::kTopLeft,
ui::mojom::MenuSourceType::kNone);
EXPECT_TRUE(nested_runner.IsRunning());
EXPECT_FALSE(runner->IsRunning());
EXPECT_EQ(1, menu_delegate()->on_menu_closed_called());
EXPECT_NE(nullptr, menu_delegate()->on_menu_closed_menu());
}
TEST_F(MenuRunnerTest, RightClickAndDragSelectsMenuItem) {
views::test::DisableMenuClosureAnimations();
InitMenuRunner(0);
menu_delegate()->DisableContextMenuForCommandId(TestCommandIds::kItem1);
ui::test::EventGenerator generator(GetContext(), owner()->GetNativeWindow());
const gfx::Point context_menu_open_location =
owner()->GetWindowBoundsInScreen().CenterPoint();
OpenMenuAsyncWithRightClick(context_menu_open_location, generator);
const MenuItemView* const item_1 =
menu_item_view()->GetMenuItemByID(TestCommandIds::kItem1);
ASSERT_TRUE(item_1);
EXPECT_FALSE(item_1->IsSelected());
generator.SetTargetWindow(item_1->GetWidget()->GetNativeWindow());
generator.MoveMouseTo(item_1->GetBoundsInScreen().CenterPoint());
EXPECT_TRUE(item_1->IsSelected());
generator.ReleaseRightButton();
EXPECT_TRUE(
base::test::RunUntil([this]() { return !menu_runner()->IsRunning(); }));
EXPECT_EQ(menu_delegate()->execute_command_id(), TestCommandIds::kItem1);
}
enum class MenuOpenLocation {
kLeft,
kRight,
};
class MenuRunnerFalseTriggerTest
: public MenuRunnerTest,
public testing::WithParamInterface<MenuOpenLocation> {
protected:
gfx::Point GetMenuOpenLocation(const gfx::Rect& screen_bounds) const {
CHECK(!screen_bounds.IsEmpty());
const int y = screen_bounds.CenterPoint().y();
switch (GetParam()) {
case MenuOpenLocation::kLeft:
return {0, y};
case MenuOpenLocation::kRight:
return {screen_bounds.right() - 1, y};
}
}
gfx::Vector2d GetAccidentalMouseMovement() {
constexpr int kNumPixels = 2;
switch (GetParam()) {
case MenuOpenLocation::kLeft:
return {kNumPixels, 0};
case MenuOpenLocation::kRight:
return {-kNumPixels, 0};
}
}
};
TEST_P(MenuRunnerFalseTriggerTest, DetectsRightClickAndDragFalseTrigger) {
views::test::DisableMenuClosureAnimations();
const gfx::Rect screen_bounds =
display::Screen::Get()->GetPrimaryDisplay().bounds();
owner()->SetBounds(screen_bounds);
InitMenuRunner(0);
ui::test::EventGenerator generator(GetContext(), owner()->GetNativeWindow());
int command_id_assigner = TestCommandIds::kMaxValue;
while (menu_item_view()->GetSubmenu()->GetPreferredSize().height() <
screen_bounds.height() * 3 / 4) {
menu_item_view()->AppendMenuItem(++command_id_assigner, u"TestItemLabel");
}
for (int id = TestCommandIds::kItem1; id <= command_id_assigner; ++id) {
menu_delegate()->DisableContextMenuForCommandId(id);
}
OpenMenuAsyncWithRightClick(GetMenuOpenLocation(screen_bounds), generator);
generator.SetTargetWindow(menu_item_view()
->GetMenuItemByID(TestCommandIds::kItem1)
->GetWidget()
->GetNativeWindow());
generator.MoveMouseTo(GetMenuOpenLocation(screen_bounds) +
GetAccidentalMouseMovement());
constexpr base::TimeDelta kMouseReleaseDelay = base::Milliseconds(50);
task_environment()->FastForwardBy(kMouseReleaseDelay);
generator.ReleaseRightButton();
task_environment()->GetMainThreadTaskRunner()->PostTask(
FROM_HERE, task_environment()->QuitClosure());
task_environment()->RunUntilQuit();
EXPECT_EQ(menu_delegate()->execute_command_id(),
TestMenuDelegate::kInvalidExecuteCommandId);
EXPECT_TRUE(menu_runner()->IsRunning());
menu_runner()->Cancel();
}
INSTANTIATE_TEST_SUITE_P(AllMenuLocations,
MenuRunnerFalseTriggerTest,
testing::Values(MenuOpenLocation::kLeft,
MenuOpenLocation::kRight));
namespace {
class MenuLauncherEventHandler : public ui::EventHandler {
public:
MenuLauncherEventHandler(MenuRunner* runner, Widget* owner)
: runner_(runner), owner_(owner) {}
MenuLauncherEventHandler(const MenuLauncherEventHandler&) = delete;
MenuLauncherEventHandler& operator=(const MenuLauncherEventHandler&) = delete;
~MenuLauncherEventHandler() override = default;
private:
void OnMouseEvent(ui::MouseEvent* event) override {
if (event->type() == ui::EventType::kMousePressed) {
runner_->RunMenuAt(owner_, nullptr, gfx::Rect(),
MenuAnchorPosition::kTopLeft,
ui::mojom::MenuSourceType::kNone);
event->SetHandled();
}
}
const raw_ptr<MenuRunner> runner_;
const raw_ptr<Widget> owner_;
};
}
class MenuRunnerWidgetTest : public MenuRunnerTest {
public:
static constexpr int kEventCountViewID = 123;
MenuRunnerWidgetTest() = default;
MenuRunnerWidgetTest(const MenuRunnerWidgetTest&) = delete;
MenuRunnerWidgetTest& operator=(const MenuRunnerWidgetTest&) = delete;
Widget* widget() { return widget_.get(); }
EventCountView* event_count_view() {
return static_cast<EventCountView*>(
widget()->GetRootView()->GetViewByID(kEventCountViewID));
}
std::unique_ptr<ui::test::EventGenerator> EventGeneratorForWidget(
Widget* widget) {
return std::make_unique<ui::test::EventGenerator>(
GetContext(), widget->GetNativeWindow());
}
void AddMenuLauncherEventHandler(Widget* widget) {
consumer_ =
std::make_unique<MenuLauncherEventHandler>(menu_runner(), widget);
event_count_view()->AddPostTargetHandler(consumer_.get());
}
void SetUp() override {
MenuRunnerTest::SetUp();
widget_ = std::make_unique<Widget>();
Widget::InitParams params =
CreateParams(Widget::InitParams::CLIENT_OWNS_WIDGET,
Widget::InitParams::TYPE_WINDOW);
widget_->Init(std::move(params));
widget_->Show();
widget_->SetSize(gfx::Size(300, 300));
auto event_count_view = std::make_unique<EventCountView>();
event_count_view->SetBounds(0, 0, 300, 300);
event_count_view->SetID(kEventCountViewID);
widget_->GetRootView()->AddChildView(std::move(event_count_view));
InitMenuRunner(0);
}
void TearDown() override {
consumer_.reset();
widget_->CloseNow();
MenuRunnerTest::TearDown();
}
private:
std::unique_ptr<Widget> widget_;
std::unique_ptr<MenuLauncherEventHandler> consumer_;
};
TEST_F(MenuRunnerWidgetTest, WidgetDoesntTakeCapture) {
AddMenuLauncherEventHandler(owner());
EXPECT_EQ(gfx::NativeView(), internal::NativeWidgetPrivate::GetGlobalCapture(
widget()->GetNativeView()));
auto generator(EventGeneratorForWidget(widget()));
generator->MoveMouseTo(widget()->GetClientAreaBoundsInScreen().CenterPoint());
generator->PressLeftButton();
EXPECT_EQ(1, event_count_view()->GetEventCount(ui::EventType::kMousePressed));
EXPECT_NE(widget()->GetNativeView(),
internal::NativeWidgetPrivate::GetGlobalCapture(
widget()->GetNativeView()));
TestMenuDelegate* delegate = menu_delegate();
EXPECT_TRUE(menu_runner()->IsRunning());
EXPECT_EQ(0, delegate->on_menu_closed_called());
}
TEST_F(MenuRunnerWidgetTest, ClearsMouseHandlerOnRun) {
AddMenuLauncherEventHandler(widget());
EventCountView* second_event_count_view = new EventCountView();
widget()->GetRootView()->AddChildViewRaw(second_event_count_view);
widget()->SetBounds(gfx::Rect(0, 0, 200, 100));
event_count_view()->SetBounds(0, 0, 100, 100);
second_event_count_view->SetBounds(100, 0, 100, 100);
auto generator(EventGeneratorForWidget(widget()));
generator->MoveMouseTo(event_count_view()->GetBoundsInScreen().CenterPoint());
generator->PressLeftButton();
EXPECT_TRUE(menu_runner()->IsRunning());
menu_runner()->Cancel();
generator.reset();
generator = EventGeneratorForWidget(widget());
generator->MoveMouseTo(
second_event_count_view->GetBoundsInScreen().CenterPoint());
generator->PressLeftButton();
EXPECT_EQ(
1, second_event_count_view->GetEventCount(ui::EventType::kMousePressed));
}
class MenuRunnerImplTest : public MenuRunnerTest {
public:
MenuRunnerImplTest() = default;
MenuRunnerImplTest(const MenuRunnerImplTest&) = delete;
MenuRunnerImplTest& operator=(const MenuRunnerImplTest&) = delete;
~MenuRunnerImplTest() override = default;
};
TEST_F(MenuRunnerImplTest, NestedMenuRunnersDestroyedOutOfOrder) {
internal::MenuRunnerImpl* menu_runner =
new internal::MenuRunnerImpl(CreateMenuItemView());
menu_runner->RunMenuAt(owner(), nullptr, gfx::Rect(),
MenuAnchorPosition::kTopLeft);
std::unique_ptr<TestMenuDelegate> menu_delegate2(new TestMenuDelegate);
MenuItemView* menu_item_view2 = new MenuItemView(menu_delegate2.get());
menu_item_view2->AppendMenuItem(1, u"One");
internal::MenuRunnerImpl* menu_runner2 = new internal::MenuRunnerImpl(
base::WrapUnique<MenuItemView>(menu_item_view2));
menu_runner2->RunMenuAt(
owner(), nullptr, gfx::Rect(), MenuAnchorPosition::kTopLeft,
ui::mojom::MenuSourceType::kNone, MenuRunner::IS_NESTED);
MenuControllerTestApi menu_controller;
menu_controller.SetShowing(false);
menu_runner->OnMenuClosed(internal::MenuControllerDelegate::NOTIFY_DELEGATE,
nullptr, 0);
menu_runner2->Release();
ResetMenuItemView();
menu_runner->Release();
}
TEST_F(MenuRunnerImplTest, MenuRunnerDestroyedWithNoActiveController) {
internal::MenuRunnerImpl* menu_runner =
new internal::MenuRunnerImpl(CreateMenuItemView());
menu_runner->RunMenuAt(owner(), nullptr, gfx::Rect(),
MenuAnchorPosition::kTopLeft,
ui::mojom::MenuSourceType::kNone, 0);
MenuControllerTestApi menu_controller;
menu_controller.SetShowing(false);
menu_controller.ClearState();
std::unique_ptr<TestMenuDelegate> menu_delegate2(new TestMenuDelegate);
MenuItemView* menu_item_view2 = new MenuItemView(menu_delegate2.get());
menu_item_view2->AppendMenuItem(1, u"One");
internal::MenuRunnerImpl* menu_runner2 = new internal::MenuRunnerImpl(
base::WrapUnique<MenuItemView>(menu_item_view2));
menu_runner2->RunMenuAt(
owner(), nullptr, gfx::Rect(), MenuAnchorPosition::kTopLeft,
ui::mojom::MenuSourceType::kNone, MenuRunner::FOR_DROP);
EXPECT_NE(menu_controller.controller(), MenuController::GetActiveInstance());
menu_controller.SetShowing(true);
menu_runner2->Release();
ResetMenuItemView();
menu_runner->Release();
if (menu_controller.controller()) {
menu_controller.controller()->Cancel(MenuController::ExitType::kAll);
}
EXPECT_EQ(nullptr, menu_controller.controller());
}
class MenuRunnerDestructionTest : public MenuRunnerTest {
public:
MenuRunnerDestructionTest() = default;
MenuRunnerDestructionTest(const MenuRunnerDestructionTest&) = delete;
MenuRunnerDestructionTest& operator=(const MenuRunnerDestructionTest&) =
delete;
~MenuRunnerDestructionTest() override = default;
base::WeakPtr<internal::MenuRunnerImpl> MenuRunnerAsWeakPtr(
internal::MenuRunnerImpl* menu_runner);
void SetUp() override;
};
base::WeakPtr<internal::MenuRunnerImpl>
MenuRunnerDestructionTest::MenuRunnerAsWeakPtr(
internal::MenuRunnerImpl* menu_runner) {
return menu_runner->weak_factory_.GetWeakPtr();
}
void MenuRunnerDestructionTest::SetUp() {
set_views_delegate(std::make_unique<ReleaseRefTestViewsDelegate>());
MenuRunnerTest::SetUp();
}
TEST_F(MenuRunnerDestructionTest, MenuRunnerDestroyedDuringReleaseRef) {
internal::MenuRunnerImpl* menu_runner =
new internal::MenuRunnerImpl(CreateMenuItemView());
menu_runner->RunMenuAt(owner(), nullptr, gfx::Rect(),
MenuAnchorPosition::kTopLeft,
ui::mojom::MenuSourceType::kNone, 0);
base::RunLoop run_loop;
static_cast<ReleaseRefTestViewsDelegate*>(test_views_delegate())
->set_release_ref_callback(base::BindLambdaForTesting([&]() {
run_loop.Quit();
ResetMenuItemView();
menu_runner->Release();
}));
base::WeakPtr<internal::MenuRunnerImpl> ref(MenuRunnerAsWeakPtr(menu_runner));
MenuControllerTestApi menu_controller;
menu_controller.controller()->Cancel(MenuController::ExitType::kAll);
EXPECT_EQ(nullptr, menu_controller.controller());
run_loop.Run();
EXPECT_EQ(nullptr, ref);
}
TEST_F(MenuRunnerImplTest, FocusOnMenuClose) {
internal::MenuRunnerImpl* menu_runner =
new internal::MenuRunnerImpl(CreateMenuItemView());
auto button_managed = std::make_unique<LabelButton>();
button_managed->SetID(1);
button_managed->SetSize(gfx::Size(20, 20));
LabelButton* button =
owner()->GetRootView()->AddChildView(std::move(button_managed));
button->SetFocusBehavior(View::FocusBehavior::ALWAYS);
button->GetWidget()->widget_delegate()->SetCanActivate(true);
button->GetWidget()->Activate();
button->RequestFocus();
menu_runner->RunMenuAt(owner(), nullptr, gfx::Rect(),
MenuAnchorPosition::kTopLeft);
MenuControllerTestApi menu_controller;
menu_controller.SetShowing(false);
bool focus_after_menu_close_sent = false;
ViewAccessibility::AccessibilityEventsCallback accessibility_events_callback =
base::BindRepeating(
[](bool* focus_after_menu_close_sent,
const ui::AXPlatformNodeDelegate* delegate,
const ax::mojom::Event event_type) {
if (event_type == ax::mojom::Event::kFocusAfterMenuClose) {
*focus_after_menu_close_sent = true;
}
},
&focus_after_menu_close_sent);
button->GetViewAccessibility().set_accessibility_events_callback(
std::move(accessibility_events_callback));
menu_runner->OnMenuClosed(internal::MenuControllerDelegate::NOTIFY_DELEGATE,
nullptr, 0);
EXPECT_TRUE(focus_after_menu_close_sent);
button->GetViewAccessibility().set_accessibility_events_callback(
base::DoNothing());
ResetMenuItemView();
menu_runner->Release();
}
TEST_F(MenuRunnerImplTest, FocusOnMenuCloseDeleteAfterRun) {
LabelButton* button = new LabelButton(
Button::PressedCallback(), std::u16string(), style::CONTEXT_BUTTON);
button->SetID(1);
button->SetSize(gfx::Size(20, 20));
owner()->GetRootView()->AddChildViewRaw(button);
button->SetFocusBehavior(View::FocusBehavior::ALWAYS);
button->GetWidget()->widget_delegate()->SetCanActivate(true);
button->GetWidget()->Activate();
button->RequestFocus();
internal::MenuRunnerImpl* menu_runner =
new internal::MenuRunnerImpl(CreateMenuItemView());
menu_runner->RunMenuAt(owner(), nullptr, gfx::Rect(),
MenuAnchorPosition::kTopLeft);
MenuControllerTestApi menu_controller;
menu_controller.SetShowing(false);
menu_controller.ClearState();
std::unique_ptr<TestMenuDelegate> menu_delegate2(new TestMenuDelegate);
MenuItemView* menu_item_view2 = new MenuItemView(menu_delegate2.get());
menu_item_view2->AppendMenuItem(1, u"One");
internal::MenuRunnerImpl* menu_runner2 = new internal::MenuRunnerImpl(
base::WrapUnique<MenuItemView>(menu_item_view2));
menu_runner2->RunMenuAt(
owner(), nullptr, gfx::Rect(), MenuAnchorPosition::kTopLeft,
ui::mojom::MenuSourceType::kNone, MenuRunner::FOR_DROP);
EXPECT_NE(menu_controller.controller(), MenuController::GetActiveInstance());
menu_controller.SetShowing(true);
bool focus_after_menu_close_sent = false;
ViewAccessibility::AccessibilityEventsCallback accessibility_events_callback =
base::BindRepeating(
[](bool* focus_after_menu_close_sent,
const ui::AXPlatformNodeDelegate* delegate,
const ax::mojom::Event event_type) {
if (event_type == ax::mojom::Event::kFocusAfterMenuClose) {
*focus_after_menu_close_sent = true;
}
},
&focus_after_menu_close_sent);
button->GetViewAccessibility().set_accessibility_events_callback(
std::move(accessibility_events_callback));
menu_runner2->Release();
EXPECT_TRUE(focus_after_menu_close_sent);
focus_after_menu_close_sent = false;
ResetMenuItemView();
menu_runner->Release();
EXPECT_TRUE(focus_after_menu_close_sent);
button->GetViewAccessibility().set_accessibility_events_callback(
base::DoNothing());
if (menu_controller.controller()) {
menu_controller.controller()->Cancel(MenuController::ExitType::kAll);
}
EXPECT_EQ(nullptr, menu_controller.controller());
}
TEST_F(MenuRunnerTest, ShowMenuHostDurationMetricsDoesLog) {
base::HistogramTester histogram_tester;
std::string histogram_name =
"Chrome.AppMenu.MenuHostInitToNextFramePresented";
InitMenuRunner(0);
MenuRunner* runner = menu_runner();
runner->RunMenuAt(owner(), nullptr, gfx::Rect(), MenuAnchorPosition::kTopLeft,
ui::mojom::MenuSourceType::kNone, gfx::NativeView(),
std::nullopt, histogram_name);
base::RunLoop run_loop;
views::MenuController::GetActiveInstance()
->GetSelectedMenuItem()
->GetSubmenu()
->GetWidget()
->GetCompositor()
->RequestSuccessfulPresentationTimeForNextFrame(base::BindOnce(
[](base::RunLoop* run_loop,
const viz::FrameTimingDetails& frame_timing_details) {
run_loop->Quit();
},
&run_loop));
histogram_tester.ExpectTotalCount(histogram_name, 0);
run_loop.Run();
histogram_tester.ExpectTotalCount(histogram_name, 1);
}
TEST_F(MenuRunnerTest, ShowMenuHostDurationMetricsDoesNotLog) {
base::HistogramTester histogram_tester;
std::string histogram_name =
"Chrome.AppMenu.MenuHostInitToNextFramePresented";
InitMenuRunner(0);
MenuRunner* runner = menu_runner();
runner->RunMenuAt(owner(), nullptr, gfx::Rect(), MenuAnchorPosition::kTopLeft,
ui::mojom::MenuSourceType::kNone);
base::RunLoop run_loop;
views::MenuController::GetActiveInstance()
->GetSelectedMenuItem()
->GetSubmenu()
->GetWidget()
->GetCompositor()
->RequestSuccessfulPresentationTimeForNextFrame(base::BindOnce(
[](base::RunLoop* run_loop,
const viz::FrameTimingDetails& frame_timing_details) {
run_loop->Quit();
},
&run_loop));
histogram_tester.ExpectTotalCount(histogram_name, 0);
run_loop.Run();
histogram_tester.ExpectTotalCount(histogram_name, 0);
}
TEST_F(MenuRunnerTest, FirstMenuItemSelectedWhenOpenedFromKeyboard) {
if (!PlatformStyle::kAutoSelectFirstMenuItemFromKeyboard) {
GTEST_SKIP() << "Behavior not present on this platform";
}
InitMenuRunner(MenuRunner::INVOKED_FROM_KEYBOARD);
menu_item_view()->AppendMenuItem(3, u"Three");
menu_runner()->RunMenuAt(owner(),
nullptr, gfx::Rect(),
MenuAnchorPosition::kTopLeft,
ui::mojom::MenuSourceType::kNone);
EXPECT_TRUE(IsItemSelected(TestCommandIds::kItem1));
EXPECT_FALSE(IsItemSelected(TestCommandIds::kItem2));
EXPECT_FALSE(IsItemSelected(3));
}
}