1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "ui/views/controls/menu/menu_model_adapter.h" 6 #include "base/callback.h" 7 #include "base/location.h" 8 #include "base/macros.h" 9 #include "base/single_thread_task_runner.h" 10 #include "base/strings/utf_string_conversions.h" 11 #include "base/task/current_thread.h" 12 #include "base/threading/thread_task_runner_handle.h" 13 #include "chrome/browser/ui/views/test/view_event_test_base.h" 14 #include "chrome/test/base/interactive_test_utils.h" 15 #include "chrome/test/base/ui_test_utils.h" 16 #include "ui/base/models/menu_model.h" 17 #include "ui/base/test/ui_controls.h" 18 #include "ui/views/controls/button/menu_button.h" 19 #include "ui/views/controls/menu/menu_controller.h" 20 #include "ui/views/controls/menu/menu_item_view.h" 21 #include "ui/views/controls/menu/menu_runner.h" 22 #include "ui/views/controls/menu/submenu_view.h" 23 #include "ui/views/test/menu_test_utils.h" 24 #include "ui/views/widget/root_view.h" 25 #include "ui/views/widget/widget.h" 26 27 namespace { 28 29 const int kTopMenuBaseId = 100; 30 const int kSubMenuBaseId = 200; 31 32 // Implement most of the ui::MenuModel pure virtual methods for subclasses 33 // 34 // Exceptions: 35 // virtual int GetItemCount() const = 0; 36 // virtual ItemType GetTypeAt(int index) const = 0; 37 // virtual int GetCommandIdAt(int index) const = 0; 38 // virtual base::string16 GetLabelAt(int index) const = 0; 39 class CommonMenuModel : public ui::MenuModel { 40 public: CommonMenuModel()41 CommonMenuModel() { 42 } 43 ~CommonMenuModel()44 ~CommonMenuModel() override {} 45 46 protected: 47 // ui::MenuModel implementation. HasIcons() const48 bool HasIcons() const override { return false; } 49 IsItemDynamicAt(int index) const50 bool IsItemDynamicAt(int index) const override { return false; } 51 GetAcceleratorAt(int index,ui::Accelerator * accelerator) const52 bool GetAcceleratorAt(int index, 53 ui::Accelerator* accelerator) const override { 54 return false; 55 } 56 GetSeparatorTypeAt(int index) const57 ui::MenuSeparatorType GetSeparatorTypeAt(int index) const override { 58 return ui::NORMAL_SEPARATOR; 59 } 60 IsItemCheckedAt(int index) const61 bool IsItemCheckedAt(int index) const override { return false; } 62 GetGroupIdAt(int index) const63 int GetGroupIdAt(int index) const override { return 0; } 64 GetIconAt(int index) const65 ui::ImageModel GetIconAt(int index) const override { 66 return ui::ImageModel(); 67 } 68 GetButtonMenuItemAt(int index) const69 ui::ButtonMenuItemModel* GetButtonMenuItemAt(int index) const override { 70 return nullptr; 71 } 72 IsEnabledAt(int index) const73 bool IsEnabledAt(int index) const override { return true; } 74 GetSubmenuModelAt(int index) const75 ui::MenuModel* GetSubmenuModelAt(int index) const override { return nullptr; } 76 ActivatedAt(int index)77 void ActivatedAt(int index) override {} 78 79 private: 80 DISALLOW_COPY_AND_ASSIGN(CommonMenuModel); 81 }; 82 83 class SubMenuModel : public CommonMenuModel { 84 public: SubMenuModel()85 SubMenuModel() 86 : showing_(false) { 87 } 88 ~SubMenuModel()89 ~SubMenuModel() override {} 90 showing() const91 bool showing() const { 92 return showing_; 93 } 94 95 private: 96 // ui::MenuModel implementation. GetItemCount() const97 int GetItemCount() const override { return 1; } 98 GetTypeAt(int index) const99 ItemType GetTypeAt(int index) const override { return TYPE_COMMAND; } 100 GetCommandIdAt(int index) const101 int GetCommandIdAt(int index) const override { 102 return index + kSubMenuBaseId; 103 } 104 GetLabelAt(int index) const105 base::string16 GetLabelAt(int index) const override { 106 return base::ASCIIToUTF16("Item"); 107 } 108 MenuWillShow()109 void MenuWillShow() override { showing_ = true; } 110 111 // Called when the menu is about to close. MenuWillClose()112 void MenuWillClose() override { showing_ = false; } 113 114 bool showing_; 115 116 DISALLOW_COPY_AND_ASSIGN(SubMenuModel); 117 }; 118 119 class TopMenuModel : public CommonMenuModel { 120 public: TopMenuModel()121 TopMenuModel() { 122 } 123 ~TopMenuModel()124 ~TopMenuModel() override {} 125 IsSubmenuShowing()126 bool IsSubmenuShowing() { 127 return sub_menu_model_.showing(); 128 } 129 130 private: 131 // ui::MenuModel implementation. GetItemCount() const132 int GetItemCount() const override { return 1; } 133 GetTypeAt(int index) const134 ItemType GetTypeAt(int index) const override { return TYPE_SUBMENU; } 135 GetCommandIdAt(int index) const136 int GetCommandIdAt(int index) const override { 137 return index + kTopMenuBaseId; 138 } 139 GetLabelAt(int index) const140 base::string16 GetLabelAt(int index) const override { 141 return base::ASCIIToUTF16("submenu"); 142 } 143 GetSubmenuModelAt(int index) const144 MenuModel* GetSubmenuModelAt(int index) const override { 145 return &sub_menu_model_; 146 } 147 148 mutable SubMenuModel sub_menu_model_; 149 150 DISALLOW_COPY_AND_ASSIGN(TopMenuModel); 151 }; 152 153 } // namespace 154 155 class MenuModelAdapterTest : public ViewEventTestBase { 156 public: 157 MenuModelAdapterTest() = default; 158 ~MenuModelAdapterTest() override = default; 159 160 // ViewEventTestBase implementation. 161 SetUp()162 void SetUp() override { 163 ViewEventTestBase::SetUp(); 164 165 menu_ = menu_model_adapter_.CreateMenu(); 166 menu_runner_ = std::make_unique<views::MenuRunner>( 167 menu_, views::MenuRunner::HAS_MNEMONICS); 168 } 169 TearDown()170 void TearDown() override { 171 menu_runner_.reset(); 172 173 ViewEventTestBase::TearDown(); 174 } 175 CreateContentsView()176 std::unique_ptr<views::View> CreateContentsView() override { 177 auto button = std::make_unique<views::MenuButton>( 178 base::BindRepeating(&MenuModelAdapterTest::ButtonPressed, 179 base::Unretained(this)), 180 base::ASCIIToUTF16("Menu Adapter Test")); 181 button_ = button.get(); 182 return button; 183 } 184 GetPreferredSizeForContents() const185 gfx::Size GetPreferredSizeForContents() const override { 186 return button_->GetPreferredSize(); 187 } 188 189 // ViewEventTestBase implementation DoTestOnMessageLoop()190 void DoTestOnMessageLoop() override { 191 Click(button_, CreateEventTask(this, &MenuModelAdapterTest::Step1)); 192 } 193 194 // Open the submenu. Step1()195 void Step1() { 196 views::test::DisableMenuClosureAnimations(); 197 views::SubmenuView* topmenu = menu_->GetSubmenu(); 198 ASSERT_TRUE(topmenu); 199 ASSERT_TRUE(topmenu->IsShowing()); 200 ASSERT_FALSE(top_menu_model_.IsSubmenuShowing()); 201 202 // Click the first item to open the submenu. 203 views::MenuItemView* item = topmenu->GetMenuItemAt(0); 204 ASSERT_TRUE(item); 205 Click(item, CreateEventTask(this, &MenuModelAdapterTest::Step2)); 206 } 207 208 // Rebuild the menu which should close the submenu. Step2()209 void Step2() { 210 views::SubmenuView* topmenu = menu_->GetSubmenu(); 211 ASSERT_TRUE(topmenu); 212 ASSERT_TRUE(topmenu->IsShowing()); 213 ASSERT_TRUE(top_menu_model_.IsSubmenuShowing()); 214 215 menu_model_adapter_.BuildMenu(menu_); 216 217 ASSERT_TRUE(base::CurrentUIThread::IsSet()); 218 base::ThreadTaskRunnerHandle::Get()->PostTask( 219 FROM_HERE, CreateEventTask(this, &MenuModelAdapterTest::Step3)); 220 } 221 222 // Verify that the submenu MenuModel received the close callback 223 // and close the menu. Step3()224 void Step3() { 225 views::SubmenuView* topmenu = menu_->GetSubmenu(); 226 ASSERT_TRUE(topmenu); 227 ASSERT_TRUE(topmenu->IsShowing()); 228 ASSERT_FALSE(top_menu_model_.IsSubmenuShowing()); 229 230 // Click the button to exit the menu. 231 Click(button_, CreateEventTask(this, &MenuModelAdapterTest::Step4)); 232 } 233 234 // All done. Step4()235 void Step4() { 236 views::SubmenuView* topmenu = menu_->GetSubmenu(); 237 views::test::WaitForMenuClosureAnimation(); 238 ASSERT_TRUE(topmenu); 239 ASSERT_FALSE(topmenu->IsShowing()); 240 ASSERT_FALSE(top_menu_model_.IsSubmenuShowing()); 241 242 Done(); 243 } 244 245 private: 246 // Generate a mouse click on the specified view and post a new task. Click(views::View * view,base::OnceClosure next)247 virtual void Click(views::View* view, base::OnceClosure next) { 248 ui_test_utils::MoveMouseToCenterAndPress( 249 view, ui_controls::LEFT, ui_controls::DOWN | ui_controls::UP, 250 std::move(next)); 251 } 252 ButtonPressed()253 void ButtonPressed() { 254 menu_runner_->RunMenuAt(button_->GetWidget(), button_->button_controller(), 255 button_->GetBoundsInScreen(), 256 views::MenuAnchorPosition::kTopLeft, 257 ui::MENU_SOURCE_NONE); 258 } 259 260 views::MenuButton* button_ = nullptr; 261 TopMenuModel top_menu_model_; 262 views::MenuModelAdapter menu_model_adapter_{&top_menu_model_}; 263 views::MenuItemView* menu_ = nullptr; 264 std::unique_ptr<views::MenuRunner> menu_runner_; 265 }; 266 267 // If this flakes, disable and log details in http://crbug.com/523255. 268 VIEW_TEST(MenuModelAdapterTest, RebuildMenu) 269