|  | // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "ui/views/controls/menu/menu_model_adapter.h" | 
|  | #include "base/callback.h" | 
|  | #include "base/location.h" | 
|  | #include "base/macros.h" | 
|  | #include "base/message_loop/message_loop.h" | 
|  | #include "base/single_thread_task_runner.h" | 
|  | #include "base/strings/utf_string_conversions.h" | 
|  | #include "base/threading/thread_task_runner_handle.h" | 
|  | #include "chrome/test/base/interactive_test_utils.h" | 
|  | #include "chrome/test/base/ui_test_utils.h" | 
|  | #include "chrome/test/base/view_event_test_base.h" | 
|  | #include "ui/base/models/menu_model.h" | 
|  | #include "ui/base/test/ui_controls.h" | 
|  | #include "ui/views/controls/button/menu_button.h" | 
|  | #include "ui/views/controls/button/menu_button_listener.h" | 
|  | #include "ui/views/controls/menu/menu_controller.h" | 
|  | #include "ui/views/controls/menu/menu_item_view.h" | 
|  | #include "ui/views/controls/menu/menu_runner.h" | 
|  | #include "ui/views/controls/menu/submenu_view.h" | 
|  | #include "ui/views/widget/root_view.h" | 
|  | #include "ui/views/widget/widget.h" | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | const int kTopMenuBaseId = 100; | 
|  | const int kSubMenuBaseId = 200; | 
|  |  | 
|  | // Implement most of the ui::MenuModel pure virtual methods for subclasses | 
|  | // | 
|  | // Exceptions: | 
|  | //  virtual int GetItemCount() const = 0; | 
|  | //  virtual ItemType GetTypeAt(int index) const = 0; | 
|  | //  virtual int GetCommandIdAt(int index) const = 0; | 
|  | //  virtual base::string16 GetLabelAt(int index) const = 0; | 
|  | class CommonMenuModel : public ui::MenuModel { | 
|  | public: | 
|  | CommonMenuModel() { | 
|  | } | 
|  |  | 
|  | ~CommonMenuModel() override {} | 
|  |  | 
|  | protected: | 
|  | // ui::MenuModel implementation. | 
|  | bool HasIcons() const override { return false; } | 
|  |  | 
|  | bool IsItemDynamicAt(int index) const override { return false; } | 
|  |  | 
|  | bool GetAcceleratorAt(int index, | 
|  | ui::Accelerator* accelerator) const override { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | ui::MenuSeparatorType GetSeparatorTypeAt(int index) const override { | 
|  | return ui::NORMAL_SEPARATOR; | 
|  | } | 
|  |  | 
|  | bool IsItemCheckedAt(int index) const override { return false; } | 
|  |  | 
|  | int GetGroupIdAt(int index) const override { return 0; } | 
|  |  | 
|  | bool GetIconAt(int index, gfx::Image* icon) override { return false; } | 
|  |  | 
|  | ui::ButtonMenuItemModel* GetButtonMenuItemAt(int index) const override { | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | bool IsEnabledAt(int index) const override { return true; } | 
|  |  | 
|  | ui::MenuModel* GetSubmenuModelAt(int index) const override { return NULL; } | 
|  |  | 
|  | void HighlightChangedTo(int index) override {} | 
|  |  | 
|  | void ActivatedAt(int index) override {} | 
|  |  | 
|  | void SetMenuModelDelegate(ui::MenuModelDelegate* delegate) override {} | 
|  |  | 
|  | ui::MenuModelDelegate* GetMenuModelDelegate() const override { return NULL; } | 
|  |  | 
|  | private: | 
|  | DISALLOW_COPY_AND_ASSIGN(CommonMenuModel); | 
|  | }; | 
|  |  | 
|  | class SubMenuModel : public CommonMenuModel { | 
|  | public: | 
|  | SubMenuModel() | 
|  | : showing_(false) { | 
|  | } | 
|  |  | 
|  | ~SubMenuModel() override {} | 
|  |  | 
|  | bool showing() const { | 
|  | return showing_; | 
|  | } | 
|  |  | 
|  | private: | 
|  | // ui::MenuModel implementation. | 
|  | int GetItemCount() const override { return 1; } | 
|  |  | 
|  | ItemType GetTypeAt(int index) const override { return TYPE_COMMAND; } | 
|  |  | 
|  | int GetCommandIdAt(int index) const override { | 
|  | return index + kSubMenuBaseId; | 
|  | } | 
|  |  | 
|  | base::string16 GetLabelAt(int index) const override { | 
|  | return base::ASCIIToUTF16("Item"); | 
|  | } | 
|  |  | 
|  | void MenuWillShow() override { showing_ = true; } | 
|  |  | 
|  | // Called when the menu is about to close. | 
|  | void MenuWillClose() override { showing_ = false; } | 
|  |  | 
|  | bool showing_; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(SubMenuModel); | 
|  | }; | 
|  |  | 
|  | class TopMenuModel : public CommonMenuModel { | 
|  | public: | 
|  | TopMenuModel() { | 
|  | } | 
|  |  | 
|  | ~TopMenuModel() override {} | 
|  |  | 
|  | bool IsSubmenuShowing() { | 
|  | return sub_menu_model_.showing(); | 
|  | } | 
|  |  | 
|  | private: | 
|  | // ui::MenuModel implementation. | 
|  | int GetItemCount() const override { return 1; } | 
|  |  | 
|  | ItemType GetTypeAt(int index) const override { return TYPE_SUBMENU; } | 
|  |  | 
|  | int GetCommandIdAt(int index) const override { | 
|  | return index + kTopMenuBaseId; | 
|  | } | 
|  |  | 
|  | base::string16 GetLabelAt(int index) const override { | 
|  | return base::ASCIIToUTF16("submenu"); | 
|  | } | 
|  |  | 
|  | MenuModel* GetSubmenuModelAt(int index) const override { | 
|  | return &sub_menu_model_; | 
|  | } | 
|  |  | 
|  | mutable SubMenuModel sub_menu_model_; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(TopMenuModel); | 
|  | }; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | class MenuModelAdapterTest : public ViewEventTestBase, | 
|  | public views::MenuButtonListener { | 
|  | public: | 
|  | MenuModelAdapterTest() | 
|  | : ViewEventTestBase(), | 
|  | button_(NULL), | 
|  | menu_model_adapter_(&top_menu_model_), | 
|  | menu_(NULL) { | 
|  | } | 
|  |  | 
|  | ~MenuModelAdapterTest() override {} | 
|  |  | 
|  | // ViewEventTestBase implementation. | 
|  |  | 
|  | void SetUp() override { | 
|  | button_ = new views::MenuButton(base::ASCIIToUTF16("Menu Adapter Test"), | 
|  | this, true); | 
|  |  | 
|  | menu_ = menu_model_adapter_.CreateMenu(); | 
|  | menu_runner_.reset( | 
|  | new views::MenuRunner(menu_, views::MenuRunner::HAS_MNEMONICS)); | 
|  |  | 
|  | ViewEventTestBase::SetUp(); | 
|  | } | 
|  |  | 
|  | void TearDown() override { | 
|  | menu_runner_.reset(NULL); | 
|  | menu_ = NULL; | 
|  | ViewEventTestBase::TearDown(); | 
|  | } | 
|  |  | 
|  | views::View* CreateContentsView() override { return button_; } | 
|  |  | 
|  | gfx::Size GetPreferredSizeForContents() const override { | 
|  | return button_->GetPreferredSize(); | 
|  | } | 
|  |  | 
|  | // views::MenuButtonListener implementation. | 
|  | void OnMenuButtonClicked(views::MenuButton* source, | 
|  | const gfx::Point& point, | 
|  | const ui::Event* event) override { | 
|  | gfx::Point screen_location; | 
|  | views::View::ConvertPointToScreen(source, &screen_location); | 
|  | gfx::Rect bounds(screen_location, source->size()); | 
|  | menu_runner_->RunMenuAt(source->GetWidget(), button_, bounds, | 
|  | views::MENU_ANCHOR_TOPLEFT, ui::MENU_SOURCE_NONE); | 
|  | } | 
|  |  | 
|  | // ViewEventTestBase implementation | 
|  | void DoTestOnMessageLoop() override { | 
|  | Click(button_, CreateEventTask(this, &MenuModelAdapterTest::Step1)); | 
|  | } | 
|  |  | 
|  | // Open the submenu. | 
|  | void Step1() { | 
|  | views::SubmenuView* topmenu = menu_->GetSubmenu(); | 
|  | ASSERT_TRUE(topmenu); | 
|  | ASSERT_TRUE(topmenu->IsShowing()); | 
|  | ASSERT_FALSE(top_menu_model_.IsSubmenuShowing()); | 
|  |  | 
|  | // Click the first item to open the submenu. | 
|  | views::MenuItemView* item = topmenu->GetMenuItemAt(0); | 
|  | ASSERT_TRUE(item); | 
|  | Click(item, CreateEventTask(this, &MenuModelAdapterTest::Step2)); | 
|  | } | 
|  |  | 
|  | // Rebuild the menu which should close the submenu. | 
|  | void Step2() { | 
|  | views::SubmenuView* topmenu = menu_->GetSubmenu(); | 
|  | ASSERT_TRUE(topmenu); | 
|  | ASSERT_TRUE(topmenu->IsShowing()); | 
|  | ASSERT_TRUE(top_menu_model_.IsSubmenuShowing()); | 
|  |  | 
|  | menu_model_adapter_.BuildMenu(menu_); | 
|  |  | 
|  | ASSERT_TRUE(base::MessageLoopForUI::IsCurrent()); | 
|  | base::ThreadTaskRunnerHandle::Get()->PostTask( | 
|  | FROM_HERE, CreateEventTask(this, &MenuModelAdapterTest::Step3)); | 
|  | } | 
|  |  | 
|  | // Verify that the submenu MenuModel received the close callback | 
|  | // and close the menu. | 
|  | void Step3() { | 
|  | views::SubmenuView* topmenu = menu_->GetSubmenu(); | 
|  | ASSERT_TRUE(topmenu); | 
|  | ASSERT_TRUE(topmenu->IsShowing()); | 
|  | ASSERT_FALSE(top_menu_model_.IsSubmenuShowing()); | 
|  |  | 
|  | // Click the button to exit the menu. | 
|  | Click(button_, CreateEventTask(this, &MenuModelAdapterTest::Step4)); | 
|  | } | 
|  |  | 
|  | // All done. | 
|  | void Step4() { | 
|  | views::SubmenuView* topmenu = menu_->GetSubmenu(); | 
|  | ASSERT_TRUE(topmenu); | 
|  | ASSERT_FALSE(topmenu->IsShowing()); | 
|  | ASSERT_FALSE(top_menu_model_.IsSubmenuShowing()); | 
|  |  | 
|  | Done(); | 
|  | } | 
|  |  | 
|  | private: | 
|  | // Generate a mouse click on the specified view and post a new task. | 
|  | virtual void Click(views::View* view, const base::Closure& next) { | 
|  | ui_test_utils::MoveMouseToCenterAndPress( | 
|  | view, | 
|  | ui_controls::LEFT, | 
|  | ui_controls::DOWN | ui_controls::UP, | 
|  | next); | 
|  | } | 
|  |  | 
|  | views::MenuButton* button_; | 
|  | TopMenuModel top_menu_model_; | 
|  | views::MenuModelAdapter menu_model_adapter_; | 
|  | views::MenuItemView* menu_; | 
|  | std::unique_ptr<views::MenuRunner> menu_runner_; | 
|  | }; | 
|  |  | 
|  | // If this flakes, disable and log details in http://crbug.com/523255. | 
|  | VIEW_TEST(MenuModelAdapterTest, RebuildMenu) |