#include "ui/views/controls/menu/menu_controller.h"
#include <functional>
#include <type_traits>
#include <utility>
#include <vector>
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/i18n/rtl.h"
#include "base/memory/raw_ptr.h"
#include "base/ranges/algorithm.h"
#include "base/strings/stringprintf.h"
#include "base/strings/to_string.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/current_thread.h"
#include "base/task/single_thread_task_runner.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_mode.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/platform/ax_platform_for_test.h"
#include "ui/accessibility/platform/ax_platform_node.h"
#include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/owned_window_anchor.h"
#include "ui/base/ozone_buildflags.h"
#include "ui/base/ui_base_types.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/event_handler.h"
#include "ui/events/event_utils.h"
#include "ui/events/test/event_generator.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/menu/menu_controller_delegate.h"
#include "ui/views/controls/menu/menu_delegate.h"
#include "ui/views/controls/menu/menu_host.h"
#include "ui/views/controls/menu/menu_host_root_view.h"
#include "ui/views/controls/menu/menu_item_view.h"
#include "ui/views/controls/menu/menu_scroll_view_container.h"
#include "ui/views/controls/menu/menu_types.h"
#include "ui/views/controls/menu/submenu_view.h"
#include "ui/views/test/ax_event_counter.h"
#include "ui/views/test/menu_test_utils.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/widget/root_view.h"
#include "ui/views/widget/widget_utils.h"
#if defined(USE_AURA)
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/drag_drop_client.h"
#include "ui/aura/client/drag_drop_client_observer.h"
#include "ui/aura/null_window_targeter.h"
#include "ui/aura/scoped_window_targeter.h"
#include "ui/aura/test/test_window_delegate.h"
#include "ui/aura/test/test_windows.h"
#include "ui/aura/window.h"
#include "ui/base/dragdrop/drag_drop_types.h"
#include "ui/base/dragdrop/mojom/drag_drop_types.mojom-shared.h"
#include "ui/views/controls/menu/menu_pre_target_handler.h"
#endif
#if BUILDFLAG(IS_OZONE)
#include "ui/ozone/public/ozone_platform.h"
#endif
#if BUILDFLAG(IS_OZONE_X11)
#include "ui/events/test/events_test_utils_x11.h"
#endif
namespace views {
namespace {
DragOperation;
constexpr MenuAnchorPosition kBubblePositions[] = …;
bool ShouldIgnoreScreenBoundsForMenus() { … }
gfx::Size GetPreferredSizeForSubmenu(SubmenuView& submenu) { … }
#define GET_CHILD_BUTTON(name, parent, index) …
class TestMenuControllerDelegate : public internal::MenuControllerDelegate { … };
void TestMenuControllerDelegate::OnMenuClosed(NotifyType type,
MenuItemView* menu,
int mouse_event_flags) { … }
void TestMenuControllerDelegate::SiblingMenuCreated(MenuItemView* menu) { … }
class TestEventHandler : public ui::EventHandler { … };
class GestureTestWidget : public Widget { … };
void GestureTestWidget::OnGestureEvent(ui::GestureEvent* event) { … }
#if defined(USE_AURA)
class TestDragDropClient : public aura::client::DragDropClient { … };
DragOperation TestDragDropClient::StartDragAndDrop(
std::unique_ptr<ui::OSExchangeData> data,
aura::Window* root_window,
aura::Window* source_window,
const gfx::Point& screen_location,
int allowed_operations,
ui::mojom::DragEventSource source) { … }
void TestDragDropClient::DragCancel() { … }
bool TestDragDropClient::IsDragDropInProgress() { … }
#endif
class CancelMenuOnMousePressView : public View { … };
bool CancelMenuOnMousePressView::OnMousePressed(const ui::MouseEvent& event) { … }
gfx::Size CancelMenuOnMousePressView::CalculatePreferredSize(
const SizeBounds& ) const { … }
BEGIN_METADATA(…)
}
struct MenuBoundsOptions { … };
class MenuControllerTest : public ViewsTestBase,
public testing::WithParamInterface<bool> { … };
void MenuControllerTest::SetUp() { … }
void MenuControllerTest::TearDown() { … }
void MenuControllerTest::ReleaseTouchId(int id) { … }
void MenuControllerTest::PressKey(ui::KeyboardCode key_code) { … }
void MenuControllerTest::DispatchKey(ui::KeyboardCode key_code) { … }
gfx::Rect MenuControllerTest::CalculateMenuBounds(
const MenuBoundsOptions& options) { … }
gfx::Rect MenuControllerTest::CalculateBubbleMenuBoundsWithoutInsets(
const MenuBoundsOptions& options,
MenuItemView* menu_item) { … }
gfx::Rect MenuControllerTest::CalculateExpectedMenuAnchorRect(
MenuItemView* menu_item) { … }
MenuController::MenuOpenDirection
MenuControllerTest::GetChildMenuOpenDirectionAtDepth(size_t depth) const { … }
void MenuControllerTest::SetChildMenuOpenDirectionAtDepth(
size_t depth,
MenuController::MenuOpenDirection direction) { … }
void MenuControllerTest::MenuChildrenChanged(MenuItemView* item) { … }
MenuAnchorPosition MenuControllerTest::AdjustAnchorPositionForRtl(
MenuAnchorPosition position) { … }
#if defined(USE_AURA)
void MenuControllerTest::TestAsyncEscapeKey() { … }
void MenuControllerTest::TestCancelEvent() { … }
#endif
void MenuControllerTest::VerifyDragCompleteThenDestroy() { … }
void MenuControllerTest::TestDragCompleteThenDestroyOnMenuClosed() { … }
void MenuControllerTest::TestMenuControllerReplacementDuringDrag() { … }
void MenuControllerTest::TestCancelAllDuringDrag() { … }
void MenuControllerTest::TestDestroyedDuringViewsRelease() { … }
void MenuControllerTest::TestMenuFitsOnScreen(
MenuAnchorPosition menu_anchor_position,
const gfx::Rect& monitor_bounds) { … }
void MenuControllerTest::TestMenuFitsOnScreenSmallAnchor(
MenuAnchorPosition menu_anchor_position,
const gfx::Rect& monitor_bounds) { … }
void MenuControllerTest::TestMenuFitsOnSmallScreen(
MenuAnchorPosition menu_anchor_position,
const gfx::Rect& monitor_bounds) { … }
void MenuControllerTest::TestSubmenuFitsOnScreen(
MenuItemView* item,
const gfx::Rect& monitor_bounds,
const gfx::Rect& parent_bounds,
MenuAnchorPosition menu_anchor) { … }
void MenuControllerTest::SetPendingStateItem(MenuItemView* item) { … }
void MenuControllerTest::SetState(MenuItemView* item) { … }
void MenuControllerTest::IncrementSelection() { … }
void MenuControllerTest::DecrementSelection() { … }
void MenuControllerTest::DestroyMenuControllerOnMenuClosed(
TestMenuControllerDelegate* delegate) { … }
void MenuControllerTest::DestroyMenuControllerDelegate() { … }
MenuItemView* MenuControllerTest::FindInitialSelectableMenuItemDown(
MenuItemView* parent) { … }
MenuItemView* MenuControllerTest::FindInitialSelectableMenuItemUp(
MenuItemView* parent) { … }
MenuHostRootView* MenuControllerTest::CreateMenuHostRootView(MenuHost* host) { … }
void MenuControllerTest::MenuHostOnDragWillStart(MenuHost* host) { … }
void MenuControllerTest::MenuHostOnDragComplete(MenuHost* host) { … }
void MenuControllerTest::SelectByChar(char16_t character) { … }
void MenuControllerTest::SetDropMenuItem(MenuItemView* target,
MenuDelegate::DropPosition position) { … }
void MenuControllerTest::SetComboboxType(
MenuController::ComboboxType combobox_type) { … }
void MenuControllerTest::SetSelectionOnPointerDown(
SubmenuView* source,
const ui::MouseEvent& event) { … }
bool MenuControllerTest::ProcessMousePressed(SubmenuView* source,
const ui::MouseEvent& event) { … }
bool MenuControllerTest::ProcessMouseDragged(SubmenuView* source,
const ui::MouseEvent& event) { … }
void MenuControllerTest::ProcessMouseReleased(SubmenuView* source,
const ui::MouseEvent& event) { … }
void MenuControllerTest::ProcessMouseMoved(SubmenuView* source,
const ui::MouseEvent& event) { … }
void MenuControllerTest::ProcessGestureEvent(SubmenuView* source,
const ui::GestureEvent& event) { … }
void MenuControllerTest::ProcessTouchEvent(SubmenuView* source,
const ui::TouchEvent& event) { … }
void MenuControllerTest::Accept(MenuItemView* item, int event_flags) { … }
void MenuControllerTest::StartDrag() { … }
void MenuControllerTest::SetUpMenuControllerForCalculateBounds(
const MenuBoundsOptions& options,
MenuItemView* menu_item) { … }
MenuItemView* MenuControllerTest::AddButtonMenuItems(bool single_child) { … }
void MenuControllerTest::DestroyMenuItem() { … }
void MenuControllerTest::SetHotTrackedButton(Button* hot_button) { … }
void MenuControllerTest::ExitMenuRun() { … }
void MenuControllerTest::DestroyMenuController() { … }
bool MenuControllerTest::SelectionWraps() { … }
void MenuControllerTest::OpenMenu(MenuItemView* parent,
const MenuBoundsOptions& options) { … }
gfx::Insets MenuControllerTest::GetBorderAndShadowInsets(bool is_submenu) { … }
INSTANTIATE_TEST_SUITE_P(…);
#if defined(USE_AURA)
TEST_F(MenuControllerTest, EventTargeter) { … }
#endif
#if BUILDFLAG(IS_OZONE_X11)
TEST_F(MenuControllerTest, TouchIdsReleasedCorrectly) { … }
#endif
TEST_F(MenuControllerTest, InitialSelectedItem) { … }
TEST_F(MenuControllerTest, VerifyMenuBubblePositionAfterSizeChanges) { … }
TEST_F(MenuControllerTest, VerifyContextMenuBubblePositionAfterSizeChanges) { … }
TEST_F(MenuControllerTest, FirstSelectedItem) { … }
TEST_F(MenuControllerTest, LastSelectedItem) { … }
class MenuControllerSelectionTest : public MenuControllerTest { … };
TEST_F(MenuControllerSelectionTest, NextSelectedItem) { … }
TEST_F(MenuControllerSelectionTest, PreviousSelectedItem) { … }
TEST_F(MenuControllerTest, CurrentSelectedItem) { … }
TEST_F(MenuControllerTest, SelectByChar) { … }
TEST_F(MenuControllerTest, SelectChildButtonView) { … }
TEST_F(MenuControllerTest, DeleteChildButtonView) { … }
TEST_F(MenuControllerTest, ChildButtonHotTrackedAfterMenuItemSelection) { … }
TEST_F(MenuControllerTest, ChildButtonHotTrackedAfterMouseMove) { … }
TEST_F(MenuControllerTest, ChildButtonHotTrackedWhenNested) { … }
TEST_F(MenuControllerTest, AsynchronousAccept) { … }
TEST_F(MenuControllerTest, AsynchronousCancelAll) { … }
TEST_F(MenuControllerTest, AsynchronousNestedDelegate) { … }
TEST_F(MenuControllerTest, AsynchronousPerformDrop) { … }
TEST_F(MenuControllerTest, AsynchronousDragComplete) { … }
TEST_F(MenuControllerTest, AsynchronousCancelDuringDrag) { … }
TEST_F(MenuControllerTest, AsynchronousDragHostDeleted) { … }
TEST_F(MenuControllerTest, AsyncDropCallback) { … }
TEST_F(MenuControllerTest, HostReceivesInputBeforeDestruction) { … }
TEST_F(MenuControllerTest, DoubleAsynchronousNested) { … }
TEST_F(MenuControllerTest, PreserveGestureForOwner) { … }
#if defined(USE_AURA)
TEST_F(MenuControllerTest, ForwardsEventsToNativeViewForGestures) { … }
#endif
TEST_F(MenuControllerTest, NoTouchCloseWhenSendingGesturesToOwner) { … }
TEST_F(MenuControllerTest, AsynchronousRepostEvent) { … }
TEST_F(MenuControllerTest, AsynchronousTouchEventRepostEvent) { … }
TEST_F(MenuControllerTest, AsynchronousRepostEventDeletesController) { … }
TEST_F(MenuControllerTest, AsynchronousGestureDeletesController) { … }
TEST_F(MenuControllerTest, CalculateMenuBoundsBestFitTest) { … }
TEST_F(MenuControllerTest, CalculateMenuBoundsAnchorTest) { … }
TEST_F(MenuControllerTest, MenuAnchorPositionFlippedInRtl) { … }
TEST_F(MenuControllerTest, CalculateMenuBoundsMonitorFitTest) { … }
TEST_P(MenuControllerTest, TestMenuFitsOnScreen) { … }
TEST_P(MenuControllerTest, TestMenuFitsOnScreenSmallAnchor) { … }
TEST_P(MenuControllerTest, TestMenuFitsOnSmallScreen) { … }
TEST_P(MenuControllerTest, TestSubmenuFitsOnScreen) { … }
TEST_F(MenuControllerTest, GrowingMenuMovesLaterallyNotVertically) { … }
#if defined(USE_AURA)
TEST_F(MenuControllerTest, MouseAtMenuItemOnShow) { … }
TEST_F(MenuControllerTest, AsynchronousCancelEvent) { … }
TEST_F(MenuControllerTest, WidgetStateChangeCancelsMenu) { … }
#if BUILDFLAG(ENABLE_DESKTOP_AURA) && !BUILDFLAG(IS_OZONE_WAYLAND)
class DesktopMenuControllerTest : public MenuControllerTest {
public:
void SetUp() override {
set_native_widget_type(NativeWidgetType::kDesktop);
MenuControllerTest::SetUp();
}
};
TEST_F(DesktopMenuControllerTest, RunWithoutWidgetDoesntCrash) {
ExitMenuRun();
menu_controller()->Run(nullptr, nullptr, menu_item(), gfx::Rect(),
MenuAnchorPosition::kTopLeft, false, false);
}
#endif
TEST_F(MenuControllerTest, MenuControllerReplacedDuringDrag) { … }
TEST_F(MenuControllerTest, CancelAllDuringDrag) { … }
TEST_F(MenuControllerTest, DestroyedDuringViewsRelease) { … }
TEST_F(MenuControllerTest, RepostEventToEmptyMenuItem) { … }
TEST_F(MenuControllerTest, DragFromViewIntoMenuAndExit) { … }
TEST_F(MenuControllerTest, AuraWindowIsInitializedWithMenuHostInitParams) { … }
TEST_F(MenuControllerTest, ContextMenuInitializesAuraWindowWhenShown) { … }
TEST_F(MenuControllerTest, RootAndChildMenusInitializeAuraWindowWhenShown) { … }
TEST_F(MenuControllerTest, RegisterClickWithChildViews) { … }
#endif
TEST_F(MenuControllerTest, NoUseAfterFreeWhenMenuCanceledOnMousePress) { … }
TEST_F(MenuControllerTest, SetSelectionIndices_MenuItemsOnly) { … }
TEST_F(MenuControllerTest,
SetSelectionIndices_MenuItemsOnly_SkipHiddenAndDisabled) { … }
TEST_F(MenuControllerTest, SetSelectionIndices_Buttons) { … }
TEST_F(MenuControllerTest, SetSelectionIndices_Buttons_SkipHiddenAndDisabled) { … }
TEST_F(MenuControllerTest, SetSelectionIndices_NestedButtons) { … }
TEST_F(MenuControllerTest, SetSelectionIndices_ChildrenChanged) { … }
TEST_F(MenuControllerTest, AccessibilityDoDefaultCallsAccept) { … }
TEST_F(MenuControllerTest, AccessibilityEmitsSelectChildrenChanged) { … }
TEST_F(MenuControllerTest, AccessibilityDisabledItemsIndices) { … }
#if BUILDFLAG(IS_MAC)
TEST_F(MenuControllerTest, BrowserHotkeysCancelMenusAndAreRedispatched) {
menu_controller()->Run(owner(), nullptr, menu_item(), gfx::Rect(),
MenuAnchorPosition::kTopLeft, false, false);
int options = ui::EF_COMMAND_DOWN;
ui::KeyEvent press_cmd(ui::EventType::kKeyPressed, ui::VKEY_COMMAND, options);
menu_controller()->OnWillDispatchKeyEvent(&press_cmd);
EXPECT_TRUE(showing());
options |= ui::EF_CONTROL_DOWN;
ui::KeyEvent press_ctrl(ui::EventType::kKeyPressed, ui::VKEY_CONTROL,
options);
menu_controller()->OnWillDispatchKeyEvent(&press_ctrl);
EXPECT_TRUE(showing());
ui::KeyEvent press_f(ui::EventType::kKeyPressed, ui::VKEY_F, options);
menu_controller()->OnWillDispatchKeyEvent(&press_f);
EXPECT_FALSE(showing());
EXPECT_FALSE(press_f.handled());
EXPECT_FALSE(press_f.stopped_propagation());
}
#endif
TEST_F(MenuControllerTest, SubmenuOpenByKey) { … }
class ExecuteCommandWithoutClosingMenuTest : public MenuControllerTest { … };
TEST_F(ExecuteCommandWithoutClosingMenuTest, OnClick) { … }
TEST_F(ExecuteCommandWithoutClosingMenuTest, OnTap) { … }
TEST_F(ExecuteCommandWithoutClosingMenuTest, OnReturnKey) { … }
TEST_F(MenuControllerTest, ChildMenuOpenDirectionStateUpdatesCorrectly) { … }
TEST_F(MenuControllerTest, MenuHostHasCorrectZOrderLevel) { … }
}