summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjonross <jonross@chromium.org>2015-11-27 07:47:10 -0800
committerCommit bot <commit-bot@chromium.org>2015-11-27 15:47:59 +0000
commita2c738a915517a5d23aa8c8431e7bd60fc3d4773 (patch)
treef013b730a9e149c82316b91026a5977e3799b43d
parent976bcb83689efff2bb1652f3073c2c9f827a0d01 (diff)
downloadchromium_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}
-rw-r--r--chrome/browser/ui/views/bookmarks/bookmark_menu_controller_views.cc11
-rw-r--r--chrome/browser/ui/views/bookmarks/bookmark_menu_controller_views.h3
-rw-r--r--chrome/browser/ui/views/toolbar/chevron_menu_button.cc14
-rw-r--r--ui/views/controls/menu/menu_controller.cc168
-rw-r--r--ui/views/controls/menu/menu_controller.h10
-rw-r--r--ui/views/controls/menu/menu_controller_delegate.h8
-rw-r--r--ui/views/controls/menu/menu_controller_unittest.cc122
-rw-r--r--ui/views/controls/menu/menu_delegate.h10
-rw-r--r--ui/views/controls/menu/menu_message_loop.h12
-rw-r--r--ui/views/controls/menu/menu_message_loop_aura.cc17
-rw-r--r--ui/views/controls/menu/menu_message_loop_aura.h3
-rw-r--r--ui/views/controls/menu/menu_message_loop_mac.cc15
-rw-r--r--ui/views/controls/menu/menu_message_loop_mac.h3
-rw-r--r--ui/views/controls/menu/menu_runner.h12
-rw-r--r--ui/views/controls/menu/menu_runner_impl.cc43
-rw-r--r--ui/views/controls/menu/menu_runner_impl.h11
-rw-r--r--ui/views/controls/menu/menu_runner_unittest.cc133
-rw-r--r--ui/views/views.gyp1
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',