diff options
author | jonross <jonross@chromium.org> | 2015-11-27 07:47:10 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-11-27 15:47:59 +0000 |
commit | a2c738a915517a5d23aa8c8431e7bd60fc3d4773 (patch) | |
tree | f013b730a9e149c82316b91026a5977e3799b43d | |
parent | 976bcb83689efff2bb1652f3073c2c9f827a0d01 (diff) | |
download | chromium_src-a2c738a915517a5d23aa8c8431e7bd60fc3d4773.zip chromium_src-a2c738a915517a5d23aa8c8431e7bd60fc3d4773.tar.gz chromium_src-a2c738a915517a5d23aa8c8431e7bd60fc3d4773.tar.bz2 |
Refactor Menus to Support Asynchronous running
Refactor MenuRunner and MenuController to provide support for menus ran without a nested message loop.
Updated MenuDelegate and MenuControllerDelegate with a callback OnMenuClosed to notify delegates of asynchronous menu closure. Refactored DropMenuClosed in both delegates to use OnMenuClosed instead.
TEST=MenuControllerTest.AsynchronousAccept, MenuControllerTest.AsynchronousCancelAll, MenuRunnerTest.AsynchronousRun, manual testing of menus on device. Ran views_unittests, browser_tests, unit_tests
BUG=557132
Review URL: https://codereview.chromium.org/1473233003
Cr-Commit-Position: refs/heads/master@{#362006}
18 files changed, 447 insertions, 149 deletions
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_menu_controller_views.cc b/chrome/browser/ui/views/bookmarks/bookmark_menu_controller_views.cc index d5cd959..c76be14 100644 --- a/chrome/browser/ui/views/bookmarks/bookmark_menu_controller_views.cc +++ b/chrome/browser/ui/views/bookmarks/bookmark_menu_controller_views.cc @@ -62,8 +62,6 @@ void BookmarkMenuController::RunMenuAt(BookmarkBarView* bookmark_bar) { bounds, anchor, ui::MENU_SOURCE_NONE)); - if (!for_drop_) - delete this; } void BookmarkMenuController::Cancel() { @@ -140,10 +138,6 @@ bool BookmarkMenuController::ShowContextMenu(MenuItemView* source, return menu_delegate_->ShowContextMenu(source, id, p, source_type); } -void BookmarkMenuController::DropMenuClosed(MenuItemView* menu) { - delete this; -} - bool BookmarkMenuController::CanDrag(MenuItemView* menu) { return menu_delegate_->CanDrag(menu); } @@ -157,6 +151,11 @@ int BookmarkMenuController::GetDragOperations(MenuItemView* sender) { return menu_delegate_->GetDragOperations(sender); } +void BookmarkMenuController::OnMenuClosed(views::MenuItemView* menu, + views::MenuRunner::RunResult result) { + delete this; +} + views::MenuItemView* BookmarkMenuController::GetSiblingMenu( views::MenuItemView* menu, const gfx::Point& screen_point, diff --git a/chrome/browser/ui/views/bookmarks/bookmark_menu_controller_views.h b/chrome/browser/ui/views/bookmarks/bookmark_menu_controller_views.h index c3a985a..995187e 100644 --- a/chrome/browser/ui/views/bookmarks/bookmark_menu_controller_views.h +++ b/chrome/browser/ui/views/bookmarks/bookmark_menu_controller_views.h @@ -101,11 +101,12 @@ class BookmarkMenuController : public bookmarks::BaseBookmarkModelObserver, int id, const gfx::Point& p, ui::MenuSourceType source_type) override; - void DropMenuClosed(views::MenuItemView* menu) override; bool CanDrag(views::MenuItemView* menu) override; void WriteDragData(views::MenuItemView* sender, ui::OSExchangeData* data) override; int GetDragOperations(views::MenuItemView* sender) override; + void OnMenuClosed(views::MenuItemView* menu, + views::MenuRunner::RunResult result) override; views::MenuItemView* GetSiblingMenu(views::MenuItemView* menu, const gfx::Point& screen_point, views::MenuAnchorPosition* anchor, diff --git a/chrome/browser/ui/views/toolbar/chevron_menu_button.cc b/chrome/browser/ui/views/toolbar/chevron_menu_button.cc index 69b207a..2e87277 100644 --- a/chrome/browser/ui/views/toolbar/chevron_menu_button.cc +++ b/chrome/browser/ui/views/toolbar/chevron_menu_button.cc @@ -95,7 +95,6 @@ class ChevronMenuButton::MenuController : public views::MenuDelegate { int id, const gfx::Point& p, ui::MenuSourceType source_type) override; - void DropMenuClosed(views::MenuItemView* menu) override; // These drag functions offer support for dragging icons into the overflow // menu. bool GetDropFormats( @@ -111,6 +110,8 @@ class ChevronMenuButton::MenuController : public views::MenuDelegate { int OnPerformDrop(views::MenuItemView* menu, DropPosition position, const ui::DropTargetEvent& event) override; + void OnMenuClosed(views::MenuItemView* menu, + views::MenuRunner::RunResult result) override; // These three drag functions offer support for dragging icons out of the // overflow menu. bool CanDrag(views::MenuItemView* menu) override; @@ -265,11 +266,6 @@ bool ChevronMenuButton::MenuController::ShowContextMenu( return true; } -void ChevronMenuButton::MenuController::DropMenuClosed( - views::MenuItemView* menu) { - owner_->MenuDone(); -} - bool ChevronMenuButton::MenuController::GetDropFormats( views::MenuItemView* menu, int* formats, @@ -336,6 +332,12 @@ int ChevronMenuButton::MenuController::OnPerformDrop( return ui::DragDropTypes::DRAG_MOVE; } +void ChevronMenuButton::MenuController::OnMenuClosed( + views::MenuItemView* menu, + views::MenuRunner::RunResult result) { + owner_->MenuDone(); +} + bool ChevronMenuButton::MenuController::CanDrag(views::MenuItemView* menu) { return true; } diff --git a/ui/views/controls/menu/menu_controller.cc b/ui/views/controls/menu/menu_controller.cc index c744840..bdc906f 100644 --- a/ui/views/controls/menu/menu_controller.cc +++ b/ui/views/controls/menu/menu_controller.cc @@ -364,6 +364,9 @@ MenuItemView* MenuController::Run(Widget* parent, if (ViewsDelegate::GetInstance()) ViewsDelegate::GetInstance()->AddRef(); + if (async_run_) + return nullptr; + // We need to turn on nestable tasks as in some situations (pressing alt-f for // one) the menus are run from a task. If we don't do this and are invoked // from a task none of the tasks we schedule are processed and the menu @@ -376,76 +379,10 @@ MenuItemView* MenuController::Run(Widget* parent, if (ViewsDelegate::GetInstance()) ViewsDelegate::GetInstance()->ReleaseRef(); - // Close any open menus. - SetSelection(NULL, SELECTION_UPDATE_IMMEDIATELY | SELECTION_EXIT); - -#if defined(OS_WIN) - // On Windows, if we select the menu item by touch and if the window at the - // location is another window on the same thread, that window gets a - // WM_MOUSEACTIVATE message and ends up activating itself, which is not - // correct. We workaround this by setting a property on the window at the - // current cursor location. We check for this property in our - // WM_MOUSEACTIVATE handler and don't activate the window if the property is - // set. - if (item_selected_by_touch_) { - item_selected_by_touch_ = false; - POINT cursor_pos; - ::GetCursorPos(&cursor_pos); - HWND window = ::WindowFromPoint(cursor_pos); - if (::GetWindowThreadProcessId(window, NULL) == - ::GetCurrentThreadId()) { - ::SetProp(window, ui::kIgnoreTouchMouseActivateForWindow, - reinterpret_cast<HANDLE>(true)); - } - } -#endif - - linked_ptr<MenuButton::PressedLock> nested_pressed_lock; - if (nested_menu) { - DCHECK(!menu_stack_.empty()); - // We're running from within a menu, restore the previous state. - // The menus are already showing, so we don't have to show them. - state_ = menu_stack_.back().first; - pending_state_ = menu_stack_.back().first; - nested_pressed_lock = menu_stack_.back().second; - menu_stack_.pop_back(); - } else { - showing_ = false; - did_capture_ = false; - } - - MenuItemView* result = result_; - // In case we're nested, reset result_. - result_ = NULL; - if (result_event_flags) *result_event_flags = accept_event_flags_; - if (exit_type_ == EXIT_OUTERMOST) { - SetExitType(EXIT_NONE); - } else { - if (nested_menu && result) { - // We're nested and about to return a value. The caller might enter - // another blocking loop. We need to make sure all menus are hidden - // before that happens otherwise the menus will stay on screen. - CloseAllNestedMenus(); - SetSelection(NULL, SELECTION_UPDATE_IMMEDIATELY | SELECTION_EXIT); - - // Set exit_all_, which makes sure all nested loops exit immediately. - if (exit_type_ != EXIT_DESTROYED) - SetExitType(EXIT_ALL); - } else if (exit_type_ != EXIT_NONE && message_loop_depth_) { - // If we're closing all menus, also mark the next topmost menu - // message loop for termination, so that we'll unwind fully. - TerminateNestedMessageLoop(); - } - } - - // Reset our pressed lock to the previous state's, if there was one. - // The lock handles the case if the button was destroyed. - pressed_lock_.reset(nested_pressed_lock.release()); - - return result; + return ExitMenuRun(); } void MenuController::Cancel(ExitType type) { @@ -474,12 +411,16 @@ void MenuController::Cancel(ExitType type) { // triggers deleting us. DCHECK(selected); showing_ = false; - delegate_->DropMenuClosed( - internal::MenuControllerDelegate::NOTIFY_DELEGATE, - selected->GetRootMenuItem()); + delegate_->OnMenuClosed(internal::MenuControllerDelegate::NOTIFY_DELEGATE, + selected->GetRootMenuItem(), accept_event_flags_); // WARNING: the call to MenuClosed deletes us. return; } + if (async_run_) { + MenuItemView* result = ExitMenuRun(); + delegate_->OnMenuClosed(internal::MenuControllerDelegate::NOTIFY_DELEGATE, + result, accept_event_flags_); + } } bool MenuController::OnMousePressed(SubmenuView* source, @@ -856,9 +797,9 @@ int MenuController::OnPerformDrop(SubmenuView* source, drop_target = drop_target->GetParentMenuItem(); if (!IsBlockingRun()) { - delegate_->DropMenuClosed( + delegate_->OnMenuClosed( internal::MenuControllerDelegate::DONT_NOTIFY_DELEGATE, - item->GetRootMenuItem()); + item->GetRootMenuItem(), accept_event_flags_); } // WARNING: the call to MenuClosed deletes us. @@ -1239,6 +1180,7 @@ MenuController::MenuController(bool blocking, message_loop_depth_(0), closing_event_time_(base::TimeDelta()), menu_start_time_(base::TimeTicks()), + async_run_(false), is_combobox_(false), item_selected_by_touch_(false), current_mouse_event_target_(nullptr), @@ -1319,6 +1261,11 @@ void MenuController::Accept(MenuItemView* item, int event_flags) { SetExitType(EXIT_ALL); } accept_event_flags_ = event_flags; + if (async_run_) { + MenuItemView* result = ExitMenuRun(); + delegate_->OnMenuClosed(internal::MenuControllerDelegate::NOTIFY_DELEGATE, + result, accept_event_flags_); + } } bool MenuController::ShowSiblingMenu(SubmenuView* source, @@ -2310,7 +2257,7 @@ void MenuController::RepostEvent(SubmenuView* source, if (!window) return; - message_loop_->RepostEventToWindow(event, window, screen_loc); + MenuMessageLoop::RepostEventToWindow(event, window, screen_loc); } void MenuController::SetDropMenuItem( @@ -2453,6 +2400,81 @@ void MenuController::TerminateNestedMessageLoop() { message_loop_->QuitNow(); } +MenuItemView* MenuController::ExitMenuRun() { + // Release the lock which prevents Chrome from shutting down while the menu is + // showing. + if (async_run_ && ViewsDelegate::GetInstance()) + ViewsDelegate::GetInstance()->ReleaseRef(); + + // Close any open menus. + SetSelection(nullptr, SELECTION_UPDATE_IMMEDIATELY | SELECTION_EXIT); + +#if defined(OS_WIN) + // On Windows, if we select the menu item by touch and if the window at the + // location is another window on the same thread, that window gets a + // WM_MOUSEACTIVATE message and ends up activating itself, which is not + // correct. We workaround this by setting a property on the window at the + // current cursor location. We check for this property in our + // WM_MOUSEACTIVATE handler and don't activate the window if the property is + // set. + if (item_selected_by_touch_) { + item_selected_by_touch_ = false; + POINT cursor_pos; + ::GetCursorPos(&cursor_pos); + HWND window = ::WindowFromPoint(cursor_pos); + if (::GetWindowThreadProcessId(window, nullptr) == ::GetCurrentThreadId()) { + ::SetProp(window, ui::kIgnoreTouchMouseActivateForWindow, + reinterpret_cast<HANDLE>(true)); + } + } +#endif + + linked_ptr<MenuButton::PressedLock> nested_pressed_lock; + bool nested_menu = !menu_stack_.empty(); + if (nested_menu) { + DCHECK(!menu_stack_.empty()); + // We're running from within a menu, restore the previous state. + // The menus are already showing, so we don't have to show them. + state_ = menu_stack_.back().first; + pending_state_ = menu_stack_.back().first; + nested_pressed_lock = menu_stack_.back().second; + menu_stack_.pop_back(); + } else { + showing_ = false; + did_capture_ = false; + } + + MenuItemView* result = result_; + // In case we're nested, reset |result_|. + result_ = nullptr; + + if (exit_type_ == EXIT_OUTERMOST) { + SetExitType(EXIT_NONE); + } else { + if (nested_menu && result) { + // We're nested and about to return a value. The caller might enter + // another blocking loop. We need to make sure all menus are hidden + // before that happens otherwise the menus will stay on screen. + CloseAllNestedMenus(); + SetSelection(nullptr, SELECTION_UPDATE_IMMEDIATELY | SELECTION_EXIT); + + // Set exit_all_, which makes sure all nested loops exit immediately. + if (exit_type_ != EXIT_DESTROYED) + SetExitType(EXIT_ALL); + } else if (exit_type_ != EXIT_NONE && message_loop_depth_) { + // If we're closing all menus, also mark the next topmost menu + // message loop for termination, so that we'll unwind fully. + TerminateNestedMessageLoop(); + } + } + + // Reset our pressed lock to the previous state's, if there was one. + // The lock handles the case if the button was destroyed. + pressed_lock_.reset(nested_pressed_lock.release()); + + return result; +} + void MenuController::HandleMouseLocation(SubmenuView* source, const gfx::Point& mouse_location) { if (showing_submenu_) diff --git a/ui/views/controls/menu/menu_controller.h b/ui/views/controls/menu/menu_controller.h index 2a26bf4..0938197 100644 --- a/ui/views/controls/menu/menu_controller.h +++ b/ui/views/controls/menu/menu_controller.h @@ -125,6 +125,8 @@ class VIEWS_EXPORT MenuController : public WidgetObserver { // Returns the time from the event which closed the menu - or 0. base::TimeDelta closing_event_time() const { return closing_event_time_; } + void set_async_run(bool is_async) { async_run_ = is_async; } + void set_is_combobox(bool is_combobox) { is_combobox_ = is_combobox; } // Various events, forwarded from the submenu. @@ -529,6 +531,10 @@ class VIEWS_EXPORT MenuController : public WidgetObserver { // Terminates the current nested message-loop. void TerminateNestedMessageLoop(); + // Performs the teardown of the menu launched by Run(). The selected item is + // returned. + MenuItemView* ExitMenuRun(); + // Handles the mouse location event on the submenu |source|. void HandleMouseLocation(SubmenuView* source, const gfx::Point& mouse_location); @@ -644,6 +650,10 @@ class VIEWS_EXPORT MenuController : public WidgetObserver { // screen coordinates). Otherwise this will be (0, 0). gfx::Point menu_start_mouse_press_loc_; + // Controls behviour differences between an asynchronous run, and other types + // of run (blocking, drag and drop). + bool async_run_; + // Controls behavior differences between a combobox and other types of menu // (like a context menu). bool is_combobox_; diff --git a/ui/views/controls/menu/menu_controller_delegate.h b/ui/views/controls/menu/menu_controller_delegate.h index 6472aea..85e9206 100644 --- a/ui/views/controls/menu/menu_controller_delegate.h +++ b/ui/views/controls/menu/menu_controller_delegate.h @@ -23,8 +23,12 @@ class MenuControllerDelegate { }; // Invoked when MenuController closes a menu and the MenuController was - // configured for drop (MenuRunner::FOR_DROP). - virtual void DropMenuClosed(NotifyType type, MenuItemView* menu) = 0; + // configured for asynchronous or drop (MenuRunner::ASYNC, + // MenuRunner::FOR_DROP). |mouse_event_flags| are the flags set on the + // ui::MouseEvent which selected |menu|, otherwise 0. + virtual void OnMenuClosed(NotifyType type, + MenuItemView* menu, + int mouse_event_flags) = 0; // Invoked when the MenuDelegate::GetSiblingMenu() returns non-NULL. virtual void SiblingMenuCreated(MenuItemView* menu) = 0; diff --git a/ui/views/controls/menu/menu_controller_unittest.cc b/ui/views/controls/menu/menu_controller_unittest.cc index 6538333..8fd6de5 100644 --- a/ui/views/controls/menu/menu_controller_unittest.cc +++ b/ui/views/controls/menu/menu_controller_unittest.cc @@ -10,6 +10,8 @@ #include "ui/events/event_handler.h" #include "ui/events/null_event_targeter.h" #include "ui/events/test/event_generator.h" +#include "ui/views/controls/menu/menu_controller_delegate.h" +#include "ui/views/controls/menu/menu_delegate.h" #include "ui/views/controls/menu/menu_item_view.h" #include "ui/views/controls/menu/submenu_view.h" #include "ui/views/test/views_test_base.h" @@ -31,6 +33,60 @@ namespace test { namespace { +// Test implementation of MenuControllerDelegate that only reports the values +// called of OnMenuClosed. +class TestMenuControllerDelegate : public internal::MenuControllerDelegate { + public: + TestMenuControllerDelegate(); + ~TestMenuControllerDelegate() override {} + + int on_menu_closed_called() { return on_menu_closed_called_; } + + NotifyType on_menu_closed_notify_type() { + return on_menu_closed_notify_type_; + } + + MenuItemView* on_menu_closed_menu() { return on_menu_closed_menu_; } + + int on_menu_closed_mouse_event_flags() { + return on_menu_closed_mouse_event_flags_; + } + + // internal::MenuControllerDelegate: + void OnMenuClosed(NotifyType type, + MenuItemView* menu, + int mouse_event_flags) override; + void SiblingMenuCreated(MenuItemView* menu) override; + + private: + // Number of times OnMenuClosed has been called. + int on_menu_closed_called_; + + // The values passed on the last call of OnMenuClosed. + NotifyType on_menu_closed_notify_type_; + MenuItemView* on_menu_closed_menu_; + int on_menu_closed_mouse_event_flags_; + + DISALLOW_COPY_AND_ASSIGN(TestMenuControllerDelegate); +}; + +TestMenuControllerDelegate::TestMenuControllerDelegate() + : on_menu_closed_called_(0), + on_menu_closed_notify_type_(NOTIFY_DELEGATE), + on_menu_closed_menu_(nullptr), + on_menu_closed_mouse_event_flags_(0) {} + +void TestMenuControllerDelegate::OnMenuClosed(NotifyType type, + MenuItemView* menu, + int mouse_event_flags) { + on_menu_closed_called_++; + on_menu_closed_notify_type_ = type; + on_menu_closed_menu_ = menu; + on_menu_closed_mouse_event_flags_ = mouse_event_flags; +} + +void TestMenuControllerDelegate::SiblingMenuCreated(MenuItemView* menu) {} + class SubmenuViewShown : public SubmenuView { public: SubmenuViewShown(MenuItemView* parent) : SubmenuView(parent) {} @@ -70,7 +126,7 @@ class TestEventHandler : public ui::EventHandler { class TestMenuItemViewShown : public MenuItemView { public: - TestMenuItemViewShown() : MenuItemView(nullptr) { + TestMenuItemViewShown(MenuDelegate* delegate) : MenuItemView(delegate) { submenu_ = new SubmenuViewShown(this); } ~TestMenuItemViewShown() override {} @@ -179,9 +235,16 @@ class MenuControllerTest : public ViewsTestBase { menu_controller_->RunMessageLoop(false); } + void Accept(MenuItemView* item, int event_flags) { + menu_controller_->Accept(item, event_flags); + } + Widget* owner() { return owner_.get(); } ui::test::EventGenerator* event_generator() { return event_generator_.get(); } TestMenuItemViewShown* menu_item() { return menu_item_.get(); } + TestMenuControllerDelegate* menu_controller_delegate() { + return menu_controller_delegate_.get(); + } MenuController* menu_controller() { return menu_controller_; } const MenuItemView* pending_state_item() const { return menu_controller_->pending_state_.item; @@ -206,7 +269,8 @@ class MenuControllerTest : public ViewsTestBase { } void SetupMenuItem() { - menu_item_.reset(new TestMenuItemViewShown); + menu_delegate_.reset(new MenuDelegate); + menu_item_.reset(new TestMenuItemViewShown(menu_delegate_.get())); menu_item_->AppendMenuItemWithLabel(1, base::ASCIIToUTF16("One")); menu_item_->AppendMenuItemWithLabel(2, base::ASCIIToUTF16("Two")); menu_item_->AppendMenuItemWithLabel(3, base::ASCIIToUTF16("Three")); @@ -214,7 +278,9 @@ class MenuControllerTest : public ViewsTestBase { } void SetupMenuController() { - menu_controller_= new MenuController(true, nullptr); + menu_controller_delegate_.reset(new TestMenuControllerDelegate); + menu_controller_ = + new MenuController(true, menu_controller_delegate_.get()); menu_controller_->owner_ = owner_.get(); menu_controller_->showing_ = true; menu_controller_->SetSelection( @@ -224,6 +290,8 @@ class MenuControllerTest : public ViewsTestBase { scoped_ptr<Widget> owner_; scoped_ptr<ui::test::EventGenerator> event_generator_; scoped_ptr<TestMenuItemViewShown> menu_item_; + scoped_ptr<TestMenuControllerDelegate> menu_controller_delegate_; + scoped_ptr<MenuDelegate> menu_delegate_; MenuController* menu_controller_; DISALLOW_COPY_AND_ASSIGN(MenuControllerTest); @@ -416,5 +484,53 @@ TEST_F(MenuControllerTest, PreviousSelectedItem) { ResetSelection(); } +// Tests that a menu opened asynchronously, will notify its +// MenuControllerDelegate when Accept is called. +TEST_F(MenuControllerTest, AsynchronousAccept) { + MenuController* controller = menu_controller(); + controller->set_async_run(true); + + int mouse_event_flags = 0; + MenuItemView* run_result = + controller->Run(owner(), nullptr, menu_item(), gfx::Rect(), + MENU_ANCHOR_TOPLEFT, false, false, &mouse_event_flags); + EXPECT_EQ(run_result, nullptr); + TestMenuControllerDelegate* delegate = menu_controller_delegate(); + EXPECT_EQ(0, delegate->on_menu_closed_called()); + + MenuItemView* accepted = menu_item()->GetSubmenu()->GetMenuItemAt(0); + const int kEventFlags = 42; + Accept(accepted, kEventFlags); + + EXPECT_EQ(1, delegate->on_menu_closed_called()); + EXPECT_EQ(accepted, delegate->on_menu_closed_menu()); + EXPECT_EQ(kEventFlags, delegate->on_menu_closed_mouse_event_flags()); + EXPECT_EQ(internal::MenuControllerDelegate::NOTIFY_DELEGATE, + delegate->on_menu_closed_notify_type()); +} + +// Tests that a menu opened asynchronously, will notify its +// MenuControllerDelegate when CancelAll is called. +TEST_F(MenuControllerTest, AsynchronousCancelAll) { + MenuController* controller = menu_controller(); + controller->set_async_run(true); + + int mouse_event_flags = 0; + MenuItemView* run_result = + controller->Run(owner(), nullptr, menu_item(), gfx::Rect(), + MENU_ANCHOR_TOPLEFT, false, false, &mouse_event_flags); + EXPECT_EQ(run_result, nullptr); + TestMenuControllerDelegate* delegate = menu_controller_delegate(); + EXPECT_EQ(0, delegate->on_menu_closed_called()); + + controller->CancelAll(); + EXPECT_EQ(1, delegate->on_menu_closed_called()); + EXPECT_EQ(nullptr, delegate->on_menu_closed_menu()); + EXPECT_EQ(0, delegate->on_menu_closed_mouse_event_flags()); + EXPECT_EQ(internal::MenuControllerDelegate::NOTIFY_DELEGATE, + delegate->on_menu_closed_notify_type()); + EXPECT_EQ(MenuController::EXIT_ALL, controller->exit_type()); +} + } // namespace test } // namespace views diff --git a/ui/views/controls/menu/menu_delegate.h b/ui/views/controls/menu/menu_delegate.h index 69133a9..cca690f 100644 --- a/ui/views/controls/menu/menu_delegate.h +++ b/ui/views/controls/menu/menu_delegate.h @@ -14,6 +14,7 @@ #include "ui/base/dragdrop/drag_drop_types.h" #include "ui/base/dragdrop/os_exchange_data.h" #include "ui/base/ui_base_types.h" +#include "ui/views/controls/menu/menu_runner.h" #include "ui/views/controls/menu/menu_types.h" #include "ui/views/views_export.h" @@ -204,11 +205,6 @@ class VIEWS_EXPORT MenuDelegate { // See DragDropTypes for possible values. virtual int GetDragOperations(MenuItemView* sender); - // Notification the menu has closed. This is only sent when running the - // menu for a drop. - virtual void DropMenuClosed(MenuItemView* menu) { - } - // Returns true if the menu should close upon a drag completing. Defaults to // true. This is only invoked for drag and drop operations performed on child // Views that are not MenuItemViews. @@ -218,6 +214,10 @@ class VIEWS_EXPORT MenuDelegate { virtual void SelectionChanged(MenuItemView* menu) { } + // Notification the menu has closed. This will not be called if MenuRunner is + // deleted during calls to ExecuteCommand(). + virtual void OnMenuClosed(MenuItemView* menu, MenuRunner::RunResult result) {} + // If the user drags the mouse outside the bounds of the menu the delegate // is queried for a sibling menu to show. If this returns non-null the // current menu is hidden, and the menu returned from this method is shown. diff --git a/ui/views/controls/menu/menu_message_loop.h b/ui/views/controls/menu/menu_message_loop.h index 221b72d..d4d5633 100644 --- a/ui/views/controls/menu/menu_message_loop.h +++ b/ui/views/controls/menu/menu_message_loop.h @@ -29,6 +29,12 @@ class MenuMessageLoop { // Create a platform specific instance. static MenuMessageLoop* Create(); + // Repost |event| to |window|. + // |screen_loc| is the event's location in screen coordinates. + static void RepostEventToWindow(const ui::LocatedEvent& event, + gfx::NativeWindow window, + const gfx::Point& screen_loc); + // Runs the platform specific bits of the message loop. If |nested_menu| is // true we're being asked to run a menu from within a menu (eg a context // menu). @@ -37,12 +43,6 @@ class MenuMessageLoop { // Quit an earlier call to Run(). virtual void QuitNow() = 0; - // Repost |event| to |window|. - // |screen_loc| is the event's location in screen coordinates. - virtual void RepostEventToWindow(const ui::LocatedEvent& event, - gfx::NativeWindow window, - const gfx::Point& screen_loc) = 0; - // Clear any references to the owner widget that was passed into the previous // call to Run(). virtual void ClearOwner() = 0; diff --git a/ui/views/controls/menu/menu_message_loop_aura.cc b/ui/views/controls/menu/menu_message_loop_aura.cc index 3656741..0182239 100644 --- a/ui/views/controls/menu/menu_message_loop_aura.cc +++ b/ui/views/controls/menu/menu_message_loop_aura.cc @@ -98,15 +98,10 @@ MenuMessageLoop* MenuMessageLoop::Create() { return new MenuMessageLoopAura; } -MenuMessageLoopAura::MenuMessageLoopAura() : owner_(nullptr) { -} - -MenuMessageLoopAura::~MenuMessageLoopAura() { -} - -void MenuMessageLoopAura::RepostEventToWindow(const ui::LocatedEvent& event, - gfx::NativeWindow window, - const gfx::Point& screen_loc) { +// static +void MenuMessageLoop::RepostEventToWindow(const ui::LocatedEvent& event, + gfx::NativeWindow window, + const gfx::Point& screen_loc) { aura::Window* root = window->GetRootWindow(); ScreenPositionClient* spc = aura::client::GetScreenPositionClient(root); if (!spc) @@ -121,6 +116,10 @@ void MenuMessageLoopAura::RepostEventToWindow(const ui::LocatedEvent& event, root->GetHost()->dispatcher()->RepostEvent(clone); } +MenuMessageLoopAura::MenuMessageLoopAura() : owner_(nullptr) {} + +MenuMessageLoopAura::~MenuMessageLoopAura() {} + void MenuMessageLoopAura::Run(MenuController* controller, Widget* owner, bool nested_menu) { diff --git a/ui/views/controls/menu/menu_message_loop_aura.h b/ui/views/controls/menu/menu_message_loop_aura.h index 80485e9..0c3b4dc 100644 --- a/ui/views/controls/menu/menu_message_loop_aura.h +++ b/ui/views/controls/menu/menu_message_loop_aura.h @@ -30,9 +30,6 @@ class MenuMessageLoopAura : public MenuMessageLoop { Widget* owner, bool nested_menu) override; void QuitNow() override; - void RepostEventToWindow(const ui::LocatedEvent& event, - gfx::NativeWindow window, - const gfx::Point& screen_loc) override; void ClearOwner() override; private: diff --git a/ui/views/controls/menu/menu_message_loop_mac.cc b/ui/views/controls/menu/menu_message_loop_mac.cc index ae5670a..c443ce9 100644 --- a/ui/views/controls/menu/menu_message_loop_mac.cc +++ b/ui/views/controls/menu/menu_message_loop_mac.cc @@ -17,17 +17,16 @@ MenuMessageLoop* MenuMessageLoop::Create() { return new MenuMessageLoopMac; } -MenuMessageLoopMac::MenuMessageLoopMac() { +// static +void MenuMessageLoop::RepostEventToWindow(const ui::LocatedEvent& event, + gfx::NativeWindow window, + const gfx::Point& screen_loc) { + NOTIMPLEMENTED(); } -MenuMessageLoopMac::~MenuMessageLoopMac() { -} +MenuMessageLoopMac::MenuMessageLoopMac() {} -void MenuMessageLoopMac::RepostEventToWindow(const ui::LocatedEvent& event, - gfx::NativeWindow window, - const gfx::Point& screen_loc) { - NOTIMPLEMENTED(); -} +MenuMessageLoopMac::~MenuMessageLoopMac() {} void MenuMessageLoopMac::Run(MenuController* controller, Widget* owner, diff --git a/ui/views/controls/menu/menu_message_loop_mac.h b/ui/views/controls/menu/menu_message_loop_mac.h index 82ef809..de97e88 100644 --- a/ui/views/controls/menu/menu_message_loop_mac.h +++ b/ui/views/controls/menu/menu_message_loop_mac.h @@ -24,9 +24,6 @@ class MenuMessageLoopMac : public MenuMessageLoop { Widget* owner, bool nested_menu) override; void QuitNow() override; - void RepostEventToWindow(const ui::LocatedEvent& event, - gfx::NativeWindow window, - const gfx::Point& screen_loc) override; void ClearOwner() override; private: diff --git a/ui/views/controls/menu/menu_runner.h b/ui/views/controls/menu/menu_runner.h index 1744c93c..a55f2d1 100644 --- a/ui/views/controls/menu/menu_runner.h +++ b/ui/views/controls/menu/menu_runner.h @@ -68,26 +68,30 @@ class VIEWS_EXPORT MenuRunner { // The menu is a nested context menu. For example, click a folder on the // bookmark bar, then right click an entry to get its context menu. - IS_NESTED = 1 << 1, + IS_NESTED = 1 << 1, // Used for showing a menu during a drop operation. This does NOT block the // caller, instead the delegate is notified when the menu closes via the // DropMenuClosed method. - FOR_DROP = 1 << 2, + FOR_DROP = 1 << 2, // The menu is a context menu (not necessarily nested), for example right // click on a link on a website in the browser. - CONTEXT_MENU = 1 << 3, + CONTEXT_MENU = 1 << 3, // The menu should behave like a Windows native Combobox dropdow menu. // This behavior includes accepting the pending item and closing on F4. - COMBOBOX = 1 << 4, + COMBOBOX = 1 << 4, // A child view is performing a drag-and-drop operation, so the menu should // stay open (even if it doesn't receive drag updated events). In this case, // the caller is responsible for closing the menu upon completion of the // drag-and-drop. NESTED_DRAG = 1 << 5, + + // Used for showing a menu which does NOT block the caller. Instead the + // delegate is notified when the menu closes via OnMenuClosed. + ASYNC = 1 << 6, }; enum RunResult { diff --git a/ui/views/controls/menu/menu_runner_impl.cc b/ui/views/controls/menu/menu_runner_impl.cc index b8270c2..dc3f0d21 100644 --- a/ui/views/controls/menu/menu_runner_impl.cc +++ b/ui/views/controls/menu/menu_runner_impl.cc @@ -31,12 +31,12 @@ MenuRunnerImpl::MenuRunnerImpl(MenuItemView* menu) : menu_(menu), running_(false), delete_after_run_(false), + async_(false), for_drop_(false), controller_(NULL), owns_controller_(false), closing_event_time_(base::TimeDelta()), - weak_factory_(this) { -} + weak_factory_(this) {} bool MenuRunnerImpl::IsRunning() const { return running_; @@ -104,6 +104,7 @@ MenuRunner::RunResult MenuRunnerImpl::RunMenuAt(Widget* parent, } running_ = true; + async_ = (run_types & MenuRunner::ASYNC) != 0; for_drop_ = (run_types & MenuRunner::FOR_DROP) != 0; bool has_mnemonics = (run_types & MenuRunner::HAS_MNEMONICS) != 0; owns_controller_ = false; @@ -112,6 +113,7 @@ MenuRunner::RunResult MenuRunnerImpl::RunMenuAt(Widget* parent, controller = new MenuController(!for_drop_, this); owns_controller_ = true; } + controller->set_async_run(async_); controller->set_is_combobox((run_types & MenuRunner::COMBOBOX) != 0); controller_ = controller; menu_->set_controller(controller_); @@ -132,11 +134,12 @@ MenuRunner::RunResult MenuRunnerImpl::RunMenuAt(Widget* parent, &mouse_event_flags); // Get the time of the event which closed this menu. closing_event_time_ = controller->closing_event_time(); - if (for_drop_) { - // Drop menus return immediately. We finish processing in DropMenuClosed. + if (for_drop_ || async_) { + // Drop and asynchronous menus return immediately. We finish processing in + // OnMenuClosed. return MenuRunner::NORMAL_EXIT; } - return MenuDone(result, mouse_event_flags); + return MenuDone(NOTIFY_DELEGATE, result, mouse_event_flags); } void MenuRunnerImpl::Cancel() { @@ -148,13 +151,10 @@ base::TimeDelta MenuRunnerImpl::GetClosingEventTime() const { return closing_event_time_; } -void MenuRunnerImpl::DropMenuClosed(NotifyType type, MenuItemView* menu) { - MenuDone(NULL, 0); - - if (type == NOTIFY_DELEGATE && menu->GetDelegate()) { - // Delegate is null when invoked from the destructor. - menu->GetDelegate()->DropMenuClosed(menu); - } +void MenuRunnerImpl::OnMenuClosed(NotifyType type, + MenuItemView* menu, + int mouse_event_flags) { + MenuDone(type, menu, mouse_event_flags); } void MenuRunnerImpl::SiblingMenuCreated(MenuItemView* menu) { @@ -170,17 +170,18 @@ MenuRunnerImpl::~MenuRunnerImpl() { delete *i; } -MenuRunner::RunResult MenuRunnerImpl::MenuDone(MenuItemView* result, +MenuRunner::RunResult MenuRunnerImpl::MenuDone(NotifyType type, + MenuItemView* result, int mouse_event_flags) { menu_->RemoveEmptyMenus(); - menu_->set_controller(NULL); + menu_->set_controller(nullptr); if (owns_controller_) { // We created the controller and need to delete it. delete controller_; owns_controller_ = false; } - controller_ = NULL; + controller_ = nullptr; // Make sure all the windows we created to show the menus have been // destroyed. menu_->DestroyAllMenuHosts(); @@ -189,13 +190,19 @@ MenuRunner::RunResult MenuRunnerImpl::MenuDone(MenuItemView* result, return MenuRunner::MENU_DELETED; } running_ = false; - if (result && menu_->GetDelegate()) { + if (menu_->GetDelegate()) { // Executing the command may also delete this. base::WeakPtr<MenuRunnerImpl> ref(weak_factory_.GetWeakPtr()); - menu_->GetDelegate()->ExecuteCommand(result->GetCommand(), - mouse_event_flags); + if (result && !for_drop_) { + // Do not execute the menu that was dragged/dropped. + menu_->GetDelegate()->ExecuteCommand(result->GetCommand(), + mouse_event_flags); + } + // Only notify the delegate if it did not delete this. if (!ref) return MenuRunner::MENU_DELETED; + else if (type == NOTIFY_DELEGATE) + menu_->GetDelegate()->OnMenuClosed(result, MenuRunner::NORMAL_EXIT); } return MenuRunner::NORMAL_EXIT; } diff --git a/ui/views/controls/menu/menu_runner_impl.h b/ui/views/controls/menu/menu_runner_impl.h index 7da4a2c..f68b987 100644 --- a/ui/views/controls/menu/menu_runner_impl.h +++ b/ui/views/controls/menu/menu_runner_impl.h @@ -38,7 +38,9 @@ class MenuRunnerImpl : public MenuRunnerImplInterface, base::TimeDelta GetClosingEventTime() const override; // MenuControllerDelegate: - void DropMenuClosed(NotifyType type, MenuItemView* menu) override; + void OnMenuClosed(NotifyType type, + MenuItemView* menu, + int mouse_event_flags) override; void SiblingMenuCreated(MenuItemView* menu) override; private: @@ -46,7 +48,9 @@ class MenuRunnerImpl : public MenuRunnerImplInterface, // Cleans up after the menu is no longer showing. |result| is the menu that // the user selected, or NULL if nothing was selected. - MenuRunner::RunResult MenuDone(MenuItemView* result, int mouse_event_flags); + MenuRunner::RunResult MenuDone(NotifyType type, + MenuItemView* result, + int mouse_event_flags); // Returns true if mnemonics should be shown in the menu. bool ShouldShowMnemonics(MenuButton* button); @@ -70,6 +74,9 @@ class MenuRunnerImpl : public MenuRunnerImplInterface, // Set if |running_| and Release() has been invoked. bool delete_after_run_; + // Are we running asynchronously? + bool async_; + // Are we running for a drop? bool for_drop_; diff --git a/ui/views/controls/menu/menu_runner_unittest.cc b/ui/views/controls/menu/menu_runner_unittest.cc new file mode 100644 index 0000000..4d45b14 --- /dev/null +++ b/ui/views/controls/menu/menu_runner_unittest.cc @@ -0,0 +1,133 @@ +// Copyright 2015 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_runner.h" + +#include "base/memory/scoped_ptr.h" +#include "ui/base/ui_base_types.h" +#include "ui/views/controls/menu/menu_delegate.h" +#include "ui/views/controls/menu/menu_item_view.h" +#include "ui/views/controls/menu/menu_types.h" +#include "ui/views/test/views_test_base.h" +#include "ui/views/widget/widget.h" + +namespace views { +namespace test { + +// Implementation of MenuDelegate that only reports the values of calls to +// OnMenuClosed. +class TestMenuDelegate : public MenuDelegate { + public: + TestMenuDelegate(); + ~TestMenuDelegate() override; + + int on_menu_closed_called() { return on_menu_closed_called_; } + MenuItemView* on_menu_closed_menu() { return on_menu_closed_menu_; } + MenuRunner::RunResult on_menu_closed_run_result() { + return on_menu_closed_run_result_; + } + + // MenuDelegate: + void OnMenuClosed(MenuItemView* menu, MenuRunner::RunResult result) override; + + private: + // The number of times OnMenuClosed was called. + int on_menu_closed_called_; + + // The values of the last call to OnMenuClosed. + MenuItemView* on_menu_closed_menu_; + MenuRunner::RunResult on_menu_closed_run_result_; + + DISALLOW_COPY_AND_ASSIGN(TestMenuDelegate); +}; + +TestMenuDelegate::TestMenuDelegate() + : on_menu_closed_called_(0), + on_menu_closed_menu_(nullptr), + on_menu_closed_run_result_(MenuRunner::MENU_DELETED) {} + +TestMenuDelegate::~TestMenuDelegate() {} + +void TestMenuDelegate::OnMenuClosed(MenuItemView* menu, + MenuRunner::RunResult result) { + on_menu_closed_called_++; + on_menu_closed_menu_ = menu; + on_menu_closed_run_result_ = result; +} + +class MenuRunnerTest : public ViewsTestBase { + public: + MenuRunnerTest(); + ~MenuRunnerTest() override; + + // Initializes a MenuRunner with |run_types|. It takes ownership of + // |menu_item_view_|. + void InitMenuRunner(int32 run_types); + + MenuItemView* 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(); } + + // ViewsTestBase: + void SetUp() override; + void TearDown() override; + + private: + // Owned by MenuRunner. + MenuItemView* menu_item_view_; + + scoped_ptr<TestMenuDelegate> menu_delegate_; + scoped_ptr<MenuRunner> menu_runner_; + scoped_ptr<Widget> owner_; + + DISALLOW_COPY_AND_ASSIGN(MenuRunnerTest); +}; + +MenuRunnerTest::MenuRunnerTest() {} + +MenuRunnerTest::~MenuRunnerTest() {} + +void MenuRunnerTest::InitMenuRunner(int32 run_types) { + menu_runner_.reset(new MenuRunner(menu_item_view_, run_types)); +} + +void MenuRunnerTest::SetUp() { + ViewsTestBase::SetUp(); + menu_delegate_.reset(new TestMenuDelegate); + menu_item_view_ = new MenuItemView(menu_delegate_.get()); + + owner_.reset(new Widget); + Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP); + params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + owner_->Init(params); + owner_->Show(); +} + +void MenuRunnerTest::TearDown() { + owner_->CloseNow(); + ViewsTestBase::TearDown(); +} + +// Tests that MenuRunner is still running after the call to RunMenuAt when +// initialized with MenuRunner::ASYNC, and that MenuDelegate is notified upon +// the closing of the menu. +TEST_F(MenuRunnerTest, AsynchronousRun) { + InitMenuRunner(MenuRunner::ASYNC); + MenuRunner* runner = menu_runner(); + MenuRunner::RunResult result = runner->RunMenuAt( + owner(), nullptr, gfx::Rect(), MENU_ANCHOR_TOPLEFT, ui::MENU_SOURCE_NONE); + EXPECT_EQ(MenuRunner::NORMAL_EXIT, result); + 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()); + EXPECT_EQ(MenuRunner::NORMAL_EXIT, delegate->on_menu_closed_run_result()); +} + +} // namespace test +} // namespace views diff --git a/ui/views/views.gyp b/ui/views/views.gyp index d96e259..536b738 100644 --- a/ui/views/views.gyp +++ b/ui/views/views.gyp @@ -558,6 +558,7 @@ 'controls/menu/menu_item_view_unittest.cc', 'controls/menu/menu_model_adapter_unittest.cc', 'controls/menu/menu_runner_cocoa_unittest.mm', + 'controls/menu/menu_runner_unittest.cc', 'controls/native/native_view_host_mac_unittest.mm', 'controls/native/native_view_host_test_base.cc', 'controls/native/native_view_host_test_base.h', |