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